aspect_rules_lint, a Bazel rule set that teaches Bazel how to run
existing linting and formatting tools like Prettier and ESLint inside Bazel projects. Aspect CLI also makes running checks for linting and formatting easier and more accessible for you.
What you’ll learn
In this guide, you’ll learn:- Formatting code locally with the
formattool and the Aspect CLI’sformattask - Performing formatting checks in continuous integration (CI)
- Running ESLint with the Aspect CLI’s
linttask - Choosing which lint findings fail the command so CI can gate on it
Formatting
Prettier is one of the most popular formatting tools in the JavaScript ecosystem. The starter repository comes preconfigured with Prettier in thetools/format/ directory at your repository root.
Formatting should be automatic, unavoidable, and fast. Developers format locally with one command, and CI rejects anything that slipped through.
Format locally
The starter exposes the formatter two ways.format is a binary that bazel_env puts on your PATH. Return to the Bazel module root (the directory containing MODULE.bazel), then use it to run Prettier, built by Bazel, against the working tree:
aspect format is the Aspect CLI task. By default it only touches files that differ from the merge base, which is what you want on a branch:
- Formatters are fast and do not disrupt your workflow.
- Git already knows which files were edited, so only those files are touched.
- Reformatting untouched files buries the real change in a pull request.
Formatting enforcement in continuous integration
Not every change goes through a developer’s terminal. Edits made in the GitHub web editor, or by a contributor who skipped the setup, arrive unformatted. The starter’s.github/workflows/ci.yaml has a format job that runs aspect format. On a CI host the task’s --severity resolves to fail, so if Prettier would change any file in the pull request the job exits non-zero and the pull request is blocked.
Linting
This starter repository uses ESLint to automatically check your code for common mistakes and style issues. ESLint is the most widely used code checker in the JavaScript world, though alternatives like oxlint are also available.The linting tools are pre-configured in the
tools/lint folder and will automatically check all TypeScript and JavaScript files in your project.How linting works
The linting system uses Bazel’s “aspect” feature, which visits your existing targets without requiring dedicated lint targets in yourBUILD files. rules_js and rules_ts expose their declared JavaScript and TypeScript sources to the aspect, so ESLint checks the same inputs Bazel builds rather than walking an unrelated copy of the source tree. The Aspect CLI’s lint task drives this for you, so you don’t have to remember which aspects to apply or which output groups to request.
1
Add a test code violation
First, let’s add some intentionally problematic code to see the linter in action. Add this line to the end of This code has three issues:
packages/web/src/hello.ts:- The variable type is unnecessarily specified
- The variable should use
constinstead oflet - The variable is declared but never used
2
Run the linter
Lint everything beneath This exercise uses the Two of these have automatic fixes. Apply them with
//packages/ with the Aspect CLI:hard strategy so it works in a newly created repository that may not have a merge base yet.Bazel runs ESLint once per target, then the CLI collects the reports and prints each finding:aspect lint --fix --strategy=hard //packages/.... The command changes the line to const unused = 1;; rerun the linter to see the one remaining unused-variable finding.With the
tools/bazel wrapper installed, your team can keep typing bazel lint //packages/... and Bazelisk routes it through the Aspect CLI.Choosing which findings fail the command
The command above exited non-zero because thehard strategy fails on every error-severity finding. In day-to-day development you can omit --strategy=hard: aspect lint then uses the default hold-the-line strategy, which fails only on error-severity findings on lines you changed relative to the merge base. Existing violations elsewhere in the repository are still visible but do not block the pull request, so a team can adopt linting without cleaning up the whole codebase first. If no merge base is available, the CLI warns and falls back to hard rather than silently skipping findings.
Pick a different strategy with --strategy:
lint job in the starter’s .github/workflows/ci.yaml runs aspect lint //... with the default strategy, so a pull request fails only on the errors it introduced. See the lint task reference for the full set of options, including posting findings as pull request comments.
aspect lint wraps aspect_rules_lint’s aspects. If you can’t use the Aspect CLI, you can apply the aspect by hand with vanilla Bazel, bazel build —aspects=//tools/lint:linters.bzl%eslint —output_groups=rules_lint_human //packages/…, then read the per-target *.AspectRulesLintESLint.out report from bazel-bin. The CLI exists so you don’t have to.
