denoland/deno

A ~3,000-LoC `tools/lint.js` orchestrator running 9 logical structural checks — about half of which collapse into one declarative `.alint.yml`.

Narrative
Other case studies
Rules
76
Last revalidated
Engineering reference
README on GitHub · .alint.yml

Why this case study matters

Deno has a Rust core + JavaScript/TypeScript tooling layout: ~75 Cargo workspace members under cli/, ext/, libs/, runtime/, tests/, plus a ~3,000-LoC orchestrator at tools/lint.js and a sibling tools/format.js that drive every CI gate. The orchestrator runs 9 logical structural checks end-to-end (alongside clippy, dprint, dlint — the lint engines, which alint shells out to via command: rather than competing with).

About 45% of the structural surface maps directly to declarative alint rules — copyright headers per language, top-level-files allowlist, clippy.toml-per-crate pairing, workflow-generator pairing, dprint/dlint config shape, file-size guard, .gitattributes LF enforcement. Roughly 30% needs new alint primitives (the AST-aware checks and the per-file violation-count baseline). The remaining 25% is out of alint’s scope by design.

Headline catch

Replacing 11 of tools/lint.js’s structural sub-checks plus the bundled OSS / Rust / Node / GHA / Cargo-workspace baselines with one declarative config + one alint check invocation in CI is the win: fewer moving parts, one place to look when CI breaks, and much easier for forks to keep their hygiene rules consistent with upstream Deno.

The cleanest cross-language pairing in this repo is the workflow-generator pair: every .github/workflows/*.ts generator has a paired .generated.yml checked in. The case-study config captures it explicitly via alint’s pair rule.

The clippy.toml-per-crate pattern is a second high-leverage shape: every ext/* and libs/* Cargo workspace crate ships a clippy.toml with a known list of ~30 banned std::fs::* / std::path::Path::* / url::Url::* methods. The structural piece (file exists per crate, contains certain strings) is in alint today via for_each_dir + when_iter. The content-completeness check (“contains every entry of a 30-method list”) collapses to one rule per entry today, and becomes one rule once the v0.10 disallowed_methods_in_file primitive ships.

Where alint earns its keep here

Two themes from this case study are load-bearing for alint’s broader positioning:

  1. The “language-AST query” boundary is real and worth documenting up front. Deno hits it twice: the Rust short-flag enforcement in cli/args/flags.rs (parses Rust to find every .short('X') call) and the TypeScript JSDoc-tag check in tools/jsdoc_checker.js (walks a TypeScript AST with ts-morph). alint’s deliberate non-goal: AST analysis. The right messaging is “alint validates structure; if you need to reach into the language AST, keep your existing tool and wrap it via a command rule. The boundary is deliberate.”

  2. The baselined-drift primitive (lintNodePolyfillDenoApis) is the single most-load-bearing missing rule kind for projects mid-migration. It runs deno lint with a custom plugin against ext/node/polyfills/**/*.ts, counts the violations, compares per-file count against an EXPECTED_VIOLATIONS baseline (drift up = error, drift down = error with message “update the baseline”). Same shape shows up for restricted-imports in Kubernetes and Python type-coverage in many other migration efforts. Worth a dedicated v0.10+ design pass: a violation_baseline rule kind that wraps a child command, parses output, diffs against a per-file snapshot.

Deno also surfaces three cross-cutting v0.10 candidates that have firmed up to ship-target: *_path_contains (third confirmation across helm + deno + bazel), the monorepo/cargo-workspace member-discovery refinement (deno’s ext/* + libs/* + runtime/* + cli/* layout doesn’t fit the hardcoded crates/* selector), and the dir_only_contains subdir-checking flag for the top-level allowlist.

Future story angles

The factual engineering writeup (tooling inventory, mapping table, gap catalogue, validation status footer) lives in the public alint repo at github.com/asamarts/alint/tree/main/examples/denoland-deno/README.md.