Skip to content

Commit 81d5301

Browse files
committed
Reuse existing markdown parser in doc_link_code
1 parent 0a8cb5d commit 81d5301

File tree

2 files changed

+86
-86
lines changed

2 files changed

+86
-86
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use std::ops::Range;
2+
3+
use clippy_utils::diagnostics::span_lint_and_then;
4+
use rustc_errors::Applicability;
5+
use rustc_lint::LateContext;
6+
use rustc_resolve::rustdoc::pulldown_cmark::{Event, Tag, TagEnd};
7+
8+
use crate::doc::Fragments;
9+
10+
use super::DOC_LINK_CODE;
11+
12+
#[derive(Default)]
13+
pub(super) struct LinkCode {
14+
start: Option<usize>,
15+
end: Option<usize>,
16+
includes_link: bool,
17+
}
18+
19+
impl LinkCode {
20+
pub fn check(
21+
&mut self,
22+
cx: &LateContext<'_>,
23+
event: &Event<'_>,
24+
range: Range<usize>,
25+
in_link: bool,
26+
doc: &str,
27+
fragments: Fragments<'_>,
28+
) {
29+
match event {
30+
Event::Start(Tag::Link { .. }) => {
31+
if self.start.is_some() {
32+
self.end = Some(range.end);
33+
} else {
34+
self.start = Some(range.start);
35+
}
36+
self.includes_link = true;
37+
},
38+
Event::End(TagEnd::Link) => {},
39+
_ if in_link => {
40+
if !matches!(event, Event::Code(_)) {
41+
self.reset();
42+
}
43+
},
44+
Event::Code(_) => {
45+
if self.start.is_some() {
46+
self.end = Some(range.end);
47+
} else {
48+
self.start = Some(range.start);
49+
}
50+
},
51+
_ => {
52+
if let Some(start) = self.start
53+
&& let Some(end) = self.end
54+
&& self.includes_link
55+
&& let Some(span) = fragments.span(cx, start..end)
56+
{
57+
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
58+
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
59+
diag.span_suggestion_verbose(
60+
span,
61+
"wrap the entire group in `<code>` tags",
62+
sugg,
63+
Applicability::MaybeIncorrect,
64+
);
65+
diag.help("separate code snippets will be shown with a gap");
66+
});
67+
}
68+
69+
self.reset();
70+
},
71+
}
72+
}
73+
74+
fn reset(&mut self) {
75+
*self = LinkCode::default();
76+
}
77+
}

clippy_lints/src/doc/mod.rs

Lines changed: 9 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ use rustc_span::Span;
2525
use std::ops::Range;
2626
use url::Url;
2727

28+
use doc_link_code::LinkCode;
29+
use doc_paragraphs_missing_punctuation::MissingPunctuation;
30+
2831
mod broken_link;
2932
mod doc_comment_double_space_linebreaks;
33+
mod doc_link_code;
3034
mod doc_paragraphs_missing_punctuation;
3135
mod doc_suspicious_footnotes;
3236
mod include_in_doc_without_cfg;
@@ -847,14 +851,6 @@ struct DocHeaders {
847851
/// back in the various late lint pass methods if they need the final doc headers, like "Safety" or
848852
/// "Panics" sections.
849853
fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[Attribute]) -> Option<DocHeaders> {
850-
// We don't want the parser to choke on intra doc links. Since we don't
851-
// actually care about rendering them, just pretend that all broken links
852-
// point to a fake address.
853-
#[expect(clippy::unnecessary_wraps)] // we're following a type signature
854-
fn fake_broken_link_callback<'a>(_: BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)> {
855-
Some(("fake".into(), "fake".into()))
856-
}
857-
858854
if suspicious_doc_comments::check(cx, attrs) || is_doc_hidden(attrs) {
859855
return None;
860856
}
@@ -889,32 +885,16 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
889885
return Some(DocHeaders::default());
890886
}
891887

