Open-Source Wikis

/

Ruff

/

Features

/

Autofix

astral-sh/ruff

Autofix

Ruff's automatic correction system: how rules emit fixes, how they're applied, and how safety is communicated.

What the user sees

ruff check --fix path/to/code/         # apply safe fixes
ruff check --fix --unsafe-fixes path/  # also apply unsafe fixes
ruff check --fix --diff path/          # preview without writing

A diagnostic with [*] in the output means a fix is available. The Ruff LSP exposes the same fixes as code actions.

Anatomy of a fix

A Fix carries:

  • A list of Edits (insertions, deletions, replacements) that combine into a single semantic change.
  • An Applicability:
    • Always — safe; applied with --fix.
    • Sometimes — unsafe; requires --unsafe-fixes.
    • DisplayOnly — never auto-applied; suggestion only.
  • An optional message describing the change.

All defined in ruff_diagnostics.

How rules build fixes

Most rules construct edits from byte ranges. A simple "remove unused import" fix removes the import statement and any trailing comma:

let edit = Edit::range_deletion(stmt.range());
let fix = Fix::safe_edit(edit);
diagnostic.set_fix(fix);

For more complex rewrites that need to emit syntax (e.g. PEP 604 conversion of Optional[int] to int | None), rules call into ruff_python_codegen to render fragments of AST back to source.

When a fix needs to add an import, ruff_python_importer handles insertion into the right block, respecting existing imports and isort-style grouping.

How fixes are applied

crates/ruff_linter/src/fix/ implements:

  1. Conflict detection. Two fixes that touch overlapping ranges can't be applied together; the linter picks one and re-runs.
  2. Iterative application. After applying a batch of fixes, the file is re-linted. New diagnostics may appear (e.g. now-unused imports surfaced after removing a function call). The loop continues until convergence or a max iteration count.
  3. Safety filtering. Unsafe fixes are skipped unless --unsafe-fixes is set.

Safety policy

A fix is safe when:

  • It preserves runtime behavior for all valid inputs.
  • It doesn't depend on assumptions about external state (file system, network, custom __import__, etc.).
  • The user would not be surprised by the change.

Removing an import that has no side effects is safe. Removing an import that might have side effects is unsafe. Promoting a fix from unsafe to safe is a behavior change and is reviewed carefully.

In the LSP

crates/ruff_server/src/fix.rs translates fixes into LSP CodeActions. Editors receive separate actions for "apply safe fix", "apply unsafe fix" (clearly labeled), and "fix all in file".

Notebook caveats

When applying fixes to Jupyter notebooks, edits are mapped back to the original cell coordinates by ruff_notebook. A fix that spans multiple cells is rejected (this would change cell boundaries).

  • noqa lint codes (RUF100 for unused noqa) interact with fixes — a fix can update or remove a noqa directive.
  • Some lint rules only make sense with a fix attached (e.g. flake8-comprehensions); they emit Sometimes fixes when behavior preservation can't be guaranteed.

Where to look in the code

  • Fix engine: crates/ruff_linter/src/fix/
  • Fix data type: crates/ruff_diagnostics
  • Renderer / preview: crates/ruff/src/printer.rs (--diff)
  • LSP integration: crates/ruff_server/src/fix.rs
  • Imports manipulation: crates/ruff_python_importer/
  • AST → source: crates/ruff_python_codegen/

For configuration, see fixable / unfixable / extend-fixable in reference/configuration.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Autofix – Ruff wiki | Factory