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
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt};
use clippy_utils::source::{snippet_with_applicability, 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};

Expand Down Expand Up @@ -88,58 +88,69 @@ 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.as_str() {
"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),
);
match ps.ident.name {
sym::span_suggestion if sle.eq_expr(call_sp, &span_call_args[0]) => {
let snippets = span_suggestion_snippets(cx, expr_ctxt, span_call_args, &mut app);
suggest_suggestion(cx, expr, &and_then_snippets, &snippets, app);
},
"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);
sym::span_help if sle.eq_expr(call_sp, &span_call_args[0]) => {
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);
},
"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);
sym::span_note if sle.eq_expr(call_sp, &span_call_args[0]) => {
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);
},
"help" => {
let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false);
sym::help => {
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);
},
"note" => {
let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false);
sym::note => {
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);
},
_ => (),
}
}
}
}

struct AndThenSnippets<'a> {
cx: Cow<'a, str>,
lint: Cow<'a, str>,
span: Cow<'a, str>,
msg: Cow<'a, str>,
struct AndThenSnippets {
cx: Cow<'static, str>,
lint: Cow<'static, str>,
span: Cow<'static, str>,
msg: Cow<'static, str>,
}

fn get_and_then_snippets(
cx: &LateContext<'_>,
expr_ctxt: SyntaxContext,
cx_span: Span,
lint_span: Span,
span_span: Span,
msg_span: Span,
) -> AndThenSnippets<'static> {
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#""...""#);
app: &mut Applicability,
) -> AndThenSnippets {
let cx_snippet = snippet_with_applicability(cx, cx_span, "cx", app);
let lint_snippet = snippet_with_applicability(cx, lint_span, "..", app);
let span_snippet = snippet_with_applicability(cx, span_span, "span", app);
let msg_snippet = snippet_with_context(cx, msg_span, expr_ctxt, r#""...""#, app).0;

AndThenSnippets {
cx: cx_snippet,
Expand All @@ -149,19 +160,22 @@ fn get_and_then_snippets(
}
}

struct SpanSuggestionSnippets<'a> {
help: Cow<'a, str>,
sugg: Cow<'a, str>,
applicability: Cow<'a, str>,
struct SpanSuggestionSnippets {
help: Cow<'static, str>,
sugg: Cow<'static, str>,
applicability: Cow<'static, str>,
}

fn span_suggestion_snippets<'a, 'hir>(
fn span_suggestion_snippets<'hir>(
cx: &LateContext<'_>,
expr_ctxt: SyntaxContext,
span_call_args: &'hir [Expr<'hir>],
) -> SpanSuggestionSnippets<'a> {
let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
let sugg_snippet = snippet(cx, span_call_args[2].span, "..");
let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable");
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_with_applicability(cx, span_call_args[3].span, "Applicability::MachineApplicable", app);

SpanSuggestionSnippets {
help: help_snippet,
Expand All @@ -173,8 +187,9 @@ fn span_suggestion_snippets<'a, 'hir>(
fn suggest_suggestion(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
span_suggestion_snippets: &SpanSuggestionSnippets<'_>,
and_then_snippets: &AndThenSnippets,
span_suggestion_snippets: &SpanSuggestionSnippets,
app: Applicability,
) {
span_lint_and_sugg(
cx,
Expand All @@ -192,16 +207,17 @@ fn suggest_suggestion(
span_suggestion_snippets.sugg,
span_suggestion_snippets.applicability
),
Applicability::MachineApplicable,
app,
);
}

fn suggest_help(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
and_then_snippets: &AndThenSnippets,
help: &str,
with_span: bool,
app: Applicability,
) {
let option_span = if with_span {
format!("Some({})", and_then_snippets.span)
Expand All @@ -219,16 +235,17 @@ 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,
);
}

fn suggest_note(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
and_then_snippets: &AndThenSnippets,
note: &str,
with_span: bool,
app: Applicability,
) {
let note_span = if with_span {
format!("Some({})", and_then_snippets.span)
Expand All @@ -246,6 +263,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,
);
}
6 changes: 3 additions & 3 deletions clippy_lints_internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extern crate rustc_session;
extern crate rustc_span;

mod almost_standard_lint_formulation;
mod collapsible_calls;
mod collapsible_span_lint_calls;
mod derive_deserialize_allowing_unknown;
mod internal_paths;
mod lint_without_lint_pass;
Expand All @@ -46,7 +46,7 @@ use rustc_lint::{Lint, LintStore};

static LINTS: &[&Lint] = &[
almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION,
collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS,
collapsible_span_lint_calls::COLLAPSIBLE_SPAN_LINT_CALLS,
derive_deserialize_allowing_unknown::DERIVE_DESERIALIZE_ALLOWING_UNKNOWN,
lint_without_lint_pass::DEFAULT_LINT,
lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE,
Expand All @@ -66,7 +66,7 @@ pub fn register_lints(store: &mut LintStore) {

store.register_early_pass(|| Box::new(unsorted_clippy_utils_paths::UnsortedClippyUtilsPaths));
store.register_early_pass(|| Box::new(produce_ice::ProduceIce));
store.register_late_pass(|_| Box::new(collapsible_calls::CollapsibleCalls));
store.register_late_pass(|_| Box::new(collapsible_span_lint_calls::CollapsibleCalls));
store.register_late_pass(|_| Box::new(derive_deserialize_allowing_unknown::DeriveDeserializeAllowingUnknown));
store.register_late_pass(|_| Box::<symbols::Symbols>::default());
store.register_late_pass(|_| Box::<lint_without_lint_pass::LintWithoutLintPass>::default());
Expand Down
4 changes: 4 additions & 0 deletions clippy_utils/src/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ generate! {
get_unchecked,
get_unchecked_mut,
has_significant_drop,
help,
hidden_glob_reexports,
hygiene,
insert,
Expand Down Expand Up @@ -310,7 +311,10 @@ generate! {
sort,
sort_by,
sort_unstable_by,
span_help,
span_lint_and_then,
span_note,
span_suggestion,
split,
split_at,
split_at_checked,
Expand Down
9 changes: 9 additions & 0 deletions tests/ui-internal/collapsible_span_lint_calls.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -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}"));
}
}

Expand Down
29 changes: 29 additions & 0 deletions tests/ui-internal/collapsible_span_lint_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}"));
});
}
}

Expand Down
50 changes: 49 additions & 1 deletion tests/ui-internal/collapsible_span_lint_calls.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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