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.
This commit is contained in:
parent
045e36d6a7
commit
aff497f17f
1 changed files with 73 additions and 37 deletions
|
|
@ -844,6 +844,21 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
|
|||
|
||||
let mut cb = fake_broken_link_callback;
|
||||
|
||||
check_for_code_clusters(
|
||||
cx,
|
||||
pulldown_cmark::Parser::new_with_broken_link_callback(
|
||||
&doc,
|
||||
main_body_opts() - Options::ENABLE_SMART_PUNCTUATION,
|
||||
Some(&mut cb),
|
||||
)
|
||||
.into_offset_iter(),
|
||||
&doc,
|
||||
Fragments {
|
||||
doc: &doc,
|
||||
fragments: &fragments,
|
||||
},
|
||||
);
|
||||
|
||||
// disable smart punctuation to pick up ['link'] more easily
|
||||
let opts = main_body_opts() - Options::ENABLE_SMART_PUNCTUATION;
|
||||
let parser = pulldown_cmark::Parser::new_with_broken_link_callback(&doc, opts, Some(&mut cb));
|
||||
|
|
@ -867,12 +882,64 @@ enum Container {
|
|||
List(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
enum CodeCluster {
|
||||
// true means already in a link, so only needs to be followed by code
|
||||
// false means we've hit code, and need to find a link
|
||||
First(usize, bool),
|
||||
Nth(usize, usize),
|
||||
/// Scan the documentation for code links that are back-to-back with code spans.
|
||||
///
|
||||
/// This is done separately from the rest of the docs, because that makes it easier to produce
|
||||
/// the correct messages.
|
||||
fn check_for_code_clusters<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
|
||||
cx: &LateContext<'_>,
|
||||
events: Events,
|
||||
doc: &str,
|
||||
fragments: Fragments<'_>,
|
||||
) {
|
||||
let mut events = events.peekable();
|
||||
let mut code_starts_at = None;
|
||||
let mut code_ends_at = None;
|
||||
let mut code_includes_link = false;
|
||||
while let Some((event, range)) = events.next() {
|
||||
match event {
|
||||
Start(Link { .. }) if matches!(events.peek(), Some((Code(_), _range))) => {
|
||||
if code_starts_at.is_some() {
|
||||
code_ends_at = Some(range.end);
|
||||
} else {
|
||||
code_starts_at = Some(range.start);
|
||||
}
|
||||
code_includes_link = true;
|
||||
// skip the nested "code", because we're already handling it here
|
||||
let _ = events.next();
|
||||
},
|
||||
Code(_) => {
|
||||
if code_starts_at.is_some() {
|
||||
code_ends_at = Some(range.end);
|
||||
} else {
|
||||
code_starts_at = Some(range.start);
|
||||
}
|
||||
},
|
||||
End(TagEnd::Link) => {},
|
||||
_ => {
|
||||
if let Some(start) = code_starts_at
|
||||
&& let Some(end) = code_ends_at
|
||||
&& code_includes_link
|
||||
{
|
||||
if let Some(span) = fragments.span(cx, start..end) {
|
||||
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
|
||||
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
|
||||
diag.span_suggestion_verbose(
|
||||
span,
|
||||
"wrap the entire group in `<code>` tags",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
diag.help("separate code snippets will be shown with a gap");
|
||||
});
|
||||
}
|
||||
}
|
||||
code_includes_link = false;
|
||||
code_starts_at = None;
|
||||
code_ends_at = None;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks parsed documentation.
|
||||
|
|
@ -906,40 +973,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
|
||||
let mut containers = Vec::new();
|
||||
|
||||
let mut code_cluster = None;
|
||||
|
||||
let mut events = events.peekable();
|
||||
|
||||
while let Some((event, range)) = events.next() {
|
||||
code_cluster = match (code_cluster, &event) {
|
||||
(None, Code(_)) if in_link.is_some() && doc.as_bytes().get(range.start.wrapping_sub(1)) == Some(&b'[') => {
|
||||
Some(CodeCluster::First(range.start - 1, true))
|
||||
},
|
||||
(None, Code(_)) => Some(CodeCluster::First(range.start, false)),
|
||||
(Some(CodeCluster::First(pos, _)), Start(Link { .. })) | (Some(CodeCluster::First(pos, true)), Code(_)) => {
|
||||
Some(CodeCluster::Nth(pos, range.end))
|
||||
},
|
||||
(Some(CodeCluster::Nth(start, end)), Code(_) | Start(Link { .. })) => {
|
||||
Some(CodeCluster::Nth(start, range.end.max(end)))
|
||||
},
|
||||
(code_cluster @ Some(_), Code(_) | End(TagEnd::Link)) => code_cluster,
|
||||
(Some(CodeCluster::First(_, _)) | None, _) => None,
|
||||
(Some(CodeCluster::Nth(start, end)), _) => {
|
||||
if let Some(span) = fragments.span(cx, start..end) {
|
||||
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
|
||||
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
|
||||
diag.span_suggestion_verbose(
|
||||
span,
|
||||
"wrap the entire group in `<code>` tags",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
diag.help("separate code snippets will be shown with a gap");
|
||||
});
|
||||
}
|
||||
None
|
||||
},
|
||||
};
|
||||
match event {
|
||||
Html(tag) | InlineHtml(tag) => {
|
||||
if tag.starts_with("<code") {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue