Context: Aspect is working on first-class Bazel support for formatters and linters across all languages our clients use.
Design invariants:
pip install
the world before they can format a .js
file.*_library
rules.
*_library
rules change, but the rest of linting setup is tucked into the macro.bazel
commands, optional syntax sugar in aspect
CLI.
aspect lint
if you use our CLI, otherwise bazel build //... --aspects=//tools/linting:aspect.bzl%lint \ --output_groups=report && bazel run @aspect_rules_format//:accept
Top-line design decisions:
*_library
rules we walk.The distinctions are useful and foundational to how you build workflows around the tools.
formatter | linter |
---|---|
Only one per language, since they could conflict with each other. | Many per language is fine; results compose. |
Invariant: program's behavior is never changed. | Suggested fixes may change behavior. |
Developer has no choices. Always blindly accept result. | Fix may be manual, or select from multiple auto-fixes. |
Changes must be applied. | Violations can be suppressed. |
Operates on a single file at a time. | Can require the dependency graph. |
Can always format just changed files / regions | New violations might be introduced in unchanged files. |
Fast enough to put in a pre-commit workflow. | Some are slow. |
For linter vs. test, we wrote about our reasoning for my Java static analysis tool ErrorProne: http://errorprone.info/docs/criteria. See also our CACM Paper Lessons from building Static Analysis tools at Google and I gave a talk at CapitalOne about it.
linter | test |
---|---|
Think of as WARNING | Think of as ERROR |
Best presented as robot review comments | Best presented as test failures |
Reviewdog is a good UI | Buildkite is a good UI |
Okay to have existing violations | Codebase must be 100% clean |
Works at scale: can enable new lints without fixing all | Hard to scale: must have a codemod/auto-fixer to enable new test in a big repo |
Okay for tool to produce false positives | False positives very painful as they must be suppressed/commented |
Sample change adding SwiftFormat:
https://github.com/thundergolfer/bazel-linting-system/compare/master…alexeagle:bazel-linting-system:master
BUILD
files don't have extra load
statements or format
lines.mypy
is specifically called out as unsupported.
This project was designed with linters like black and gofmt in mind. Given their behaviour, they're perhaps more accurately called formatters, but to me formatters are a subclass of linters.
*_library
rule, rather than just bazel run
a tool that can talk to git and look around the whole workspace.Code: https://github.com/apple/apple_rules_lint
From Son:
Run as part of go_library GoCompilePkg
action.
The compilepkg command serves several purposes: transpile cgo code, create test code wrapper, generate test coverage collection, run staticanalysis, compile code etc… Leverages existing Go's https://pkg.go.dev/golang.org/x/tools/go/analysis framework that is widely used in other linters.
Good:
Simple. Just fetch the tools and run them with bazel run
. Proof-of-concept: https://github.com/aspect-build/bazel-super-formatter
Aspect CLI could have a format
command/plugin but it's small sugar over bazel run @aspect_rules_fmt//fmt
.
--workspace_status_file