| use clippy_utils::diagnostics::span_lint_and_help; |
| use clippy_utils::source::snippet; |
| use rustc_ast::node_id::NodeSet; |
| use rustc_ast::visit::{walk_block, walk_item, Visitor}; |
| use rustc_ast::{Block, Crate, Inline, Item, ItemKind, ModKind, NodeId}; |
| use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; |
| use rustc_middle::lint::in_external_macro; |
| use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| use rustc_span::Span; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Checks for blocks which are nested beyond a certain threshold. |
| /// |
| /// 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. |
| /// |
| /// ### Why is this bad? |
| /// It can severely hinder readability. |
| /// |
| /// ### Example |
| /// An example clippy.toml configuration: |
| /// ```toml |
| /// # clippy.toml |
| /// excessive-nesting-threshold = 3 |
| /// ``` |
| /// ```rust,ignore |
| /// // lib.rs |
| /// pub mod a { |
| /// pub struct X; |
| /// impl X { |
| /// pub fn run(&self) { |
| /// if true { |
| /// // etc... |
| /// } |
| /// } |
| /// } |
| /// } |
| /// ``` |
| /// Use instead: |
| /// ```rust,ignore |
| /// // a.rs |
| /// fn private_run(x: &X) { |
| /// if true { |
| /// // etc... |
| /// } |
| /// } |
| /// |
| /// pub struct X; |
| /// impl X { |
| /// pub fn run(&self) { |
| /// private_run(self); |
| /// } |
| /// } |
| /// ``` |
| /// ```rust,ignore |
| /// // lib.rs |
| /// pub mod a; |
| /// ``` |
| #[clippy::version = "1.70.0"] |
| pub EXCESSIVE_NESTING, |
| complexity, |
| "checks for blocks nested beyond a certain threshold" |
| } |
| impl_lint_pass!(ExcessiveNesting => [EXCESSIVE_NESTING]); |
| |
| #[derive(Clone)] |
| pub struct ExcessiveNesting { |
| pub excessive_nesting_threshold: u64, |
| pub nodes: NodeSet, |
| } |
| |
| impl ExcessiveNesting { |
| pub fn check_node_id(&self, cx: &EarlyContext<'_>, span: Span, node_id: NodeId) { |
| if self.nodes.contains(&node_id) { |
| span_lint_and_help( |
| cx, |
| EXCESSIVE_NESTING, |
| span, |
| "this block is too nested", |
| None, |
| "try refactoring your code to minimize nesting", |
| ); |
| } |
| } |
| } |
| |
| impl EarlyLintPass for ExcessiveNesting { |
| fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) { |
| if self.excessive_nesting_threshold == 0 { |
| return; |
| } |
| |
| let mut visitor = NestingVisitor { |
| conf: self, |
| cx, |
| nest_level: 0, |
| }; |
| |
| for item in &krate.items { |
| visitor.visit_item(item); |
| } |
| } |
| |
| fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) { |
| self.check_node_id(cx, block.span, block.id); |
| } |
| |
| fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { |
| self.check_node_id(cx, item.span, item.id); |
| } |
| } |
| |
| struct NestingVisitor<'conf, 'cx> { |
| conf: &'conf mut ExcessiveNesting, |
| cx: &'cx EarlyContext<'cx>, |
| nest_level: u64, |
| } |
| |
| impl NestingVisitor<'_, '_> { |
| fn check_indent(&mut self, span: Span, id: NodeId) -> bool { |
| if self.nest_level > self.conf.excessive_nesting_threshold && !in_external_macro(self.cx.sess(), span) { |
| self.conf.nodes.insert(id); |
| |
| return true; |
| } |
| |
| false |
| } |
| } |
| |
| impl<'conf, 'cx> Visitor<'_> for NestingVisitor<'conf, 'cx> { |
| fn visit_block(&mut self, block: &Block) { |
| if block.span.from_expansion() { |
| return; |
| } |
| |
| // TODO: This should be rewritten using `LateLintPass` so we can use `is_from_proc_macro` instead, |
| // but for now, this is fine. |
| let snippet = snippet(self.cx, block.span, "{}").trim().to_owned(); |
| if !snippet.starts_with('{') || !snippet.ends_with('}') { |
| return; |
| } |
| |
| self.nest_level += 1; |
| |
| if !self.check_indent(block.span, block.id) { |
| walk_block(self, block); |
| } |
| |
| self.nest_level -= 1; |
| } |
| |
| fn visit_item(&mut self, item: &Item) { |
| if item.span.from_expansion() { |
| return; |
| } |
| |
| match &item.kind { |
| ItemKind::Trait(_) | ItemKind::Impl(_) | ItemKind::Mod(.., ModKind::Loaded(_, Inline::Yes, _)) => { |
| self.nest_level += 1; |
| |
| if !self.check_indent(item.span, item.id) { |
| walk_item(self, item); |
| } |
| |
| self.nest_level -= 1; |
| }, |
| // Reset nesting level for non-inline modules (since these are in another file) |
| ItemKind::Mod(..) => walk_item( |
| &mut NestingVisitor { |
| conf: self.conf, |
| cx: self.cx, |
| nest_level: 0, |
| }, |
| item, |
| ), |
| _ => walk_item(self, item), |
| } |
| } |
| } |