diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b6e3b865f..ec62d399aa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4867,6 +4867,7 @@ Released 2018-09-13 [`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref [`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args [`format_push_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string +[`four_forward_slashes`]: https://rust-lang.github.io/rust-clippy/master/index.html#four_forward_slashes [`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect [`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into [`from_raw_with_void_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_raw_with_void_ptr diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 3723de7299b..d8b14a9f967 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -184,6 +184,7 @@ crate::formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING_INFO, crate::formatting::SUSPICIOUS_ELSE_FORMATTING_INFO, crate::formatting::SUSPICIOUS_UNARY_OP_FORMATTING_INFO, + crate::four_forward_slashes::FOUR_FORWARD_SLASHES_INFO, crate::from_over_into::FROM_OVER_INTO_INFO, crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO, crate::from_str_radix_10::FROM_STR_RADIX_10_INFO, diff --git a/clippy_lints/src/four_forward_slashes.rs b/clippy_lints/src/four_forward_slashes.rs new file mode 100644 index 00000000000..7efa8c1b31f --- /dev/null +++ b/clippy_lints/src/four_forward_slashes.rs @@ -0,0 +1,82 @@ +use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet_opt}; +use rustc_errors::Applicability; +use rustc_hir::Item; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{Span, SyntaxContext}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for outer doc comments written with 4 forward slashes (`////`). + /// + /// ### Why is this bad? + /// This is (probably) a typo, and results in it not being a doc comment; just a regular + /// comment. + /// + /// ### Example + /// ```rust + /// //// My amazing data structure + /// pub struct Foo { + /// // ... + /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// /// My amazing data structure + /// pub struct Foo { + /// // ... + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub FOUR_FORWARD_SLASHES, + suspicious, + "comments with 4 forward slashes (`////`) likely intended to be doc comments (`///`)" +} +declare_lint_pass!(FourForwardSlashes => [FOUR_FORWARD_SLASHES]); + +impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if item.span.from_expansion() { + return; + } + let src = cx.sess().source_map(); + let item_and_attrs_span = cx + .tcx + .hir() + .attrs(item.hir_id()) + .iter() + .fold(item.span.shrink_to_lo(), |span, attr| span.to(attr.span)); + let (Some(file), _, _, end_line, _) = src.span_to_location_info(item_and_attrs_span) else { + return; + }; + for line in (0..end_line.saturating_sub(1)).rev() { + let Some(contents) = file.get_line(line) else { + continue; + }; + let contents = contents.trim(); + if contents.is_empty() { + break; + } + if contents.starts_with("////") { + let bounds = file.line_bounds(line); + let span = Span::new(bounds.start, bounds.end, SyntaxContext::root(), None); + + if snippet_opt(cx, span).is_some_and(|s| s.trim().starts_with("////")) { + span_lint_and_sugg( + cx, + FOUR_FORWARD_SLASHES, + span, + "comment with 4 forward slashes (`////`). This looks like a doc comment, but it isn't", + "make this a doc comment by removing one `/`", + // It's a little unfortunate but the span includes the `\n` yet the contents + // do not, so we must add it back. If some codebase uses `\r\n` instead they + // will need normalization but it should be fine + contents.replacen("////", "///", 1) + "\n", + Applicability::MachineApplicable, + ); + } + } + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index cebe2e44c0f..607c718b562 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -137,6 +137,7 @@ mod format_impl; mod format_push_string; mod formatting; +mod four_forward_slashes; mod from_over_into; mod from_raw_with_void_ptr; mod from_str_radix_10; @@ -1081,6 +1082,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(visibility::Visibility)); store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions { msrv: msrv() })); store.register_late_pass(|_| Box::new(manual_float_methods::ManualFloatMethods)); + store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/tests/ui/four_forward_slashes.fixed b/tests/ui/four_forward_slashes.fixed new file mode 100644 index 00000000000..4f9b1f4b7c8 --- /dev/null +++ b/tests/ui/four_forward_slashes.fixed @@ -0,0 +1,44 @@ +//// first line borked doc comment. doesn't combust! +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![feature(custom_inner_attributes)] +#![allow(unused)] +#![warn(clippy::four_forward_slashes)] +#![no_main] +#![rustfmt::skip] + +#[macro_use] +extern crate proc_macros; + +/// whoops +fn a() {} + +/// whoops +#[allow(dead_code)] +fn b() {} + +/// whoops +/// two borked comments! +#[track_caller] +fn c() {} + +fn d() {} + +#[test] +/// between attributes +#[allow(dead_code)] +fn g() {} + +/// not very start of contents +fn h() {} + +external! { + //// don't lint me bozo + fn e() {} +} + +with_span! { + span + //// don't lint me bozo + fn f() {} +} diff --git a/tests/ui/four_forward_slashes.rs b/tests/ui/four_forward_slashes.rs new file mode 100644 index 00000000000..02f2084c17c --- /dev/null +++ b/tests/ui/four_forward_slashes.rs @@ -0,0 +1,44 @@ +//// first line borked doc comment. doesn't combust! +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![feature(custom_inner_attributes)] +#![allow(unused)] +#![warn(clippy::four_forward_slashes)] +#![no_main] +#![rustfmt::skip] + +#[macro_use] +extern crate proc_macros; + +//// whoops +fn a() {} + +//// whoops +#[allow(dead_code)] +fn b() {} + +//// whoops +//// two borked comments! +#[track_caller] +fn c() {} + +fn d() {} + +#[test] +//// between attributes +#[allow(dead_code)] +fn g() {} + + //// not very start of contents +fn h() {} + +external! { + //// don't lint me bozo + fn e() {} +} + +with_span! { + span + //// don't lint me bozo + fn f() {} +} diff --git a/tests/ui/four_forward_slashes.stderr b/tests/ui/four_forward_slashes.stderr new file mode 100644 index 00000000000..1640b241bf3 --- /dev/null +++ b/tests/ui/four_forward_slashes.stderr @@ -0,0 +1,75 @@ +error: comment with 4 forward slashes (`////`). This looks like a doc comment, but it isn't + --> $DIR/four_forward_slashes.rs:13:1 + | +LL | / //// whoops +LL | | fn a() {} + | |_ + | + = note: `-D clippy::four-forward-slashes` implied by `-D warnings` +help: make this a doc comment by removing one `/` + | +LL + /// whoops + | + +error: comment with 4 forward slashes (`////`). This looks like a doc comment, but it isn't + --> $DIR/four_forward_slashes.rs:16:1 + | +LL | / //// whoops +LL | | #[allow(dead_code)] + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// whoops + | + +error: comment with 4 forward slashes (`////`). This looks like a doc comment, but it isn't + --> $DIR/four_forward_slashes.rs:21:1 + | +LL | / //// two borked comments! +LL | | #[track_caller] + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// two borked comments! + | + +error: comment with 4 forward slashes (`////`). This looks like a doc comment, but it isn't + --> $DIR/four_forward_slashes.rs:20:1 + | +LL | / //// whoops +LL | | //// two borked comments! + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// whoops + | + +error: comment with 4 forward slashes (`////`). This looks like a doc comment, but it isn't + --> $DIR/four_forward_slashes.rs:28:1 + | +LL | / //// between attributes +LL | | #[allow(dead_code)] + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// between attributes + | + +error: comment with 4 forward slashes (`////`). This looks like a doc comment, but it isn't + --> $DIR/four_forward_slashes.rs:32:1 + | +LL | / //// not very start of contents +LL | | fn h() {} + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// not very start of contents + | + +error: aborting due to 6 previous errors +