blob: 7c9e7286925ef6452a9bf9f4d734e2513937fa22 [file] [log] [blame] [edit]
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,
);
}