Skip to content

Commit c736989

Browse files
committed
Refactor lint from rustc to rustdoc
1 parent e583318 commit c736989

File tree

6 files changed

+519
-102
lines changed

6 files changed

+519
-102
lines changed

src/librustdoc/passes/collect_intra_doc_links.rs

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,7 +1041,6 @@ impl LinkCollector<'_, '_> {
10411041
)?;
10421042

10431043
self.check_redundant_explicit_link(
1044-
&res,
10451044
path_str,
10461045
ResolutionInfo {
10471046
item_id,
@@ -1388,7 +1387,6 @@ impl LinkCollector<'_, '_> {
13881387
/// Check if resolution of inline link's display text and explicit link are same.
13891388
fn check_redundant_explicit_link(
13901389
&mut self,
1391-
explicit_res: &Res,
13921390
explicit_link: &Box<str>,
13931391
display_res_info: ResolutionInfo,
13941392
ori_link: &MarkdownLink,
@@ -1415,38 +1413,14 @@ impl LinkCollector<'_, '_> {
14151413
if explicit_len >= display_len
14161414
&& &explicit_link[(explicit_len - display_len)..] == display_text
14171415
{
1418-
let Some((display_res, _)) = self.resolve_with_disambiguator_cached(
1416+
self.resolve_with_disambiguator_cached(
14191417
display_res_info,
14201418
diag_info.clone(), // this struct should really be Copy, but Range is not :(
14211419
// For reference-style links we want to report only one error so unsuccessful
14221420
// resolutions are cached, for other links we want to report an error every
14231421
// time so they are not cached.
14241422
matches!(ori_link.kind, LinkType::Reference),
1425-
) else {
1426-
return;
1427-
};
1428-
1429-
if &display_res == explicit_res {
1430-
use crate::lint::REDUNDANT_EXPLICIT_LINKS;
1431-
1432-
report_diagnostic(
1433-
self.cx.tcx,
1434-
REDUNDANT_EXPLICIT_LINKS,
1435-
"redundant explicit rustdoc link",
1436-
&diag_info,
1437-
|diag, sp, _link_range| {
1438-
if let Some(sp) = sp {
1439-
diag.note("Explicit link does not affect the original link")
1440-
.span_suggestion_hidden(
1441-
sp,
1442-
"Remove explicit link instead",
1443-
format!(""),
1444-
Applicability::MachineApplicable,
1445-
);
1446-
}
1447-
},
1448-
);
1449-
}
1423+
);
14501424
}
14511425
}
14521426
}

src/librustdoc/passes/lint.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
mod bare_urls;
55
mod check_code_block_syntax;
66
mod html_tags;
7+
mod redundant_explicit_links;
78
mod unescaped_backticks;
89

910
use super::Pass;
@@ -29,6 +30,7 @@ impl<'a, 'tcx> DocVisitor for Linter<'a, 'tcx> {
2930
check_code_block_syntax::visit_item(self.cx, item);
3031
html_tags::visit_item(self.cx, item);
3132
unescaped_backticks::visit_item(self.cx, item);
33+
redundant_explicit_links::visit_item(self.cx, item);
3234

3335
self.visit_item_recur(item)
3436
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use std::ops::Range;
2+
3+
use pulldown_cmark::{Parser, BrokenLink, Event, Tag, LinkType, OffsetIter};
4+
use rustc_ast::NodeId;
5+
use rustc_errors::SuggestionStyle;
6+
use rustc_hir::HirId;
7+
use rustc_hir::def::{Namespace, DefKind, DocLinkResMap, Res};
8+
use rustc_lint_defs::Applicability;
9+
use rustc_span::Symbol;
10+
11+
use crate::clean::Item;
12+
use crate::clean::utils::find_nearest_parent_module;
13+
use crate::core::DocContext;
14+
use crate::html::markdown::main_body_opts;
15+
use crate::passes::source_span_for_markdown_range;
16+
17+
struct LinkData {
18+
resolvable_link: Option<String>,
19+
resolvable_link_range: Option<Range<usize>>,
20+
display_link: String,
21+
}
22+
23+
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
24+
let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else {
25+
// If non-local, no need to check anything.
26+
return;
27+
};
28+
29+
let doc = item.doc_value();
30+
if doc.is_empty() {
31+
return;
32+
}
33+
34+
check_redundant_explicit_link(cx, item, hir_id, &doc);
35+
}
36+
37+
fn check_redundant_explicit_link<'md>(cx: &DocContext<'_>, item: &Item, hir_id: HirId, doc: &'md str) {
38+
let mut broken_line_callback = |link: BrokenLink<'md>| Some((link.reference, "".into()));
39+
let mut offset_iter = Parser::new_with_broken_link_callback(&doc, main_body_opts(), Some(&mut broken_line_callback)).into_offset_iter();
40+
41+
while let Some((event, link_range)) = offset_iter.next() {
42+
match event {
43+
Event::Start(Tag::Link(link_type, dest, _)) => {
44+
let link_data = collect_link_data(&mut offset_iter);
45+
let dest = dest.to_string();
46+
47+
if link_type == LinkType::Inline {
48+
check_inline_link_redundancy(cx, item, hir_id, doc, link_range, dest, link_data);
49+
}
50+
}
51+
_ => {}
52+
}
53+
}
54+
}
55+
56+
fn check_inline_link_redundancy(cx: &DocContext<'_>, item: &Item, hir_id: HirId, doc: &str, link_range: Range<usize>, dest: String, link_data: LinkData) -> Option<()> {
57+
let item_id = item.def_id()?;
58+
let module_id = match cx.tcx.def_kind(item_id) {
59+
DefKind::Mod if item.inner_docs(cx.tcx) => item_id,
60+
_ => find_nearest_parent_module(cx.tcx, item_id).unwrap(),
61+
};
62+
let resolutions = cx.tcx.doc_link_resolutions(module_id);
63+
64+
let (resolvable_link, resolvable_link_range) = (&link_data.resolvable_link?, &link_data.resolvable_link_range?);
65+
let (dest_res, display_res) = (find_resolution(resolutions, &dest)?, find_resolution(resolutions, resolvable_link)?);
66+
67+
if dest_res == display_res {
68+
let link_span = source_span_for_markdown_range(
69+
cx.tcx,
70+
&doc,
71+
&link_range,
72+
&item.attrs,
73+
).unwrap_or(item.attr_span(cx.tcx));
74+
let explicit_span = source_span_for_markdown_range(
75+
cx.tcx,
76+
&doc,
77+
&offset_explicit_range(doc, &link_range, b'(', b')'),
78+
&item.attrs
79+
)?;
80+
let display_span = source_span_for_markdown_range(
81+
cx.tcx,
82+
&doc,
83+
&resolvable_link_range,
84+
&item.attrs
85+
)?;
86+
87+
88+
cx.tcx.struct_span_lint_hir(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, "redundant explicit link target", |lint| {
89+
lint.span_label(explicit_span, "explicit target is redundant")
90+
.span_label(display_span, "because label contains path that resolves to same destination")
91+
.note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links")
92+
.span_suggestion_with_style(link_span, "remove explicit link target", format!("[{}]", link_data.display_link), Applicability::MaybeIncorrect, SuggestionStyle::ShowAlways);
93+
94+
lint
95+
});
96+
}
97+
98+
None
99+
}
100+
101+
fn find_resolution<'tcx>(resolutions: &'tcx DocLinkResMap, path: &str) -> Option<&'tcx Res<NodeId>> {
102+
for ns in [Namespace::TypeNS, Namespace::ValueNS, Namespace::MacroNS] {
103+
let Some(Some(res)) = resolutions.get(&(Symbol::intern(path), ns))
104+
else {
105+
continue;
106+
};
107+
108+
return Some(res);
109+
}
110+
111+
None
112+
}
113+
114+
/// Collects all neccessary data of link.
115+
fn collect_link_data(offset_iter: &mut OffsetIter<'_, '_>) -> LinkData {
116+
let mut resolvable_link = None;
117+
let mut resolvable_link_range = None;
118+
let mut display_link = String::new();
119+
120+
while let Some((event, range)) = offset_iter.next() {
121+
match event {
122+
Event::Text(code) => {
123+
let code = code.to_string();
124+
display_link.push_str(&code);
125+
resolvable_link = Some(code);
126+
resolvable_link_range = Some(range);
127+
}
128+
Event::Code(code) => {
129+
let code = code.to_string();
130+
display_link.push('`');
131+
display_link.push_str(&code);
132+
display_link.push('`');
133+
resolvable_link = Some(code);
134+
resolvable_link_range = Some(range);
135+
}
136+
Event::End(_) => {
137+
break;
138+
}
139+
_ => {}
140+
}
141+
}
142+
143+
LinkData {
144+
resolvable_link,
145+
resolvable_link_range,
146+
display_link,
147+
}
148+
}
149+
150+
fn offset_explicit_range(md: &str, link_range: &Range<usize>, open: u8, close: u8) -> Range<usize> {
151+
let mut open_brace = !0;
152+
let mut close_brace = !0;
153+
for (i, b) in md.as_bytes()[link_range.clone()].iter().copied().enumerate().rev() {
154+
let i = i + link_range.start;
155+
if b == close {
156+
close_brace = i;
157+
break;
158+
}
159+
}
160+
161+
if close_brace < link_range.start || close_brace >= link_range.end {
162+
return link_range.clone();
163+
}
164+
165+
let mut nesting = 1;
166+
167+
for (i, b) in md.as_bytes()[link_range.start..close_brace].iter().copied().enumerate().rev() {
168+
let i = i + link_range.start;
169+
if b == close {
170+
nesting += 1;
171+
}
172+
if b == open {
173+
nesting -= 1;
174+
}
175+
if nesting == 0 {
176+
open_brace = i;
177+
break;
178+
}
179+
}
180+
181+
assert!(open_brace != close_brace);
182+
183+
if open_brace < link_range.start || open_brace >= link_range.end {
184+
return link_range.clone();
185+
}
186+
// do not actually include braces in the span
187+
(open_brace + 1)..close_brace
188+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// run-rustfix
2+
3+
#![deny(rustdoc::redundant_explicit_links)]
4+
5+
pub fn dummy_target() {}
6+
7+
/// [dummy_target]
8+
//~^ ERROR redundant explicit link target
9+
/// [`dummy_target`]
10+
//~^ ERROR redundant explicit link target
11+
///
12+
/// [Vec]
13+
//~^ ERROR redundant explicit link target
14+
/// [`Vec`]
15+
//~^ ERROR redundant explicit link target
16+
/// [Vec]
17+
//~^ ERROR redundant explicit link target
18+
/// [`Vec`]
19+
//~^ ERROR redundant explicit link target
20+
/// [std::vec::Vec]
21+
//~^ ERROR redundant explicit link target
22+
/// [`std::vec::Vec`]
23+
//~^ ERROR redundant explicit link target
24+
/// [std::vec::Vec]
25+
//~^ ERROR redundant explicit link target
26+
/// [`std::vec::Vec`]
27+
//~^ ERROR redundant explicit link target
28+
///
29+
/// [usize]
30+
//~^ ERROR redundant explicit link target
31+
/// [`usize`]
32+
//~^ ERROR redundant explicit link target
33+
/// [usize]
34+
//~^ ERROR redundant explicit link target
35+
/// [`usize`]
36+
//~^ ERROR redundant explicit link target
37+
/// [std::primitive::usize]
38+
//~^ ERROR redundant explicit link target
39+
/// [`std::primitive::usize`]
40+
//~^ ERROR redundant explicit link target
41+
/// [std::primitive::usize]
42+
//~^ ERROR redundant explicit link target
43+
/// [`std::primitive::usize`]
44+
//~^ ERROR redundant explicit link target
45+
///
46+
/// [dummy_target] TEXT
47+
//~^ ERROR redundant explicit link target
48+
/// [`dummy_target`] TEXT
49+
//~^ ERROR redundant explicit link target
50+
pub fn should_warn_inline() {}
51+
52+
/// [`Vec<T>`](Vec)
53+
/// [`Vec<T>`](std::vec::Vec)
54+
pub fn should_not_warn_inline() {}
Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,54 @@
1+
// run-rustfix
2+
13
#![deny(rustdoc::redundant_explicit_links)]
24

35
pub fn dummy_target() {}
46

57
/// [dummy_target](dummy_target)
8+
//~^ ERROR redundant explicit link target
69
/// [`dummy_target`](dummy_target)
10+
//~^ ERROR redundant explicit link target
711
///
812
/// [Vec](Vec)
13+
//~^ ERROR redundant explicit link target
914
/// [`Vec`](Vec)
15+
//~^ ERROR redundant explicit link target
1016
/// [Vec](std::vec::Vec)
17+
//~^ ERROR redundant explicit link target
1118
/// [`Vec`](std::vec::Vec)
19+
//~^ ERROR redundant explicit link target
1220
/// [std::vec::Vec](Vec)
21+
//~^ ERROR redundant explicit link target
1322
/// [`std::vec::Vec`](Vec)
23+
//~^ ERROR redundant explicit link target
1424
/// [std::vec::Vec](std::vec::Vec)
25+
//~^ ERROR redundant explicit link target
1526
/// [`std::vec::Vec`](std::vec::Vec)
27+
//~^ ERROR redundant explicit link target
1628
///
1729
/// [usize](usize)
30+
//~^ ERROR redundant explicit link target
1831
/// [`usize`](usize)
32+
//~^ ERROR redundant explicit link target
1933
/// [usize](std::primitive::usize)
34+
//~^ ERROR redundant explicit link target
2035
/// [`usize`](std::primitive::usize)
36+
//~^ ERROR redundant explicit link target
2137
/// [std::primitive::usize](usize)
38+
//~^ ERROR redundant explicit link target
2239
/// [`std::primitive::usize`](usize)
40+
//~^ ERROR redundant explicit link target
2341
/// [std::primitive::usize](std::primitive::usize)
42+
//~^ ERROR redundant explicit link target
2443
/// [`std::primitive::usize`](std::primitive::usize)
25-
pub fn should_warn() {}
44+
//~^ ERROR redundant explicit link target
45+
///
46+
/// [dummy_target](dummy_target) TEXT
47+
//~^ ERROR redundant explicit link target
48+
/// [`dummy_target`](dummy_target) TEXT
49+
//~^ ERROR redundant explicit link target
50+
pub fn should_warn_inline() {}
2651

2752
/// [`Vec<T>`](Vec)
2853
/// [`Vec<T>`](std::vec::Vec)
29-
pub fn should_not_warn() {}
54+
pub fn should_not_warn_inline() {}

0 commit comments

Comments
 (0)