> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Announcing Linting for Bazel

> Bazel now offers linting with Aspect, boosting developer productivity through rules_lint and the Aspect CLI for seamless code analysis

export const BlogPost = ({title, date, authors, tags, image, children}) => {
  const tagList = tags ? tags.split(", ").filter(Boolean) : [];
  const tagSlug = t => t.toLowerCase().replace(/&/g, "").replace(/\+/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
  const formattedDate = date ? new Date(date + "T00:00:00").toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric"
  }) : "";
  return <section className="w-full flex justify-center px-4 py-12 md:py-16">
      <div style={{
    maxWidth: "800px",
    width: "100%"
  }}>
        {image && (typeof image === "string" ? <img noZoom src={image} alt={title} className="w-full rounded-xl mb-8" style={{
    maxHeight: "400px",
    objectFit: "cover"
  }} /> : <div className="blog-post-hero-image">{image}</div>)}
        <h1 className="text-3xl md:text-4xl font-bold text-zinc-900 dark:text-white">
          {title}
        </h1>
        <div className="flex flex-wrap items-center gap-3 mt-4 text-sm text-zinc-500 dark:text-zinc-400">
          {authors && <span>{authors}</span>}
          {authors && formattedDate && <span>·</span>}
          {formattedDate && <span>{formattedDate}</span>}
        </div>
        {tagList.length > 0 && <div className="flex flex-wrap gap-2 mt-3">
            {tagList.map(tag => <a key={tag} href={"/blog/tags/" + tagSlug(tag)} className="px-2 py-0.5 rounded-full text-xs bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-blue-100 dark:hover:bg-blue-900/40 hover:text-blue-700 dark:hover:text-blue-300 transition">
                {tag}
              </a>)}
          </div>}
        <hr className="my-8 border-zinc-200 dark:border-zinc-700" />
        <div className="prose dark:prose-invert max-w-none">{children}</div>
      </div>
    </section>;
};

export const MarketingPage = () => <div className="marketing-page-marker" style={{
  display: "none"
}} />;

export const Section = ({children, className = "", gray = false, dark = false, id}) => <section id={id} className={`w-full flex justify-center px-4 py-16 md:py-24 ${gray ? "bg-gray-50 dark:bg-zinc-900" : dark ? "bg-zinc-900 dark:bg-zinc-950" : ""} ${className}`}>
    <div className="w-full" style={{
  maxWidth: "1140px"
}}>
      {children}
    </div>
  </section>;

<MarketingPage />

