| 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 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 std::borrow::{Borrow, Cow}; |
| |
| use crate::internal_paths; |
| |
| declare_tool_lint! { |
| /// ### What it does |
| /// Lints `span_lint_and_then` function calls, where the |
| /// closure argument has only one statement and that statement is a method |
| /// call to `span_suggestion`, `span_help`, `span_note` (using the same |
| /// span), `help` or `note`. |
| /// |
| /// These usages of `span_lint_and_then` should be replaced with one of the |
| /// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or |
| /// `span_lint_and_note`. |
| /// |
| /// ### Why is this bad? |
| /// Using the wrapper `span_lint_and_*` functions, is more |
| /// convenient, readable and less error prone. |
| /// |
| /// ### Example |
| /// ```rust,ignore |
| /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { |
| /// diag.span_suggestion( |
| /// expr.span, |
| /// help_msg, |
| /// sugg.to_string(), |
| /// Applicability::MachineApplicable, |
| /// ); |
| /// }); |
| /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { |
| /// diag.span_help(expr.span, help_msg); |
| /// }); |
| /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { |
| /// diag.help(help_msg); |
| /// }); |
| /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { |
| /// diag.span_note(expr.span, note_msg); |
| /// }); |
| /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { |
| /// diag.note(note_msg); |
| /// }); |
| /// ``` |
| /// |
| /// Use instead: |
| /// ```rust,ignore |
| /// span_lint_and_sugg( |
| /// cx, |
| /// TEST_LINT, |
| /// expr.span, |
| /// lint_msg, |
| /// help_msg, |
| /// sugg.to_string(), |
| /// Applicability::MachineApplicable, |
| /// ); |
| /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg); |
| /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg); |
| /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg); |
| /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg); |
| /// ``` |
| pub clippy::COLLAPSIBLE_SPAN_LINT_CALLS, |
| Warn, |
| "found collapsible `span_lint_and_then` calls", |
| report_in_external_macro: true |
| } |
| |
| declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]); |
| |
| impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
| if is_lint_allowed(cx, COLLAPSIBLE_SPAN_LINT_CALLS, expr.hir_id) { |
| return; |
| } |
| |
| if let ExprKind::Call(func, [call_cx, call_lint, call_sp, call_msg, call_f]) = expr.kind |
| && internal_paths::SPAN_LINT_AND_THEN.matches_path(cx, func) |
| && let ExprKind::Closure(&Closure { body, .. }) = call_f.kind |
| && let body = cx.tcx.hir_body(body) |
| && let only_expr = peel_blocks_with_stmt(body.value) |
| && 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 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), |
| ); |
| }, |
| "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); |
| }, |
| "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); |
| }, |
| "help" => { |
| let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#); |
| suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false); |
| }, |
| "note" => { |
| let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#); |
| suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false); |
| }, |
| _ => (), |
| } |
| } |
| } |
| } |
| |
| struct AndThenSnippets<'a> { |
| cx: Cow<'a, str>, |
| lint: Cow<'a, str>, |
| span: Cow<'a, str>, |
| msg: Cow<'a, str>, |
| } |
| |
| fn get_and_then_snippets( |
| cx: &LateContext<'_>, |
| 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#""...""#); |
| |
| AndThenSnippets { |
| cx: cx_snippet, |
| lint: lint_snippet, |
| span: span_snippet, |
| msg: msg_snippet, |
| } |
| } |
| |
| struct SpanSuggestionSnippets<'a> { |
| help: Cow<'a, str>, |
| sugg: Cow<'a, str>, |
| applicability: Cow<'a, str>, |
| } |
| |
| fn span_suggestion_snippets<'a, 'hir>( |
| cx: &LateContext<'_>, |
| 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"); |
| |
| SpanSuggestionSnippets { |
| help: help_snippet, |
| sugg: sugg_snippet, |
| applicability: applicability_snippet, |
| } |
| } |
| |
| fn suggest_suggestion( |
| cx: &LateContext<'_>, |
| expr: &Expr<'_>, |
| and_then_snippets: &AndThenSnippets<'_>, |
| span_suggestion_snippets: &SpanSuggestionSnippets<'_>, |
| ) { |
| span_lint_and_sugg( |
| cx, |
| COLLAPSIBLE_SPAN_LINT_CALLS, |
| expr.span, |
| "this call is collapsible", |
| "collapse into", |
| format!( |
| "span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})", |
| and_then_snippets.cx, |
| and_then_snippets.lint, |
| and_then_snippets.span, |
| and_then_snippets.msg, |
| span_suggestion_snippets.help, |
| span_suggestion_snippets.sugg, |
| span_suggestion_snippets.applicability |
| ), |
| Applicability::MachineApplicable, |
| ); |
| } |
| |
| fn suggest_help( |
| cx: &LateContext<'_>, |
| expr: &Expr<'_>, |
| and_then_snippets: &AndThenSnippets<'_>, |
| help: &str, |
| with_span: bool, |
| ) { |
| let option_span = if with_span { |
| format!("Some({})", and_then_snippets.span) |
| } else { |
| "None".to_string() |
| }; |
| |
| span_lint_and_sugg( |
| cx, |
| COLLAPSIBLE_SPAN_LINT_CALLS, |
| expr.span, |
| "this call is collapsible", |
| "collapse into", |
| format!( |
| "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, |
| ); |
| } |
| |
| fn suggest_note( |
| cx: &LateContext<'_>, |
| expr: &Expr<'_>, |
| and_then_snippets: &AndThenSnippets<'_>, |
| note: &str, |
| with_span: bool, |
| ) { |
| let note_span = if with_span { |
| format!("Some({})", and_then_snippets.span) |
| } else { |
| "None".to_string() |
| }; |
| |
| span_lint_and_sugg( |
| cx, |
| COLLAPSIBLE_SPAN_LINT_CALLS, |
| expr.span, |
| "this call is collapsible", |
| "collapse into", |
| format!( |
| "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, |
| ); |
| } |