dylint_linting
Macros and config helpers for authoring custom Dylint lint libraries without boilerplate.
Repository Health
Technical Analysis
dylint_linting is the authoring toolkit inside the Dylint project (Trail of Bits’ framework for running Rust lints from dynamic libraries). Where a raw Dylint library has to hand-wire the FFI entry point, register a LintPass with rustc’s LintStore, and declare the lint itself, dylint_linting collapses that into a handful of macros: dylint_library! for the exported dylint_version symbol, declare_late_lint!/declare_early_lint!/declare_pre_expansion_lint! for single-lint libraries, and the impl_*_lint! variants when the LintPass struct needs custom construction (for example, to hold parsed configuration).
The crate also owns Dylint’s per-workspace configuration story. Libraries that need settings read from a target repository’s dylint.toml file call config_or_default, config, or config_toml, all built on init_config/try_init_config plus TOML deserialization via dylint_internal::config. A constituent feature flag changes what the macros expand to: with it off, a lint compiles as a standalone cdylib Dylint can load directly; with it on, the same source drops the #[no_mangle] FFI plumbing so it can be folded into a larger aggregate library (which is exactly how the examples/general and examples/supplementary libraries in the parent monorepo are structured).
Because it links against rustc’s internal rustc_driver, rustc_lint, and rustc_session crates, dylint_linting only builds on the nightly toolchain with rustc-dev and llvm-tools-preview components — the same driver Dylint itself uses to run the compiled libraries, so a library written with dylint_linting and dylint-driver stay compiler-version-locked to each other.
What You Get
dylint_library!- expands to the#[no_mangle] extern "C" fn dylint_version()export that lets Dylint’s driver identify and load the compiledcdylibat runtime.declare_late_lint!/declare_early_lint!/declare_pre_expansion_lint!- one-macro path for single-lint libraries: declares the lint, generates itsLintPass, and registers it, mirroringrustc_session::declare_lint!plusdeclare_lint_pass!.impl_late_lint!/impl_early_lint!/impl_pre_expansion_lint!- same registration wiring as thedeclare_*macros but takes a caller-suppliedLintPassvalue instead of an empty struct, needed when the pass carries state such as parsed configuration.constituentfeature - toggles whether the macros emit the FFI entry point and#[no_mangle]attribute, letting one lint’s source compile either standalone or as part of a combined library alongside other lints.config_or_default/config/config_toml- read and deserialize a named entry from the target workspace’sdylint.tomlfile, withconfig_or_defaultfalling back toT::default()when the file or key is absent.init_config/try_init_config- must run (via the generatedregister_lintsfunction, or manually for multi-lint libraries) before any of the config-reading functions can resolve the target workspace’sdylint.tomlpath.
Common Use Cases
- Writing a single custom lint - a team uses
cargo dylint newto scaffold a library, thendeclare_late_lint!orimpl_late_lint!to wire the generatedLintPassintoregister_lintswithout hand-writing the FFI export. - Building a configurable lint - a library author exposes tunables (e.g. a
work_limitor an allowlist) by deserializing aConfigstruct fromdylint.tomlviaconfig_or_default, read once when theLintPassis constructed. - Bundling many lints into one aggregate library - the
constituentfeature lets each lint in a collection (as done in Dylint’s ownexamples/generallibrary) compile as a standalone crate for testing and also compile mangle-free when combined into a single shared library. - Porting Clippy-style lints into a private collection - teams that want organization-specific restriction lints without maintaining a Clippy fork use dylint_linting’s macros to keep new lints structurally close to
rustc_session::declare_lint!idioms, minimizing the delta from upstream lint-writing docs.
Under The Hood
Architecture
dylint_linting is a macro-heavy, thin wrapper around rustc’s own lint-registration APIs rather than an independent runtime. Its macros (dylint_library!, the declare_*_lint! family, and the impl_*_lint! family) expand at the call site into: an FFI-exported dylint_version function, an extern crate rustc_lint/rustc_session declaration, a #[no_mangle] register_lints function that calls init_config and registers the pass with rustc’s LintStore, and a rustc_session::declare_lint!/declare_lint_pass! or impl_lint_pass! call for the pass itself. The constituent feature is implemented via two paired macro variants, __maybe_exclude! and __maybe_mangle! (selected through #[cfg(feature = "constituent")]), which conditionally drop the FFI plumbing so the exact same lint source can be compiled as either a standalone cdylib or folded into a larger aggregate library — this is the mechanism the parent repository’s examples/general and examples/supplementary libraries depend on. Configuration reading is layered separately: config_toml walks up from the target workspace to find dylint.toml, parses it once via dylint_internal::config, and caches it behind init_config/try_init_config so later config/config_or_default calls are cheap lookups rather than repeated file reads.
Tech Stack
The crate targets Rust’s 2024 edition and only builds on the nightly toolchain (pinned via rust-toolchain.toml with the rustc-dev and llvm-tools-preview components), because it links directly against rustc’s internal rustc_driver, rustc_lint, rustc_session, rustc_data_structures, and rustc_span crates behind the rustc_private feature gate. Runtime dependencies are deliberately small: dylint_internal (shared config/env/path utilities within the Dylint monorepo), toml and serde for dylint.toml deserialization, paste for generating CamelCase struct identifiers from lint names inside the macros, rustversion for conditionally compiling around a specific rustc release date, and thiserror for the crate’s ConfigError type. A build.rs script (using cargo_metadata and toml) runs at compile time. Dev-dependencies (assert_cmd, rustc_version, tempfile) back a small integration-test suite that mostly asserts the toolchain channel is nightly and that the crate still builds under --cfg docsrs.
Code Quality
The crate is exhaustively doc-commented — nearly every public macro and function carries doc-comments explaining preconditions (for example, that init_config must run before config_or_default), which the project also re-exports as its published README via cargo-rdme. Its own automated tests are thin (an integration test file asserting the nightly channel and one --cfg docsrs build check, the latter currently #[ignore]d due to an upstream nightly regression), but the crate’s real correctness coverage comes from the parent dylint monorepo’s broader CI matrix (ci.yml runs across ubuntu/macos/windows and exercises the example lint libraries, which are themselves built with these macros) rather than unit tests local to this crate. Error handling for config parsing goes through a typed ConfigError (via thiserror) surfaced as a ConfigResult, and the workspace enforces #[lints.rust.unexpected_cfgs] at deny level to keep the dylint_lib cfg-based conditional-compilation trick from silently breaking.
What Makes It Unique
Most lint-authoring helpers either fork an existing linter’s internals or require reimplementing rustc’s LintPass/LintStore wiring by hand each time. dylint_linting’s differentiator is the constituent feature: a single flag flips the same lint source between “standalone loadable library” and “folded into a shared aggregate library” without source-level changes, which is what lets the sibling examples/ directory in the Dylint repository maintain dozens of individually testable lints that also ship as one combined library for end users. Combining that with first-class, per-target-workspace dylint.toml configuration (rather than compile-time cargo features) gives lint authors a way to ship tunable lints to consumers who never touch the lint’s own source tree.
Used by 2 apps in this directory
codex
AI Code Assistants · Developer Tools
OpenAI's open-source CLI coding agent that reads, edits, and runs code in your terminal using natural language prompts.
Zed
Developer Tools · Collaboration · Code Editors
High-performance, multiplayer code editor built in Rust by the creators of Atom and Tree-sitter, with native AI integration and real-time collaboration.