892-
check_for_code_clusters(
893-
cx,
894-
pulldown_cmark::Parser::new_with_broken_link_callback(
895-
&doc,
896-
main_body_opts() - Options::ENABLE_SMART_PUNCTUATION,
897-
Some(&mut fake_broken_link_callback),
898-
)
899-
.into_offset_iter(),
900-
&doc,
901-
Fragments {
902-
doc: &doc,
903-
fragments: &fragments,
904-
},
905-
);
906-
907888
// NOTE: check_doc uses it own cb function,
908889
// to avoid causing duplicated diagnostics for the broken link checker.
909-
let mut full_fake_broken_link_callback = |bl: BrokenLink<'_>| -> Option<(CowStr<'_>, CowStr<'_>)> {
890+
let mut broken_link_callback = |bl: BrokenLink<'_>| -> Option<(CowStr<'_>, CowStr<'_>)> {
910891
broken_link::check(cx, &bl, &doc, &fragments);
911892
Some(("fake".into(), "fake".into()))
912893
};
913894

914895
// disable smart punctuation to pick up ['link'] more easily
915896
let opts = main_body_opts() - Options::ENABLE_SMART_PUNCTUATION;
916-
let parser =
917-
pulldown_cmark::Parser::new_with_broken_link_callback(&doc, opts, Some(&mut full_fake_broken_link_callback));
897+
let parser = pulldown_cmark::Parser::new_with_broken_link_callback(&doc, opts, Some(&mut broken_link_callback));
918898

919899
Some(check_doc(
920900
cx,
@@ -934,65 +914,6 @@ enum Container {
934914
List(usize),
935915
}
936916

937-
/// Scan the documentation for code links that are back-to-back with code spans.
938-
///
939-
/// This is done separately from the rest of the docs, because that makes it easier to produce
940-
/// the correct messages.
941-
fn check_for_code_clusters<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
942-
cx: &LateContext<'_>,
943-
events: Events,
944-
doc: &str,
945-
fragments: Fragments<'_>,
946-
) {
947-
let mut events = events.peekable();
948-
let mut code_starts_at = None;
949-
let mut code_ends_at = None;
950-
let mut code_includes_link = false;
951-
while let Some((event, range)) = events.next() {
952-
match event {
953-
Start(Link { .. }) if matches!(events.peek(), Some((Code(_), _range))) => {
954-
if code_starts_at.is_some() {
955-
code_ends_at = Some(range.end);
956-
} else {
957-
code_starts_at = Some(range.start);
958-
}
959-
code_includes_link = true;
960-
// skip the nested "code", because we're already handling it here
961-
let _ = events.next();
962-
},
963-
Code(_) => {
964-
if code_starts_at.is_some() {
965-
code_ends_at = Some(range.end);
966-
} else {
967-
code_starts_at = Some(range.start);
968-
}
969-
},
970-
End(TagEnd::Link) => {},
971-
_ => {
972-
if let Some(start) = code_starts_at
973-
&& let Some(end) = code_ends_at
974-
&& code_includes_link
975-
&& let Some(span) = fragments.span(cx, start..end)
976-
{
977-
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
978-
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
979-
diag.span_suggestion_verbose(
980-
span,
981-
"wrap the entire group in `<code>` tags",
982-
sugg,
983-
Applicability::MaybeIncorrect,
984-
);
985-
diag.help("separate code snippets will be shown with a gap");
986-
});
987-
}
988-
code_includes_link = false;
989-
code_starts_at = None;
990-
code_ends_at = None;
991-
},
992-
}
993-
}
994-
}
995-
996917
#[derive(Clone, Copy)]
997918
#[expect(clippy::struct_excessive_bools)]
998919
struct CodeTags {
@@ -1070,7 +991,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
1070991
fragments: Fragments<'_>,
1071992
attrs: &[Attribute],
1072993
) -> DocHeaders {
1073-
let mut missing_punctuation = doc_paragraphs_missing_punctuation::MissingPunctuation::default();
994+
let mut missing_punctuation = MissingPunctuation::default();
995+
let mut link_code = LinkCode::default();
1074996

1075997
// true if a safety header was found
1076998
let mut headers = DocHeaders::default();
@@ -1092,6 +1014,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
10921014

10931015
while let Some((event, range)) = events.next() {
10941016
missing_punctuation.check(cx, &event, range.clone(), doc, fragments);
1017+
link_code.check(cx, &event, range.clone(), in_link.is_some(), doc, fragments);
10951018

10961019
match event {
10971020
Html(tag) | InlineHtml(tag) => {

0 commit comments

Comments
 (0)