| use clippy_utils::diagnostics::span_lint_and_sugg; |
| 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, SyntaxContext}; |
| |
| 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 mut app = Applicability::MachineApplicable; |
| let ctxt = expr.span.ctxt(); |
| let and_then_snippets = get_and_then_snippets( |
| cx, |
| 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(ctxt, call_sp, &span_call_args[0]) => { |
| let snippets = span_suggestion_snippets(cx, ctxt, span_call_args, &mut app); |
| suggest_suggestion(cx, expr, &and_then_snippets, &snippets, app); |
| }, |
| sym::span_help if sle.eq_expr(ctxt, call_sp, &span_call_args[0]) => { |
| let help_snippet = snippet_with_context(cx, span_call_args[1].span, ctxt, r#""...""#, &mut app).0; |
| suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true, app); |
| }, |
| sym::span_note if sle.eq_expr(ctxt, call_sp, &span_call_args[0]) => { |
| let note_snippet = snippet_with_context(cx, span_call_args[1].span, ctxt, r#""...""#, &mut app).0; |
| suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true, app); |
| }, |
| sym::help => { |
| let help_snippet = snippet_with_context(cx, span_call_args[0].span, ctxt, r#""...""#, &mut app).0; |
| suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false, app); |
| }, |
| sym::note => { |
| let note_snippet = snippet_with_context(cx, span_call_args[0].span, ctxt, r#""...""#, &mut app).0; |
| suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false, app); |
| }, |
| _ => (), |
| } |
| } |
| } |
| } |
| |
| 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<'_>, |
| ctxt: SyntaxContext, |
| cx_span: Span, |
| lint_span: Span, |
| span_span: Span, |
| msg_span: Span, |
| 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, ctxt, r#""...""#, app).0; |
| |
| AndThenSnippets { |
| cx: cx_snippet, |
| lint: lint_snippet, |
| span: span_snippet, |
| msg: msg_snippet, |
| } |
| } |
| |
| struct SpanSuggestionSnippets { |
| help: Cow<'static, str>, |
| sugg: Cow<'static, str>, |
| applicability: Cow<'static, str>, |
| } |
| |
| fn span_suggestion_snippets<'hir>( |
| cx: &LateContext<'_>, |
| ctxt: SyntaxContext, |
| span_call_args: &'hir [Expr<'hir>], |
| app: &mut Applicability, |
| ) -> SpanSuggestionSnippets { |
| let help_snippet = snippet_with_context(cx, span_call_args[1].span, ctxt, r#""...""#, app).0; |
| let sugg_snippet = snippet_with_context(cx, span_call_args[2].span, ctxt, "..", app).0; |
| let applicability_snippet = |
| snippet_with_applicability(cx, span_call_args[3].span, "Applicability::MachineApplicable", app); |
| |
| 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, |
| app: Applicability, |
| ) { |
| 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 |
| ), |
| app, |
| ); |
| } |
| |
| fn suggest_help( |
| cx: &LateContext<'_>, |
| expr: &Expr<'_>, |
| and_then_snippets: &AndThenSnippets, |
| help: &str, |
| with_span: bool, |
| app: Applicability, |
| ) { |
| 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({}, {}, {}, {}, {option_span}, {help})", |
| and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg |
| ), |
| app, |
| ); |
| } |
| |
| fn suggest_note( |
| cx: &LateContext<'_>, |
| expr: &Expr<'_>, |
| and_then_snippets: &AndThenSnippets, |
| note: &str, |
| with_span: bool, |
| app: Applicability, |
| ) { |
| 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, |
| ), |
| app, |
| ); |
| } |