Skip to content

Commit 72c0d80

Browse files
committed
Auto merge of rust-lang#11801 - y21:split_doc_pass, r=blyxyas
Split `doc.rs` up into a subdirectory So, first, sorry for the bad diff. 😅 In rust-lang#11798, `@flip1995` suggested splitting `doc.rs` up, much like how we have the `methods/`, `matches/`, `types/` subdirectories. I agree with this, the file is getting bigger as we add more and more doc lints that it makes sense to do this refactoring. This is purely an internal change that moves things around a bit. (**EDIT:** depending on the outcome of rust-lang/rust-clippy#11801 (comment) , this may change the lint group name from `doc_markdoc` to `doc`). I tried to not change any of the actual logic of the lints and as such some things weren't as easy to move to a separate file. So we still have some `span_lint*` calls in the `doc/mod.rs` file, which I think is fine. This is also the case in `methods/mod.rs`. Also worth mentioning that the lints missing_errors_doc, missing_panics_doc, missing_safety_doc and unnecessary_safety_doc have a lot of the same logic so it didn't make much sense for each of these to be in their own file. Instead I just put them all in `missing_headers.rs` I also added a bit of documentation to the involved `check_{attrs,doc}` methods. changelog: none
2 parents 11a2eb0 + 56cee3c commit 72c0d80

8 files changed

+423
-343
lines changed
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::ops::Range;
2+
3+
use clippy_utils::diagnostics::span_lint;
4+
use rustc_lint::LateContext;
5+
6+
use super::{Fragments, DOC_LINK_WITH_QUOTES};
7+
8+
pub fn check(cx: &LateContext<'_>, trimmed_text: &str, range: Range<usize>, fragments: Fragments<'_>) {
9+
if ((trimmed_text.starts_with('\'') && trimmed_text.ends_with('\''))
10+
|| (trimmed_text.starts_with('"') && trimmed_text.ends_with('"')))
11+
&& let Some(span) = fragments.span(cx, range)
12+
{
13+
span_lint(
14+
cx,
15+
DOC_LINK_WITH_QUOTES,
16+
span,
17+
"possible intra-doc link using quotes instead of backticks",
18+
);
19+
}
20+
}

