|
| 1 | +# Emitting a lint |
| 2 | + |
| 3 | +Once we have [defined a lint](defining_lints.md), written [UI |
| 4 | +tests](writing_tests.md) and chosen [the lint pass](lint_passes.md) for the lint, |
| 5 | +we can begin the implementation of the lint logic so that we can emit it and |
| 6 | +gradually work towards a lint that behaves as expected. |
| 7 | + |
| 8 | +Note that we will not go into concrete implementation of a lint logic in this |
| 9 | +chapter. We will go into details in later chapters as well as in two examples of |
| 10 | +real Clippy lints. |
| 11 | + |
| 12 | +To emit a lint, we must implement a pass (see [Lint Passes](lint_passes.md)) for |
| 13 | +the lint that we have declared. In this example we'll implement a "late" lint, |
| 14 | +so take a look at the [LateLintPass][late_lint_pass] documentation, which |
| 15 | +provides an abundance of methods that we can implement for our lint. |
| 16 | + |
| 17 | +```rust |
| 18 | +pub trait LateLintPass<'tcx>: LintPass { |
| 19 | + // Trait methods |
| 20 | +} |
| 21 | +``` |
| 22 | + |
| 23 | +By far the most common method used for Clippy lints is [`check_expr` |
| 24 | +method][late_check_expr], this is because Rust is an expression language and, |
| 25 | +more often than not, the lint we want to work on must examine expressions. |
| 26 | + |
| 27 | +> _Note:_ If you don't fully understand what expressions are in Rust, take a |
| 28 | +> look at the official documentation on [expressions][rust_expressions] |
| 29 | +
|
| 30 | +Other common ones include the [`check_fn` method][late_check_fn] and the |
| 31 | +[`check_item` method][late_check_item]. |
| 32 | + |
| 33 | +### Emitting a lint |
| 34 | + |
| 35 | +Inside the trait method that we implement, we can write down the lint logic and |
| 36 | +emit the lint with suggestions. |
| 37 | + |
| 38 | +Clippy's [diagnostics] provides quite a few diagnostic functions that we can use |
| 39 | +to emit lints. Take a look at the documentation to pick one that suits your |
| 40 | +lint's needs the best. Some common ones you will encounter in the Clippy |
| 41 | +repository includes: |
| 42 | + |
| 43 | +- [`span_lint`]: Emits a lint without providing any other information |
| 44 | +- [`span_lint_and_note`]: Emits a lint and adds a note |
| 45 | +- [`span_lint_and_help`]: Emits a lint and provides a helpful message |
| 46 | +- [`span_lint_and_sugg`]: Emits a lint and provides a suggestion to fix the code |
| 47 | +- [`span_lint_and_then`]: Like `span_lint`, but allows for a lot of output |
| 48 | + customization. |
| 49 | + |
| 50 | +```rust |
| 51 | +impl<'tcx> LateLintPass<'tcx> for LintName { |
| 52 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
| 53 | + // Imagine that `some_lint_expr_logic` checks for requirements for emitting the lint |
| 54 | + if some_lint_expr_logic(expr) { |
| 55 | + span_lint_and_help( |
| 56 | + cx, // < The context |
| 57 | + LINT_NAME, // < The name of the lint in ALL CAPS |
| 58 | + expr.span, // < The span to lint |
| 59 | + "message on why the lint is emitted", |
| 60 | + None, // < An optional help span (to highlight something in the lint) |
| 61 | + "message that provides a helpful suggestion", |
| 62 | + ); |
| 63 | + } |
| 64 | + } |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +> Note: The message should be matter of fact and avoid capitalization and |
| 69 | +> punctuation. If multiple sentences are needed, the messages should probably be |
| 70 | +> split up into an error + a help / note / suggestion message. |
| 71 | +
|
| 72 | +## Suggestions: Automatic fixes |
| 73 | + |
| 74 | +Some lints know what to change in order to fix the code. For example, the lint |
| 75 | +[`range_plus_one`][range_plus_one] warns for ranges where the user wrote `x..y + |
| 76 | +1` instead of using an [inclusive range][inclusive_range] (`x..=y`). The fix to |
| 77 | +this code would be changing the `x..y + 1` expression to `x..=y`. **This is |
| 78 | +where suggestions come in**. |
| 79 | + |
| 80 | +A suggestion is a change that the lint provides to fix the issue it is linting. |
| 81 | +The output looks something like this (from the example earlier): |
| 82 | + |
| 83 | +```text |
| 84 | +error: an inclusive range would be more readable |
| 85 | + --> $DIR/range_plus_minus_one.rs:37:14 |
| 86 | + | |
| 87 | +LL | for _ in 1..1 + 1 {} |
| 88 | + | ^^^^^^^^ help: use: `1..=1` |
| 89 | +``` |
| 90 | + |
| 91 | +**Not all suggestions are always right**, some of them require human |
| 92 | +supervision, that's why we have [Applicability][applicability]. |
| 93 | + |
| 94 | +Applicability indicates confidence in the correctness of the suggestion, some |
| 95 | +are always right (`Applicability::MachineApplicable`), but we use |
| 96 | +`Applicability::MaybeIncorrect` and others when talking about a suggestion that |
| 97 | +may be incorrect. |
| 98 | + |
| 99 | +### Example |
| 100 | + |
| 101 | +The same lint `LINT_NAME` but that emits a suggestion would look something like this: |
| 102 | + |
| 103 | +```rust |
| 104 | +impl<'tcx> LateLintPass<'tcx> for LintName { |
| 105 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
| 106 | + // Imagine that `some_lint_expr_logic` checks for requirements for emitting the lint |
| 107 | + if some_lint_expr_logic(expr) { |
| 108 | + span_lint_and_sugg( // < Note this change |
| 109 | + cx, |
| 110 | + LINT_NAME, |
| 111 | + span, |
| 112 | + "message on why the lint is emitted", |
| 113 | + "use", |
| 114 | + format!("foo + {} * bar", snippet(cx, expr.span, "<default>")), // < Suggestion |
| 115 | + Applicability::MachineApplicable, |
| 116 | + ); |
| 117 | + } |
| 118 | + } |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +Suggestions generally use the [`format!`][format_macro] macro to interpolate the |
| 123 | +old values with the new ones. To get code snippets, use one of the `snippet*` |
| 124 | +functions from `clippy_utils::source`. |
| 125 | + |
| 126 | +## How to choose between notes, help messages and suggestions |
| 127 | + |
| 128 | +Notes are presented separately from the main lint message, they provide useful |
| 129 | +information that the user needs to understand why the lint was activated. They |
| 130 | +are the most helpful when attached to a span. |
| 131 | + |
| 132 | +Examples: |
| 133 | + |
| 134 | +### Notes |
| 135 | + |
| 136 | +```text |
| 137 | +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. |
| 138 | + --> $DIR/drop_forget_ref.rs:10:5 |
| 139 | + | |
| 140 | +10 | forget(&SomeStruct); |
| 141 | + | ^^^^^^^^^^^^^^^^^^^ |
| 142 | + | |
| 143 | + = note: `-D clippy::forget-ref` implied by `-D warnings` |
| 144 | +note: argument has type &SomeStruct |
| 145 | + --> $DIR/drop_forget_ref.rs:10:12 |
| 146 | + | |
| 147 | +10 | forget(&SomeStruct); |
| 148 | + | ^^^^^^^^^^^ |
| 149 | +``` |
| 150 | + |
| 151 | +### Help Messages |
| 152 | + |
| 153 | +Help messages are specifically to help the user. These are used in situation |
| 154 | +where you can't provide a specific machine applicable suggestion. They can also |
| 155 | +be attached to a span. |
| 156 | + |
| 157 | +Example: |
| 158 | + |
| 159 | +```text |
| 160 | +error: constant division of 0.0 with 0.0 will always result in NaN |
| 161 | + --> $DIR/zero_div_zero.rs:6:25 |
| 162 | + | |
| 163 | +6 | let other_f64_nan = 0.0f64 / 0.0; |
| 164 | + | ^^^^^^^^^^^^ |
| 165 | + | |
| 166 | + = help: consider using `f64::NAN` if you would like a constant representing NaN |
| 167 | +``` |
| 168 | + |
| 169 | +### Suggestions |
| 170 | + |
| 171 | +Suggestions are the most helpful, they are changes to the source code to fix the |
| 172 | +error. The magic in suggestions is that tools like `rustfix` can detect them and |
| 173 | +automatically fix your code. |
| 174 | + |
| 175 | +Example: |
| 176 | + |
| 177 | +```text |
| 178 | +error: This `.fold` can be more succinctly expressed as `.any` |
| 179 | +--> $DIR/methods.rs:390:13 |
| 180 | + | |
| 181 | +390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2); |
| 182 | + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)` |
| 183 | + | |
| 184 | +``` |
| 185 | + |
| 186 | +### Snippets |
| 187 | + |
| 188 | +Snippets are pieces of the source code (as a string), they are extracted |
| 189 | +generally using the [`snippet`][snippet_fn] function. |
| 190 | + |
| 191 | +For example, if you want to know how an item looks (and you know the item's |
| 192 | +span), you could use `snippet(cx, span, "..")`. |
| 193 | + |
| 194 | +## Final: Run UI Tests to Emit the Lint |
| 195 | + |
| 196 | +Now, if we run our [UI test](writing_tests.md), we should see that Clippy now |
| 197 | +produces output that contains the lint message we designed. |
| 198 | + |
| 199 | +The next step is to implement the logic properly, which is a detail that we will |
| 200 | +cover in the next chapters. |
| 201 | + |
| 202 | +[diagnostics]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/diagnostics/index.html |
| 203 | +[late_check_expr]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_expr |
| 204 | +[late_check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_fn |
| 205 | +[late_check_item]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_item |
| 206 | +[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html |
| 207 | +[rust_expressions]: https://doc.rust-lang.org/reference/expressions.html |
| 208 | +[`span_lint`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/diagnostics/fn.span_lint.html |
| 209 | +[`span_lint_and_note`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_note.html |
| 210 | +[`span_lint_and_help`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_help.html |
| 211 | +[`span_lint_and_sugg`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_sugg.html |
| 212 | +[`span_lint_and_then`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_then.html |
| 213 | +[range_plus_one]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one |
| 214 | +[inclusive_range]: https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html |
| 215 | +[applicability]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_errors/enum.Applicability.html |
| 216 | +[snippet_fn]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/source/fn.snippet.html |
| 217 | +[format_macro]: https://doc.rust-lang.org/std/macro.format.html |
0 commit comments