Skip to content

Commit d3b808b

Browse files
committed
Implement collapsible_span_lint_calls.
1 parent 34763a5 commit d3b808b

File tree

6 files changed

+454
-7
lines changed

6 files changed

+454
-7
lines changed

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
830830
&unwrap::UNNECESSARY_UNWRAP,
831831
&use_self::USE_SELF,
832832
&utils::internal_lints::CLIPPY_LINTS_INTERNAL,
833+
&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
833834
&utils::internal_lints::COMPILER_LINT_FUNCTIONS,
834835
&utils::internal_lints::DEFAULT_LINT,
835836
&utils::internal_lints::LINT_WITHOUT_LINT_PASS,
@@ -1039,6 +1040,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10391040
store.register_late_pass(|| box verbose_file_reads::VerboseFileReads);
10401041
store.register_late_pass(|| box redundant_pub_crate::RedundantPubCrate::default());
10411042
store.register_late_pass(|| box unnamed_address::UnnamedAddress);
1043+
store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls);
10421044

10431045
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
10441046
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1148,6 +1150,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11481150

11491151
store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
11501152
LintId::of(&utils::internal_lints::CLIPPY_LINTS_INTERNAL),
1153+
LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
11511154
LintId::of(&utils::internal_lints::COMPILER_LINT_FUNCTIONS),
11521155
LintId::of(&utils::internal_lints::DEFAULT_LINT),
11531156
LintId::of(&utils::internal_lints::LINT_WITHOUT_LINT_PASS),

clippy_lints/src/utils/diagnostics.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ pub fn span_lint_hir_and_then(
163163
/// |
164164
/// = note: `-D fold-any` implied by `-D warnings`
165165
/// ```
166+
#[allow(clippy::collapsible_span_lint_calls)]
166167
pub fn span_lint_and_sugg<'a, T: LintContext>(
167168
cx: &'a T,
168169
lint: &'static Lint,

clippy_lints/src/utils/internal_lints.rs

Lines changed: 225 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use crate::utils::SpanlessEq;
12
use crate::utils::{
2-
is_expn_of, match_def_path, match_type, method_calls, paths, span_lint, span_lint_and_help, span_lint_and_sugg,
3-
walk_ptrs_ty,
3+
is_expn_of, match_def_path, match_qpath, match_type, method_calls, paths, snippet as other_snippet, span_lint,
4+
span_lint_and_help, span_lint_and_sugg, walk_ptrs_ty,
45
};
56
use if_chain::if_chain;
67
use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, Name, NodeId};
@@ -10,13 +11,15 @@ use rustc_errors::Applicability;
1011
use rustc_hir as hir;
1112
use rustc_hir::def::{DefKind, Res};
1213
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
13-
use rustc_hir::{Crate, Expr, ExprKind, HirId, Item, MutTy, Mutability, Path, Ty, TyKind};
14+
use rustc_hir::{Crate, Expr, ExprKind, HirId, Item, MutTy, Mutability, Path, StmtKind, Ty, TyKind};
1415
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
1516
use rustc_middle::hir::map::Map;
1617
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
1718
use rustc_span::source_map::{Span, Spanned};
1819
use rustc_span::symbol::SymbolStr;
1920