clippy_lints/src/doc/markdown.rs

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
2+
use clippy_utils::source::snippet_with_applicability;
3+
use rustc_data_structures::fx::FxHashSet;
4+
use rustc_errors::{Applicability, SuggestionStyle};
5+
use rustc_lint::LateContext;
6+
use rustc_span::{BytePos, Pos, Span};
7+
use url::Url;
8+
9+
use crate::doc::DOC_MARKDOWN;
10+
11+
pub fn check(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, text: &str, span: Span) {
12+
for word in text.split(|c: char| c.is_whitespace() || c == '\'') {
13+
// Trim punctuation as in `some comment (see foo::bar).`
14+
// ^^
15+
// Or even as in `_foo bar_` which is emphasized. Also preserve `::` as a prefix/suffix.
16+
let mut word = word.trim_matches(|c: char| !c.is_alphanumeric() && c != ':');
17+
18+
// Remove leading or trailing single `:` which may be part of a sentence.
19+
if word.starts_with(':') && !word.starts_with("::") {
20+
word = word.trim_start_matches(':');
21+
}
22+
if word.ends_with(':') && !word.ends_with("::") {
23+
word = word.trim_end_matches(':');
24+
}
25+
26+
if valid_idents.contains(word) || word.chars().all(|c| c == ':') {
27+
continue;
28+
}
29+
30+
// Adjust for the current word
31+
let offset = word.as_ptr() as usize - text.as_ptr() as usize;
32+
let span = Span::new(
33+
span.lo() + BytePos::from_usize(offset),
34+
span.lo() + BytePos::from_usize(offset + word.len()),
35+
span.ctxt(),
36+
span.parent(),
37+
);
38+
39+
check_word(cx, word, span);
40+
}
41+
}
42+
43+
fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
44+
/// Checks if a string is upper-camel-case, i.e., starts with an uppercase and
45+
/// contains at least two uppercase letters (`Clippy` is ok) and one lower-case
46+
/// letter (`NASA` is ok).
47+
/// Plurals are also excluded (`IDs` is ok).
48+
fn is_camel_case(s: &str) -> bool {
49+
if s.starts_with(|c: char| c.is_ascii_digit() | c.is_ascii_lowercase()) {
50+
return false;
51+
}
52+
53+
let s = s.strip_suffix('s').unwrap_or(s);
54+
55+
s.chars().all(char::is_alphanumeric)
56+
&& s.chars().filter(|&c| c.is_uppercase()).take(2).count() > 1
57+
&& s.chars().filter(|&c| c.is_lowercase()).take(1).count() > 0
58+
}
59+
60+
fn has_underscore(s: &str) -> bool {
61+
s != "_" && !s.contains("\\_") && s.contains('_')
62+
}
63+
64+
fn has_hyphen(s: &str) -> bool {
65+
s != "-" && s.contains('-')
66+
}
67+
68+
if let Ok(url) = Url::parse(word) {
69+
// try to get around the fact that `foo::bar` parses as a valid URL
70+
if !url.cannot_be_a_base() {
71+
span_lint(
72+
cx,
73+
DOC_MARKDOWN,
74+
span,
75+
"you should put bare URLs between `<`/`>` or make a proper Markdown link",
76+
);
77+
78+
return;
79+
}
80+
}
81+
82+
// We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
83+
if has_underscore(word) && has_hyphen(word) {
84+
return;
85+
}
86+
87+
if has_underscore(word) || word.contains("::") || is_camel_case(word) {
88+
let mut applicability = Applicability::MachineApplicable;
89+
90+
span_lint_and_then(
91+
cx,
92+
DOC_MARKDOWN,
93+
span,
94+
"item in documentation is missing backticks",
95+
|diag| {
96+
let snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
97+
diag.span_suggestion_with_style(
98+
span,
99+
"try",
100+
format!("`{snippet}`"),
101+
applicability,
102+
// always show the suggestion in a separate line, since the
103+
// inline presentation adds another pair of backticks
104+
SuggestionStyle::ShowAlways,
105+
);
106+
},
107+
);
108+
}
109+
}
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
2+
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
3+
use clippy_utils::{is_doc_hidden, return_ty};
4+
use rustc_hir::{BodyId, FnSig, OwnerId, Unsafety};
5+
use rustc_lint::LateContext;
6+
use rustc_middle::ty;
7+
use rustc_span::{sym, Span};
8+
9+
use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC};
10+
11+
pub fn check(
12+
cx: &LateContext<'_>,
13+
owner_id: OwnerId,
14+
sig: &FnSig<'_>,
15+
headers: DocHeaders,
16+
body_id: Option<BodyId>,
17+
panic_span: Option<Span>,
18+
) {
19+
if !cx.effective_visibilities.is_exported(owner_id.def_id) {
20+
return; // Private functions do not require doc comments
21+
}
22+
23+
// do not lint if any parent has `#[doc(hidden)]` attribute (#7347)
24+
if cx
25+
.tcx
26+
.hir()
27+
.parent_iter(owner_id.into())
28+
.any(|(id, _node)| is_doc_hidden(cx.tcx.hir().attrs(id)))
29+
{
30+
return;
31+
}
32+
33+
let span = cx.tcx.def_span(owner_id);
34+
match (headers.safety, sig.header.unsafety) {
35+
(false, Unsafety::Unsafe) => span_lint(
36+
cx,
37+
MISSING_SAFETY_DOC,
38+
span,
39+
"unsafe function's docs miss `# Safety` section",
40+
),
41+
(true, Unsafety::Normal) => span_lint(
42+
cx,
43+
UNNECESSARY_SAFETY_DOC,
44+
span,
45+
"safe function's docs have unnecessary `# Safety` section",
46+
),
47+
_ => (),
48+
}
49+
if !headers.panics && panic_span.is_some() {
50+
span_lint_and_note(
51+
cx,
52+
MISSING_PANICS_DOC,
53+
span,
54+
"docs for function which may panic missing `# Panics` section",
55+
panic_span,
56+
"first possible panic found here",
57+
);
58+
}
59+
if !headers.errors {
60+
if is_type_diagnostic_item(cx, return_ty(cx, owner_id), sym::Result) {
61+
span_lint(
62+
cx,
63+
MISSING_ERRORS_DOC,
64+
span,
65+
"docs for function returning `Result` missing `# Errors` section",
66+
);
67+
} else if let Some(body_id) = body_id
68+
&& let Some(future) = cx.tcx.lang_items().future_trait()
69+
&& let typeck = cx.tcx.typeck_body(body_id)
70+
&& let body = cx.tcx.hir().body(body_id)
71+
&& let ret_ty = typeck.expr_ty(body.value)
72+
&& implements_trait(cx, ret_ty, future, &[])
73+
&& let ty::Coroutine(_, subs, _) = ret_ty.kind()
74+
&& is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result)
75+
{
76+
span_lint(
77+
cx,
78+
MISSING_ERRORS_DOC,
79+
span,
80+
"docs for function returning `Result` missing `# Errors` section",
81+
);
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)