]>
Commit | Line | Data |
---|---|---|
add651ee FG |
1 | use clippy_utils::diagnostics::span_lint_and_help; |
2 | use clippy_utils::source::snippet; | |
3 | use rustc_ast::node_id::NodeSet; | |
4 | use rustc_ast::visit::{walk_block, walk_item, Visitor}; | |
5 | use rustc_ast::{Block, Crate, Inline, Item, ItemKind, ModKind, NodeId}; | |
fe692bf9 FG |
6 | use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; |
7 | use rustc_middle::lint::in_external_macro; | |
8 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
9 | use rustc_span::Span; | |
10 | ||
11 | declare_clippy_lint! { | |
12 | /// ### What it does | |
13 | /// Checks for blocks which are nested beyond a certain threshold. | |
14 | /// | |
15 | /// Note: Even though this lint is warn-by-default, it will only trigger if a maximum nesting level is defined in the clippy.toml file. | |
16 | /// | |
17 | /// ### Why is this bad? | |
18 | /// It can severely hinder readability. | |
19 | /// | |
20 | /// ### Example | |
21 | /// An example clippy.toml configuration: | |
22 | /// ```toml | |
23 | /// # clippy.toml | |
24 | /// excessive-nesting-threshold = 3 | |
25 | /// ``` | |
26 | /// ```rust,ignore | |
27 | /// // lib.rs | |
28 | /// pub mod a { | |
29 | /// pub struct X; | |
30 | /// impl X { | |
31 | /// pub fn run(&self) { | |
32 | /// if true { | |
33 | /// // etc... | |
34 | /// } | |
35 | /// } | |
36 | /// } | |
37 | /// } | |
38 | /// ``` | |
39 | /// Use instead: | |
40 | /// ```rust,ignore | |
41 | /// // a.rs | |
42 | /// fn private_run(x: &X) { | |
43 | /// if true { | |
44 | /// // etc... | |
45 | /// } | |
46 | /// } | |
47 | /// | |
48 | /// pub struct X; | |
49 | /// impl X { | |
50 | /// pub fn run(&self) { | |
51 | /// private_run(self); | |
52 | /// } | |
53 | /// } | |
54 | /// ``` | |
55 | /// ```rust,ignore | |
56 | /// // lib.rs | |
57 | /// pub mod a; | |
58 | /// ``` | |
781aab86 | 59 | #[clippy::version = "1.72.0"] |
fe692bf9 FG |
60 | pub EXCESSIVE_NESTING, |
61 | complexity, | |
62 | "checks for blocks nested beyond a certain threshold" | |
63 | } | |
64 | impl_lint_pass!(ExcessiveNesting => [EXCESSIVE_NESTING]); | |
65 | ||
66 | #[derive(Clone)] | |
67 | pub struct ExcessiveNesting { | |
68 | pub excessive_nesting_threshold: u64, | |
69 | pub nodes: NodeSet, | |
70 | } | |
71 | ||
72 | impl ExcessiveNesting { | |
73 | pub fn check_node_id(&self, cx: &EarlyContext<'_>, span: Span, node_id: NodeId) { | |
74 | if self.nodes.contains(&node_id) { | |
75 | span_lint_and_help( | |
76 | cx, | |
77 | EXCESSIVE_NESTING, | |
78 | span, | |
79 | "this block is too nested", | |
80 | None, | |
81 | "try refactoring your code to minimize nesting", | |
82 | ); | |
83 | } | |
84 | } | |
85 | } | |
86 | ||
87 | impl EarlyLintPass for ExcessiveNesting { | |
88 | fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) { | |
89 | if self.excessive_nesting_threshold == 0 { | |
90 | return; | |
91 | } | |
92 | ||
93 | let mut visitor = NestingVisitor { | |
94 | conf: self, | |
95 | cx, | |
96 | nest_level: 0, | |
97 | }; | |
98 | ||
99 | for item in &krate.items { | |
100 | visitor.visit_item(item); | |
101 | } | |
102 | } | |
103 | ||
104 | fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) { | |
105 | self.check_node_id(cx, block.span, block.id); | |
106 | } | |
107 | ||
108 | fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { | |
109 | self.check_node_id(cx, item.span, item.id); | |
110 | } | |
111 | } | |
112 | ||
113 | struct NestingVisitor<'conf, 'cx> { | |
114 | conf: &'conf mut ExcessiveNesting, | |
115 | cx: &'cx EarlyContext<'cx>, | |
116 | nest_level: u64, | |
117 | } | |
118 | ||
119 | impl NestingVisitor<'_, '_> { | |
120 | fn check_indent(&mut self, span: Span, id: NodeId) -> bool { | |
121 | if self.nest_level > self.conf.excessive_nesting_threshold && !in_external_macro(self.cx.sess(), span) { | |
122 | self.conf.nodes.insert(id); | |
123 | ||
124 | return true; | |
125 | } | |
126 | ||
127 | false | |
128 | } | |
129 | } | |
130 | ||
131 | impl<'conf, 'cx> Visitor<'_> for NestingVisitor<'conf, 'cx> { | |
132 | fn visit_block(&mut self, block: &Block) { | |
133 | if block.span.from_expansion() { | |
134 | return; | |
135 | } | |
136 | ||
137 | // TODO: This should be rewritten using `LateLintPass` so we can use `is_from_proc_macro` instead, | |
138 | // but for now, this is fine. | |
139 | let snippet = snippet(self.cx, block.span, "{}").trim().to_owned(); | |
140 | if !snippet.starts_with('{') || !snippet.ends_with('}') { | |
141 | return; | |
142 | } | |
143 | ||
144 | self.nest_level += 1; | |
145 | ||
146 | if !self.check_indent(block.span, block.id) { | |
147 | walk_block(self, block); | |
148 | } | |
149 | ||
150 | self.nest_level -= 1; | |
151 | } | |
152 | ||
153 | fn visit_item(&mut self, item: &Item) { | |
154 | if item.span.from_expansion() { | |
155 | return; | |
156 | } | |
157 | ||
158 | match &item.kind { | |
159 | ItemKind::Trait(_) | ItemKind::Impl(_) | ItemKind::Mod(.., ModKind::Loaded(_, Inline::Yes, _)) => { | |
160 | self.nest_level += 1; | |
161 | ||
162 | if !self.check_indent(item.span, item.id) { | |
163 | walk_item(self, item); | |
164 | } | |
165 | ||
166 | self.nest_level -= 1; | |
167 | }, | |
168 | // Reset nesting level for non-inline modules (since these are in another file) | |
169 | ItemKind::Mod(..) => walk_item( | |
170 | &mut NestingVisitor { | |
171 | conf: self.conf, | |
172 | cx: self.cx, | |
173 | nest_level: 0, | |
174 | }, | |
175 | item, | |
176 | ), | |
177 | _ => walk_item(self, item), | |
178 | } | |
179 | } | |
180 | } |