Skip to content

Commit 25ade69

Browse files
authored
Rollup merge of rust-lang#83543 - camelid:lint-unknown-disambiguator, r=jyn514
Lint on unknown intra-doc link disambiguators
2 parents 772582e + 141df6f commit 25ade69

File tree

4 files changed

+123
-26
lines changed

4 files changed

+123
-26
lines changed

src/librustdoc/html/markdown.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,7 @@ crate fn plain_text_summary(md: &str) -> String {
11621162
s
11631163
}
11641164

1165+
#[derive(Debug)]
11651166
crate struct MarkdownLink {
11661167
pub kind: LinkType,
11671168
pub link: String,

src/librustdoc/passes/collect_intra_doc_links.rs

+64-26
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,7 @@ impl LinkCollector<'_, '_> {
950950
}
951951

952952
let link = ori_link.link.replace("`", "");
953+
let no_backticks_range = range_between_backticks(&ori_link);
953954
let parts = link.split('#').collect::<Vec<_>>();
954955
let (link, extra_fragment) = if parts.len() > 2 {
955956
// A valid link can't have multiple #'s
@@ -973,10 +974,15 @@ impl LinkCollector<'_, '_> {
973974
};
974975

975976
// Parse and strip the disambiguator from the link, if present.
976-
let (mut path_str, disambiguator) = if let Ok((d, path)) = Disambiguator::from_str(&link) {
977-
(path.trim(), Some(d))
978-
} else {
979-
(link.trim(), None)
977+
let (mut path_str, disambiguator) = match Disambiguator::from_str(&link) {
978+
Ok(Some((d, path))) => (path.trim(), Some(d)),
979+
Ok(None) => (link.trim(), None),
980+
Err((err_msg, relative_range)) => {
981+
let disambiguator_range = (no_backticks_range.start + relative_range.start)
982+
..(no_backticks_range.start + relative_range.end);
983+
disambiguator_error(self.cx, &item, dox, disambiguator_range, &err_msg);
984+
return None;
985+
}
980986
};
981987

982988
if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, !*&;".contains(ch))) {
@@ -1488,6 +1494,27 @@ impl LinkCollector<'_, '_> {
14881494
}
14891495
}
14901496

1497+
/// Get the section of a link between the backticks,
1498+
/// or the whole link if there aren't any backticks.
1499+
///
1500+
/// For example:
1501+
///
1502+
/// ```text
1503+
/// [`Foo`]
1504+
/// ^^^
1505+
/// ```
1506+
fn range_between_backticks(ori_link: &MarkdownLink) -> Range<usize> {
1507+
let after_first_backtick_group = ori_link.link.bytes().position(|b| b != b'`').unwrap_or(0);
1508+
let before_second_backtick_group = ori_link
1509+
.link
1510+
.bytes()
1511+
.skip(after_first_backtick_group)
1512+
.position(|b| b == b'`')
1513+
.unwrap_or(ori_link.link.len());
1514+
(ori_link.range.start + after_first_backtick_group)
1515+
..(ori_link.range.start + before_second_backtick_group)
1516+
}
1517+
14911518
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
14921519
/// Disambiguators for a link.
14931520
enum Disambiguator {
@@ -1514,27 +1541,14 @@ impl Disambiguator {
15141541
}
15151542
}
15161543

1517-
/// Given a link, parse and return `(disambiguator, path_str)`
1518-
fn from_str(link: &str) -> Result<(Self, &str), ()> {
1544+
/// Given a link, parse and return `(disambiguator, path_str)`.
1545+
///
1546+
/// This returns `Ok(Some(...))` if a disambiguator was found,
1547+
/// `Ok(None)` if no disambiguator was found, or `Err(...)`
1548+
/// if there was a problem with the disambiguator.
1549+
fn from_str(link: &str) -> Result<Option<(Self, &str)>, (String, Range<usize>)> {
15191550
use Disambiguator::{Kind, Namespace as NS, Primitive};
15201551

1521-
let find_suffix = || {
1522-
let suffixes = [
1523-
("!()", DefKind::Macro(MacroKind::Bang)),
1524-
("()", DefKind::Fn),
1525-
("!", DefKind::Macro(MacroKind::Bang)),
1526-
];
1527-
for &(suffix, kind) in &suffixes {
1528-
if let Some(link) = link.strip_suffix(suffix) {
1529-
// Avoid turning `!` or `()` into an empty string
1530-
if !link.is_empty() {
1531-
return Ok((Kind(kind), link));
1532-
}
1533-
}
1534-
}
1535-
Err(())
1536-
};
1537-
15381552
if let Some(idx) = link.find('@') {
15391553
let (prefix, rest) = link.split_at(idx);
15401554
let d = match prefix {
@@ -1551,11 +1565,24 @@ impl Disambiguator {
15511565
"value" => NS(Namespace::ValueNS),
15521566
"macro" => NS(Namespace::MacroNS),
15531567
"prim" | "primitive" => Primitive,
1554-
_ => return find_suffix(),
1568+
_ => return Err((format!("unknown disambiguator `{}`", prefix), 0..idx)),
15551569
};
1556-
Ok((d, &rest[1..]))
1570+
Ok(Some((d, &rest[1..])))
15571571
} else {
1558-
find_suffix()
1572+
let suffixes = [
1573+
("!()", DefKind::Macro(MacroKind::Bang)),
1574+
("()", DefKind::Fn),
1575+
("!", DefKind::Macro(MacroKind::Bang)),
1576+
];
1577+
for &(suffix, kind) in &suffixes {
1578+
if let Some(link) = link.strip_suffix(suffix) {
1579+
// Avoid turning `!` or `()` into an empty string
1580+
if !link.is_empty() {
1581+
return Ok(Some((Kind(kind), link)));
1582+
}
1583+
}
1584+
}
1585+
Ok(None)
15591586
}
15601587
}
15611588

@@ -1979,6 +2006,17 @@ fn anchor_failure(
19792006
});
19802007
}
19812008

2009+
/// Report an error in the link disambiguator.
2010+
fn disambiguator_error(
2011+
cx: &DocContext<'_>,
2012+
item: &Item,
2013+
dox: &str,
2014+
link_range: Range<usize>,
2015+
msg: &str,
2016+
) {
2017+
report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, item, dox, &link_range, |_diag, _sp| {});
2018+
}
2019+
19822020
/// Report an ambiguity error, where there were multiple possible resolutions.
19832021
fn ambiguity_error(
19842022
cx: &DocContext<'_>,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![deny(warnings)]
2+
3+
//! Linking to [foo@banana] and [`bar@banana!()`].
4+
//~^ ERROR unknown disambiguator `foo`
5+
//~| ERROR unknown disambiguator `bar`
6+
//! And to [no disambiguator](@nectarine) and [another](@apricot!()).
7+
//~^ ERROR unknown disambiguator ``
8+
//~| ERROR unknown disambiguator ``
9+
//! And with weird backticks: [``foo@hello``] [foo`@`hello].
10+
//~^ ERROR unknown disambiguator `foo`
11+
//~| ERROR unknown disambiguator `foo`
12+
13+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
error: unknown disambiguator `foo`
2+
--> $DIR/unknown-disambiguator.rs:3:17
3+
|
4+
LL | //! Linking to [foo@banana] and [`bar@banana!()`].
5+
| ^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/unknown-disambiguator.rs:1:9
9+
|
10+
LL | #![deny(warnings)]
11+
| ^^^^^^^^
12+
= note: `#[deny(rustdoc::broken_intra_doc_links)]` implied by `#[deny(warnings)]`
13+
14+
error: unknown disambiguator `bar`
15+
--> $DIR/unknown-disambiguator.rs:3:35
16+
|
17+
LL | //! Linking to [foo@banana] and [`bar@banana!()`].
18+
| ^^^
19+
20+
error: unknown disambiguator `foo`
21+
--> $DIR/unknown-disambiguator.rs:9:34
22+
|
23+
LL | //! And with weird backticks: [``foo@hello``] [foo`@`hello].
24+
| ^^^
25+
26+
error: unknown disambiguator `foo`
27+
--> $DIR/unknown-disambiguator.rs:9:48
28+
|
29+
LL | //! And with weird backticks: [``foo@hello``] [foo`@`hello].
30+
| ^^^
31+
32+
error: unknown disambiguator ``
33+
--> $DIR/unknown-disambiguator.rs:6:31
34+
|
35+
LL | //! And to [no disambiguator](@nectarine) and [another](@apricot!()).
36+
| ^
37+
38+
error: unknown disambiguator ``
39+
--> $DIR/unknown-disambiguator.rs:6:57
40+
|
41+
LL | //! And to [no disambiguator](@nectarine) and [another](@apricot!()).
42+
| ^
43+
44+
error: aborting due to 6 previous errors
45+

0 commit comments

Comments
 (0)