From f76615a7ac220ceb9a0db25fd7a3b00f48aba591 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Wed, 5 Apr 2023 13:49:34 +0200 Subject: [PATCH 1/3] New chapter: Emitting lints --- book/src/SUMMARY.md | 1 + book/src/development/emitting_lints.md | 206 +++++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 book/src/development/emitting_lints.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 22fbdce75e8..e65a4c50e6f 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -14,6 +14,7 @@ - [Basics](development/basics.md) - [Adding Lints](development/adding_lints.md) - [Lint Passes](development/lint_passes.md) + - [Emitting lints](development/emitting_lints.md) - [Type Checking](development/type_checking.md) - [Macro Expansions](development/macro_expansions.md) - [Common Tools](development/common_tools_writing_lints.md) diff --git a/book/src/development/emitting_lints.md b/book/src/development/emitting_lints.md new file mode 100644 index 00000000000..a716ae315d1 --- /dev/null +++ b/book/src/development/emitting_lints.md @@ -0,0 +1,206 @@ +# Emitting a lint + +Once we have [defined a lint](define_lints.md), written [UI tests](write_tests.md) +and chosen [the lint pass](lint_passes.md) for the lint, we can begin the +implementation of the lint logic so that we can emit it and gradually work +towards a lint that behaves as expected. + +Note that we will not go into concrete implementation of a lint logic in this +chapter. We will go into details in later chapters as well as in two examples +of real Clippy lints. + +To emit a lint, we must implement a pass (see [Lint Passes](lint_passes.md)) for the lint that we have +declared. In this example we'll implement a "late" lint, so take a look at the [LateLintPass][late_lint_pass] +documentation, which provides an abundance of methods that we can implement for our lint. + +```rust +pub trait LateLintPass<'tcx>: LintPass { + // Trait methods +} +``` + +By far the most common method used for Clippy lints is [`check_expr` method][late_check_expr], +this is because Rust is an expression language and, more often than not, +the lint we want to work on must examine expressions. + +> _Note:_ If you don't fully understand what expressions are in Rust, +> take a look at the official documentation on [expressions][rust_expressions] + +Other common ones include the [`check_fn` method][late_check_fn] and the +[`check_item` method][late_check_item]. + +### Emitting a lint + +Inside the trait method that we implement, we can write down the lint logic +and emit the lint with suggestions. + +Clippy's [diagnostics] provides quite a few diagnostic functions that we can +use to emit lints. Take a look at the documentation to pick one that suits +your lint's needs the best. Some common ones you will encounter in the Clippy +repository includes: + +- [`span_lint`]: Emits a lint without providing any other information +- [`span_lint_and_note`]: Emits a lint and adds a note +- [`span_lint_and_help`]: Emits a lint and provides a helpful message +- [`span_lint_and_sugg`]: Emits a lint and provides a suggestion to fix the code +- [`span_lint_and_then`]: Like `span_lint`, but allows for a lot of output customization. + +```rust +impl<'tcx> LateLintPass<'tcx> for LintName { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // Imagine that `some_lint_expr_logic` checks for requirements for emitting the lint + if some_lint_expr_logic(expr) { + span_lint_and_help( + cx, // < The context + LINT_NAME, // < The name of the lint in ALL CAPS + expr.span, // < The span to lint + "message on why the lint is emitted", + None, // < An optional help span (to highlight something in the lint) + "message that provides a helpful suggestion", + ); + } + } +} +``` + +> Note: The message should be matter of fact and avoid +> capitalization and punctuation. If multiple sentences are needed, the messages should probably be split up into an +> error + a help / note / suggestion message. + +## Suggestions: Automatic fixes + +Some lints know what to change in order to fix the code. For example, the lint +[`range_plus`][range_plus_one] warns for ranges where the user wrote `x..y + 1` instead of using an +[inclusive range][inclusive_range] (`x..=1`). The fix to this code would be changing the `x..y + 1` expression +to `x..=y`. **This is where suggestions come in**. + +A suggestion is a change that the lint provides to fix the issue it is linting. +The output looks something like this (from the example earlier): + +```text +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:37:14 + | +LL | for _ in 1..1 + 1 {} + | ^^^^^^^^ help: use: `1..=1` +``` + +**Not all suggestions are always right**, some of them require human supervision, that's why we have +[Applicability][applicability]. + +Applicability indicates confidence in the correctness of the suggestion, some are always right +(`Applicability::MachineApplicable`), but we use `Applicability::MaybeIncorrect` and others when talking about a lint +that may be incorrect. + +--- + +The same lint `LINT_NAME` but that emits a suggestion would be something like this: + +```rust +impl<'tcx> LateLintPass<'tcx> for LintName { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // Imagine that `some_lint_expr_logic` checks for requirements for emitting the lint + if some_lint_expr_logic(expr) { + span_lint_and_sugg( // < Note this change + cx, + LINT_NAME, + span, + "message on why the lint is emitted", + "use", + "suggestion (don't forget to integrate things from the source, like variable names)", // < Suggestion + Applicability::MachineApplicable + ); + } + } +} +``` + +Suggestions generally use the [`format!`](format_macro) macro to interpolate the old values with the new ones. + +## How to choose between notes, help messages and suggestions + +Notes are presented separately from the main lint message, they provide useful information that the user needs to +understand why the lint was activated. They are the most helpful when attached to a span. + +Example: + +```text +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. + --> $DIR/drop_forget_ref.rs:10:5 + | +10 | forget(&SomeStruct); + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::forget-ref` implied by `-D warnings` +note: argument has type &SomeStruct + --> $DIR/drop_forget_ref.rs:10:12 + | +10 | forget(&SomeStruct); + | ^^^^^^^^^^^ +``` + +--- + +Help messages are specifically to help the user. These are used in situation where you can't provide a specific +machine applicable suggestion. They can also be attached to a span. + +Example: + +```text +error: constant division of 0.0 with 0.0 will always result in NaN + --> $DIR/zero_div_zero.rs:6:25 + | +6 | let other_f64_nan = 0.0f64 / 0.0; + | ^^^^^^^^^^^^ + | + = help: consider using `f64::NAN` if you would like a constant representing NaN +``` + +--- + +Suggestions are the most helpful, they are changes to the source code to fix the error. The magic +in suggestions is that tools like `rustfix` can detect them and automatically fix your code. + +Example: + +```text +error: This `.fold` can be more succinctly expressed as `.any` +--> $DIR/methods.rs:390:13 + | +390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)` + | + = note: `-D fold-any` implied by `-D warnings` +``` + +### Snippets + +Snippets are pieces of the source code (as a string), they are extracted generally using the [`snippet`][snippet_fn] +function. + +For example, if you want to know how an item looks (and you know the item's span), you could use +`snippet(cx, span, "..")`. + +## Final: Run UI Tests to Emit the Lint + +Now, if we run our [UI test](write_tests.md), we should see that the compiler now +produce output that contains the lint message we designed. + +The next step is to implement the logic properly, which is a detail that we will +cover in the next chapters. + +[diagnostics]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/diagnostics/index.html +[late_check_expr]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_expr +[late_check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_fn +[late_check_item]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_item +[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html +[rust_expressions]: https://doc.rust-lang.org/reference/expressions.html +[`span_lint`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/diagnostics/fn.span_lint.html +[`span_lint_and_note`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_note.html +[`span_lint_and_help`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_help.html +[`span_lint_and_sugg`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_sugg.html +[`span_lint_and_then`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_then.html +[range_plus_one]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one +[inclusive_range]: https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html +[applicability]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_errors/enum.Applicability.html +[snippet_fn]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/source/fn.snippet.html From 9b8b83556b508076d9bc1d5195fc4dafff7fbd0e Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 18 Aug 2023 19:55:01 +0200 Subject: [PATCH 2/3] Formatting and minor wording fixes --- book/src/development/emitting_lints.md | 114 ++++++++++++++----------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/book/src/development/emitting_lints.md b/book/src/development/emitting_lints.md index a716ae315d1..e803aa126d6 100644 --- a/book/src/development/emitting_lints.md +++ b/book/src/development/emitting_lints.md @@ -1,17 +1,18 @@ # Emitting a lint -Once we have [defined a lint](define_lints.md), written [UI tests](write_tests.md) -and chosen [the lint pass](lint_passes.md) for the lint, we can begin the -implementation of the lint logic so that we can emit it and gradually work -towards a lint that behaves as expected. +Once we have [defined a lint](define_lints.md), written [UI +tests](write_tests.md) and chosen [the lint pass](lint_passes.md) for the lint, +we can begin the implementation of the lint logic so that we can emit it and +gradually work towards a lint that behaves as expected. Note that we will not go into concrete implementation of a lint logic in this -chapter. We will go into details in later chapters as well as in two examples -of real Clippy lints. +chapter. We will go into details in later chapters as well as in two examples of +real Clippy lints. -To emit a lint, we must implement a pass (see [Lint Passes](lint_passes.md)) for the lint that we have -declared. In this example we'll implement a "late" lint, so take a look at the [LateLintPass][late_lint_pass] -documentation, which provides an abundance of methods that we can implement for our lint. +To emit a lint, we must implement a pass (see [Lint Passes](lint_passes.md)) for +the lint that we have declared. In this example we'll implement a "late" lint, +so take a look at the [LateLintPass][late_lint_pass] documentation, which +provides an abundance of methods that we can implement for our lint. ```rust pub trait LateLintPass<'tcx>: LintPass { @@ -19,31 +20,32 @@ pub trait LateLintPass<'tcx>: LintPass { } ``` -By far the most common method used for Clippy lints is [`check_expr` method][late_check_expr], -this is because Rust is an expression language and, more often than not, -the lint we want to work on must examine expressions. +By far the most common method used for Clippy lints is [`check_expr` +method][late_check_expr], this is because Rust is an expression language and, +more often than not, the lint we want to work on must examine expressions. -> _Note:_ If you don't fully understand what expressions are in Rust, -> take a look at the official documentation on [expressions][rust_expressions] +> _Note:_ If you don't fully understand what expressions are in Rust, take a +> look at the official documentation on [expressions][rust_expressions] Other common ones include the [`check_fn` method][late_check_fn] and the [`check_item` method][late_check_item]. ### Emitting a lint -Inside the trait method that we implement, we can write down the lint logic -and emit the lint with suggestions. +Inside the trait method that we implement, we can write down the lint logic and +emit the lint with suggestions. -Clippy's [diagnostics] provides quite a few diagnostic functions that we can -use to emit lints. Take a look at the documentation to pick one that suits -your lint's needs the best. Some common ones you will encounter in the Clippy +Clippy's [diagnostics] provides quite a few diagnostic functions that we can use +to emit lints. Take a look at the documentation to pick one that suits your +lint's needs the best. Some common ones you will encounter in the Clippy repository includes: - [`span_lint`]: Emits a lint without providing any other information - [`span_lint_and_note`]: Emits a lint and adds a note - [`span_lint_and_help`]: Emits a lint and provides a helpful message - [`span_lint_and_sugg`]: Emits a lint and provides a suggestion to fix the code -- [`span_lint_and_then`]: Like `span_lint`, but allows for a lot of output customization. +- [`span_lint_and_then`]: Like `span_lint`, but allows for a lot of output + customization. ```rust impl<'tcx> LateLintPass<'tcx> for LintName { @@ -63,16 +65,17 @@ impl<'tcx> LateLintPass<'tcx> for LintName { } ``` -> Note: The message should be matter of fact and avoid -> capitalization and punctuation. If multiple sentences are needed, the messages should probably be split up into an -> error + a help / note / suggestion message. +> Note: The message should be matter of fact and avoid capitalization and +> punctuation. If multiple sentences are needed, the messages should probably be +> split up into an error + a help / note / suggestion message. ## Suggestions: Automatic fixes Some lints know what to change in order to fix the code. For example, the lint -[`range_plus`][range_plus_one] warns for ranges where the user wrote `x..y + 1` instead of using an -[inclusive range][inclusive_range] (`x..=1`). The fix to this code would be changing the `x..y + 1` expression -to `x..=y`. **This is where suggestions come in**. +[`range_plus_one`][range_plus_one] warns for ranges where the user wrote `x..y + +1` instead of using an [inclusive range][inclusive_range] (`x..=y`). The fix to +this code would be changing the `x..y + 1` expression to `x..=y`. **This is +where suggestions come in**. A suggestion is a change that the lint provides to fix the issue it is linting. The output looks something like this (from the example earlier): @@ -85,16 +88,17 @@ LL | for _ in 1..1 + 1 {} | ^^^^^^^^ help: use: `1..=1` ``` -**Not all suggestions are always right**, some of them require human supervision, that's why we have -[Applicability][applicability]. +**Not all suggestions are always right**, some of them require human +supervision, that's why we have [Applicability][applicability]. -Applicability indicates confidence in the correctness of the suggestion, some are always right -(`Applicability::MachineApplicable`), but we use `Applicability::MaybeIncorrect` and others when talking about a lint -that may be incorrect. +Applicability indicates confidence in the correctness of the suggestion, some +are always right (`Applicability::MachineApplicable`), but we use +`Applicability::MaybeIncorrect` and others when talking about a suggestion that +may be incorrect. ---- +### Example -The same lint `LINT_NAME` but that emits a suggestion would be something like this: +The same lint `LINT_NAME` but that emits a suggestion would look something like this: ```rust impl<'tcx> LateLintPass<'tcx> for LintName { @@ -107,22 +111,27 @@ impl<'tcx> LateLintPass<'tcx> for LintName { span, "message on why the lint is emitted", "use", - "suggestion (don't forget to integrate things from the source, like variable names)", // < Suggestion - Applicability::MachineApplicable + format!("foo + {} * bar", snippet(cx, expr.span, "")), // < Suggestion + Applicability::MachineApplicable, ); } } } ``` -Suggestions generally use the [`format!`](format_macro) macro to interpolate the old values with the new ones. +Suggestions generally use the [`format!`](format_macro) macro to interpolate the +old values with the new ones. To get code snippets, use one of the `snippet*` +functions from `clippy_utils::source`. ## How to choose between notes, help messages and suggestions -Notes are presented separately from the main lint message, they provide useful information that the user needs to -understand why the lint was activated. They are the most helpful when attached to a span. +Notes are presented separately from the main lint message, they provide useful +information that the user needs to understand why the lint was activated. They +are the most helpful when attached to a span. -Example: +Examples: + +### Notes ```text error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. @@ -139,10 +148,11 @@ note: argument has type &SomeStruct | ^^^^^^^^^^^ ``` ---- +### Help Messages -Help messages are specifically to help the user. These are used in situation where you can't provide a specific -machine applicable suggestion. They can also be attached to a span. +Help messages are specifically to help the user. These are used in situation +where you can't provide a specific machine applicable suggestion. They can also +be attached to a span. Example: @@ -156,10 +166,11 @@ error: constant division of 0.0 with 0.0 will always result in NaN = help: consider using `f64::NAN` if you would like a constant representing NaN ``` ---- +### Suggestions -Suggestions are the most helpful, they are changes to the source code to fix the error. The magic -in suggestions is that tools like `rustfix` can detect them and automatically fix your code. +Suggestions are the most helpful, they are changes to the source code to fix the +error. The magic in suggestions is that tools like `rustfix` can detect them and +automatically fix your code. Example: @@ -170,21 +181,20 @@ error: This `.fold` can be more succinctly expressed as `.any` 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)` | - = note: `-D fold-any` implied by `-D warnings` ``` ### Snippets -Snippets are pieces of the source code (as a string), they are extracted generally using the [`snippet`][snippet_fn] -function. +Snippets are pieces of the source code (as a string), they are extracted +generally using the [`snippet`][snippet_fn] function. -For example, if you want to know how an item looks (and you know the item's span), you could use -`snippet(cx, span, "..")`. +For example, if you want to know how an item looks (and you know the item's +span), you could use `snippet(cx, span, "..")`. ## Final: Run UI Tests to Emit the Lint -Now, if we run our [UI test](write_tests.md), we should see that the compiler now -produce output that contains the lint message we designed. +Now, if we run our [UI test](write_tests.md), we should see that Clippy now +produces output that contains the lint message we designed. The next step is to implement the logic properly, which is a detail that we will cover in the next chapters. From a26937ff0ee654a46846dbdf50afb9866362b9ff Mon Sep 17 00:00:00 2001 From: blyxyas Date: Sat, 2 Sep 2023 11:30:16 +0200 Subject: [PATCH 3/3] Fix links --- book/src/development/emitting_lints.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/book/src/development/emitting_lints.md b/book/src/development/emitting_lints.md index e803aa126d6..a12f6aa91b3 100644 --- a/book/src/development/emitting_lints.md +++ b/book/src/development/emitting_lints.md @@ -1,7 +1,7 @@ # Emitting a lint -Once we have [defined a lint](define_lints.md), written [UI -tests](write_tests.md) and chosen [the lint pass](lint_passes.md) for the lint, +Once we have [defined a lint](defining_lints.md), written [UI +tests](writing_tests.md) and chosen [the lint pass](lint_passes.md) for the lint, we can begin the implementation of the lint logic so that we can emit it and gradually work towards a lint that behaves as expected. @@ -119,7 +119,7 @@ impl<'tcx> LateLintPass<'tcx> for LintName { } ``` -Suggestions generally use the [`format!`](format_macro) macro to interpolate the +Suggestions generally use the [`format!`][format_macro] macro to interpolate the old values with the new ones. To get code snippets, use one of the `snippet*` functions from `clippy_utils::source`. @@ -193,7 +193,7 @@ span), you could use `snippet(cx, span, "..")`. ## Final: Run UI Tests to Emit the Lint -Now, if we run our [UI test](write_tests.md), we should see that Clippy now +Now, if we run our [UI test](writing_tests.md), we should see that Clippy now produces output that contains the lint message we designed. The next step is to implement the logic properly, which is a detail that we will @@ -214,3 +214,4 @@ cover in the next chapters. [inclusive_range]: https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html [applicability]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_errors/enum.Applicability.html [snippet_fn]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/source/fn.snippet.html +[format_macro]: https://doc.rust-lang.org/std/macro.format.html