21+
use std::borrow::{Borrow, Cow};
22+
2023
declare_clippy_lint! {
2124
/// **What it does:** Checks for various things we like to keep tidy in clippy.
2225
///
@@ -142,6 +145,65 @@ declare_clippy_lint! {
142145
"found 'default lint description' in a lint declaration"
143146
}
144147

148+
declare_clippy_lint! {
149+
/// **What it does:** When `span_lint_and_then` function call and its
150+
/// closure have one statment that is a method call of `span_suggestion`,
151+
/// `span_help`, or `span_note` are using the same span argument,
152+
/// or when closure have one statment that is a function call of `note` or
153+
/// `help`, they should be replaced with one of three function calls which is
154+
/// `span_lint_and_sugg`, span_lint_and_help` or `span_lint_and_note`.
155+
///
156+
/// **Why is this bad?** There are wrapper `span_lint_and_*` functions, it is
157+
/// convenient, more readable and more error resistant to use wrapper functions.
158+
///
159+
/// **Known problems:** None
160+
///
161+
/// *Example:**
162+
/// Bad:
163+
/// ```rust,ignore
164+
/// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
165+
/// db.span_suggestion(
166+
/// expr.span,
167+
/// help_msg,
168+
/// sugg.to_string(),
169+
/// Applicability::MachineApplicable,
170+
/// );
171+
/// });
172+
/// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
173+
/// db.span_help(expr.span, help_msg);
174+
/// });
175+
/// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
176+
/// db.help(help_msg);
177+
/// });
178+
/// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
179+
/// db.span_note(expr.span, note_msg);
180+
/// });
181+
/// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
182+
/// db.note(note_msg);
183+
/// });
184+
/// ```
185+
///
186+
/// Good:
187+
/// ```rust,ignore
188+
/// span_lint_and_sugg(
189+
/// cx,
190+
/// TEST_LINT,
191+
/// expr.span,
192+
/// lint_msg,
193+
/// help_msg,
194+
/// sugg.to_string(),
195+
/// Applicability::MachineApplicable,
196+
/// );
197+
/// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, help_msg);
198+
/// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, help_msg);
199+
/// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, expr.span, note_msg);
200+
/// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, expr.span, note_msg);
201+
/// ```
202+
pub COLLAPSIBLE_SPAN_LINT_CALLS,
203+
internal,
204+
"found collapsible `span_lint_and_then` calls"
205+
}
206+
145207
declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
146208

147209
impl EarlyLintPass for ClippyLintsInternal {
@@ -165,7 +227,7 @@ impl EarlyLintPass for ClippyLintsInternal {
165227
CLIPPY_LINTS_INTERNAL,
166228
item.span,
167229
"this constant should be before the previous constant due to lexical \
168-
ordering",
230+
ordering",
169231
);
170232
}
171233
}
@@ -195,8 +257,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LintWithoutLintPass {
195257
if let ExprKind::AddrOf(_, _, ref inner_exp) = expr.kind;
196258
if let ExprKind::Struct(_, ref fields, _) = inner_exp.kind;
197259
let field = fields.iter()
198-
.find(|f| f.ident.as_str() == "desc")
199-
.expect("lints must have a description field");
260+
.find(|f| f.ident.as_str() == "desc")
261+
.expect("lints must have a description field");
200262
if let ExprKind::Lit(Spanned {
201263
node: LitKind::Str(ref sym, _),
202264
..
@@ -332,7 +394,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CompilerLintFunctions {
332394
if let Some(sugg) = self.map.get(&*fn_name.as_str());
333395
let ty = walk_ptrs_ty(cx.tables.expr_ty(&args[0]));
334396
if match_type(cx, ty, &paths::EARLY_CONTEXT)
335-
|| match_type(cx, ty, &paths::LATE_CONTEXT);
397+
|| match_type(cx, ty, &paths::LATE_CONTEXT);
336398
then {
337399
span_lint_and_help(
338400
cx,
@@ -391,3 +453,159 @@ fn is_trigger_fn(fn_kind: FnKind<'_>) -> bool {
391453
FnKind::Closure(..) => false,
392454
}
393455
}
456+
457+
declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]);
458+
459+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CollapsibleCalls {
460+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) {
461+
if_chain! {
462+
if let ExprKind::Call(ref func, ref and_then_args) = expr.kind;
463+
if let ExprKind::Path(ref path) = func.kind;
464+
if match_qpath(path, &["span_lint_and_then"]);
465+
if and_then_args.len() == 5;
466+
if let ExprKind::Closure(_, _, body_id, _, _) = &and_then_args[4].kind;
467+
if let body = cx.tcx.hir().body(*body_id);
468+
if let ExprKind::Block(block, _) = &body.value.kind;
469+
let stmts = &block.stmts;
470+
if stmts.len() == 1 && block.expr.is_none();
471+
if let StmtKind::Semi(only_expr) = &stmts[0].kind;
472+
if let ExprKind::MethodCall(ref ps, _, ref span_call_args) = &only_expr.kind;
473+
let and_then_snippets = get_and_then_snippets(cx, and_then_args);
474+
let mut sle = SpanlessEq::new(cx).ignore_fn();
475+
then {
476+
match &*ps.ident.as_str() {
477+
"span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
478+
suggest_suggestion(cx, expr, &and_then_snippets, &span_suggestion_snippets(cx, span_call_args));
479+
},
480+
"span_help" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
481+
let help_snippet = snippet(cx, span_call_args[2].span);
482+
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow());
483+
},
484+
"span_note" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
485+
let note_snippet = snippet(cx, span_call_args[2].span);
486+
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow());
487+
},
488+
"help" => {
489+
let help_snippet = snippet(cx, span_call_args[1].span);
490+
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow());
491+
}
492+
"note" => {
493+
let note_snippet = snippet(cx, span_call_args[1].span);
494+
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow());
495+
}
496+
_ => (),
497+
}
498+
}
499+
}
500+
}
501+
}
502+
503+
struct AndThenSnippets<'a> {
504+
cx: Cow<'a, str>,
505+
lint: Cow<'a, str>,
506+
span: Cow<'a, str>,
507+
msg: Cow<'a, str>,
508+
}
509+
510+
fn get_and_then_snippets<'a, 'hir>(
511+
cx: &LateContext<'_, '_>,
512+
and_then_snippets: &'hir [Expr<'hir>],
513+
) -> AndThenSnippets<'a> {
514+
let cx_snippet = snippet(cx, and_then_snippets[0].span);
515+
let lint_snippet = snippet(cx, and_then_snippets[1].span);
516+
let span_snippet = snippet(cx, and_then_snippets[2].span);
517+
let msg_snippet = snippet(cx, and_then_snippets[3].span);
518+
519+
AndThenSnippets {
520+
cx: cx_snippet,
521+
lint: lint_snippet,
522+
span: span_snippet,
523+
msg: msg_snippet,
524+
}
525+
}
526+
527+
struct SpanSuggestionSnippets<'a> {
528+
help: Cow<'a, str>,
529+
sugg: Cow<'a, str>,
530+
applicability: Cow<'a, str>,
531+
}
532+
533+
fn span_suggestion_snippets<'a, 'hir>(
534+
cx: &LateContext<'_, '_>,
535+
span_call_args: &'hir [Expr<'hir>],
536+
) -> SpanSuggestionSnippets<'a> {
537+
let help_snippet = snippet(cx, span_call_args[2].span);
538+
let sugg_snippet = snippet(cx, span_call_args[3].span);
539+
let applicability_snippet = snippet(cx, span_call_args[4].span);
540+
541+
SpanSuggestionSnippets {
542+
help: help_snippet,
543+
sugg: sugg_snippet,
544+
applicability: applicability_snippet,
545+
}
546+
}
547+
548+
fn suggest_suggestion(
549+
cx: &LateContext<'_, '_>,
550+
expr: &Expr<'_>,
551+
and_then_snippets: &AndThenSnippets<'_>,
552+
span_suggestion_snippets: &SpanSuggestionSnippets<'_>,
553+
) {
554+
span_lint_and_sugg(
555+
cx,
556+
COLLAPSIBLE_SPAN_LINT_CALLS,
557+
expr.span,
558+
"this call is collapsible",
559+
"collapse into",
560+
format!(
561+
"span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})",
562+
and_then_snippets.cx,
563+
and_then_snippets.lint,
564+
and_then_snippets.span,
565+
and_then_snippets.msg,
566+
span_suggestion_snippets.help,
567+
span_suggestion_snippets.sugg,
568+
span_suggestion_snippets.applicability
569+
),
570+
Applicability::MachineApplicable,
571+
);
572+
}
573+
574+
fn suggest_help(cx: &LateContext<'_, '_>, expr: &Expr<'_>, and_then_snippets: &AndThenSnippets<'_>, help: &str) {
575+
span_lint_and_sugg(
576+
cx,
577+
COLLAPSIBLE_SPAN_LINT_CALLS,
578+
expr.span,
579+
"this call is collapsible",
580+
"collapse into",
581+
format!(
582+
"span_lint_and_help({}, {}, {}, {}, {})",
583+
and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, help
584+
),
585+
Applicability::MachineApplicable,
586+
);
587+
}
588+
589+
fn suggest_note(cx: &LateContext<'_, '_>, expr: &Expr<'_>, and_then_snippets: &AndThenSnippets<'_>, note: &str) {
590+
span_lint_and_sugg(
591+
cx,
592+
COLLAPSIBLE_SPAN_LINT_CALLS,
593+
expr.span,
594+
"this call is collspible",
595+
"collapse into",
596+
format!(
597+
"span_lint_and_note({}, {}, {}, {}, {}, {})",
598+
and_then_snippets.cx,
599+
and_then_snippets.lint,
600+
and_then_snippets.span,
601+
and_then_snippets.msg,
602+
and_then_snippets.span,
603+
note
604+
),
605+
Applicability::MachineApplicable,
606+
);
607+
}
608+
609+
fn snippet<'a>(cx: &LateContext<'_, '_>, span: Span) -> Cow<'a, str> {
610+
other_snippet(cx, span, "Should not be this snippet")
611+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// run-rustfix
2+
#![deny(clippy::internal)]
3+
#![feature(rustc_private)]
4+
5+
extern crate rustc_ast;
6+
extern crate rustc_errors;
7+
extern crate rustc_lint;
8+
extern crate rustc_session;
9+
extern crate rustc_span;
10+
11+
use rustc_ast::ast::Expr;
12+
use rustc_errors::{Applicability, DiagnosticBuilder};
13+
use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
14+
use rustc_session::{declare_lint_pass, declare_tool_lint};
15+
use rustc_span::source_map::Span;
16+
17+
#[allow(unused_variables)]
18+
pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F)
19+
where
20+
F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>),
21+
{
22+
}
23+
24+
#[allow(unused_variables)]
25+
fn span_lint_and_help<'a, T: LintContext>(cx: &'a T, lint: &'static Lint, span: Span, msg: &str, help: &str) {}
26+
27+
#[allow(unused_variables)]
28+
fn span_lint_and_note<'a, T: LintContext>(
29+
cx: &'a T,
30+
lint: &'static Lint,
31+
span: Span,
32+
msg: &str,
33+
note_span: Span,
34+
note: &str,
35+
) {
36+
}
37+
38+
#[allow(unused_variables)]
39+
fn span_lint_and_sugg<'a, T: LintContext>(
40+
cx: &'a T,
41+
lint: &'static Lint,
42+
sp: Span,
43+
msg: &str,
44+
help: &str,
45+
sugg: String,
46+
applicability: Applicability,
47+
) {
48+
}
49+
50+
declare_tool_lint! {
51+
pub clippy::TEST_LINT,
52+
Warn,
53+
"",
54+
report_in_external_macro: true
55+
}
56+
57+
declare_lint_pass!(Pass => [TEST_LINT]);
58+
59+
impl EarlyLintPass for Pass {
60+
fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
61+
let lint_msg = "lint message";
62+
let help_msg = "help message";
63+
let note_msg = "note message";
64+
let sugg = "new_call()";
65+
let predicate = true;
66+
67+
span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable);
68+
span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, help_msg);
69+
span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, help_msg);
70+
span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, expr.span, note_msg);
71+
span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, expr.span, note_msg);
72+
73+
// This expr shouldn't trigger this lint.
74+
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
75+
db.note(note_msg);
76+
if predicate {
77+
db.note(note_msg);
78+
}
79+
})
80+
}
81+
}
82+
83+
fn main() {}

0 commit comments

Comments
 (0)