Skip to content

Commit e68dbc3

Browse files
committed
add excessive_nesting
Close code block in example
1 parent 4af3ac1 commit e68dbc3

File tree

17 files changed

+893
-215
lines changed

17 files changed

+893
-215
lines changed

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4758,9 +4758,8 @@ Released 2018-09-13
47584758
[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
47594759
[`err_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#err_expect
47604760
[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
4761-
[`excessive_indentation`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_indentation
4761+
[`excessive_nesting`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_nesting
47624762
[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
4763-
[`excessive_width`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_width
47644763
[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
47654764
[`exhaustive_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_structs
47664765
[`exit`]: https://rust-lang.github.io/rust-clippy/master/index.html#exit

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
159159
crate::eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS_INFO,
160160
crate::excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS_INFO,
161161
crate::excessive_bools::STRUCT_EXCESSIVE_BOOLS_INFO,
162-
crate::excessive_width::EXCESSIVE_INDENTATION_INFO,
163-
crate::excessive_width::EXCESSIVE_WIDTH_INFO,
162+
crate::excessive_nesting::EXCESSIVE_NESTING_INFO,
164163
crate::exhaustive_items::EXHAUSTIVE_ENUMS_INFO,
165164
crate::exhaustive_items::EXHAUSTIVE_STRUCTS_INFO,
166165
crate::exit::EXIT_INFO,

clippy_lints/src/excessive_nesting.rs

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
use clippy_utils::diagnostics::span_lint_and_help;
2+
use rustc_ast::{
3+
node_id::NodeId,
4+
ptr::P,
5+
visit::{FnKind, Visitor},
6+
Arm, AssocItemKind, Block, Expr, ExprKind, Inline, Item, ItemKind, Local, LocalKind, ModKind, ModSpans, Pat,
7+
PatKind, Stmt, StmtKind,
8+
};
9+
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
10+
use rustc_middle::lint::in_external_macro;
11+
use rustc_session::{declare_tool_lint, impl_lint_pass};
12+
use rustc_span::Span;
13+
use thin_vec::ThinVec;
14+
15+
declare_clippy_lint! {
16+
/// ### What it does
17+
///
18+
/// Checks for blocks which are indented beyond a certain threshold.
19+
///
20+
/// ### Why is this bad?
21+
///
22+
/// It can severely hinder readability. The default is very generous; if you
23+
/// exceed this, it's a sign you should refactor.
24+
///
25+
/// ### Known issues
26+
///
27+
/// Nested inline modules will all be linted, rather than just the outermost one
28+
/// that applies. This makes the output a bit verbose.
29+
///
30+
/// ### Example
31+
/// An example clippy.toml configuration:
32+
/// ```toml
33+
/// # clippy.toml
34+
/// excessive-nesting-threshold = 3
35+
/// ```
36+
/// lib.rs:
37+
/// ```rust,ignore
38+
/// pub mod a {
39+
/// pub struct X;
40+
/// impl X {
41+
/// pub fn run(&self) {
42+
/// if true {
43+
/// // etc...
44+
/// }
45+
/// }
46+
/// }
47+
/// }
48+
/// Use instead:
49+
/// a.rs:
50+
/// ```rust,ignore
51+
/// fn private_run(x: &X) {
52+
/// if true {
53+
/// // etc...
54+
/// }
55+
/// }
56+
///
57+
/// pub struct X;
58+
/// impl X {
59+
/// pub fn run(&self) {
60+
/// private_run(self);
61+
/// }
62+
/// }
63+
/// ```
64+
#[clippy::version = "1.70.0"]
65+
pub EXCESSIVE_NESTING,
66+
restriction,
67+
"checks for blocks nested beyond a certain threshold"
68+
}
69+
impl_lint_pass!(ExcessiveNesting => [EXCESSIVE_NESTING]);
70+
71+
#[derive(Clone, Copy)]
72+
pub struct ExcessiveNesting {
73+
pub excessive_nesting_threshold: u64,
74+
}
75+
76+
impl EarlyLintPass for ExcessiveNesting {
77+
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
78+
let conf = self;
79+
NestingVisitor {
80+
conf,
81+
cx,
82+
nest_level: 0,
83+
}
84+
.visit_item(item);
85+
}
86+
}
87+
88+
struct NestingVisitor<'conf, 'cx> {
89+
conf: &'conf ExcessiveNesting,
90+
cx: &'cx EarlyContext<'cx>,
91+
nest_level: u64,
92+
}
93+
94+
impl<'conf, 'cx> Visitor<'_> for NestingVisitor<'conf, 'cx> {
95+
fn visit_local(&mut self, local: &Local) {
96+
self.visit_pat(&local.pat);
97+
98+
match &local.kind {
99+
LocalKind::Init(expr) => self.visit_expr(expr),
100+
LocalKind::InitElse(expr, block) => {
101+
self.visit_expr(expr);
102+
self.visit_block(block);
103+
},
104+
LocalKind::Decl => (),
105+
}
106+
}
107+
108+
fn visit_block(&mut self, block: &Block) {
109+
self.nest_level += 1;
110+
111+
if !check_indent(self, block.span) {
112+
for stmt in &block.stmts {
113+
self.visit_stmt(stmt);
114+
}
115+
}
116+
117+
self.nest_level -= 1;
118+
}
119+
120+
fn visit_stmt(&mut self, stmt: &Stmt) {
121+
match &stmt.kind {
122+
StmtKind::Local(local) => self.visit_local(local),
123+
StmtKind::Item(item) => self.visit_item(item),
124+
StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(expr),
125+
_ => (),
126+
}
127+
}
128+
129+
fn visit_arm(&mut self, arm: &Arm) {
130+
self.visit_pat(&arm.pat);
131+
if let Some(expr) = &arm.guard {
132+
self.visit_expr(expr);
133+
}
134+
self.visit_expr(&arm.body);
135+
}
136+
137+
// TODO: Is this necessary?
138+
fn visit_pat(&mut self, pat: &Pat) {
139+
match &pat.kind {
140+
PatKind::Box(pat) | PatKind::Ref(pat, ..) | PatKind::Paren(pat) => self.visit_pat(pat),
141+
PatKind::Lit(expr) => self.visit_expr(expr),
142+
PatKind::Range(start, end, ..) => {
143+
if let Some(expr) = start {
144+
self.visit_expr(expr);
145+
}
146+
if let Some(expr) = end {
147+
self.visit_expr(expr);
148+
}
149+
},
150+
PatKind::Ident(.., pat) if let Some(pat) = pat => {
151+
self.visit_pat(pat);
152+
},
153+
PatKind::Struct(.., pat_fields, _) => {
154+
for pat_field in pat_fields {
155+
self.visit_pat(&pat_field.pat);
156+
}
157+
},
158+
PatKind::TupleStruct(.., pats) | PatKind::Or(pats) | PatKind::Tuple(pats) | PatKind::Slice(pats) => {
159+
for pat in pats {
160+
self.visit_pat(pat);
161+
}
162+
},
163+
_ => (),
164+
}
165+
}
166+
167+
fn visit_expr(&mut self, expr: &Expr) {
168+
// This is a mess, but really all it does is extract every expression from every applicable variant
169+
// of ExprKind until it finds a Block.
170+
match &expr.kind {
171+
ExprKind::ConstBlock(anon_const) => self.visit_expr(&anon_const.value),
172+
ExprKind::Call(.., args) => {
173+
for expr in args {
174+
self.visit_expr(expr);
175+
}
176+
},
177+
ExprKind::MethodCall(method_call) => {
178+
for expr in &method_call.args {
179+
self.visit_expr(expr);
180+
}
181+
},
182+
ExprKind::Tup(exprs) | ExprKind::Array(exprs) => {
183+
for expr in exprs {
184+
self.visit_expr(expr);
185+
}
186+
},
187+
ExprKind::Binary(.., left, right)
188+
| ExprKind::Assign(left, right, ..)
189+
| ExprKind::AssignOp(.., left, right)
190+
| ExprKind::Index(left, right) => {
191+
self.visit_expr(left);
192+
self.visit_expr(right);
193+
},
194+
ExprKind::Let(pat, expr, ..) => {
195+
self.visit_pat(pat);
196+
self.visit_expr(expr);
197+
},
198+
ExprKind::Unary(.., expr)
199+
| ExprKind::Await(expr)
200+
| ExprKind::Field(expr, ..)
201+
| ExprKind::AddrOf(.., expr)
202+
| ExprKind::Try(expr) => {
203+
self.visit_expr(expr);
204+
},
205+
ExprKind::Repeat(expr, anon_const) => {
206+
self.visit_expr(expr);
207+
self.visit_expr(&anon_const.value);
208+
},
209+
ExprKind::If(expr, block, else_expr) => {
210+
self.visit_expr(expr);
211+
self.visit_block(block);
212+
213+
if let Some(expr) = else_expr {
214+
self.visit_expr(expr);
215+
}
216+
},
217+
ExprKind::While(expr, block, ..) => {
218+
self.visit_expr(expr);
219+
self.visit_block(block);
220+
},
221+
ExprKind::ForLoop(pat, expr, block, ..) => {
222+
self.visit_pat(pat);
223+
self.visit_expr(expr);
224+
self.visit_block(block);
225+
},
226+
ExprKind::Loop(block, ..)
227+
| ExprKind::Block(block, ..)
228+
| ExprKind::Async(.., block)
229+
| ExprKind::TryBlock(block) => {
230+
self.visit_block(block);
231+
},
232+
ExprKind::Match(expr, arms) => {
233+
self.visit_expr(expr);
234+
235+
for arm in arms {
236+
self.visit_arm(arm);
237+
}
238+
},
239+
ExprKind::Closure(closure) => self.visit_expr(&closure.body),
240+
ExprKind::Range(start, end, ..) => {
241+
if let Some(expr) = start {
242+
self.visit_expr(expr);
243+
}
244+
if let Some(expr) = end {
245+
self.visit_expr(expr);
246+
}
247+
},
248+
ExprKind::Break(.., expr) | ExprKind::Ret(expr) | ExprKind::Yield(expr) | ExprKind::Yeet(expr) => {
249+
if let Some(expr) = expr {
250+
self.visit_expr(expr);
251+
}
252+
},
253+
ExprKind::Struct(struct_expr) => {
254+
for field in &struct_expr.fields {
255+
self.visit_expr(&field.expr);
256+
}
257+
},
258+
_ => (),
259+
}
260+
}
261+
262+
fn visit_fn(&mut self, fk: FnKind<'_>, _: Span, _: NodeId) {
263+
match fk {
264+
FnKind::Fn(.., block) if let Some(block) = block => self.visit_block(block),
265+
FnKind::Closure(.., expr) => self.visit_expr(expr),
266+
// :/
267+
FnKind::Fn(..) => (),
268+
}
269+
}
270+
271+
fn visit_item(&mut self, item: &Item) {
272+
match &item.kind {
273+
ItemKind::Static(static_item) if let Some(expr) = static_item.expr.as_ref() => self.visit_expr(expr),
274+
ItemKind::Const(const_item) if let Some(expr) = const_item.expr.as_ref() => self.visit_expr(expr),
275+
ItemKind::Fn(fk) if let Some(block) = fk.body.as_ref() => self.visit_block(block),
276+
ItemKind::Mod(.., mod_kind)
277+
if let ModKind::Loaded(items, Inline::Yes, ModSpans { inner_span, ..}) = mod_kind =>
278+
{
279+
self.nest_level += 1;
280+
281+
check_indent(self, *inner_span);
282+
283+
self.nest_level -= 1;
284+
}
285+
ItemKind::Trait(trit) => check_trait_and_impl(self, item, &trit.items),
286+
ItemKind::Impl(imp) => check_trait_and_impl(self, item, &imp.items),
287+
_ => (),
288+
}
289+
}
290+
}
291+
292+
fn check_trait_and_impl(visitor: &mut NestingVisitor<'_, '_>, item: &Item, items: &ThinVec<P<Item<AssocItemKind>>>) {
293+
visitor.nest_level += 1;
294+
295+
if !check_indent(visitor, item.span) {
296+
for item in items {
297+
match &item.kind {
298+
AssocItemKind::Const(const_item) if let Some(expr) = const_item.expr.as_ref() => {
299+
visitor.visit_expr(expr);
300+
},
301+
AssocItemKind::Fn(fk) if let Some(block) = fk.body.as_ref() => visitor.visit_block(block),
302+
_ => (),
303+
}
304+
}
305+
}
306+
307+
visitor.nest_level -= 1;
308+
}
309+
310+
fn check_indent(visitor: &NestingVisitor<'_, '_>, span: Span) -> bool {
311+
if visitor.nest_level > visitor.conf.excessive_nesting_threshold && !in_external_macro(visitor.cx.sess(), span) {
312+
span_lint_and_help(
313+
visitor.cx,
314+
EXCESSIVE_NESTING,
315+
span,
316+
"this block is too nested",
317+
None,
318+
"try refactoring your code, extraction is often both easier to read and less nested",
319+
);
320+
321+
return true;
322+
}
323+
324+
false
325+
}

0 commit comments

Comments
 (0)