]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/excessive_nesting.rs
New upstream version 1.74.1+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / excessive_nesting.rs
CommitLineData
add651ee
FG
1use clippy_utils::diagnostics::span_lint_and_help;
2use clippy_utils::source::snippet;
3use rustc_ast::node_id::NodeSet;
4use rustc_ast::visit::{walk_block, walk_item, Visitor};
5use rustc_ast::{Block, Crate, Inline, Item, ItemKind, ModKind, NodeId};
fe692bf9
FG
6use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
7use rustc_middle::lint::in_external_macro;
8use rustc_session::{declare_tool_lint, impl_lint_pass};
9use rustc_span::Span;
10
11declare_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}
64impl_lint_pass!(ExcessiveNesting => [EXCESSIVE_NESTING]);
65
66#[derive(Clone)]
67pub struct ExcessiveNesting {
68 pub excessive_nesting_threshold: u64,
69 pub nodes: NodeSet,
70}
71
72impl 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
87impl 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
113struct NestingVisitor<'conf, 'cx> {
114 conf: &'conf mut ExcessiveNesting,
115 cx: &'cx EarlyContext<'cx>,
116 nest_level: u64,
117}
118
119impl 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
131impl<'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}