astral-sh/ruff
Patterns and conventions
What the codebase expects of new code. The bulk of these come from AGENTS.md, CONTRIBUTING.md, and reviewer custom; a few are inferred from how the code is shaped.
Rust style
- Edition 2024, MSRV 1.93 (see
Cargo.tomlandrust-toolchain.toml). - Imports at the top of the file, never inside functions. The pre-commit hook will flag this; reviewers will too.
- Prefer let chains (
if let Pat = expr && cond) over nestedif letto flatten control flow. - Sentence case in headings in docs and
///comments. #[expect(lint)]over#[allow(lint)]when you must suppress a Clippy/rustc warning. If a warning is about unused/dead code, prefer to delete the code.- Comments are for invariants and rationale, not narration. Don't write
// increment x.
Avoid panics
The repo's stance is firm. From AGENTS.md:
Try hard to avoid patterns that require
panic!,unreachable!, or.unwrap(). Instead, try to encode those constraints in the type system. Don't be afraid to write code that's more verbose or requires largeish refactors if it enables you to avoid these unsafe calls.
In practice:
- Return
Option/Resultrather than panicking on missing data. - Use enums to model exhaustive choices, so the compiler enforces match completeness.
- If you really do have a "should never happen" branch, prefer
tracing::error!+ a sensible fallback overpanic!.
Avoid writing too much new code
Also from AGENTS.md:
Avoid writing significant amounts of new code. This is often a sign that we're missing an existing method or mechanism that could help solve the problem. Look for existing utilities first.
Before introducing a new helper, search for a similar one. The ruff_python_ast::helpers module, the SemanticModel, and ruff_python_semantic::analyze modules contain a lot of utilities that beginners often re-implement.
Diagnostics
- Concise messages. From
AGENTS.md: "Think about how an error message would look on a narrow terminal screen." Long detail belongs in subdiagnostics or annotations. - Stable rule codes. Once a rule code is published, it's a public API. Renumbering or repurposing is a breaking change.
- Severity — most rules are
Warning; reserveErrorfor things that would prevent the file from running.
AST navigation
- The
ruff_python_ast::Visitorandruff_python_ast::visitor::transformer::Transformertraits are the canonical way to walk and rewrite trees. New rules typically don't write a custom visitor; they hook intocrates/ruff_linter/src/checkers/ast.rs. ruff_python_ast::helpershasis_const_none,is_dunder,pep_604_union,map_subscript, etc. — search before reimplementing.- Use
TextRangefromruff_text_sizefor spans. Never compute byte offsets by hand.
Semantic model usage
Lint rules access the semantic model via Checker::semantic(). Common patterns:
- Resolve a name to its binding:
semantic.resolve_name(expr_name). - Walk to the enclosing scope:
semantic.scope().kind— distinguishes module / function / class / lambda / type-param scopes. - Check whether a call resolves to a typing import:
semantic.match_typing_expr(...)and friends inruff_python_semantic::analyze::typing.
Salsa rules (ty)
From AGENTS.md:
Salsa incrementality (ty): Any method that accesses
.node()must be#[salsa::tracked], or it will break incrementality. Prefer higher-level semantic APIs over raw AST access.
This is the single most common review comment on ty PRs. When in doubt, look for a tracked function that already returns what you need.
Configuration options
- New options live in
ruff_workspace::Settings(or a sub-struct). - Options are documented in their
derive(Options)macro attributes (seecrates/ruff_options_metadata). - After adding an option: run
RUFF_UPDATE_SCHEMA=1 cargo test(orcargo dev generate-all) to regenerateruff.schema.json. - Avoid breaking changes to existing options. Deprecate first, then remove in a major version.
Naming
- Crates:
ruff_*for Ruff-side code,ty_*for ty-side code. Shared crates use theruff_prefix and are imported by ty. - Rules:
<plugin>/rules/<rule_name>.rs, with the function name matching the rule's snake_case identifier. - Test files:
crates/<crate>/tests/<feature>.rsfor integration tests; inline#[cfg(test)]modules for unit tests.
Documentation
- New rules: write a triple-slash doc on the rule's struct describing What it does, Why it's bad, Example, and (where relevant) Options. The mkdocs site and the
--explainCLI both read from these. - Don't update
README.mdordocs/to reflect implementation details; reviewers will steer you to the right place. - ty's contributing notes (
crates/ty/CONTRIBUTING.md) cover ty-specific style.
Pull request etiquette
- Small, focused PRs.
- One rule, one option, one ty check per PR — usually.
- ty PRs: title prefix
[ty], labeltyon GitHub. - Run
uvx prek run -aafter every rebase and after addressing review comments. - Don't push generated files that haven't been regenerated; CI will tell you if they drifted.
What good looks like
A typical "add a new lint rule" PR contains:
- The rule implementation under
crates/ruff_linter/src/rules/<plugin>/rules/<rule>.rs. - A registration in
crates/ruff_linter/src/codes.rs. - A fixture under
crates/ruff_linter/resources/test/fixtures/<plugin>/<rule>.py. - A snapshot test that asserts the diagnostics, and the resulting
.snapfile. - Generated artifacts (
ruff.schema.json, docs) updated bycargo dev generate-all. - A changelog entry (added to the unreleased section).
Reviewers will check the diff for all six.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.