about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-06-09 20:47:06 +0000
committerbors <bors@rust-lang.org>2023-06-09 20:47:06 +0000
commit476efe92e773dcc7c7363a0421810f5d5ca3a751 (patch)
treeafdb5a3d0adc7adb1bebfec6aa42bf00ee001339
parent05de7870899f43d67add6c00aec33e23e2d4c09c (diff)
parent6afb3555d303b0708fc0f687f531c25e47806ce8 (diff)
downloadrust-476efe92e773dcc7c7363a0421810f5d5ca3a751.tar.gz
rust-476efe92e773dcc7c7363a0421810f5d5ca3a751.zip
Auto merge of #10672 - Centri3:excessive-width-lints, r=Alexendoo
Add `excessive_nesting` lint

changelog: new lint [`excessive_nesting`]
-rw-r--r--CHANGELOG.md1
-rw-r--r--book/src/lint_configuration.md10
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/excessive_nesting.rs180
-rw-r--r--clippy_lints/src/lib.rs8
-rw-r--r--clippy_lints/src/utils/conf.rs4
-rw-r--r--tests/ui-toml/excessive_nesting/auxiliary/proc_macros.rs476
-rw-r--r--tests/ui-toml/excessive_nesting/clippy.toml1
-rw-r--r--tests/ui-toml/excessive_nesting/excessive_nesting.rs196
-rw-r--r--tests/ui-toml/excessive_nesting/excessive_nesting.stderr314
-rw-r--r--tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr2
-rw-r--r--tests/ui/auxiliary/macro_rules.rs1
12 files changed, 1193 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a53567f25c4..0d00f2c16e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4759,6 +4759,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
diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md
index 4c888b63793..3c955c9e574 100644
--- a/book/src/lint_configuration.md
+++ b/book/src/lint_configuration.md
@@ -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
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index f82118e569f..b6ceab26006 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -160,6 +160,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,
diff --git a/clippy_lints/src/excessive_nesting.rs b/clippy_lints/src/excessive_nesting.rs
new file mode 100644
index 00000000000..1c6cd27789d
--- /dev/null
+++ b/clippy_lints/src/excessive_nesting.rs
@@ -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),
+        }
+    }
+}
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 2dbdee977b0..22ff5ef5859 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -123,6 +123,7 @@ mod equatable_if_let;
 mod escape;
 mod eta_reduction;
 mod excessive_bools;
+mod excessive_nesting;
 mod exhaustive_items;
 mod exit;
 mod explicit_write;
