about summary refs log tree commit diff
diff options
context:
space:
mode:
authorblyxyas <blyxyas@gmail.com>2025-06-10 18:30:03 +0200
committerblyxyas <blyxyas@gmail.com>2025-06-20 12:07:35 +0200
commit3745a3f7ab1438ebc35836405c3b4f23bd6262a6 (patch)
tree0ee9e10788427657bd534d4ec17e5d0d8a10882a
parent506411d9d1c45fc65a87a7bd2d5edebf249234a0 (diff)
downloadrust-3745a3f7ab1438ebc35836405c3b4f23bd6262a6.tar.gz
rust-3745a3f7ab1438ebc35836405c3b4f23bd6262a6.zip
Even more optimizing documentation lints? (3/2)
Avoid creating so many SessionGlobals

Improve filtering and account for spacing

Actually return early
-rw-r--r--clippy_lints/src/doc/needless_doctest_main.rs22
-rw-r--r--tests/ui/doc/needless_doctest_main.rs96
-rw-r--r--tests/ui/doc/needless_doctest_main.stderr36
3 files changed, 150 insertions, 4 deletions
diff --git a/clippy_lints/src/doc/needless_doctest_main.rs b/clippy_lints/src/doc/needless_doctest_main.rs
index ec4538039a9..ef251860494 100644
--- a/clippy_lints/src/doc/needless_doctest_main.rs
+++ b/clippy_lints/src/doc/needless_doctest_main.rs
@@ -72,6 +72,7 @@ pub fn check(
                                 if !ignore {
                                     get_test_spans(&item, *ident, &mut test_attr_spans);
                                 }
+
                                 let is_async = matches!(sig.header.coroutine_kind, Some(CoroutineKind::Async { .. }));
                                 let returns_nothing = match &sig.decl.output {
                                     FnRetTy::Default(..) => true,
@@ -90,9 +91,14 @@ pub fn check(
                             // Another function was found; this case is ignored for needless_doctest_main
                             ItemKind::Fn(fn_) => {
                                 eligible = false;
-                                if !ignore {
-                                    get_test_spans(&item, fn_.ident, &mut test_attr_spans);
+                                if ignore {
+                                    // If ignore is active invalidating one lint,
+                                    // and we already found another function thus
+                                    // invalidating the other one, we have no
+                                    // business continuing.
+                                    return (false, test_attr_spans);
                                 }
+                                get_test_spans(&item, fn_.ident, &mut test_attr_spans);
                             },
                             // Tests with one of these items are ignored
                             ItemKind::Static(..)
@@ -120,6 +126,18 @@ pub fn check(
 
     let trailing_whitespace = text.len() - text.trim_end().len();
 
+    // We currently only test for "fn main". Checking for the real
+    // entrypoint (with tcx.entry_fn(())) in each block would be unnecessarily
+    // expensive, as those are probably intended and relevant. Same goes for
+    // macros and other weird ways of declaring a main function.
+    //
+    // Also, as we only check for attribute names and don't do macro expansion,
+    // we can check only for #[test]
+
+    if !((text.contains("main") && text.contains("fn")) || text.contains("#[test]")) {
+        return;
+    }
+
     // Because of the global session, we need to create a new session in a different thread with
     // the edition we need.
     let text = text.to_owned();
diff --git a/tests/ui/doc/needless_doctest_main.rs b/tests/ui/doc/needless_doctest_main.rs
index 633a435ca5e..3c082f0b881 100644
--- a/tests/ui/doc/needless_doctest_main.rs
+++ b/tests/ui/doc/needless_doctest_main.rs
@@ -1,5 +1,3 @@
-//@ check-pass
-
 #![warn(clippy::needless_doctest_main)]
 //! issue 10491:
 //! ```rust,no_test
@@ -19,4 +17,98 @@
 /// ```
 fn foo() {}
 
+#[rustfmt::skip]
+/// Description
+/// ```rust
+/// fn main() {
+//~^ error: needless `fn main` in doctest
+///     let a = 0;
+/// }
+/// ```
+fn mulpipulpi() {}
+
+#[rustfmt::skip]
+/// With a `#[no_main]`
+/// ```rust
+/// #[no_main]
+/// fn a() {
+///     let _ = 0;
+/// }
+/// ```
+fn pulpimulpi() {}
+
+// Without a `#[no_main]` attribute
+/// ```rust
+/// fn a() {
+///     let _ = 0;
+/// }
+/// ```
+fn plumilupi() {}
+
+#[rustfmt::skip]
+/// Additional function, shouldn't trigger
+/// ```rust
+/// fn additional_function() {
+///     let _ = 0;
+///     // Thus `fn main` is actually relevant!
+/// }
+/// fn main() {
+///     let _ = 0;
+/// }
+/// ```
+fn mlupipupi() {}
+
+#[rustfmt::skip]
+/// Additional function AFTER main, shouldn't trigger
+/// ```rust
+/// fn main() {
+///     let _ = 0;
+/// }
+/// fn additional_function() {
+///     let _ = 0;
+///     // Thus `fn main` is actually relevant!
+/// }
+/// ```
+fn lumpimupli() {}
+
+#[rustfmt::skip]
+/// Ignore code block, should not lint at all
+/// ```rust, ignore
+/// fn main() {
+//~^ error: needless `fn main` in doctest
+///     // Hi!
+///     let _ = 0;
+/// }
+/// ```
+fn mpulpilumi() {}
+
+#[rustfmt::skip]
+/// Spaces in weird positions (including an \u{A0} after `main`)
+/// ```rust
+/// fn     main (){
+//~^ error: needless `fn main` in doctest
+///     let _ = 0;
+/// }
+/// ```
+fn plumpiplupi() {}
+
+/// 4 Functions, this should not lint because there are several function
+///
+/// ```rust
+/// fn a() {let _ = 0; }
+/// fn b() {let _ = 0; }
+/// fn main() { let _ = 0; }
+/// fn d() { let _ = 0; }
+/// ```
+fn pulmipulmip() {}
+
+/// 3 Functions but main is first, should also not lint
+///
+///```rust
+/// fn main() { let _ = 0; }
+/// fn b() { let _ = 0; }
+/// fn c() { let _ = 0; }
+/// ```
+fn pmuplimulip() {}
+
 fn main() {}
diff --git a/tests/ui/doc/needless_doctest_main.stderr b/tests/ui/doc/needless_doctest_main.stderr
new file mode 100644
index 00000000000..dd5474ccb85
--- /dev/null
+++ b/tests/ui/doc/needless_doctest_main.stderr
@@ -0,0 +1,36 @@
+error: needless `fn main` in doctest
+  --> tests/ui/doc/needless_doctest_main.rs:23:5
+   |
+LL |   /// fn main() {
+   |  _____^
+LL | |
+LL | | ///     let a = 0;
+LL | | /// }
+   | |_____^
+   |
+   = note: `-D clippy::needless-doctest-main` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::needless_doctest_main)]`
+
+error: needless `fn main` in doctest
+  --> tests/ui/doc/needless_doctest_main.rs:77:5
+   |
+LL |   /// fn main() {
+   |  _____^
+LL | |
+LL | | ///     // Hi!
+LL | | ///     let _ = 0;
+LL | | /// }
+   | |_____^
+
+error: needless `fn main` in doctest
+  --> tests/ui/doc/needless_doctest_main.rs:88:5
+   |
+LL |   /// fn     main (){
+   |  _____^
+LL | |
+LL | | ///     let _ = 0;
+LL | | /// }
+   | |_____^
+
+error: aborting due to 3 previous errors
+