Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 55 additions & 10 deletions book/src/development/emitting_lints.md
Original file line number Diff line number Diff line change
Expand Up @@ -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].

Expand All @@ -105,23 +107,26 @@ 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, "<default>")), // < Suggestion
Applicability::MachineApplicable,
|diag| {
// v Build and emit the suggestion
let mut app = Applicability::MachineApplicable;
let expr_snippet = snippet_with_applicability(cx, expr.span, "<default>", &mut app);
let sugg = format!("foo + {expr_snippet} * bar");
diag.span_suggestion(expr.span, "use", sugg, app);
}
);
}
}
}
```

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

Expand Down Expand Up @@ -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

Expand All @@ -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
37 changes: 21 additions & 16 deletions clippy_utils/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<'_>,
Expand Down Expand Up @@ -518,8 +518,10 @@ fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>,
/// 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
Expand All @@ -528,7 +530,7 @@ fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>,
/// // ^^^^^ ^^^^^^^^^^
/// // 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()"
/// ```
Expand All @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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.
Expand Down
Loading