<BlogPost title="Announcing Linting for Bazel" date="2024-09-11" authors="Alex Eagle" tags="Linting, Bazel">
  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-709935109c.png?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=dca4230f2e396f8f143690ab20459853" alt="" className="blog-post-cover" width="1600" height="840" data-path="images/blog/hashnode/hn-709935109c.png" />

  Aspect's mission is to make developers productive in large-scale polyglot repositories. We largely rely on the Bazel build system to power that productivity gain. But what happens when Bazel has a major missing feature that all developers need? There is a `bazel coverage` command for collecting Test Coverage, but no `bazel lint` command for running code analysis tools. Why?

  At Google, we built a separate system for this called Tricorder which integrates with the code review tool, Critique. You can read an academic paper about it:

  <a href="https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/43322.pdf">
    <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-0285c63727.png?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=efd2c42a9aa2c32d42ff13e43f3cea6e" alt="" className="block mx-auto" width="804" height="396" data-path="images/blog/hashnode/hn-0285c63727.png" />
  </a>

  However, most engineers and team leads aren't looking for research; they need solutions which are ready to deploy. When Bazel was open-sourced, this ecosystem for running linters wasn't included. Aspect has put these parts back together and we're very excited to share what we've built.

  The solution is in several layers, so I'll explore them one at a time.

  ## rules\_lint

  [rules\_lint](https://github.com/aspect-build/rules_lint) is the lowest layer in Aspect's linting support is a "ruleset" which you can think of as a plugin for Bazel. It's open-sourced under an Apache 2.0 license, because we want everyone in the Bazel ecosystem to be able to use and contribute to it!

  First we split out formatting as a special case. Formatters run on individual files, and the modifications they make are always safe to apply. In fact, engineers shouldn't even need to think about formatting: because they're guaranteed to be fast and only run on modified files, they can be in a pre-commit version control hook so they're run automatically by `git commit`. rules\_lint includes an aggregator rule, `format_multirun` that gives you a simple runnable command that formats any files you pass to it, regardless of language.

  Remaining linters may need to operate on a whole program, and their suggested fixes may require human review. For these we use Bazel's aspect feature, which is like Aspect-oriented programming: you can apply some logic across an existing object model. In Bazel's case this means running some tools over the existing dependency graph. That's perfect for linting, since you already declared your "library targets" to Bazel. We just need to visit them.

  The [1.0 release](https://github.com/aspect-build/rules_lint/releases) of rules\_lint includes a TON of tool integrations already, and more are added all the time, thanks to our design that requires a minimal layer of Bazel idioms on top of the tools themselves:

  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-9409fc3bdf.png?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=b537697fd8d92bf950a2fbbaa2ec5368" alt="" className="block mx-auto" width="526" height="934" data-path="images/blog/hashnode/hn-9409fc3bdf.png" />

  The rules\_lint layer concludes with a bare-bones developer experience for library code in all languages:

  1. Run `bazel build --config=lint //...` to produce lint reports.\
     (See the `build:lint` lines in [this `.bazelrc`](https://github.com/aspect-build/bazel-examples/blob/5c5cb8b1fae29d03ad802196be0e0b253bc9b4a4/.bazelrc#L11-L19) for the flags this config option expands to.)

  2. Print the resulting reports, for example a simplistic one-liner using `find` looks like: `find $(bazel info bazel-bin) -name "*AspectRulesLint*report" -exec cat {} \;`

  A slightly better way to view just the newly-produced reports is to make a script like [https://github.com/aspect-build/rules\_lint/blob/main/example/lint.sh](https://github.com/aspect-build/rules_lint/blob/main/example/lint.sh). However we can do much better, so keep reading.

  ## `aspect lint`

  [Aspect CLI](https://github.com/aspect-build/aspect-cli) is a replacement for the Bazel command-line interface. It has better usability, and a plugin model. It is available with an Apache-2 license, and the behavior matches `bazel` so it's safe to simply drop into your `.bazeliskrc` file to switch over for your whole team. In fact, our Homebrew formula installs the Aspect CLI as `bazel` on your path, just like the [officially recommended](https://bazel.build/install) Bazelisk package which we modeled after.

  It also lets us add the `lint` command that Bazel is missing!

  `aspect lint` picks up where we left off in rules\_lint. It can read the report files similarly to the `lint.sh` wrapper I linked to - but it can **also** read the suggested fixes produced by linter tools. When `lint` is running in an interactive terminal, it will prompt the user to accept the proposed patches from the linter tool (they can be previewed before apply if desired.) Alternatively like most linter tools, you can run `lint --fix` to request the fixes be applied.

  <a href="https://asciinema.org/a/673942">
    <img noZoom src="https://asciinema.org/a/673942.svg" alt="asciicast" className="block" />
  </a>

  ### Try it

  The easiest way to try rules\_lint and the `lint` command is to run `aspect init` to get a blank Bazel repository with formatting and linting already configured for the language(s) you code in.

  ## Lint in Code Review

  The two layers described so far give developers a local experience for linting their code and applying fixes. However most of us don't remember to run `lint` on our changes before we send them for review. In fact, it's often a waste of time, because there's no reason to polish code that's still a work-in-progress: we often refactor several times before getting to a shape that's ready to request feedback.

  The **typical options for integrating linting into the code review process are terrible**:

  1. Print warnings to the terminal during the build and test step on CI. The developer ignores them, especially when existing warnings are mixed with new ones. The code reviewer is unlikely to click into the CI logs to discover that warnings were printed. New warnings continue to be added to the codebase. I've been [writing about this](https://medium.com/@Jakeherringbone/warning-warnings-may-be-distracting-e6112cfb7cca) since 2016.

  2. Promote all lint warnings to errors. Developers are now forced to fix any linting violation, even if it is trivial, regardless of whether their code reviewer agrees. They quickly learn how to suppress the error by adding `//ignore` lines in their code. When you want to enable a new linter check, you're also forced to `//ignore` the existing violations in the codebase, or take the risk of editing code you don't understand. Linting is now equivalent to tests, which block merge when red.

  What if I told you there's a third way: the way we did it at Google. The linter should actually be re-thought, not as warnings, not as tests, but as code review comments.

  > Of course, you might still promote certain lint rules to errors, such as data tainting rules for security, and our tooling respects that and blocks the developer. Our goal is to support both warnings AND errors well.

  ## Marvin

  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-181dc31af6.jpeg?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=9b955b94411f7b6057f3338cac03de92" alt="" className="block mx-auto" width="200" height="200" data-path="images/blog/hashnode/hn-181dc31af6.jpeg" />

  Marvin is adorable, and he's your Bazel buddy. For linting, he acts like a code review bot. He uses rules\_lint but requests the machine-readable outputs. Then he shows up in your code review and presents the lint warnings on the lines you've changed, and suggests the fixes offered by the linter tool. Deciding how to act (or maybe not act) on the warnings is now a human task for the author and reviewer to perform, along with other things that come up during a review.

  This works for any language rules\_lint supports! Follow the links to the live PR on our examples repo where I made the code change.

  [Java, using PMD](https://github.com/aspect-build/bazel-examples/pull/342):

  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-b3fbbadd40.png?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=c0d1bba1905943c67c9f89674f3b5ede" alt="" className="block mx-auto" width="781" height="558" data-path="images/blog/hashnode/hn-b3fbbadd40.png" />

  [TypeScript, using ESLint](https://github.com/aspect-build/bazel-examples/pull/343/files#annotation_25632591416):\
  This demonstrates what happens if the user configures this rule as an error in the eslint config.

  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-844d740a4f.png?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=adb436805dd862a6dacff71cd70144d5" alt="" className="block mx-auto" width="920" height="852" data-path="images/blog/hashnode/hn-844d740a4f.png" />

  [Python, using Ruff:](https://github.com/aspect-build/bazel-examples/pull/344/checks?check_run_id=29395134023)

  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-41d92f0fd4.png?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=1b455f3a4e70980aaba1d6b5e252a514" alt="" className="block mx-auto" width="789" height="497" data-path="images/blog/hashnode/hn-41d92f0fd4.png" />

  [C++, using Clang-Tidy](https://github.com/aspect-build/bazel-examples/pull/354/files):

  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-c6158cff16.png?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=38eef56e8938c59f659c1146cb66f986" alt="" className="block mx-auto" width="985" height="386" data-path="images/blog/hashnode/hn-c6158cff16.png" />

  ### Try it

  The screenshots above are from our "kitchen sink" monorepo, [github.com/aspect-build/bazel-examples](https://github.com/aspect-build/bazel-examples). Try editing some code to produce linter warnings. You can interact with Marvin by sending a PR to the repository, and watch the automation run!

  ## Sign up today!

  We offer a free trial of our Workflows solution for Bazel CI/CD which includes Marvin in the latest release.
</BlogPost>
