Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4758,6 +4758,7 @@ Released 2018-09-13
[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
[`err_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#err_expect
[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
[`excessive_nesting`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_nesting
[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
[`exhaustive_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_structs
Expand Down
10 changes: 10 additions & 0 deletions book/src/lint_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ The maximum cognitive complexity a function can have
* [`cognitive_complexity`](https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity)


## `excessive-nesting-threshold`
The maximum amount of nesting a block can reside in

**Default Value:** `0` (`u64`)

---
**Affected lints:**
* [`excessive_nesting`](https://rust-lang.github.io/rust-clippy/master/index.html#excessive_nesting)


## `disallowed-names`
The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
`".."` can be used as part of the list to indicate, that the configured values should be appended to the
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS_INFO,
crate::excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS_INFO,
crate::excessive_bools::STRUCT_EXCESSIVE_BOOLS_INFO,
crate::excessive_nesting::EXCESSIVE_NESTING_INFO,
crate::exhaustive_items::EXHAUSTIVE_ENUMS_INFO,
crate::exhaustive_items::EXHAUSTIVE_STRUCTS_INFO,
crate::exit::EXIT_INFO,
Expand Down
180 changes: 180 additions & 0 deletions clippy_lints/src/excessive_nesting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use clippy_utils::{diagnostics::span_lint_and_help, source::snippet};
use rustc_ast::{
node_id::NodeSet,
visit::{walk_block, walk_item, Visitor},
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
/// ```
/// lib.rs:
/// ```rust,ignore
/// pub mod a {
/// pub struct X;
/// impl X {
/// pub fn run(&self) {
/// if true {
/// // etc...
/// }
/// }
/// }
/// }
/// Use instead:
/// a.rs:
/// ```rust,ignore
/// fn private_run(x: &X) {
/// if true {
/// // etc...
/// }
/// }
///
/// pub struct X;
/// impl X {
/// pub fn run(&self) {
/// private_run(self);
/// }
/// }
/// ```
/// lib.rs:
/// ```rust,ignore
/// 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),
}
}
}
8 changes: 8 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ mod equatable_if_let;
mod escape;
mod eta_reduction;
mod excessive_bools;
mod excessive_nesting;
mod exhaustive_items;
mod exit;
mod explicit_write;
Expand Down Expand Up @@ -1007,6 +1008,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments));
let excessive_nesting_threshold = conf.excessive_nesting_threshold;
store.register_early_pass(move || {
Box::new(excessive_nesting::ExcessiveNesting {
excessive_nesting_threshold,
nodes: rustc_ast::node_id::NodeSet::new(),
})
});
store.register_late_pass(|_| Box::new(items_after_test_module::ItemsAfterTestModule));
store.register_early_pass(|| Box::new(ref_patterns::RefPatterns));
store.register_late_pass(|_| Box::new(default_constructed_unit_structs::DefaultConstructedUnitStructs));
Expand Down
4 changes: 4 additions & 0 deletions clippy_lints/src/utils/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ define_Conf! {
///
/// The maximum cognitive complexity a function can have
(cognitive_complexity_threshold: u64 = 25),
/// Lint: EXCESSIVE_NESTING.
///
/// The maximum amount of nesting a block can reside in
(excessive_nesting_threshold: u64 = 0),
/// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY.
///
/// Use the Cognitive Complexity lint instead.
Expand Down
1 change: 1 addition & 0 deletions tests/ui-toml/excessive_nesting/above/clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
excessive-nesting-threshold = 4
Loading