Skip to content

Commit 3089653

Browse files
Rollup merge of #112014 - notriddle:notriddle/intra-doc-weird-syntax, r=GuillaumeGomez,fmease
rustdoc: get unnormalized link destination for suggestions Fixes #110111 This bug, and the workaround in this PR, is closely linked to [pulldown-cmark/pulldown-cmark#441], getting offsets of link components. In particular, pulldown-cmark doesn't provide the offsets of the contents of a link. To work around this, rustdoc parser parts of a link definition itself. [pulldown-cmark/pulldown-cmark#441]: pulldown-cmark/pulldown-cmark#441
2 parents 480ac69 + 1a77d9a commit 3089653

File tree

5 files changed

+639
-74
lines changed

5 files changed

+639
-74
lines changed

src/librustdoc/html/markdown.rs

+111-9
Original file line numberDiff line numberDiff line change
@@ -1237,7 +1237,27 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin
12371237
pub(crate) struct MarkdownLink {
12381238
pub kind: LinkType,
12391239
pub link: String,
1240-
pub range: Range<usize>,
1240+
pub range: MarkdownLinkRange,
1241+
}
1242+
1243+
#[derive(Clone, Debug)]
1244+
pub(crate) enum MarkdownLinkRange {
1245+
/// Normally, markdown link warnings point only at the destination.
1246+
Destination(Range<usize>),
1247+
/// In some cases, it's not possible to point at the destination.
1248+
/// Usually, this happens because backslashes `\\` are used.
1249+
/// When that happens, point at the whole link, and don't provide structured suggestions.
1250+
WholeLink(Range<usize>),
1251+
}
1252+
1253+
impl MarkdownLinkRange {
1254+
/// Extracts the inner range.
1255+
pub fn inner_range(&self) -> &Range<usize> {
1256+
match self {
1257+
MarkdownLinkRange::Destination(range) => range,
1258+
MarkdownLinkRange::WholeLink(range) => range,
1259+
}
1260+
}
12411261
}
12421262

12431263
pub(crate) fn markdown_links<R>(
@@ -1257,16 +1277,17 @@ pub(crate) fn markdown_links<R>(
12571277
if md_start <= s_start && s_end <= md_end {
12581278
let start = s_start.offset_from(md_start) as usize;
12591279
let end = s_end.offset_from(md_start) as usize;
1260-
start..end
1280+
MarkdownLinkRange::Destination(start..end)
12611281
} else {
1262-
fallback
1282+
MarkdownLinkRange::WholeLink(fallback)
12631283
}
12641284
};
12651285

12661286
let span_for_link = |link: &CowStr<'_>, span: Range<usize>| {
12671287
// For diagnostics, we want to underline the link's definition but `span` will point at
12681288
// where the link is used. This is a problem for reference-style links, where the definition
12691289
// is separate from the usage.
1290+
12701291
match link {
12711292
// `Borrowed` variant means the string (the link's destination) may come directly from
12721293
// the markdown text and we can locate the original link destination.
@@ -1275,8 +1296,80 @@ pub(crate) fn markdown_links<R>(
12751296
CowStr::Borrowed(s) => locate(s, span),
12761297

12771298
// For anything else, we can only use the provided range.
1278-
CowStr::Boxed(_) | CowStr::Inlined(_) => span,
1299+
CowStr::Boxed(_) | CowStr::Inlined(_) => MarkdownLinkRange::WholeLink(span),
1300+
}
1301+
};
1302+
1303+
let span_for_offset_backward = |span: Range<usize>, open: u8, close: u8| {
1304+
let mut open_brace = !0;
1305+
let mut close_brace = !0;
1306+
for (i, b) in md.as_bytes()[span.clone()].iter().copied().enumerate().rev() {
1307+
let i = i + span.start;
1308+
if b == close {
1309+
close_brace = i;
1310+
break;
1311+
}
1312+
}
1313+
if close_brace < span.start || close_brace >= span.end {
1314+
return MarkdownLinkRange::WholeLink(span);
1315+
}
1316+
let mut nesting = 1;
1317+
for (i, b) in md.as_bytes()[span.start..close_brace].iter().copied().enumerate().rev() {
1318+
let i = i + span.start;
1319+
if b == close {
1320+
nesting += 1;
1321+
}
1322+
if b == open {
1323+
nesting -= 1;
1324+
}
1325+
if nesting == 0 {
1326+
open_brace = i;
1327+
break;
1328+
}
1329+
}
1330+
assert!(open_brace != close_brace);
1331+
if open_brace < span.start || open_brace >= span.end {
1332+
return MarkdownLinkRange::WholeLink(span);
1333+
}
1334+
// do not actually include braces in the span
1335+
let range = (open_brace + 1)..close_brace;
1336+
MarkdownLinkRange::Destination(range.clone())
1337+
};
1338+
1339+
let span_for_offset_forward = |span: Range<usize>, open: u8, close: u8| {
1340+
let mut open_brace = !0;
1341+
let mut close_brace = !0;
1342+
for (i, b) in md.as_bytes()[span.clone()].iter().copied().enumerate() {
1343+
let i = i + span.start;
1344+
if b == open {
1345+
open_brace = i;
1346+
break;
1347+
}
1348+
}
1349+
if open_brace < span.start || open_brace >= span.end {
1350+
return MarkdownLinkRange::WholeLink(span);
12791351
}
1352+
let mut nesting = 0;
1353+
for (i, b) in md.as_bytes()[open_brace..span.end].iter().copied().enumerate() {
1354+
let i = i + open_brace;
1355+
if b == close {
1356+
nesting -= 1;
1357+
}
1358+
if b == open {
1359+
nesting += 1;
1360+
}
1361+
if nesting == 0 {
1362+
close_brace = i;
1363+
break;
1364+
}
1365+
}
1366+
assert!(open_brace != close_brace);
1367+
if open_brace < span.start || open_brace >= span.end {
1368+
return MarkdownLinkRange::WholeLink(span);
1369+
}
1370+
// do not actually include braces in the span
1371+
let range = (open_brace + 1)..close_brace;
1372+
MarkdownLinkRange::Destination(range.clone())
12801373
};
12811374

12821375
Parser::new_with_broken_link_callback(
@@ -1287,11 +1380,20 @@ pub(crate) fn markdown_links<R>(
12871380
.into_offset_iter()
12881381
.filter_map(|(event, span)| match event {
12891382
Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => {
1290-
preprocess_link(MarkdownLink {
1291-
kind: link_type,
1292-
range: span_for_link(&dest, span),
1293-
link: dest.into_string(),
1294-
})
1383+
let range = match link_type {
1384+
// Link is pulled from the link itself.
1385+
LinkType::ReferenceUnknown | LinkType::ShortcutUnknown => {
1386+
span_for_offset_backward(span, b'[', b']')
1387+
}
1388+
LinkType::CollapsedUnknown => span_for_offset_forward(span, b'[', b']'),
1389+
LinkType::Inline => span_for_offset_backward(span, b'(', b')'),
1390+
// Link is pulled from elsewhere in the document.
1391+
LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => {
1392+
span_for_link(&dest, span)
1393+
}
1394+
LinkType::Autolink | LinkType::Email => unreachable!(),
1395+
};
1396+
preprocess_link(MarkdownLink { kind: link_type, range, link: dest.into_string() })
12951397
}
12961398
_ => None,
12971399
})

0 commit comments

Comments
 (0)