summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/doc.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/doc.rs')
-rw-r--r--src/tools/clippy/clippy_lints/src/doc.rs102
1 files changed, 80 insertions, 22 deletions
diff --git a/src/tools/clippy/clippy_lints/src/doc.rs b/src/tools/clippy/clippy_lints/src/doc.rs
index eb158d850..24d6a6951 100644
--- a/src/tools/clippy/clippy_lints/src/doc.rs
+++ b/src/tools/clippy/clippy_lints/src/doc.rs
@@ -198,6 +198,29 @@ declare_clippy_lint! {
"presence of `fn main() {` in code examples"
}
+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.63.0"]
+ pub DOC_LINK_WITH_QUOTES,
+ pedantic,
+ "possible typo for an intra-doc link"
+}
+
#[expect(clippy::module_name_repetitions)]
#[derive(Clone)]
pub struct DocMarkdown {
@@ -214,9 +237,14 @@ impl DocMarkdown {
}
}
-impl_lint_pass!(DocMarkdown =>
- [DOC_MARKDOWN, MISSING_SAFETY_DOC, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, NEEDLESS_DOCTEST_MAIN]
-);
+impl_lint_pass!(DocMarkdown => [
+ DOC_LINK_WITH_QUOTES,
+ DOC_MARKDOWN,
+ MISSING_SAFETY_DOC,
+ MISSING_ERRORS_DOC,
+ MISSING_PANICS_DOC,
+ NEEDLESS_DOCTEST_MAIN
+]);
impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
@@ -229,15 +257,23 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
let headers = check_attrs(cx, &self.valid_idents, attrs);
match item.kind {
hir::ItemKind::Fn(ref sig, _, body_id) => {
- if !(is_entrypoint_fn(cx, item.def_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) {
+ if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) {
let body = cx.tcx.hir().body(body_id);
let mut fpu = FindPanicUnwrap {
cx,
- typeck_results: cx.tcx.typeck(item.def_id),
+ typeck_results: cx.tcx.typeck(item.owner_id.def_id),
panic_span: None,
};
fpu.visit_expr(body.value);
- lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
+ lint_for_missing_headers(
+ cx,
+ item.owner_id.def_id,
+ item.span,
+ sig,
+ headers,
+ Some(body_id),
+ fpu.panic_span,
+ );
}
},
hir::ItemKind::Impl(impl_) => {
@@ -268,7 +304,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
let headers = check_attrs(cx, &self.valid_idents, attrs);
if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
if !in_external_macro(cx.tcx.sess, item.span) {
- lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, None, None);
+ lint_for_missing_headers(cx, item.owner_id.def_id, item.span, sig, headers, None, None);
}
}
}
@@ -283,11 +319,19 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
let body = cx.tcx.hir().body(body_id);
let mut fpu = FindPanicUnwrap {
cx,
- typeck_results: cx.tcx.typeck(item.def_id),
+ typeck_results: cx.tcx.typeck(item.owner_id.def_id),
panic_span: None,
};
fpu.visit_expr(body.value);
- lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
+ lint_for_missing_headers(
+ cx,
+ item.owner_id.def_id,
+ item.span,
+ sig,
+ headers,
+ Some(body_id),
+ fpu.panic_span,
+ );
}
}
}
@@ -301,7 +345,7 @@ fn lint_for_missing_headers<'tcx>(
body_id: Option<hir::BodyId>,
panic_span: Option<Span>,
) {
- if !cx.access_levels.is_exported(def_id) {
+ if !cx.effective_visibilities.is_exported(def_id) {
return; // Private functions do not require doc comments
}
@@ -416,7 +460,7 @@ pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span:
(no_stars, sizes)
}
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
struct DocHeaders {
safety: bool,
errors: bool,
@@ -460,11 +504,7 @@ fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs
}
if doc.is_empty() {
- return DocHeaders {
- safety: false,
- errors: false,
- panics: false,
- };
+ return DocHeaders::default();
}
let mut cb = fake_broken_link_callback;
@@ -505,11 +545,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
use pulldown_cmark::{CodeBlockKind, CowStr};
- let mut headers = DocHeaders {
- safety: false,
- errors: false,
- panics: false,
- };
+ let mut headers = DocHeaders::default();
let mut in_code = false;
let mut in_link = None;
let mut in_heading = false;
@@ -596,6 +632,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
check_code(cx, &text, edition, span);
}
} else {
+ check_link_quotes(cx, in_link.is_some(), trimmed_text, span, &range, begin, text.len());
// Adjust for the beginning of the current `Event`
let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin));
text_to_check.push((text, span));
@@ -606,6 +643,27 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
headers
}
+fn check_link_quotes(
+ cx: &LateContext<'_>,
+ in_link: bool,
+ trimmed_text: &str,
+ span: Span,
+ range: &Range<usize>,
+ begin: usize,
+ text_len: usize,
+) {
+ if in_link && trimmed_text.starts_with('\'') && trimmed_text.ends_with('\'') {
+ // fix the span to only point at the text within the link
+ let lo = span.lo() + BytePos::from_usize(range.start - begin);
+ span_lint(
+ cx,
+ DOC_LINK_WITH_QUOTES,
+ span.with_lo(lo).with_hi(lo + BytePos::from_usize(text_len)),
+ "possible intra-doc link using quotes instead of backticks",
+ );
+ }
+}
+
fn get_current_span(spans: &[(usize, Span)], idx: usize) -> (usize, Span) {
let index = match spans.binary_search_by(|c| c.0.cmp(&idx)) {
Ok(o) => o,
@@ -790,7 +848,7 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
diag.span_suggestion_with_style(
span,
"try",
- format!("`{}`", snippet),
+ format!("`{snippet}`"),
applicability,
// always show the suggestion in a separate line, since the
// inline presentation adds another pair of backticks