@@ -8,6 +8,7 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
8
8
- [ Project Structure] ( #project-structure )
9
9
- [ Example: Adding a new lint rule] ( #example-adding-a-new-lint-rule )
10
10
- [ Rule naming convention] ( #rule-naming-convention )
11
+ - [ Rule testing: fixtures and snapshots] ( #rule-testing-fixtures-and-snapshots )
11
12
- [ Example: Adding a new configuration option] ( #example-adding-a-new-configuration-option )
12
13
- [ MkDocs] ( #mkdocs )
13
14
- [ Release Process] ( #release-process )
@@ -93,24 +94,32 @@ At time of writing, the repository includes the following crates:
93
94
94
95
- ` crates/ruff ` : library crate containing all lint rules and the core logic for running them.
95
96
- ` crates/ruff_cli ` : binary crate containing Ruff's command-line interface.
96
- - ` crates/ruff_dev ` : binary crate containing utilities used in the development of Ruff itself (e.g., ` cargo dev generate-all ` ).
97
+ - ` crates/ruff_dev ` : binary crate containing utilities used in the development of Ruff itself (e.g.,
98
+ ` cargo dev generate-all ` ).
97
99
- ` crates/ruff_macros ` : library crate containing macros used by Ruff.
98
- - ` crates/ruff_python ` : library crate implementing Python-specific functionality (e.g., lists of standard library modules by versionb).
100
+ - ` crates/ruff_python ` : library crate implementing Python-specific functionality (e.g., lists of
101
+ standard library modules by version).
99
102
- ` crates/flake8_to_ruff ` : binary crate for generating Ruff configuration from Flake8 configuration.
100
103
101
104
### Example: Adding a new lint rule
102
105
103
106
At a high level, the steps involved in adding a new lint rule are as follows:
104
107
105
108
1 . Determine a name for the new rule as per our [ rule naming convention] ( #rule-naming-convention ) .
109
+
106
110
1 . Create a file for your rule (e.g., ` crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs ` ).
111
+
107
112
1 . In that file, define a violation struct. You can grep for ` #[violation] ` to see examples.
108
- 1 . Map the violation struct to a rule code in ` crates/ruff/src/registry.rs ` (e.g., ` E402 ` ).
109
- 1 . Define the logic for triggering the violation in ` crates/ruff/src/checkers/ast/mod.rs ` (for AST-based
110
- checks), ` crates/ruff/src/checkers/tokens.rs ` (for token-based checks), ` crates/ruff/src/checkers/lines.rs `
111
- (for text-based checks), or ` crates/ruff/src/checkers/filesystem.rs ` (for filesystem-based
112
- checks).
113
- 1 . Add a test fixture.
113
+
114
+ 1 . Map the violation struct to a rule code in ` crates/ruff/src/codes.rs ` (e.g., ` E402 ` ).
115
+
116
+ 1 . Define the logic for triggering the violation in ` crates/ruff/src/checkers/ast/mod.rs ` (for
117
+ AST-based checks), ` crates/ruff/src/checkers/tokens.rs ` (for token-based checks),
118
+ ` crates/ruff/src/checkers/lines.rs ` (for text-based checks), or
119
+ ` crates/ruff/src/checkers/filesystem.rs ` (for filesystem-based checks).
120
+
121
+ 1 . Add proper [ testing] ( #rule-testing-fixtures-and-snapshots ) for your rule.
122
+
114
123
1 . Update the generated files (documentation and generated code).
115
124
116
125
To define the violation, start by creating a dedicated file for your rule under the appropriate
@@ -125,18 +134,8 @@ collecting diagnostics as it goes.
125
134
If you need to inspect the AST, you can run ` cargo dev print-ast ` with a Python file. Grep
126
135
for the ` Check::new ` invocations to understand how other, similar rules are implemented.
127
136
128
- To add a test fixture, create a file under ` crates/ruff/resources/test/fixtures/[linter] ` , named to match
129
- the code you defined earlier (e.g., ` crates/ruff/resources/test/fixtures/pycodestyle/E402.py ` ). This file should
130
- contain a variety of violations and non-violations designed to evaluate and demonstrate the behavior
131
- of your lint rule.
132
-
133
- Run ` cargo dev generate-all ` to generate the code for your new fixture. Then run Ruff
134
- locally with (e.g.) ` cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402 ` .
135
-
136
- Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
137
- ` test_case ` macro in the relevant ` crates/ruff/src/rules/[linter]/mod.rs ` file. Then, run ` cargo test ` .
138
- Your test will fail, but you'll be prompted to follow-up with ` cargo insta review ` . Accept the
139
- generated snapshot, then commit the snapshot file alongside the rest of your changes.
137
+ Once you're satisfied with your code, add tests for your rule. See [ rule testing] ( #rule-testing-fixtures-and-snapshots )
138
+ for more details.
140
139
141
140
Finally, regenerate the documentation and generated code with ` cargo dev generate-all ` .
142
141
@@ -154,6 +153,38 @@ This implies that rule names:
154
153
When re-implementing rules from other linters, this convention is given more importance than
155
154
preserving the original rule name.
156
155
156
+ #### Rule testing: fixtures and snapshots
157
+
158
+ To test rules, Ruff uses snapshots of Ruff's output for a given file (fixture). Generally, there
159
+ will be one file per rule (e.g., ` E402.py ` ), and each file will contain all necessary examples of
160
+ both violations and non-violations. ` cargo insta review ` will generate a snapshot file containing
161
+ Ruff's output for each fixture, which you can then commit alongside your changes.
162
+
163
+ Once you've completed the code for the rule itself, you can define tests with the following steps:
164
+
165
+ 1 . Add a Python file to ` crates/ruff/resources/test/fixtures/[linter] ` that contains the code you
166
+ want to test. The file name should match the rule name (e.g., ` E402.py ` ), and it should include
167
+ examples of both violations and non-violations.
168
+
169
+ 1 . Run Ruff locally against your file and verify the output is as expected. Once you're satisfied
170
+ with the output (you see the violations you expect, and no others), proceed to the next step.
171
+ For example, if you're adding a new rule named ` E402 ` , you would run:
172
+
173
+ ``` shell
174
+ cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache
175
+ ```
176
+
177
+ 1 . Add the test to the relevant ` crates/ruff/src/rules/[linter]/mod.rs ` file. If you're contributing
178
+ a rule to a pre-existing set, you should be able to find a similar example to pattern-match
179
+ against. If you're adding a new linter, you'll need to create a new ` mod.rs ` file (see,
180
+ e.g., ` crates/ruff/src/rules/flake8_bugbear/mod.rs ` )
181
+
182
+ 1 . Run ` cargo test ` . Your test will fail, but you'll be prompted to follow-up
183
+ with ` cargo insta review ` . Run ` cargo insta review ` , review and accept the generated snapshot,
184
+ then commit the snapshot file alongside the rest of your changes.
185
+
186
+ 1 . Run ` cargo test ` again to ensure that your test passes.
187
+
157
188
### Example: Adding a new configuration option
158
189
159
190
Ruff's user-facing settings live in a few different places.
@@ -184,6 +215,8 @@ Finally, regenerate the documentation and generated code with `cargo dev generat
184
215
185
216
To preview any changes to the documentation locally:
186
217
218
+ 1 . Install the [ Rust toolchain] ( https://www.rust-lang.org/tools/install ) .
219
+
187
220
1 . Install MkDocs and Material for MkDocs with:
188
221
189
222
``` shell
0 commit comments