@@ -1013,6 +1014,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));
diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs
index 35f40830681..69143c3c726 100644
--- a/clippy_lints/src/utils/conf.rs
+++ b/clippy_lints/src/utils/conf.rs
@@ -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.
diff --git a/tests/ui-toml/excessive_nesting/auxiliary/proc_macros.rs b/tests/ui-toml/excessive_nesting/auxiliary/proc_macros.rs
new file mode 100644
index 00000000000..df17974de03
--- /dev/null
+++ b/tests/ui-toml/excessive_nesting/auxiliary/proc_macros.rs
@@ -0,0 +1,476 @@
+//@compile-flags: --emit=link
+//@no-prefer-dynamic
+
+// NOTE: Copied from `ui/auxiliary/proc_macros.rs`, couldn't get `../` to work for some reason
+
+#![crate_type = "proc-macro"]
+#![feature(let_chains)]
+#![feature(proc_macro_span)]
+#![allow(clippy::excessive_nesting, dead_code)]
+
+extern crate proc_macro;
+
+use core::mem;
+use proc_macro::{
+    token_stream::IntoIter,
+    Delimiter::{self, Brace, Parenthesis},
+    Group, Ident, Literal, Punct,
+    Spacing::{self, Alone, Joint},
+    Span, TokenStream, TokenTree as TT,
+};
+
+type Result<T> = core::result::Result<T, TokenStream>;
+
+/// Make a `compile_error!` pointing to the given span.
+fn make_error(msg: &str, span: Span) -> TokenStream {
+    TokenStream::from_iter([
+        TT::Ident(Ident::new("compile_error", span)),
+        TT::Punct(punct_with_span('!', Alone, span)),
+        TT::Group({
+            let mut msg = Literal::string(msg);
+            msg.set_span(span);
+            group_with_span(Parenthesis, TokenStream::from_iter([TT::Literal(msg)]), span)
+        }),
+    ])
+}
+
+fn expect_tt<T>(tt: Option<TT>, f: impl FnOnce(TT) -> Option<T>, expected: &str, span: Span) -> Result<T> {
+    match tt {
+        None => Err(make_error(
+            &format!("unexpected end of input, expected {expected}"),
+            span,
+        )),
+        Some(tt) => {
+            let span = tt.span();
+            match f(tt) {
+                Some(x) => Ok(x),
+                None => Err(make_error(&format!("unexpected token, expected {expected}"), span)),
+            }
+        },
+    }
+}
+
+fn punct_with_span(c: char, spacing: Spacing, span: Span) -> Punct {
+    let mut p = Punct::new(c, spacing);
+    p.set_span(span);
+    p
+}
+
+fn group_with_span(delimiter: Delimiter, stream: TokenStream, span: Span) -> Group {
+    let mut g = Group::new(delimiter, stream);
+    g.set_span(span);
+    g
+}
+
+/// Token used to escape the following token from the macro's span rules.
+const ESCAPE_CHAR: char = '$';
+
+/// Takes a single token followed by a sequence of tokens. Returns the sequence of tokens with their
+/// span set to that of the first token. Tokens may be escaped with either `#ident` or `#(tokens)`.
+#[proc_macro]
+pub fn with_span(input: TokenStream) -> TokenStream {
+    let mut iter = input.into_iter();
+    let span = iter.next().unwrap().span();
+    let mut res = TokenStream::new();
+    if let Err(e) = write_with_span(span, iter, &mut res) {
+        e
+    } else {
+        res
+    }
+}
+
+/// Takes a sequence of tokens and return the tokens with the span set such that they appear to be
+/// from an external macro. Tokens may be escaped with either `#ident` or `#(tokens)`.
+#[proc_macro]
+pub fn external(input: TokenStream) -> TokenStream {
+    let mut res = TokenStream::new();
+    if let Err(e) = write_with_span(Span::mixed_site(), input.into_iter(), &mut res) {
+        e
+    } else {
+        res
+    }
+}
+
+/// Copies all the tokens, replacing all their spans with the given span. Tokens can be escaped
+/// either by `#ident` or `#(tokens)`.
+fn write_with_span(s: Span, mut input: IntoIter, out: &mut TokenStream) -> Result<()> {
+    while let Some(tt) = input.next() {
+        match tt {
+            TT::Punct(p) if p.as_char() == ESCAPE_CHAR => {
+                expect_tt(
+                    input.next(),
+                    |tt| match tt {
+                        tt @ (TT::Ident(_) | TT::Literal(_)) => {
+                            out.extend([tt]);
+                            Some(())
+                        },
+                        TT::Punct(mut p) if p.as_char() == ESCAPE_CHAR => {
+                            p.set_span(s);
+                            out.extend([TT::Punct(p)]);
+                            Some(())
+                        },
+                        TT::Group(g) if g.delimiter() == Parenthesis => {
+                            out.extend([TT::Group(group_with_span(Delimiter::None, g.stream(), g.span()))]);
+                            Some(())
+                        },
+                        _ => None,
+                    },
+                    "an ident, a literal, or parenthesized tokens",
+                    p.span(),
+                )?;
+            },
+            TT::Group(g) => {
+                let mut stream = TokenStream::new();
+                write_with_span(s, g.stream().into_iter(), &mut stream)?;
+                out.extend([TT::Group(group_with_span(g.delimiter(), stream, s))]);
+            },
+            mut tt => {
+                tt.set_span(s);
+                out.extend([tt]);
+            },
+        }
+    }
+    Ok(())
+}
+
+/// Within the item this attribute is attached to, an `inline!` macro is available which expands the
+/// contained tokens as though they came from a macro expansion.
+///
+/// Within the `inline!` macro, any token preceded by `$` is passed as though it were an argument
+/// with an automatically chosen fragment specifier. `$ident` will be passed as `ident`, `$1` or
+/// `$"literal"` will be passed as `literal`, `$'lt` will be passed as `lifetime`, and `$(...)` will
+/// pass the contained tokens as a `tt` sequence (the wrapping parenthesis are removed). If another
+/// specifier is required it can be specified within parenthesis like `$(@expr ...)`. This will
+/// expand the remaining tokens as a single argument.
+///
+/// Multiple `inline!` macros may be nested within each other. This will expand as nested macro
+/// calls. However, any arguments will be passed as though they came from the outermost context.
+#[proc_macro_attribute]
+pub fn inline_macros(args: TokenStream, input: TokenStream) -> TokenStream {
+    let mut args = args.into_iter();
+    let mac_name = match args.next() {
+        Some(TT::Ident(name)) => Some(name),
+        Some(tt) => {
+            return make_error(
+                "unexpected argument, expected either an ident or no arguments",
+                tt.span(),
+            );
+        },
+        None => None,
+    };
+    if let Some(tt) = args.next() {
+        return make_error(
+            "unexpected argument, expected either an ident or no arguments",
+            tt.span(),
+        );
+    };
+
+    let mac_name = if let Some(mac_name) = mac_name {
+        Ident::new(&format!("__inline_mac_{mac_name}"), Span::call_site())
+    } else {
+        let mut input = match LookaheadIter::new(input.clone().into_iter()) {
+            Some(x) => x,
+            None => return input,
+        };
+        loop {
+            match input.next() {
+                None => break Ident::new("__inline_mac", Span::call_site()),
+                Some(TT::Ident(kind)) => match &*kind.to_string() {
+                    "impl" => break Ident::new("__inline_mac_impl", Span::call_site()),
+                    kind @ ("struct" | "enum" | "union" | "fn" | "mod" | "trait" | "type" | "const" | "static") => {
+                        if let TT::Ident(name) = &input.tt {
+                            break Ident::new(&format!("__inline_mac_{kind}_{name}"), Span::call_site());
+                        } else {
+                            break Ident::new(&format!("__inline_mac_{kind}"), Span::call_site());
+                        }
+                    },
+                    _ => {},
+                },
+                _ => {},
+            }
+        }
+    };
+
+    let mut expander = Expander::default();
+    let mut mac = MacWriter::new(mac_name);
+    if let Err(e) = expander.expand(input.into_iter(), &mut mac) {
+        return e;
+    }
+    let mut out = TokenStream::new();
+    mac.finish(&mut out);
+    out.extend(expander.expn);
+    out
+}
+
+/// Wraps a `TokenStream` iterator with a single token lookahead.
+struct LookaheadIter {
+    tt: TT,
+    iter: IntoIter,
+}
+impl LookaheadIter {
+    fn new(mut iter: IntoIter) -> Option<Self> {
+        iter.next().map(|tt| Self { tt, iter })
+    }
+
+    /// Get's the lookahead token, replacing it with the next token in the stream.
+    /// Note: If there isn't a next token, this will not return the lookahead token.
+    fn next(&mut self) -> Option<TT> {
+        self.iter.next().map(|tt| mem::replace(&mut self.tt, tt))
+    }
+}
+
+/// Builds the macro used to implement all the `inline!` macro calls.
+struct MacWriter {
+    name: Ident,
+    macros: TokenStream,
+    next_idx: usize,
+}
+impl MacWriter {
+    fn new(name: Ident) -> Self {
+        Self {
+            name,
+            macros: TokenStream::new(),
+            next_idx: 0,
+        }
+    }
+
+    /// Inserts a new `inline!` call.
+    fn insert(&mut self, name_span: Span, bang_span: Span, body: Group, expander: &mut Expander) -> Result<()> {
+        let idx = self.next_idx;
+        self.next_idx += 1;
+
+        let mut inner = Expander::for_arm(idx);
+        inner.expand(body.stream().into_iter(), self)?;
+        let new_arm = inner.arm.unwrap();
+
+        self.macros.extend([
+            TT::Group(Group::new(Parenthesis, new_arm.args_def)),
+            TT::Punct(Punct::new('=', Joint)),
+            TT::Punct(Punct::new('>', Alone)),
+            TT::Group(Group::new(Parenthesis, inner.expn)),
+            TT::Punct(Punct::new(';', Alone)),
+        ]);
+
+        expander.expn.extend([
+            TT::Ident({
+                let mut name = self.name.clone();
+                name.set_span(name_span);
+                name
+            }),
+            TT::Punct(punct_with_span('!', Alone, bang_span)),
+        ]);
+        let mut call_body = TokenStream::from_iter([TT::Literal(Literal::usize_unsuffixed(idx))]);
+        if let Some(arm) = expander.arm.as_mut() {
+            if !new_arm.args.is_empty() {
+                arm.add_sub_args(new_arm.args, &mut call_body);
+            }
+        } else {
+            call_body.extend(new_arm.args);
+        }
+        let mut g = Group::new(body.delimiter(), call_body);
+        g.set_span(body.span());
+        expander.expn.extend([TT::Group(g)]);
+        Ok(())
+    }
+
+    /// Creates the macro definition.
+    fn finish(self, out: &mut TokenStream) {
+        if self.next_idx != 0 {
+            out.extend([
+                TT::Ident(Ident::new("macro_rules", Span::call_site())),
+                TT::Punct(Punct::new('!', Alone)),
+                TT::Ident(self.name),
+                TT::Group(Group::new(Brace, self.macros)),
+            ])
+        }
+    }
+}
+
+struct MacroArm {
+    args_def: TokenStream,
+    args: Vec<TT>,
+}
+impl MacroArm {
+    fn add_single_arg_def(&mut self, kind: &str, dollar_span: Span, arg_span: Span, out: &mut TokenStream) {
+        let mut name = Ident::new(&format!("_{}", self.args.len()), Span::call_site());
+        self.args_def.extend([
+            TT::Punct(Punct::new('$', Alone)),
+            TT::Ident(name.clone()),
+            TT::Punct(Punct::new(':', Alone)),
+            TT::Ident(Ident::new(kind, Span::call_site())),
+        ]);
+        name.set_span(arg_span);
+        out.extend([TT::Punct(punct_with_span('$', Alone, dollar_span)), TT::Ident(name)]);
+    }
+
+    fn add_parenthesized_arg_def(&mut self, kind: Ident, dollar_span: Span, arg_span: Span, out: &mut TokenStream) {
+        let mut name = Ident::new(&format!("_{}", self.args.len()), Span::call_site());
+        self.args_def.extend([TT::Group(Group::new(
+            Parenthesis,
+            TokenStream::from_iter([
+                TT::Punct(Punct::new('$', Alone)),
+                TT::Ident(name.clone()),
+                TT::Punct(Punct::new(':', Alone)),
+                TT::Ident(kind),
+            ]),
+        ))]);
+        name.set_span(arg_span);
+        out.extend([TT::Punct(punct_with_span('$', Alone, dollar_span)), TT::Ident(name)]);
+    }
+
+    fn add_multi_arg_def(&mut self, dollar_span: Span, arg_span: Span, out: &mut TokenStream) {
+        let mut name = Ident::new(&format!("_{}", self.args.len()), Span::call_site());
+        self.args_def.extend([TT::Group(Group::new(
+            Parenthesis,
+            TokenStream::from_iter([
+                TT::Punct(Punct::new('$', Alone)),
+                TT::Group(Group::new(
+                    Parenthesis,
+                    TokenStream::from_iter([
+                        TT::Punct(Punct::new('$', Alone)),
+                        TT::Ident(name.clone()),
+                        TT::Punct(Punct::new(':', Alone)),
+                        TT::Ident(Ident::new("tt", Span::call_site())),
+                    ]),
+                )),
+                TT::Punct(Punct::new('*', Alone)),
+            ]),
+        ))]);
+        name.set_span(arg_span);
+        out.extend([
+            TT::Punct(punct_with_span('$', Alone, dollar_span)),
+            TT::Group(group_with_span(
+                Parenthesis,
+                TokenStream::from_iter([TT::Punct(punct_with_span('$', Alone, dollar_span)), TT::Ident(name)]),
+                dollar_span,
+            )),
+            TT::Punct(punct_with_span('*', Alone, dollar_span)),
+        ]);
+    }
+
+    fn add_arg(&mut self, dollar_span: Span, tt: TT, input: &mut IntoIter, out: &mut TokenStream) -> Result<()> {
+        match tt {
+            TT::Punct(p) if p.as_char() == ESCAPE_CHAR => out.extend([TT::Punct(p)]),
+            TT::Punct(p) if p.as_char() == '\'' && p.spacing() == Joint => {
+                let lt_name = expect_tt(
+                    input.next(),
+                    |tt| match tt {
+                        TT::Ident(x) => Some(x),
+                        _ => None,
+                    },
+                    "lifetime name",
+                    p.span(),
+                )?;
+                let arg_span = p.span().join(lt_name.span()).unwrap_or(p.span());
+                self.add_single_arg_def("lifetime", dollar_span, arg_span, out);
+                self.args.extend([TT::Punct(p), TT::Ident(lt_name)]);
+            },
+            TT::Ident(x) => {
+                self.add_single_arg_def("ident", dollar_span, x.span(), out);
+                self.args.push(TT::Ident(x));
+            },
+            TT::Literal(x) => {
+                self.add_single_arg_def("literal", dollar_span, x.span(), out);
+                self.args.push(TT::Literal(x));
+            },
+            TT::Group(g) if g.delimiter() == Parenthesis => {
+                let mut inner = g.stream().into_iter();
+                if let Some(TT::Punct(p)) = inner.next()
+                    && p.as_char() == '@'
+                {
+                    let kind = expect_tt(
+                        inner.next(),
+                        |tt| match tt {
+                            TT::Ident(kind) => Some(kind),
+                            _ => None,
+                        },
+                        "a macro fragment specifier",
+                        p.span(),
+                    )?;
+                    self.add_parenthesized_arg_def(kind, dollar_span, g.span(), out);
+                    self.args.push(TT::Group(group_with_span(Parenthesis, inner.collect(), g.span())))
+                } else {
+                    self.add_multi_arg_def(dollar_span, g.span(), out);
+                    self.args.push(TT::Group(g));
+                }
+            },
+            tt => return Err(make_error("unsupported escape", tt.span())),
+        };
+        Ok(())
+    }
+
+    fn add_sub_args(&mut self, args: Vec<TT>, out: &mut TokenStream) {
+        self.add_multi_arg_def(Span::call_site(), Span::call_site(), out);
+        self.args
+            .extend([TT::Group(Group::new(Parenthesis, TokenStream::from_iter(args)))]);
+    }
+}
+
+#[derive(Default)]
+struct Expander {
+    arm: Option<MacroArm>,
+    expn: TokenStream,
+}
+impl Expander {
+    fn for_arm(idx: usize) -> Self {
+        Self {
+            arm: Some(MacroArm {
+                args_def: TokenStream::from_iter([TT::Literal(Literal::usize_unsuffixed(idx))]),
+                args: Vec::new(),
+            }),
+            expn: TokenStream::new(),
+        }
+    }
+
+    fn write_tt(&mut self, tt: TT, mac: &mut MacWriter) -> Result<()> {
+        match tt {
+            TT::Group(g) => {
+                let outer = mem::take(&mut self.expn);
+                self.expand(g.stream().into_iter(), mac)?;
+                let inner = mem::replace(&mut self.expn, outer);
+                self.expn
+                    .extend([TT::Group(group_with_span(g.delimiter(), inner, g.span()))]);
+            },
+            tt => self.expn.extend([tt]),
+        }
+        Ok(())
+    }
+
+    fn expand(&mut self, input: IntoIter, mac: &mut MacWriter) -> Result<()> {
+        let Some(mut input) = LookaheadIter::new(input) else {
+            return Ok(());
+        };
+        while let Some(tt) = input.next() {
+            if let TT::Punct(p) = &tt
+                && p.as_char() == ESCAPE_CHAR
+                && let Some(arm) = self.arm.as_mut()
+            {
+                arm.add_arg(p.span(), mem::replace(&mut input.tt, tt), &mut input.iter, &mut self.expn)?;
+                if input.next().is_none() {
+                    return Ok(());
+                }
+            } else if let TT::Punct(p) = &input.tt
+                && p.as_char() == '!'
+                && let TT::Ident(name) = &tt
+                && name.to_string() == "inline"
+            {
+                let g = expect_tt(
+                    input.iter.next(),
+                    |tt| match tt {
+                        TT::Group(g) => Some(g),
+                        _ => None,
+                    },
+                    "macro arguments",
+                    p.span(),
+                )?;
+                mac.insert(name.span(), p.span(), g, self)?;
+                if input.next().is_none() {
+                    return Ok(());
+                }
+            } else {
+                self.write_tt(tt, mac)?;
+            }
+        }
+        self.write_tt(input.tt, mac)
+    }
+}
diff --git a/tests/ui-toml/excessive_nesting/clippy.toml b/tests/ui-toml/excessive_nesting/clippy.toml
new file mode 100644
index 00000000000..e60ac978cac
--- /dev/null
+++ b/tests/ui-toml/excessive_nesting/clippy.toml
@@ -0,0 +1 @@
+excessive-nesting-threshold = 4
diff --git a/tests/ui-toml/excessive_nesting/excessive_nesting.rs b/tests/ui-toml/excessive_nesting/excessive_nesting.rs
new file mode 100644
index 00000000000..d771cb9b55e
--- /dev/null
+++ b/tests/ui-toml/excessive_nesting/excessive_nesting.rs
@@ -0,0 +1,196 @@
+//@aux-build:proc_macros.rs
+#![rustfmt::skip]
+#![feature(custom_inner_attributes)]
+#![allow(unused)]
+#![allow(clippy::let_and_return)]
+#![allow(clippy::redundant_closure_call)]
+#![allow(clippy::no_effect)]
+#![allow(clippy::unnecessary_operation)]
+#![allow(clippy::never_loop)]
+#![warn(clippy::excessive_nesting)]
+#![allow(clippy::collapsible_if)]
+
+#[macro_use]
+extern crate proc_macros;
+
+static X: u32 = {
+    let x = {
+        let y = {
+            let z = {
+                let w = { 3 };
+                w
+            };
+            z
+        };
+        y
+    };
+    x
+};
+
+macro_rules! xx {
+    () => {{
+        {
+            {
+                {
+                    {
+                        {
+                            {
+                                {
+                                    {
+                                        {
+                                            {
+                                                println!("ehe"); // should not lint
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }};
+}
+
+struct A;
+
+impl A {
+    pub fn a(&self, v: u32) {
+        struct B;
+
+        impl B {
+            pub fn b() {
+                struct C;
+
+                impl C {
+                    pub fn c() {}
+                }
+            }
+        }
+    }
+}
+
+struct D { d: u32 }
+
+trait Lol {
+    fn lmao() {
+        fn bb() {
+            fn cc() {
+                let x = { 1 }; // not a warning, but cc is
+            }
+
+            let x = { 1 }; // warning
+        }
+    }
+}
+
+#[allow(clippy::excessive_nesting)]
+fn l() {{{{{{{{{}}}}}}}}}
+
+use a::{b::{c::{d::{e::{f::{}}}}}}; // should not lint
+
+pub mod a {
+    pub mod b {
+        pub mod c {
+            pub mod d {
+                pub mod e {
+                    pub mod f {}
+                } // not here
+            } // only warning should be here
+        }
+    }
+}
+
+fn a_but_not(v: u32) {}
+
+fn main() {
+    let a = A;
+
+    a_but_not({{{{{{{{0}}}}}}}});
+    a.a({{{{{{{{{0}}}}}}}}});
+    (0, {{{{{{{1}}}}}}});
+
+    if true {
+        if true {
+            if true {
+                if true {
+                    if true {
+
+                    }
+                }
+            }
+        }
+    }
+
+    let y = (|| {
+        let x = (|| {
+            let y = (|| {
+                let z = (|| {
+                    let w = { 3 };
+                    w
+                })();
+                z
+            })();
+            y
+        })();
+        x
+    })();
+
+    external! { {{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}} }; // ensure this isn't linted in external macros
+    with_span! { span {{{{{{{{{{{{}}}}}}}}}}}} }; // don't lint for proc macros
+    xx!(); // ensure this is never linted
+    let boo = true;
+    !{boo as u32 + !{boo as u32 + !{boo as u32}}};
+
+    // this is a mess, but that's intentional
+    let mut y = 1;
+    y += {{{{{5}}}}};
+    let z = y + {{{{{{{{{5}}}}}}}}};
+    [0, {{{{{{{{{{0}}}}}}}}}}];
+    let mut xx = [0; {{{{{{{{100}}}}}}}}];
+    xx[{{{{{{{{{{{{{{{{{{{{{{{{3}}}}}}}}}}}}}}}}}}}}}}}}];
+    &mut {{{{{{{{{{y}}}}}}}}}};
+
+    for i in {{{{xx}}}} {{{{{{{{}}}}}}}}
+
+    while let Some(i) = {{{{{{Some(1)}}}}}} {{{{{{{}}}}}}}
+    
+    while {{{{{{{{true}}}}}}}} {{{{{{{{{}}}}}}}}}
+
+    let d = D { d: {{{{{{{{{{{{{{{{{{{{{{{3}}}}}}}}}}}}}}}}}}}}}}} };
+
+    {{{{1;}}}}..{{{{{{3}}}}}};
+    {{{{1;}}}}..={{{{{{{{{{{{{{{{{{{{{{{{{{6}}}}}}}}}}}}}}}}}}}}}}}}}};
+    ..{{{{{{{5}}}}}}};
+    ..={{{{{3}}}}};
+    {{{{{1;}}}}}..;
+
+    loop { break {{{{1}}}} };
+    loop {{{{{{}}}}}}
+
+    match {{{{{{true}}}}}} {
+        true => {{{{}}}},
+        false => {{{{}}}},
+    }
+
+    {
+        {
+            {
+                {
+                    println!("warning! :)");
+                }
+            }
+        }
+    }
+}
+
+async fn b() -> u32 {
+    async fn c() -> u32 {{{{{{{0}}}}}}}
+
+    c().await
+}
+
+async fn a() {
+    {{{{b().await}}}};
+}
diff --git a/tests/ui-toml/excessive_nesting/excessive_nesting.stderr b/tests/ui-toml/excessive_nesting/excessive_nesting.stderr
new file mode 100644
index 00000000000..a9fb159ef2a
--- /dev/null
+++ b/tests/ui-toml/excessive_nesting/excessive_nesting.stderr
@@ -0,0 +1,314 @@
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:20:25
+   |
+LL |                 let w = { 3 };
+   |                         ^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+   = note: `-D clippy::excessive-nesting` implied by `-D warnings`
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:66:17
+   |
+LL | /                 impl C {
+LL | |                     pub fn c() {}
+LL | |                 }
+   | |_________________^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:80:25
+   |
+LL |                 let x = { 1 }; // not a warning, but cc is
+   |                         ^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:97:17
+   |
+LL | /                 pub mod e {
+LL | |                     pub mod f {}
+LL | |                 } // not here
+   | |_________________^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:110:18
+   |
+LL |     a_but_not({{{{{{{{0}}}}}}}});
+   |                  ^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:111:12
+   |
+LL |     a.a({{{{{{{{{0}}}}}}}}});
+   |            ^^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:112:12
+   |
+LL |     (0, {{{{{{{1}}}}}}});
+   |            ^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:117:25
+   |
+LL |                   if true {
+   |  _________________________^
+LL | |                     if true {
+LL | |
+LL | |                     }
+LL | |                 }
+   | |_________________^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:129:29
+   |
+LL |                   let z = (|| {
+   |  _____________________________^
+LL | |                     let w = { 3 };
+LL | |                     w
+LL | |                 })();
+   | |_________________^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:148:13
+   |
+LL |     y += {{{{{5}}}}};
+   |             ^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:149:20
+   |
+LL |     let z = y + {{{{{{{{{5}}}}}}}}};
+   |                    ^^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:150:12
+   |
+LL |     [0, {{{{{{{{{{0}}}}}}}}}}];
+   |            ^^^^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:151:25
+   |
+LL |     let mut xx = [0; {{{{{{{{100}}}}}}}}];
+   |                         ^^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:152:11
+   |
+LL |     xx[{{{{{{{{{{{{{{{{{{{{{{{{3}}}}}}}}}}}}}}}}}}}}}}}}];
+   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:153:13
+   |
+LL |     &mut {{{{{{{{{{y}}}}}}}}}};
+   |             ^^^^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:155:17
+   |
+LL |     for i in {{{{xx}}}} {{{{{{{{}}}}}}}}
+   |                 ^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:155:28
+   |
+LL |     for i in {{{{xx}}}} {{{{{{{{}}}}}}}}
+   |                            ^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:157:28
+   |
+LL |     while let Some(i) = {{{{{{Some(1)}}}}}} {{{{{{{}}}}}}}
+   |                            ^^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:157:48
+   |
+LL |     while let Some(i) = {{{{{{Some(1)}}}}}} {{{{{{{}}}}}}}
+   |                                                ^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:159:14
+   |
+LL |     while {{{{{{{{true}}}}}}}} {{{{{{{{{}}}}}}}}}
+   |              ^^^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:159:35
+   |
+LL |     while {{{{{{{{true}}}}}}}} {{{{{{{{{}}}}}}}}}
+   |                                   ^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:161:23
+   |
+LL |     let d = D { d: {{{{{{{{{{{{{{{{{{{{{{{3}}}}}}}}}}}}}}}}}}}}}}} };
+   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:163:8
+   |
+LL |     {{{{1;}}}}..{{{{{{3}}}}}};
+   |        ^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:163:20
+   |
+LL |     {{{{1;}}}}..{{{{{{3}}}}}};
+   |                    ^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:164:8
+   |
+LL |     {{{{1;}}}}..={{{{{{{{{{{{{{{{{{{{{{{{{{6}}}}}}}}}}}}}}}}}}}}}}}}}};
+   |        ^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:164:21
+   |
+LL |     {{{{1;}}}}..={{{{{{{{{{{{{{{{{{{{{{{{{{6}}}}}}}}}}}}}}}}}}}}}}}}}};
+   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:165:10
+   |
+LL |     ..{{{{{{{5}}}}}}};
+   |          ^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:166:11
+   |
+LL |     ..={{{{{3}}}}};
+   |           ^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:167:8
+   |
+LL |     {{{{{1;}}}}}..;
+   |        ^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:169:20
+   |
+LL |     loop { break {{{{1}}}} };
+   |                    ^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:170:13
+   |
+LL |     loop {{{{{{}}}}}}
+   |             ^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:172:14
+   |
+LL |     match {{{{{{true}}}}}} {
+   |              ^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:173:20
+   |
+LL |         true => {{{{}}}},
+   |                    ^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:174:21
+   |
+LL |         false => {{{{}}}},
+   |                     ^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:180:17
+   |
+LL | /                 {
+LL | |                     println!("warning! :)");
+LL | |                 }
+   | |_________________^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:189:28
+   |
+LL |     async fn c() -> u32 {{{{{{{0}}}}}}}
+   |                            ^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: this block is too nested
+  --> $DIR/excessive_nesting.rs:195:8
+   |
+LL |     {{{{b().await}}}};
+   |        ^^^^^^^^^^^
+   |
+   = help: try refactoring your code to minimize nesting
+
+error: aborting due to 37 previous errors
+
diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
index b6038f031f3..5ec6f3decc3 100644
--- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
+++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
@@ -24,6 +24,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
            enforced-import-renames
            enum-variant-name-threshold
            enum-variant-size-threshold
+           excessive-nesting-threshold
            future-size-threshold
            ignore-interior-mutability
            large-error-threshold
@@ -85,6 +86,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
            enforced-import-renames
            enum-variant-name-threshold
            enum-variant-size-threshold
+           excessive-nesting-threshold
            future-size-threshold
            ignore-interior-mutability
            large-error-threshold
diff --git a/tests/ui/auxiliary/macro_rules.rs b/tests/ui/auxiliary/macro_rules.rs
index e5bb906663c..d9a1e76c077 100644
--- a/tests/ui/auxiliary/macro_rules.rs
+++ b/tests/ui/auxiliary/macro_rules.rs
@@ -1,7 +1,6 @@
 #![allow(dead_code)]
 
 //! Used to test that certain lints don't trigger in imported external macros
-
 #[macro_export]
 macro_rules! try_err {
     () => {