Project2024–presentActive

HTML Linter

A Rust CLI and library that lints HTML against JSON-defined rules, published to crates.io.

The story

I've always believed HTML deserves the same rigor we give JavaScript and CSS. ESLint changed how people write JavaScript; Stylelint catches CSS problems before they ship. HTML — the skeleton everything else hangs on — mostly gets checked by eye.

The existing options felt incomplete. Browser validators focus on spec compliance rather than best practices, editor plugins work in isolation with no way to run the same checks in CI, and few tools made it easy to define your own rules.

I also wanted an excuse to learn Rust properly. A parser was the ideal project: memory safety mattered, performance was measurable, and the domain was familiar enough that I could spend my attention on the language instead of the problem.

Building a tree-walking linter turned out to be a good way in. Most of the work is holding a parsed tree and asking questions of every node, and Rust's ownership rules made me think harder than I usually would about who owned that tree and for how long. Pattern matching made the rule checks read almost like their own descriptions. I argued with the borrow checker for the first few weeks; after that it mostly went quiet.

The result is a CLI and library that lints HTML against rules defined in JSON. It's published tocrates.io, the API docs live ondocs.rs, and the source is onGitHub. Of the projects on this site, it's the one I still pick up.

How it works

Rules are JSON documents rather than code. Each rule names what to look for and what to report when the check fails, and the built-in checks cover structural problems like missing alt attributes, non-semantic markup where semantic elements belong, and missing or malformed meta tags.

The linter parses a document into a tree, walks it, and evaluates each rule against the nodes it applies to. It runs as a standalone CLI, which is the point: the same checks run unchanged in a terminal or a CI pipeline, so structural problems get caught before they reach production. The same code is also usable as a library from other Rust programs — that's the crate on crates.io.

Demo

Ambient illustration — canned example, not a live linter
HTML Linter
1<div class="container">
2 <h1>Welcome!</h1>
3 <p>This is a sample<p>
4 <div>Nested content
5 <span>More text</div>
6 </span>
7 <img src="photo.jpg">
8</div>
Unclosed paragraph tagLine 3
Mismatched closing tagsLine 5
<img> is missing an alt attributeLine 7
The snippet, the sweep, and the problems list are hardcoded; the real linter runs as a command-line tool.

Artifacts & further reading

  1. [1]GitHubgithub.com
  2. [2]crates.iocrates.io
  3. [3]docs.rsdocs.rs