This is an experimental project with the goals of providing a simple tool for linting source code within a polyglot Bazel repo and learning more about aspects.
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.
If a linting tool restricts itself to only doing evaluation using your source code files, without needing access to any other information like dependencies or compiler-configuration then it will fit nicely into this project. I think of this project's model linter as a pure function from a source code file to a linted source code file: f(source_code: str) -> str
.
Now this restriction does allow for things beyond formatting, for example you can check for unused variables, unused imports, or missing return values. But some powerful static analysis tools are outside of project scope, like mypy
.
An example Bazel workspace exists in examples
, with a lint.sh
that runs the registered linters against
all source code within the workspace.
Below is further explanation of the constituents of this system.
Add the following to your WORKSPACE file:
http_archive(
name = "linting_system",
sha256 = "",
strip_prefix = "bazel-linting-system-0.4.0",
url = "https://github.com/thundergolfer/bazel-linting-system/archive/v0.4.0.zip",
)
load("@linting_system//repositories:repositories.bzl", linting_sys_repositories = "repositories")
linting_sys_repositories()
load("@linting_system//repositories:go_repositories.bzl", linting_sys_deps = "go_deps")
linting_sys_deps()
Create an aspect.bzl
extension file in a folder called tools/linting
with the following:
load("@linting_system//:generator.bzl", "linting_aspect_generator")
lint = linting_aspect_generator(
name = "lint",
linters = [
"@//tools/linting:python",
]
)
"@//tools/linting:python"
is a label reference to target in a sibling BUILD
file, for example:
load("@linting_system//:rules.bzl", "linter")
package(default_visibility = ['//visibility:public'])
linter(
name = "python",
executable_path = "/usr/local/bin/black",
config = ":configuration/pyproject.toml",
config_option = "--config",
)
linter
targets define a path to the linter executable and optionally a config file for that linter.
β οΈ Thename
field in thelinter
rule must exactly match one of the supported languages. The list of supported languages is shown at the top ofgenerator.bzl
.
Run with:
bazel build //... \
--aspects //tools/linting:aspect.bzl%lint \
--output_groups=report
bazel run @linting_system//apply_changes -- \
"$(git rev-parse --show-toplevel)" \
"$(bazel info bazel-genfiles)"
Usually you'll want to wrap up the above in a simple script named something like lint.sh
.
You can also add the aspect to your .bazelrc
π:
build --aspects //tools/linting:aspect.bzl%lint
build --output_groups=+report