Skip to content

Commit aff497f

Browse files
committed
Use a separate loop to drive the check for code clusters
By using a separate loop, I can just skip nodes that I don't want to process twice, instead of having to hand-build a state machine with an enum.
1 parent 045e36d commit aff497f

File tree

1 file changed

+73
-37
lines changed

1 file changed

+73
-37
lines changed

clippy_lints/src/doc/mod.rs

+73-37
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,21 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
844844

845845
let mut cb = fake_broken_link_callback;
846846

847+
check_for_code_clusters(
848+
cx,
849+
pulldown_cmark::Parser::new_with_broken_link_callback(
850+
&doc,
851+
main_body_opts() - Options::ENABLE_SMART_PUNCTUATION,
852+
Some(&mut cb),
853+
)
854+
.into_offset_iter(),
855+
&doc,
856+
Fragments {
857+
doc: &doc,
858+
fragments: &fragments,
859+
},
860+
);
861+
847862
// disable smart punctuation to pick up ['link'] more easily
848863
let opts = main_body_opts() - Options::ENABLE_SMART_PUNCTUATION;
849864
let parser = pulldown_cmark::Parser::new_with_broken_link_callback(&doc, opts, Some(&mut cb));
@@ -867,12 +882,64 @@ enum Container {
867882
List(usize),
868883
}
869884

870-
#[derive(Clone, Copy, Eq, PartialEq)]
871-
enum CodeCluster {
872-
// true means already in a link, so only needs to be followed by code
873-
// false means we've hit code, and need to find a link
874-
First(usize, bool),
875-
Nth(usize, usize),
885+
/// Scan the documentation for code links that are back-to-back with code spans.
886+
///
887+
/// This is done separately from the rest of the docs, because that makes it easier to produce
888+
/// the correct messages.
889+
fn check_for_code_clusters<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
890+
cx: &LateContext<'_>,
891+
events: Events,
892+
doc: &str,
893+
fragments: Fragments<'_>,
894+
) {
895+
let mut events = events.peekable();
896+
let mut code_starts_at = None;
897+
let mut code_ends_at = None;
898+
let mut code_includes_link = false;
899+
while let Some((event, range)) = events.next() {
900+
match event {
901+
Start(Link { .. }) if matches!(events.peek(), Some((Code(_), _range))) => {
902+
if code_starts_at.is_some() {
903+
code_ends_at = Some(range.end);
904+
} else {
905+
code_starts_at = Some(range.start);
906+
}
907+
code_includes_link = true;
908+
// skip the nested "code", because we're already handling it here
909+
let _ = events.next();
910+
},
911+
Code(_) => {
912+
if code_starts_at.is_some() {
913+
code_ends_at = Some(range.end);
914+
} else {
915+
code_starts_at = Some(range.start);
916+
}
917+
},
918+
End(TagEnd::Link) => {},
919+
_ => {
920+
if let Some(start) = code_starts_at
921+
&& let Some(end) = code_ends_at
922+
&& code_includes_link
923+
{
924+
if let Some(span) = fragments.span(cx, start..end) {
925+
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
926+
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
927+
diag.span_suggestion_verbose(
928+
span,
929+
"wrap the entire group in `<code>` tags",
930+
sugg,
931+
Applicability::MaybeIncorrect,
932+
);
933+
diag.help("separate code snippets will be shown with a gap");
934+
});
935+
}
936+
}
937+
code_includes_link = false;
938+
code_starts_at = None;
939+
code_ends_at = None;
940+
},
941+
}
942+
}
876943
}
877944

878945
/// Checks parsed documentation.
@@ -906,40 +973,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
906973

907974
let mut containers = Vec::new();
908975

909-
let mut code_cluster = None;
910-
911976
let mut events = events.peekable();
912977

913978
while let Some((event, range)) = events.next() {
914-
code_cluster = match (code_cluster, &event) {
915-
(None, Code(_)) if in_link.is_some() && doc.as_bytes().get(range.start.wrapping_sub(1)) == Some(&b'[') => {
916-
Some(CodeCluster::First(range.start - 1, true))
917-
},
918-
(None, Code(_)) => Some(CodeCluster::First(range.start, false)),
919-
(Some(CodeCluster::First(pos, _)), Start(Link { .. })) | (Some(CodeCluster::First(pos, true)), Code(_)) => {
920-
Some(CodeCluster::Nth(pos, range.end))
921-
},
922-
(Some(CodeCluster::Nth(start, end)), Code(_) | Start(Link { .. })) => {
923-
Some(CodeCluster::Nth(start, range.end.max(end)))
924-
},
925-
(code_cluster @ Some(_), Code(_) | End(TagEnd::Link)) => code_cluster,
926-
(Some(CodeCluster::First(_, _)) | None, _) => None,
927-
(Some(CodeCluster::Nth(start, end)), _) => {
928-
if let Some(span) = fragments.span(cx, start..end) {
929-
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
930-
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
931-
diag.span_suggestion_verbose(
932-
span,
933-
"wrap the entire group in `<code>` tags",
934-
sugg,
935-
Applicability::MaybeIncorrect,
936-
);
937-
diag.help("separate code snippets will be shown with a gap");
938-
});
939-
}
940-
None
941-
},
942-
};
943979
match event {
944980
Html(tag) | InlineHtml(tag) => {
945981
if tag.starts_with("<code") {

0 commit comments

Comments
 (0)