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