diff --git a/CHANGELOG.md b/CHANGELOG.md index 137fd793e754..3aaa79a1719c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3362,6 +3362,7 @@ Released 2018-09-13 [`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type [`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types [`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression +[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes [`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown [`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons [`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use diff --git a/clippy_lints/src/doc_link_with_quotes.rs b/clippy_lints/src/doc_link_with_quotes.rs new file mode 100644 index 000000000000..cb07f57e8700 --- /dev/null +++ b/clippy_lints/src/doc_link_with_quotes.rs @@ -0,0 +1,60 @@ +use clippy_utils::diagnostics::span_lint; +use itertools::Itertools; +use rustc_ast::{AttrKind, Attribute}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// Detects the syntax `['foo']` in documentation comments (notice quotes instead of backticks) + /// outside of code blocks + /// ### Why is this bad? + /// It is likely a typo when defining an intra-doc link + /// + /// ### Example + /// ```rust + /// /// See also: ['foo'] + /// fn bar() {} + /// ``` + /// Use instead: + /// ```rust + /// /// See also: [`foo`] + /// fn bar() {} + /// ``` + #[clippy::version = "1.60.0"] + pub DOC_LINK_WITH_QUOTES, + pedantic, + "possible typo for an intra-doc link" +} +declare_lint_pass!(DocLinkWithQuotes => [DOC_LINK_WITH_QUOTES]); + +impl EarlyLintPass for DocLinkWithQuotes { + fn check_attribute(&mut self, ctx: &EarlyContext<'_>, attr: &Attribute) { + if let AttrKind::DocComment(_, symbol) = attr.kind { + if contains_quote_link(symbol.as_str()) { + span_lint( + ctx, + DOC_LINK_WITH_QUOTES, + attr.span, + "possible intra-doc link using quotes instead of backticks", + ); + } + } + } +} + +fn contains_quote_link(s: &str) -> bool { + let mut in_backticks = false; + let mut found_opening = false; + + for c in s.chars().tuple_windows::<(char, char)>() { + match c { + ('`', _) => in_backticks = !in_backticks, + ('[', '\'') if !in_backticks => found_opening = true, + ('\'', ']') if !in_backticks && found_opening => return true, + _ => {}, + } + } + + false +} diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 02171a0909ed..1f2132cf6200 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -124,6 +124,7 @@ store.register_lints(&[ doc::MISSING_PANICS_DOC, doc::MISSING_SAFETY_DOC, doc::NEEDLESS_DOCTEST_MAIN, + doc_link_with_quotes::DOC_LINK_WITH_QUOTES, double_comparison::DOUBLE_COMPARISONS, double_parens::DOUBLE_PARENS, drop_forget_ref::DROP_COPY, diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index 2ee2c6e3358c..5684972b8bed 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -26,6 +26,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(doc::DOC_MARKDOWN), LintId::of(doc::MISSING_ERRORS_DOC), LintId::of(doc::MISSING_PANICS_DOC), + LintId::of(doc_link_with_quotes::DOC_LINK_WITH_QUOTES), LintId::of(empty_enum::EMPTY_ENUM), LintId::of(enum_variants::MODULE_NAME_REPETITIONS), LintId::of(eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ffbf9a659170..d611600cf711 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -208,6 +208,7 @@ mod disallowed_methods; mod disallowed_script_idents; mod disallowed_types; mod doc; +mod doc_link_with_quotes; mod double_comparison; mod double_parens; mod drop_forget_ref; @@ -886,6 +887,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(borrow_as_ptr::BorrowAsPtr::new(msrv))); store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv))); store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation)); + store.register_early_pass(|| Box::new(doc_link_with_quotes::DocLinkWithQuotes)); store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion)); store.register_late_pass(|| Box::new(significant_drop_in_scrutinee::SignificantDropInScrutinee)); let allow_dbg_in_tests = conf.allow_dbg_in_tests; diff --git a/tests/ui/doc_link_with_quotes.rs b/tests/ui/doc_link_with_quotes.rs new file mode 100644 index 000000000000..ab52fb1a4a69 --- /dev/null +++ b/tests/ui/doc_link_with_quotes.rs @@ -0,0 +1,12 @@ +#![warn(clippy::doc_link_with_quotes)] + +fn main() { + foo() +} + +/// Calls ['bar'] +pub fn foo() { + bar() +} + +pub fn bar() {} diff --git a/tests/ui/doc_link_with_quotes.stderr b/tests/ui/doc_link_with_quotes.stderr new file mode 100644 index 000000000000..bf6d57d8afe8 --- /dev/null +++ b/tests/ui/doc_link_with_quotes.stderr @@ -0,0 +1,10 @@ +error: possible intra-doc link using quotes instead of backticks + --> $DIR/doc_link_with_quotes.rs:7:1 + | +LL | /// Calls ['bar'] + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::doc-link-with-quotes` implied by `-D warnings` + +error: aborting due to previous error +