diff --git a/book/src/development/emitting_lints.md b/book/src/development/emitting_lints.md index d70f4fc17ebf..32a700f549ff 100644 --- a/book/src/development/emitting_lints.md +++ b/book/src/development/emitting_lints.md @@ -88,6 +88,8 @@ LL | for _ in 1..1 + 1 {} | ^^^^^^^^ help: use: `1..=1` ``` +### Applicability + **Not all suggestions are always right**, some of them require human supervision, that's why we have [Applicability][applicability]. @@ -105,14 +107,18 @@ 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 + span_lint_and_then( // < Note this change cx, LINT_NAME, - span, + expr.span, "message on why the lint is emitted", - "use", - format!("foo + {} * bar", snippet(cx, expr.span, "")), // < Suggestion - Applicability::MachineApplicable, + |diag| { + // v Build and emit the suggestion + let mut app = Applicability::MachineApplicable; + let expr_snippet = snippet_with_applicability(cx, expr.span, "", &mut app); + let sugg = format!("foo + {expr_snippet} * bar"); + diag.span_suggestion(expr.span, "use", sugg, app); + } ); } } @@ -120,8 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for LintName { ``` 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`. +old values with the new ones. For information on getting code snippets, see [Snippets](emitting_lints.md#snippets). ## How to choose between notes, help messages and suggestions @@ -183,13 +188,50 @@ error: This `.fold` can be more succinctly expressed as `.any` | ``` -### Snippets +## Snippets Snippets are pieces of the source code (as a string), they are extracted -generally using the [`snippet`][snippet_fn] function. +generally using the various `snippet_*` functions from [`clippy_utils::source`][]. + +If you're using the snippets _not_ to build a suggestion, it's usually +enough to use [`snippet`][snippet_fn] -- it accepts the span of the item, and +also a fallback string (see [Fallback string](emitting_lints.md#fallback-string)). For example, if you want to know how an item looks (and you know the item's -span), you could use `snippet(cx, span, "..")`. +span), you could use `snippet(cx, span, "_")`. + +If you do use the snippet for a suggestion, it's recommended to use +[`snippet_with_applicability`] instead. This is so that Clippy can reduce the +[applicability](emitting_lints.md#applicability) of the suggestion in case it couldn't extract +the snippet "cleanly" -- see the function's documentation for more information. + +It's often necessary to create multiple snippets to build a suggestion, in which +case you'll have the following pattern: + +```rust +// inside `span_lint_and_then` + +// 1. Create initial applicability. +let mut app = Applicability::MachineApplicable; + +// 2. Use it to create all the snippets +let foo_snippet = snippet_with_applicability(cx, foo.span, "_", &mut app); +let bar_snippet = snippet_with_applicability(cx, bar.span, "_", &mut app); +let sugg = format!("{foo_snippet} + {bar_snippet}"); // or whatever + +// 3. Use it to emit the final suggestion +diag.span_suggestion(span, msg, sugg, app); +``` + +### Fallback string +These are used when the source code that the span points to is unavailable. This +mostly only happens when proc-macros mishandle spans -- see [the section on +proc-macros][proc-macro-spans]. + +Most of the time, the snippets in a suggestion come from a (span of a) single +expression, and so `"_"` is an appropriate fallback string. If you instead +find yourself suggesting to insert multiple statements, or a block--basically +anything "big"--then consider using `".."` instead. ## Final: Run UI Tests to Emit the Lint @@ -210,8 +252,11 @@ cover in the next chapters. [`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 +[`clippy_utils::source`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/source/index.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 +[`snippet_with_applicability`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/source/fn.snippet_with_applicability.html +[proc-macro-spans]: macro_expansions.html#the-is_from_proc_macro-function [format_macro]: https://doc.rust-lang.org/std/macro.format.html diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index f30f26f3a70b..02d502f8b67f 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -352,7 +352,7 @@ impl SourceFileRange { } } -/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block` with no label. +/// Like [`snippet_block`], but add braces if the expr is not an `ExprKind::Block` with no label. pub fn expr_block( sess: &impl HasSession, expr: &Expr<'_>, @@ -518,8 +518,10 @@ fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option, /// Converts a span to a code snippet if available, otherwise returns the default. /// /// This is useful if you want to provide suggestions for your lint or more generally, if you want -/// to convert a given `Span` to a `str`. To create suggestions consider using -/// [`snippet_with_applicability`] to ensure that the applicability stays correct. +/// to convert a given `Span` to a `str`. +/// +/// To create suggestions consider using [`snippet_with_applicability`] to ensure that the +/// applicability stays correct. /// /// # Example /// ```rust,ignore @@ -528,7 +530,7 @@ fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option, /// // ^^^^^ ^^^^^^^^^^ /// // span1 span2 /// -/// // The snipped call would return the corresponding code snippet +/// // The snippet call would return the corresponding code snippet /// snippet(cx, span1, "..") // -> "value" /// snippet(cx, span2, "..") // -> "Vec::new()" /// ``` @@ -542,6 +544,9 @@ pub fn snippet<'a>(sess: &impl HasSession, span: Span, default: &'a str) -> Cow< /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`. /// - If the default value is used and the applicability level is `MachineApplicable`, change it to /// `HasPlaceholders` +/// +/// If the span might realistically contain a macro call (e.g. `vec![]`), consider using +/// [`snippet_with_context`] instead. pub fn snippet_with_applicability<'a>( sess: &impl HasSession, span: Span, @@ -560,15 +565,14 @@ fn snippet_with_applicability_sess<'a>( if *applicability != Applicability::Unspecified && span.from_expansion() { *applicability = Applicability::MaybeIncorrect; } - snippet_opt(sess, span).map_or_else( - || { - if *applicability == Applicability::MachineApplicable { - *applicability = Applicability::HasPlaceholders; - } - Cow::Borrowed(default) - }, - From::from, - ) + if let Some(t) = snippet_opt(sess, span) { + Cow::Owned(t) + } else { + if *applicability == Applicability::MachineApplicable { + *applicability = Applicability::HasPlaceholders; + } + Cow::Borrowed(default) + } } /// Converts a span to a code snippet. Returns `None` if not available. @@ -616,8 +620,8 @@ pub fn snippet_block(sess: &impl HasSession, span: Span, default: &str, indent_r reindent_multiline(&snip, true, indent) } -/// Same as `snippet_block`, but adapts the applicability level by the rules of -/// `snippet_with_applicability`. +/// Same as [`snippet_block`], but adapts the applicability level by the rules of +/// [`snippet_with_applicability`]. pub fn snippet_block_with_applicability( sess: &impl HasSession, span: Span, @@ -630,6 +634,7 @@ pub fn snippet_block_with_applicability( reindent_multiline(&snip, true, indent) } +/// Combination of [`snippet_block`] and [`snippet_with_context`]. pub fn snippet_block_with_context( sess: &impl HasSession, span: Span, @@ -643,7 +648,7 @@ pub fn snippet_block_with_context( (reindent_multiline(&snip, true, indent), from_macro) } -/// Same as `snippet_with_applicability`, but first walks the span up to the given context. +/// Same as [`snippet_with_applicability`], but first walks the span up to the given context. /// /// This will result in the macro call, rather than the expansion, if the span is from a child /// context. If the span is not from a child context, it will be used directly instead.