astral-sh/ruff
Architecture
Ruff and ty are layered tools. Both consume Python source code, parse it into a typed AST, and then run domain-specific analyses on top. The parser, AST definitions, semantic model, and database layer are shared.
Top-level layout
graph TD
subgraph Binaries
RuffCLI[ruff CLI<br/>crates/ruff]
TyCLI[ty CLI<br/>crates/ty]
RuffServer[ruff_server<br/>LSP for Ruff]
TyServer[ty_server<br/>LSP for ty]
Wasm[ruff_wasm / ty_wasm<br/>browser playground]
end
subgraph Domain
Linter[ruff_linter<br/>900+ lint rules]
Formatter[ruff_python_formatter<br/>code formatter]
TySemantic[ty_python_semantic<br/>type inference + checks]
end
subgraph Shared
Parser[ruff_python_parser]
AST[ruff_python_ast]
Sem[ruff_python_semantic<br/>name resolution / scopes]
Db[ruff_db<br/>files, system, salsa db]
Diag[ruff_diagnostics<br/>ruff_annotate_snippets]
Workspace[ruff_workspace<br/>config + settings]
end
RuffCLI --> Linter
RuffCLI --> Formatter
RuffCLI --> Workspace
RuffServer --> Linter
RuffServer --> Formatter
TyCLI --> TySemantic
TyServer --> TySemantic
Wasm --> Linter
Wasm --> Formatter
Linter --> AST
Linter --> Sem
Linter --> Diag
Formatter --> AST
TySemantic --> AST
TySemantic --> Db
TySemantic --> Diag
AST --> Parser
Sem --> AST
Workspace --> DbHow a ruff check request flows
sequenceDiagram
actor User
participant CLI as ruff (crates/ruff)
participant Workspace as ruff_workspace
participant Linter as ruff_linter
participant Parser as ruff_python_parser
participant Checkers as linter/checkers/*
participant Diag as ruff_diagnostics
User->>CLI: ruff check path/
CLI->>Workspace: resolve config (pyproject.toml / ruff.toml)
CLI->>CLI: discover .py / .pyi / .ipynb files
loop per file (rayon parallel)
CLI->>Parser: tokenize + parse to AST
Parser-->>CLI: Mod (untyped AST)
CLI->>Linter: check_path(file, settings)
Linter->>Checkers: run AST/tokens/imports/physical/logical line checkers
Checkers->>Diag: emit Diagnostic + optional Fix
Linter-->>CLI: Vec<Diagnostic>
end
CLI->>CLI: aggregate, apply --fix if requested, sort
CLI-->>User: textual / JSON / GitHub / SARIF outputruff check is the most-used pipeline. The same files (crates/ruff/src/commands/) coordinate format, analyze graph, server, clean, and config subcommands. See features/linter and features/formatter for end-to-end traces.
How a ty check request flows
ty is built around Salsa for incremental, demand-driven analysis. Instead of running checkers in a fixed order, ty computes "tracked queries" (e.g. infer the type of expression e in file f) and Salsa caches them by their inputs.
graph LR
Files[Files: ty_project] --> Db[ProjectDatabase<br/>ty_python_semantic::db]
Db -->|salsa::tracked| Index[ruff_python_index<br/>scopes, definitions]
Db -->|salsa::tracked| Inference[ty_python_semantic::types<br/>infer_definition_types,<br/>infer_expression_type]
Inference --> Checks[ty_python_semantic::lint<br/>diagnostic registries]
Checks --> Output[Diagnostics]Salsa incrementality is critical: any function that touches an AST node must be #[salsa::tracked], otherwise edits will not invalidate downstream queries. This invariant is documented in AGENTS.md and reinforced by code review.
Shared infrastructure
Several crates are reused by both products:
ruff_python_parser— handwritten Python parser producingruff_python_ast::Mod. Supports CPython 3.6+ syntax and is permissive enough to recover from common errors.ruff_python_ast— typed AST + visitor traits + helper utilities (ast::ExprName,ast::StmtFunctionDef, etc.).ruff_python_trivia— comment/whitespace lexing, used by the formatter and bynoqaparsing.ruff_python_semantic— name binding, scope resolution, branch prediction, and theSemanticModelconsumed by lint rules.ruff_python_index— pre-pass that records definitions and references.ruff_python_codegen— round-trip a small subset of AST nodes to source (used by autofixes).ruff_db—Systemabstraction (memory, native, WASM file systems),Files, line tables, source ranges, and the SalsaDatabasetraits ty depends on.ruff_diagnostics+ruff_annotate_snippets— diagnostic types, severity, fixes, and rich terminal renderer (forked fromannotate-snippets-rs).ruff_workspace— the strongly-typedSettingsstruct, resolution frompyproject.toml/ruff.toml, and per-file overrides.ruff_macros— proc macros used to derive rule metadata, configuration option metadata, and codegen.
Important boundaries
ruff_linterdoes not depend onruff_dbor Salsa. It is a one-shot, file-by-file linter parallelized viarayon. This keeps Ruff fast and lock-free for the common case (lint a directory once).- ty is built on Salsa and a
System-abstracted file layer because incremental and language-server use cases require demand-driven recomputation. - The two systems intentionally share lower-level crates (parser, AST, trivia, semantic) but diverge above them.
Where to dig deeper
| If you want to understand… | Read |
|---|---|
| How rules are registered and selected | crates/ruff_linter |
| How the formatter turns AST into output | crates/ruff_python_formatter |
| ty's type system and inference | crates/ty_python_semantic |
| LSP wiring | crates/ruff_server, crates/ty_server |
| Configuration plumbing | crates/ruff_workspace, reference/configuration |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.