fix: use snippet_with_context for spans that are likely to contain macro expns

This commit is contained in:
Ada Alakbarova 2025-10-13 19:32:34 +02:00
parent 1566879798
commit 4d79522905
No known key found for this signature in database
4 changed files with 131 additions and 26 deletions

View file

@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::source::{snippet, snippet_with_context};
use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt, sym};
use rustc_errors::Applicability;
use rustc_hir::{Closure, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
use rustc_span::{Span, SyntaxContext};
use std::borrow::{Borrow, Cow};
@ -88,33 +88,42 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
&& let ExprKind::MethodCall(ps, recv, span_call_args, _) = &only_expr.kind
&& let ExprKind::Path(..) = recv.kind
{
let and_then_snippets =
get_and_then_snippets(cx, call_cx.span, call_lint.span, call_sp.span, call_msg.span);
let mut app = Applicability::MachineApplicable;
let expr_ctxt = expr.span.ctxt();
let and_then_snippets = get_and_then_snippets(
cx,
expr_ctxt,
call_cx.span,
call_lint.span,
call_sp.span,
call_msg.span,
&mut app,
);
let mut sle = SpanlessEq::new(cx).deny_side_effects();
match ps.ident.name {
sym::span_suggestion if sle.eq_expr(call_sp, &span_call_args[0]) => {
suggest_suggestion(
cx,
expr,
&and_then_snippets,
&span_suggestion_snippets(cx, span_call_args),
);
let snippets = span_suggestion_snippets(cx, expr_ctxt, span_call_args, &mut app);
suggest_suggestion(cx, expr, &and_then_snippets, &snippets, app);
},
sym::span_help if sle.eq_expr(call_sp, &span_call_args[0]) => {
let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true);
let help_snippet =
snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true, app);
},
sym::span_note if sle.eq_expr(call_sp, &span_call_args[0]) => {
let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true);
let note_snippet =
snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true, app);
},
sym::help => {
let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false);
let help_snippet =
snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false, app);
},
sym::note => {
let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false);
let note_snippet =
snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false, app);
},
_ => (),
}
@ -131,15 +140,17 @@ struct AndThenSnippets {
fn get_and_then_snippets(
cx: &LateContext<'_>,
expr_ctxt: SyntaxContext,
cx_span: Span,
lint_span: Span,
span_span: Span,
msg_span: Span,
app: &mut Applicability,
) -> AndThenSnippets {
let cx_snippet = snippet(cx, cx_span, "cx");
let lint_snippet = snippet(cx, lint_span, "..");
let span_snippet = snippet(cx, span_span, "span");
let msg_snippet = snippet(cx, msg_span, r#""...""#);
let msg_snippet = snippet_with_context(cx, msg_span, expr_ctxt, r#""...""#, app).0;
AndThenSnippets {
cx: cx_snippet,
@ -155,9 +166,14 @@ struct SpanSuggestionSnippets {
applicability: Cow<'static, str>,
}
fn span_suggestion_snippets<'hir>(cx: &LateContext<'_>, span_call_args: &'hir [Expr<'hir>]) -> SpanSuggestionSnippets {
let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
let sugg_snippet = snippet(cx, span_call_args[2].span, "..");
fn span_suggestion_snippets<'hir>(
cx: &LateContext<'_>,
expr_ctxt: SyntaxContext,
span_call_args: &'hir [Expr<'hir>],
app: &mut Applicability,
) -> SpanSuggestionSnippets {
let help_snippet = snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, app).0;
let sugg_snippet = snippet_with_context(cx, span_call_args[2].span, expr_ctxt, "..", app).0;
let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable");
SpanSuggestionSnippets {
@ -172,6 +188,7 @@ fn suggest_suggestion(
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets,
span_suggestion_snippets: &SpanSuggestionSnippets,
app: Applicability,
) {
span_lint_and_sugg(
cx,
@ -189,7 +206,7 @@ fn suggest_suggestion(
span_suggestion_snippets.sugg,
span_suggestion_snippets.applicability
),
Applicability::MachineApplicable,
app,
);
}
@ -199,6 +216,7 @@ fn suggest_help(
and_then_snippets: &AndThenSnippets,
help: &str,
with_span: bool,
app: Applicability,
) {
let option_span = if with_span {
format!("Some({})", and_then_snippets.span)
@ -216,7 +234,7 @@ fn suggest_help(
"span_lint_and_help({}, {}, {}, {}, {}, {help})",
and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, &option_span,
),
Applicability::MachineApplicable,
app,
);
}
@ -226,6 +244,7 @@ fn suggest_note(
and_then_snippets: &AndThenSnippets,
note: &str,
with_span: bool,
app: Applicability,
) {
let note_span = if with_span {
format!("Some({})", and_then_snippets.span)
@ -243,6 +262,6 @@ fn suggest_note(
"span_lint_and_note({}, {}, {}, {}, {note_span}, {note})",
and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg,
),
Applicability::MachineApplicable,
app,
);
}

View file

@ -50,6 +50,15 @@ impl EarlyLintPass for Pass {
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
db.help(help_msg).help(help_msg);
});
// Issue #15880
#[expect(clippy::disallowed_names)]
let foo = "foo";
span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, format!("try using {foo}"), format!("{foo}.use"), Applicability::MachineApplicable);
span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("try using {foo}"));
span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, format!("try using {foo}"));
span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("required because of {foo}"));
span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, format!("required because of {foo}"));
}
}

View file

@ -65,6 +65,35 @@ impl EarlyLintPass for Pass {
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
db.help(help_msg).help(help_msg);
});
// Issue #15880
#[expect(clippy::disallowed_names)]
let foo = "foo";
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_suggestion(
expr.span,
format!("try using {foo}"),
format!("{foo}.use"),
Applicability::MachineApplicable,
);
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_help(expr.span, format!("try using {foo}"));
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.help(format!("try using {foo}"));
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_note(expr.span, format!("required because of {foo}"));
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.note(format!("required because of {foo}"));
});
}
}

View file

@ -49,5 +49,53 @@ LL | | db.note(note_msg);
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg)`
error: aborting due to 5 previous errors
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:72:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_suggestion(
LL | | expr.span,
... |
LL | | );
LL | | });
| |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, format!("try using {foo}"), format!("{foo}.use"), Applicability::MachineApplicable)`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:81:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_help(expr.span, format!("try using {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("try using {foo}"))`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:85:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.help(format!("try using {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, format!("try using {foo}"))`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:89:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_note(expr.span, format!("required because of {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("required because of {foo}"))`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:93:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.note(format!("required because of {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, format!("required because of {foo}"))`
error: aborting due to 10 previous errors