Proposing Lint Rules
Rule suggestions can start out as brief issues. If a maintainer thinks the rule could be a good
addition to Ruff, they will apply the needs-design label to suggest filling out the steps
described below in support of the rule's acceptance. Some of this information will be easier to
obtain with a working rule implementation and doesn't need to be present in the initial proposal.
A design for a new lint rule in Ruff should include the following components:
-
A proposed name that follows our rule naming convention
-
A proposed category that follows our rule categorization guidelines
-
A draft of the rule documentation with the following sections:
- "What it does": a one line description of what the rule checks
- "Why is this bad?": a longer explanation of the pattern flagged by the rule and why it causes problems in real projects
- "Example": a code example showing the problematic code, as well as a code block showing the fixed code
Some rules benefit from additional documentation. These sections usually emerge through the implementation process and aren't required in a design proposal. "Fix safety" and "Options" sections are required for rules with unsafe fixes and that rely on any settings, respectively, but the rest of these are fully optional. You can
grepfor each heading to see where they are often used.- "Known problems": any known limitations of the rule, such as false positives or negatives
- "Fix availability": if the rule only has an autofix in some cases, explain why
- "Fix safety": if the rule’s fix is ever unsafe, explain why
- "Options": if the rule depends upon any configuration options, list them
- "See also": if there are other similar or synergistic rules, list them
- "References": if there are any relevant external references to Python or other documentation, list them
A few examples of great rule documentation include
mutable-argument-default(B006),quoted-annotation(UP037), andused-dummy-variable(RUF052). -
An example diagnostic including the proposed name, primary message, and fix title (if applicable)
This is another nice bonus that isn't required for a design proposal but concisely reveals a lot of helpful information about a rule. For example:
When choosing a diagnostic range (marked by
^^^^above), also consider that the start of the range determines wherenoqacomments will be valid
Rule naming convention
Like Clippy, Ruff's rule names should make grammatical and logical sense when read as "ignore ${rule}" or "ignore ${rule} items", as in the context of suppression comments.
For example, AssertFalse fits this convention: it flags assert False statements, and so a
suppression comment would be framed as "ignore assert False".
As such, rule names should...
-
Highlight the pattern that is being linted against, rather than the preferred alternative. For example,
AssertFalseguards againstassert Falsestatements. -
Not contain instructions on how to fix the violation, which instead belong in the rule documentation and the
fix_title. -
Not contain a redundant prefix, like
DisalloworBanned, which are already implied by the convention.
When re-implementing rules from other linters, we prioritize adhering to this convention over preserving the original rule name.
Rule categorization guidelines
Choosing a category is a crucial part of the rule proposal and acceptance process. To paraphrase the Clippy documentation, if a rule doesn't fit in the categories, it probably doesn't fit in Ruff. Descriptions of each category can be found in the rule category documentation, but the flow chart below is intended to facilitate category assignment.
---
config:
flowchart:
nodeSpacing: 20
rankSpacing: 25
padding: 8
themeVariables:
fontSize: 13px
---
flowchart TD
A("Formatting, security,<br/>or language restriction?")
A -->|Yes| B["Formatting · Security · Restriction"]
A -->|No| C("Too noisy or opinionated?")
C -->|Yes| D["Pedantic"]
C -->|No| E("Incorrect or deprecated?")
E -->|Yes| F("Definitely wrong<br/>today?")
F -->|Yes| G["Correctness"]
F -->|No| H["Suspicious"]
E -->|No| I("Primary improvement?")
I -->|Simpler| J["Complexity"]
I -->|Faster| K["Performance"]
I -->|Idiomatic| L["Style"]
The first question filters out special categories of rules: those that relate to the visual
presentation of code (formatting), those that relate to security vulnerabilities, and those that
impose restrictions on language features. "Restriction" here specifically means an arbitrary or
severe restriction, not the broad way in which any lint rule could be considered to restrict usage.
Examples of restriction lints are rules like assert (S101) and print (T201), which ban basic
language features across the board.
If none of these special categories is quite right, the next question asks you to judge whether the
rule is too noisy or opinionated for general use. This is somewhat subjective, but an ecosystem
report can be helpful to see how many
diagnostics are emitted in real projects.
A large number of diagnostics doesn't immediately make a rule pedantic, but many false positives
or diagnostics that reasonable Python users would disagree with do.
If a rule is not overly pedantic, we next consider the intention of the rule. If the main goal is
detecting code that is incorrect, the options narrow to correctness or suspicious. Rules in the
correctness category typically cause immediate issues like syntax or runtime errors, or silently
do something the user didn't intend. Similarly, suspicious lints flag the same kind of code, but
in cases where Ruff can't be sure what the user intended. A perfect example of a suspicious rule
is mutable-argument-default (B006). This classic footgun is almost always a mistake, but in some
cases, it may be intentional, in which case a noqa or ruff: ignore comment should be used. Such
suppression comments should essentially never be reasonable for a correctness lint but are fine
for suspicious lints. The suspicious category also includes deprecations, which aren't incorrect
today but will cause errors in the future.
The final branch of the flow chart deals with stylistic lints, which are again somewhat subjective
to differentiate between changes that make code simpler often also make the code faster and
more idiomatic. Thus, the question prompts you to consider the primary improvement. Rules that
primarily make code simpler are complexity lints, those that primarily make code faster or use
less memory are performance lints, and those that primarily make code more idiomatic are style.
Other guidelines
Following these steps should generally ensure that a rule is a good fit for Ruff. A couple of additional things to watch out for are:
-
Rules that conflict with other tools, or especially other rules
Although we have many existing
formattingrules that overlap and even conflict with our formatter, we are not eager to add more. Similarly, we should avoid rules that mainly support type checker usage, when type checkers themselves emit similar diagnostics. Most clearly, we should avoid rules that overlap or conflict with other lint rules. This often suggests that the existing rule should instead be made configurable to toggle between the two behaviors.Checking both the input and output examples from your rule proposal with
ALLrules selected in the linter, with the formatter, and with a type checker like ty is a good quick check for conflicts. -
Rules that apply to third-party libraries
Most Ruff rules should be helpful for large numbers of Python developers. This means that rules should generally apply to Python language features or functionality from the standard library. However, rules for widely-used third-party libraries can also meet this bar and be good candidates for inclusion in Ruff.
-
Rules that require additional configuration
Most rules should function correctly once enabled without requiring additional settings. If this isn’t possible, the rule should typically “fail safe” and avoid emitting diagnostics.
banned-api(TID251) is an example of such a rule that has no effect without configuringlint.flake8-tidy-imports.banned-api. Avoid rules that emit a ton of diagnostics until some kind of allowlist is configured. -
Rules that are hard to explain
This guideline is inspired by ESLint’s “Generic” rule guideline:
Rules cannot be so specific that users will have trouble understanding when to use them. A rule is typically too specific if describing what it does requires more than two "and"s (if a and b and c and d, then this rule warns).
Watch out for this kind of pattern when writing your
Why is this bad?orKnown problemssection, or if you have a hard time categorizing the rule. Thepedanticcategory exists to hold rules that are controversial or niche, but very niche rules may still not be good fits for Ruff.