Open-Source Wikis

/

Ruff

/

Ruff and ty

/

Architecture

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 --> Db

How 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 output

ruff 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 producing ruff_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 by noqa parsing.
  • ruff_python_semantic — name binding, scope resolution, branch prediction, and the SemanticModel consumed 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_dbSystem abstraction (memory, native, WASM file systems), Files, line tables, source ranges, and the Salsa Database traits ty depends on.
  • ruff_diagnostics + ruff_annotate_snippets — diagnostic types, severity, fixes, and rich terminal renderer (forked from annotate-snippets-rs).
  • ruff_workspace — the strongly-typed Settings struct, resolution from pyproject.toml / ruff.toml, and per-file overrides.
  • ruff_macros — proc macros used to derive rule metadata, configuration option metadata, and codegen.

Important boundaries

  • ruff_linter does not depend on ruff_db or Salsa. It is a one-shot, file-by-file linter parallelized via rayon. 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.

Architecture – Ruff wiki | Factory