Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9c699a4

Browse files
committedAug 20, 2023
Auto merge of #113167 - ChAoSUnItY:redundant_explicit_link, r=GuillaumeGomez
rustdoc: Add lint `redundant_explicit_links` Closes #87799. - Lint warns by default - Reworks link parser to cache original link's display text r? `@jyn514`
2 parents f32ced6 + 297ff8c commit 9c699a4

File tree

24 files changed

+2066
-118
lines changed

24 files changed

+2066
-118
lines changed
 

‎compiler/rustc_borrowck/src/consumers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub use super::{
3030
/// will be retrieved.
3131
#[derive(Debug, Copy, Clone)]
3232
pub enum ConsumerOptions {
33-
/// Retrieve the [`Body`] along with the [`BorrowSet`](super::borrow_set::BorrowSet)
33+
/// Retrieve the [`Body`] along with the [`BorrowSet`]
3434
/// and [`RegionInferenceContext`]. If you would like the body only, use
3535
/// [`TyCtxt::mir_promoted`].
3636
///

‎compiler/rustc_errors/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,11 +452,11 @@ struct HandlerInner {
452452
/// have been converted.
453453
check_unstable_expect_diagnostics: bool,
454454

455-
/// Expected [`Diagnostic`][diagnostic::Diagnostic]s store a [`LintExpectationId`] as part of
455+
/// Expected [`Diagnostic`][struct@diagnostic::Diagnostic]s store a [`LintExpectationId`] as part of
456456
/// the lint level. [`LintExpectationId`]s created early during the compilation
457457
/// (before `HirId`s have been defined) are not stable and can therefore not be
458458
/// stored on disk. This buffer stores these diagnostics until the ID has been
459-
/// replaced by a stable [`LintExpectationId`]. The [`Diagnostic`][diagnostic::Diagnostic]s are the
459+
/// replaced by a stable [`LintExpectationId`]. The [`Diagnostic`][struct@diagnostic::Diagnostic]s are the
460460
/// submitted for storage and added to the list of fulfilled expectations.
461461
unstable_expect_diagnostics: Vec<Diagnostic>,
462462

‎compiler/rustc_resolve/src/rustdoc.rs

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use pulldown_cmark::{BrokenLink, Event, LinkType, Options, Parser, Tag};
1+
use pulldown_cmark::{BrokenLink, CowStr, Event, LinkType, Options, Parser, Tag};
22
use rustc_ast as ast;
33
use rustc_ast::util::comments::beautify_doc_string;
44
use rustc_data_structures::fx::FxHashMap;
@@ -392,16 +392,73 @@ pub(crate) fn attrs_to_preprocessed_links(attrs: &[ast::Attribute]) -> Vec<Box<s
392392
let (doc_fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true);
393393
let doc = prepare_to_doc_link_resolution(&doc_fragments).into_values().next().unwrap();
394394

395-
Parser::new_with_broken_link_callback(
395+
parse_links(&doc)
396+
}
397+
398+
/// Similiar version of `markdown_links` from rustdoc.
399+
/// This will collect destination links and display text if exists.
400+
fn parse_links<'md>(doc: &'md str) -> Vec<Box<str>> {
401+
let mut broken_link_callback = |link: BrokenLink<'md>| Some((link.reference, "".into()));
402+
let mut event_iter = Parser::new_with_broken_link_callback(
396403
&doc,
397404
main_body_opts(),
398-
Some(&mut |link: BrokenLink<'_>| Some((link.reference, "".into()))),
405+
Some(&mut broken_link_callback),
399406
)
400-
.filter_map(|event| match event {
401-
Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => {
402-
Some(preprocess_link(&dest))
407+
.into_iter();
408+
let mut links = Vec::new();
409+
410+
while let Some(event) = event_iter.next() {
411+
match event {
412+
Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => {
413+
if matches!(
414+
link_type,
415+
LinkType::Inline
416+
| LinkType::ReferenceUnknown
417+
| LinkType::Reference
418+
| LinkType::Shortcut
419+
| LinkType::ShortcutUnknown
420+
) {
421+
if let Some(display_text) = collect_link_data(&mut event_iter) {
422+
links.push(display_text);
423+
}
424+
}
425+
426+
links.push(preprocess_link(&dest));
427+
}
428+
_ => {}
429+
}
430+
}
431+
432+
links
433+
}
434+
435+
/// Collects additional data of link.
436+
fn collect_link_data<'input, 'callback>(
437+
event_iter: &mut Parser<'input, 'callback>,
438+
) -> Option<Box<str>> {
439+
let mut display_text: Option<String> = None;
440+
let mut append_text = |text: CowStr<'_>| {
441+
if let Some(display_text) = &mut display_text {
442+
display_text.push_str(&text);
443+
} else {
444+
display_text = Some(text.to_string());
445+
}
446+
};
447+
448+
while let Some(event) = event_iter.next() {
449+
match event {
450+
Event::Text(text) => {
451+
append_text(text);
452+
}
453+
Event::Code(code) => {
454+
append_text(code);
455+
}
456+
Event::End(_) => {
457+
break;
458+
}
459+
_ => {}
403460
}
404-
_ => None,
405-
})
406-
.collect()
461+
}
462+
463+
display_text.map(String::into_boxed_str)
407464
}

‎library/alloc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
#![allow(explicit_outlives_requirements)]
9090
#![warn(multiple_supertrait_upcastable)]
9191
#![cfg_attr(not(bootstrap), allow(internal_features))]
92+
#![cfg_attr(not(bootstrap), allow(rustdoc::redundant_explicit_links))]
9293
//
9394
// Library features:
9495
// tidy-alphabetical-start

‎library/alloc/src/sync.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ macro_rules! acquire {
153153
///
154154
/// ## `Deref` behavior
155155
///
156-
/// `Arc<T>` automatically dereferences to `T` (via the [`Deref`][deref] trait),
156+
/// `Arc<T>` automatically dereferences to `T` (via the [`Deref`] trait),
157157
/// so you can call `T`'s methods on a value of type `Arc<T>`. To avoid name
158158
/// clashes with `T`'s methods, the methods of `Arc<T>` itself are associated
159159
/// functions, called using [fully qualified syntax]:
@@ -187,7 +187,6 @@ macro_rules! acquire {
187187
/// [mutex]: ../../std/sync/struct.Mutex.html
188188
/// [rwlock]: ../../std/sync/struct.RwLock.html
189189
/// [atomic]: core::sync::atomic
190-
/// [deref]: core::ops::Deref
191190
/// [downgrade]: Arc::downgrade
192191
/// [upgrade]: Weak::upgrade
193192
/// [RefCell\<T>]: core::cell::RefCell
@@ -1495,7 +1494,7 @@ impl<T: ?Sized, A: Allocator> Arc<T, A> {
14951494
/// alignment as `T`. This is trivially true if `U` is `T`.
14961495
/// Note that if `U` is not `T` but has the same size and alignment, this is
14971496
/// basically like transmuting references of different types. See
1498-
/// [`mem::transmute`][transmute] for more information on what
1497+
/// [`mem::transmute`] for more information on what
14991498
/// restrictions apply in this case.
15001499
///
15011500
/// The raw pointer must point to a block of memory allocated by `alloc`
@@ -1507,7 +1506,6 @@ impl<T: ?Sized, A: Allocator> Arc<T, A> {
15071506
/// even if the returned `Arc<T>` is never accessed.
15081507
///
15091508
/// [into_raw]: Arc::into_raw
1510-
/// [transmute]: core::mem::transmute
15111509
///
15121510
/// # Examples
15131511
///

‎library/core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@
9797
#![allow(incomplete_features)]
9898
#![warn(multiple_supertrait_upcastable)]
9999
#![cfg_attr(not(bootstrap), allow(internal_features))]
100+
// Do not check link redundancy on bootstraping phase
101+
#![cfg_attr(not(bootstrap), allow(rustdoc::redundant_explicit_links))]
100102
//
101103
// Library features:
102104
// tidy-alphabetical-start

‎library/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@
223223
#![cfg_attr(not(bootstrap), allow(internal_features))]
224224
#![deny(rustc::existing_doc_keyword)]
225225
#![deny(fuzzy_provenance_casts)]
226+
#![cfg_attr(not(bootstrap), allow(rustdoc::redundant_explicit_links))]
226227
// Ensure that std can be linked against panic_abort despite compiled with `-C panic=unwind`
227228
#![deny(ffi_unwind_calls)]
228229
// std may use features in a platform-specific way

‎src/doc/rustdoc/src/lints.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,37 @@ help: if you meant to use a literal backtick, escape it
412412
413413
warning: 1 warning emitted
414414
```
415+
416+
## `redundant_explicit_links`
417+
418+
This lint is **warned by default**. It detects explicit links that are same
419+
as computed automatic links.
420+
This usually means the explicit links is removeable. For example:
421+
422+
```rust
423+
#![warn(rustdoc::redundant_explicit_links)] // note: unnecessary - warns by default.
424+
425+
/// add takes 2 [`usize`](usize) and performs addition
426+
/// on them, then returns result.
427+
pub fn add(left: usize, right: usize) -> usize {
428+
left + right
429+
}
430+
```
431+
432+
Which will give:
433+
434+
```text
435+
error: redundant explicit rustdoc link
436+
--> src/lib.rs:3:27
437+
|
438+
3 | /// add takes 2 [`usize`](usize) and performs addition
439+
| ^^^^^
440+
|
441+
= note: Explicit link does not affect the original link
442+
note: the lint level is defined here
443+
--> src/lib.rs:1:9
444+
|
445+
1 | #![deny(rustdoc::redundant_explicit_links)]
446+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
447+
= help: Remove explicit link instead
448+
```

‎src/librustdoc/html/markdown.rs

Lines changed: 83 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use crate::html::render::small_url_encode;
5050
use crate::html::toc::TocBuilder;
5151

5252
use pulldown_cmark::{
53-
html, BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag,
53+
html, BrokenLink, CodeBlockKind, CowStr, Event, LinkType, OffsetIter, Options, Parser, Tag,
5454
};
5555

5656
#[cfg(test)]
@@ -1240,6 +1240,7 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin
12401240
pub(crate) struct MarkdownLink {
12411241
pub kind: LinkType,
12421242
pub link: String,
1243+
pub display_text: Option<String>,
12431244
pub range: MarkdownLinkRange,
12441245
}
12451246

@@ -1263,8 +1264,8 @@ impl MarkdownLinkRange {
12631264
}
12641265
}
12651266

1266-
pub(crate) fn markdown_links<R>(
1267-
md: &str,
1267+
pub(crate) fn markdown_links<'md, R>(
1268+
md: &'md str,
12681269
preprocess_link: impl Fn(MarkdownLink) -> Option<R>,
12691270
) -> Vec<R> {
12701271
if md.is_empty() {
@@ -1375,32 +1376,90 @@ pub(crate) fn markdown_links<R>(
13751376
MarkdownLinkRange::Destination(range.clone())
13761377
};
13771378

1378-
Parser::new_with_broken_link_callback(
1379+
let mut broken_link_callback = |link: BrokenLink<'md>| Some((link.reference, "".into()));
1380+
let mut event_iter = Parser::new_with_broken_link_callback(
13791381
md,
13801382
main_body_opts(),
1381-
Some(&mut |link: BrokenLink<'_>| Some((link.reference, "".into()))),
1383+
Some(&mut broken_link_callback),
13821384
)
1383-
.into_offset_iter()
1384-
.filter_map(|(event, span)| match event {
1385-
Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => {
1386-
let range = match link_type {
1387-
// Link is pulled from the link itself.
1388-
LinkType::ReferenceUnknown | LinkType::ShortcutUnknown => {
1389-
span_for_offset_backward(span, b'[', b']')
1390-
}
1391-
LinkType::CollapsedUnknown => span_for_offset_forward(span, b'[', b']'),
1392-
LinkType::Inline => span_for_offset_backward(span, b'(', b')'),
1393-
// Link is pulled from elsewhere in the document.
1394-
LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => {
1395-
span_for_link(&dest, span)
1385+
.into_offset_iter();
1386+
let mut links = Vec::new();
1387+
1388+
while let Some((event, span)) = event_iter.next() {
1389+
match event {
1390+
Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => {
1391+
let range = match link_type {
1392+
// Link is pulled from the link itself.
1393+
LinkType::ReferenceUnknown | LinkType::ShortcutUnknown => {
1394+
span_for_offset_backward(span, b'[', b']')
1395+
}
1396+
LinkType::CollapsedUnknown => span_for_offset_forward(span, b'[', b']'),
1397+
LinkType::Inline => span_for_offset_backward(span, b'(', b')'),
1398+
// Link is pulled from elsewhere in the document.
1399+
LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => {
1400+
span_for_link(&dest, span)
1401+
}
1402+
LinkType::Autolink | LinkType::Email => unreachable!(),
1403+
};
1404+
1405+
let display_text = if matches!(
1406+
link_type,
1407+
LinkType::Inline
1408+
| LinkType::ReferenceUnknown
1409+
| LinkType::Reference
1410+
| LinkType::Shortcut
1411+
| LinkType::ShortcutUnknown
1412+
) {
1413+
collect_link_data(&mut event_iter)
1414+
} else {
1415+
None
1416+
};
1417+
1418+
if let Some(link) = preprocess_link(MarkdownLink {
1419+
kind: link_type,
1420+
link: dest.into_string(),
1421+
display_text,
1422+
range,
1423+
}) {
1424+
links.push(link);
13961425
}
1397-
LinkType::Autolink | LinkType::Email => unreachable!(),
1398-
};
1399-
preprocess_link(MarkdownLink { kind: link_type, range, link: dest.into_string() })
1426+
}
1427+
_ => {}
14001428
}
1401-
_ => None,
1402-
})
1403-
.collect()
1429+
}
1430+
1431+
links
1432+
}
1433+
1434+
/// Collects additional data of link.
1435+
fn collect_link_data<'input, 'callback>(
1436+
event_iter: &mut OffsetIter<'input, 'callback>,
1437+
) -> Option<String> {
1438+
let mut display_text: Option<String> = None;
1439+
let mut append_text = |text: CowStr<'_>| {
1440+
if let Some(display_text) = &mut display_text {
1441+
display_text.push_str(&text);
1442+
} else {
1443+
display_text = Some(text.to_string());
1444+
}
1445+
};
1446+
1447+
while let Some((event, _span)) = event_iter.next() {
1448+
match event {
1449+
Event::Text(text) => {
1450+
append_text(text);
1451+
}
1452+
Event::Code(code) => {
1453+
append_text(code);
1454+
}
1455+
Event::End(_) => {
1456+
break;
1457+
}
1458+
_ => {}
1459+
}
1460+
}
1461+
1462+
display_text
14041463
}
14051464

14061465
#[derive(Debug)]

‎src/librustdoc/lint.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,17 @@ declare_rustdoc_lint! {
185185
"detects unescaped backticks in doc comments"
186186
}
187187

188+
declare_rustdoc_lint! {
189+
/// This lint is **warned by default**. It detects explicit links that are same
190+
/// as computed automatic links. This usually means the explicit links is removeable.
191+
/// This is a `rustdoc` only lint, see the documentation in the [rustdoc book].
192+
///
193+
/// [rustdoc book]: ../../../rustdoc/lints.html#redundant_explicit_links
194+
REDUNDANT_EXPLICIT_LINKS,
195+
Warn,
196+
"detects redundant explicit links in doc comments"
197+
}
198+
188199
pub(crate) static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
189200
vec![
190201
BROKEN_INTRA_DOC_LINKS,
@@ -197,6 +208,7 @@ pub(crate) static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
197208
BARE_URLS,
198209
MISSING_CRATE_LEVEL_DOCS,
199210
UNESCAPED_BACKTICKS,
211+
REDUNDANT_EXPLICIT_LINKS,
200212
]
201213
});
202214

‎src/librustdoc/passes/collect_intra_doc_links.rs

Lines changed: 95 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -994,7 +994,7 @@ impl LinkCollector<'_, '_> {
994994
_ => find_nearest_parent_module(self.cx.tcx, item_id).unwrap(),
995995
};
996996
for md_link in preprocessed_markdown_links(&doc) {
997-
let link = self.resolve_link(item, item_id, module_id, &doc, &md_link);
997+
let link = self.resolve_link(&doc, item, item_id, module_id, &md_link);
998998
if let Some(link) = link {
999999
self.cx.cache.intra_doc_links.entry(item.item_id).or_default().insert(link);
10001000
}
@@ -1007,13 +1007,12 @@ impl LinkCollector<'_, '_> {
10071007
/// FIXME(jynelson): this is way too many arguments
10081008
fn resolve_link(
10091009
&mut self,
1010+
dox: &String,
10101011
item: &Item,
10111012
item_id: DefId,
10121013
module_id: DefId,
1013-
dox: &str,
1014-
link: &PreprocessedMarkdownLink,
1014+
PreprocessedMarkdownLink(pp_link, ori_link): &PreprocessedMarkdownLink,
10151015
) -> Option<ItemLink> {
1016-
let PreprocessedMarkdownLink(pp_link, ori_link) = link;
10171016
trace!("considering link '{}'", ori_link.link);
10181017

10191018
let diag_info = DiagnosticInfo {
@@ -1022,7 +1021,6 @@ impl LinkCollector<'_, '_> {
10221021
ori_link: &ori_link.link,
10231022
link_range: ori_link.range.clone(),
10241023
};
1025-
10261024
let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } =
10271025
pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?;
10281026
let disambiguator = *disambiguator;
@@ -1040,8 +1038,24 @@ impl LinkCollector<'_, '_> {
10401038
// resolutions are cached, for other links we want to report an error every
10411039
// time so they are not cached.
10421040
matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut),
1041+
false,
10431042
)?;
10441043

1044+
if ori_link.display_text.is_some() {
1045+
self.resolve_display_text(
1046+
path_str,
1047+
ResolutionInfo {
1048+
item_id,
1049+
module_id,
1050+
dis: disambiguator,
1051+
path_str: ori_link.display_text.clone()?.into_boxed_str(),
1052+
extra_fragment: extra_fragment.clone(),
1053+
},
1054+
&ori_link,
1055+
&diag_info,
1056+
);
1057+
}
1058+
10451059
// Check for a primitive which might conflict with a module
10461060
// Report the ambiguity and require that the user specify which one they meant.
10471061
// FIXME: could there ever be a primitive not in the type namespace?
@@ -1221,14 +1235,17 @@ impl LinkCollector<'_, '_> {
12211235
// If errors are cached then they are only reported on first occurrence
12221236
// which we want in some cases but not in others.
12231237
cache_errors: bool,
1238+
// If this call is intended to be recoverable, then pass true to silence.
1239+
// This is only recoverable when path is failed to resolved.
1240+
recoverable: bool,
12241241
) -> Option<(Res, Option<UrlFragment>)> {
12251242
if let Some(res) = self.visited_links.get(&key) {
12261243
if res.is_some() || cache_errors {
12271244
return res.clone();
12281245
}
12291246
}
12301247

1231-
let mut candidates = self.resolve_with_disambiguator(&key, diag.clone());
1248+
let mut candidates = self.resolve_with_disambiguator(&key, diag.clone(), recoverable);
12321249

12331250
// FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
12341251
// However I'm not sure how to check that across crates.
@@ -1279,6 +1296,9 @@ impl LinkCollector<'_, '_> {
12791296
&mut self,
12801297
key: &ResolutionInfo,
12811298
diag: DiagnosticInfo<'_>,
1299+
// If this call is intended to be recoverable, then pass true to silence.
1300+
// This is only recoverable when path is failed to resolved.
1301+
recoverable: bool,
12821302
) -> Vec<(Res, Option<DefId>)> {
12831303
let disambiguator = key.dis;
12841304
let path_str = &key.path_str;
@@ -1308,7 +1328,9 @@ impl LinkCollector<'_, '_> {
13081328
}
13091329
}
13101330
}
1311-
resolution_failure(self, diag, path_str, disambiguator, smallvec![err]);
1331+
if !recoverable {
1332+
resolution_failure(self, diag, path_str, disambiguator, smallvec![err]);
1333+
}
13121334
return vec![];
13131335
}
13141336
}
@@ -1345,13 +1367,15 @@ impl LinkCollector<'_, '_> {
13451367
.fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc });
13461368

13471369
if len == 0 {
1348-
resolution_failure(
1349-
self,
1350-
diag,
1351-
path_str,
1352-
disambiguator,
1353-
candidates.into_iter().filter_map(|res| res.err()).collect(),
1354-
);
1370+
if !recoverable {
1371+
resolution_failure(
1372+
self,
1373+
diag,
1374+
path_str,
1375+
disambiguator,
1376+
candidates.into_iter().filter_map(|res| res.err()).collect(),
1377+
);
1378+
}
13551379
return vec![];
13561380
} else if len == 1 {
13571381
candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::<Vec<_>>()
@@ -1372,6 +1396,63 @@ impl LinkCollector<'_, '_> {
13721396
}
13731397
}
13741398
}
1399+
1400+
/// Resolve display text if the provided link has separated parts of links.
1401+
///
1402+
/// For example:
1403+
/// Inline link `[display_text](dest_link)` and reference link `[display_text][reference_link]` has
1404+
/// separated parts of links.
1405+
fn resolve_display_text(
1406+
&mut self,
1407+
explicit_link: &Box<str>,
1408+
display_res_info: ResolutionInfo,
1409+
ori_link: &MarkdownLink,
1410+
diag_info: &DiagnosticInfo<'_>,
1411+
) {
1412+
// Check if explicit resolution's path is same as resolution of original link's display text path, see
1413+
// tests/rustdoc-ui/lint/redundant_explicit_links.rs for more cases.
1414+
//
1415+
// To avoid disambiguator from panicking, we check if display text path is possible to be disambiguated
1416+
// into explicit path.
1417+
if !matches!(
1418+
ori_link.kind,
1419+
LinkType::Inline | LinkType::Reference | LinkType::ReferenceUnknown
1420+
) {
1421+
return;
1422+
}
1423+
1424+
// Algorithm to check if display text could possibly be the explicit link:
1425+
//
1426+
// Consider 2 links which are display text and explicit link, pick the shorter
1427+
// one as symbol and longer one as full qualified path, and tries to match symbol
1428+
// to the full qualified path's last symbol.
1429+
//
1430+
// Otherwise, check if 2 links are same, if so, skip the resolve process.
1431+
//
1432+
// Notice that this algorithm is passive, might possibly miss actual redudant cases.
1433+
let explicit_link = &explicit_link.to_string();
1434+
let display_text = ori_link.display_text.as_ref().unwrap();
1435+
let display_len = display_text.len();
1436+
let explicit_len = explicit_link.len();
1437+
1438+
if display_len == explicit_len {
1439+
// Whether they are same or not, skip the resolve process.
1440+
return;
1441+
}
1442+
1443+
if (explicit_len >= display_len
1444+
&& &explicit_link[(explicit_len - display_len)..] == display_text)
1445+
|| (display_len >= explicit_len
1446+
&& &display_text[(display_len - explicit_len)..] == explicit_link)
1447+
{
1448+
self.resolve_with_disambiguator_cached(
1449+
display_res_info,
1450+
diag_info.clone(), // this struct should really be Copy, but Range is not :(
1451+
false,
1452+
true,
1453+
);
1454+
}
1455+
}
13751456
}
13761457

13771458
/// Get the section of a link between the backticks,

‎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: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
use std::ops::Range;
2+
3+
use pulldown_cmark::{BrokenLink, CowStr, Event, LinkType, OffsetIter, Parser, Tag};
4+
use rustc_ast::NodeId;
5+
use rustc_errors::SuggestionStyle;
6+
use rustc_hir::def::{DefKind, DocLinkResMap, Namespace, Res};
7+
use rustc_hir::HirId;
8+
use rustc_lint_defs::Applicability;
9+
use rustc_span::Symbol;
10+
11+
use crate::clean::utils::find_nearest_parent_module;
12+
use crate::clean::utils::inherits_doc_hidden;
13+
use crate::clean::Item;
14+
use crate::core::DocContext;
15+
use crate::html::markdown::main_body_opts;
16+
use crate::passes::source_span_for_markdown_range;
17+
18+
#[derive(Debug)]
19+
struct LinkData {
20+
resolvable_link: Option<String>,
21+
resolvable_link_range: Option<Range<usize>>,
22+
display_link: String,
23+
}
24+
25+
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
26+
let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else {
27+
// If non-local, no need to check anything.
28+
return;
29+
};
30+
31+
let doc = item.doc_value();
32+
if doc.is_empty() {
33+
return;
34+
}
35+
36+
if item.link_names(&cx.cache).is_empty() {
37+
// If there's no link names in this item,
38+
// then we skip resolution querying to
39+
// avoid from panicking.
40+
return;
41+
}
42+
43+
let Some(item_id) = item.def_id() else {
44+
return;
45+
};
46+
let Some(local_item_id) = item_id.as_local() else {
47+
return;
48+
};
49+
50+
let is_hidden = !cx.render_options.document_hidden
51+
&& (item.is_doc_hidden() || inherits_doc_hidden(cx.tcx, local_item_id, None));
52+
if is_hidden {
53+
return;
54+
}
55+
let is_private = !cx.render_options.document_private
56+
&& !cx.cache.effective_visibilities.is_directly_public(cx.tcx, item_id);
57+
if is_private {
58+
return;
59+
}
60+
61+
check_redundant_explicit_link(cx, item, hir_id, &doc);
62+
}
63+
64+
fn check_redundant_explicit_link<'md>(
65+
cx: &DocContext<'_>,
66+
item: &Item,
67+
hir_id: HirId,
68+
doc: &'md str,
69+
) -> Option<()> {
70+
let mut broken_line_callback = |link: BrokenLink<'md>| Some((link.reference, "".into()));
71+
let mut offset_iter = Parser::new_with_broken_link_callback(
72+
&doc,
73+
main_body_opts(),
74+
Some(&mut broken_line_callback),
75+
)
76+
.into_offset_iter();
77+
let item_id = item.def_id()?;
78+
let module_id = match cx.tcx.def_kind(item_id) {
79+
DefKind::Mod if item.inner_docs(cx.tcx) => item_id,
80+
_ => find_nearest_parent_module(cx.tcx, item_id).unwrap(),
81+
};
82+
let resolutions = cx.tcx.doc_link_resolutions(module_id);
83+
84+
while let Some((event, link_range)) = offset_iter.next() {
85+
match event {
86+
Event::Start(Tag::Link(link_type, dest, _)) => {
87+
let link_data = collect_link_data(&mut offset_iter);
88+
89+
if let Some(resolvable_link) = link_data.resolvable_link.as_ref() {
90+
if &link_data.display_link.replace("`", "") != resolvable_link {
91+
// Skips if display link does not match to actual
92+
// resolvable link, usually happens if display link
93+
// has several segments, e.g.
94+
// [this is just an `Option`](Option)
95+
continue;
96+
}
97+
}
98+
99+
let explicit_link = dest.to_string();
100+
let display_link = link_data.resolvable_link.clone()?;
101+
let explicit_len = explicit_link.len();
102+
let display_len = display_link.len();
103+
104+
if (explicit_len >= display_len
105+
&& &explicit_link[(explicit_len - display_len)..] == display_link)
106+
|| (display_len >= explicit_len
107+
&& &display_link[(display_len - explicit_len)..] == explicit_link)
108+
{
109+
match link_type {
110+
LinkType::Inline | LinkType::ReferenceUnknown => {
111+
check_inline_or_reference_unknown_redundancy(
112+
cx,
113+
item,
114+
hir_id,
115+
doc,
116+
resolutions,
117+
link_range,
118+
dest.to_string(),
119+
link_data,
120+
if link_type == LinkType::Inline {
121+
(b'(', b')')
122+
} else {
123+
(b'[', b']')
124+
},
125+
);
126+
}
127+
LinkType::Reference => {
128+
check_reference_redundancy(
129+
cx,
130+
item,
131+
hir_id,
132+
doc,
133+
resolutions,
134+
link_range,
135+
&dest,
136+
link_data,
137+
);
138+
}
139+
_ => {}
140+
}
141+
}
142+
}
143+
_ => {}
144+
}
145+
}
146+
147+
None
148+
}
149+
150+
/// FIXME(ChAoSUnItY): Too many arguments.
151+
fn check_inline_or_reference_unknown_redundancy(
152+
cx: &DocContext<'_>,
153+
item: &Item,
154+
hir_id: HirId,
155+
doc: &str,
156+
resolutions: &DocLinkResMap,
157+
link_range: Range<usize>,
158+
dest: String,
159+
link_data: LinkData,
160+
(open, close): (u8, u8),
161+
) -> Option<()> {
162+
let (resolvable_link, resolvable_link_range) =
163+
(&link_data.resolvable_link?, &link_data.resolvable_link_range?);
164+
let (dest_res, display_res) =
165+
(find_resolution(resolutions, &dest)?, find_resolution(resolutions, resolvable_link)?);
166+
167+
if dest_res == display_res {
168+
let link_span = source_span_for_markdown_range(cx.tcx, &doc, &link_range, &item.attrs)
169+
.unwrap_or(item.attr_span(cx.tcx));
170+
let explicit_span = source_span_for_markdown_range(
171+
cx.tcx,
172+
&doc,
173+
&offset_explicit_range(doc, link_range, open, close),
174+
&item.attrs,
175+
)?;
176+
let display_span =
177+
source_span_for_markdown_range(cx.tcx, &doc, &resolvable_link_range, &item.attrs)?;
178+
179+
cx.tcx.struct_span_lint_hir(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, "redundant explicit link target", |lint| {
180+
lint.span_label(explicit_span, "explicit target is redundant")
181+
.span_label(display_span, "because label contains path that resolves to same destination")
182+
.note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links")
183+
.span_suggestion_with_style(link_span, "remove explicit link target", format!("[{}]", link_data.display_link), Applicability::MaybeIncorrect, SuggestionStyle::ShowAlways);
184+
185+
lint
186+
});
187+
}
188+
189+
None
190+
}
191+
192+
/// FIXME(ChAoSUnItY): Too many arguments.
193+
fn check_reference_redundancy(
194+
cx: &DocContext<'_>,
195+
item: &Item,
196+
hir_id: HirId,
197+
doc: &str,
198+
resolutions: &DocLinkResMap,
199+
link_range: Range<usize>,
200+
dest: &CowStr<'_>,
201+
link_data: LinkData,
202+
) -> Option<()> {
203+
let (resolvable_link, resolvable_link_range) =
204+
(&link_data.resolvable_link?, &link_data.resolvable_link_range?);
205+
let (dest_res, display_res) =
206+
(find_resolution(resolutions, &dest)?, find_resolution(resolutions, resolvable_link)?);
207+
208+
if dest_res == display_res {
209+
let link_span = source_span_for_markdown_range(cx.tcx, &doc, &link_range, &item.attrs)
210+
.unwrap_or(item.attr_span(cx.tcx));
211+
let explicit_span = source_span_for_markdown_range(
212+
cx.tcx,
213+
&doc,
214+
&offset_explicit_range(doc, link_range.clone(), b'[', b']'),
215+
&item.attrs,
216+
)?;
217+
let display_span =
218+
source_span_for_markdown_range(cx.tcx, &doc, &resolvable_link_range, &item.attrs)?;
219+
let def_span = source_span_for_markdown_range(
220+
cx.tcx,
221+
&doc,
222+
&offset_reference_def_range(doc, dest, link_range),
223+
&item.attrs,
224+
)?;
225+
226+
cx.tcx.struct_span_lint_hir(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, "redundant explicit link target", |lint| {
227+
lint.span_label(explicit_span, "explicit target is redundant")
228+
.span_label(display_span, "because label contains path that resolves to same destination")
229+
.span_note(def_span, "referenced explicit link target defined here")
230+
.note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links")
231+
.span_suggestion_with_style(link_span, "remove explicit link target", format!("[{}]", link_data.display_link), Applicability::MaybeIncorrect, SuggestionStyle::ShowAlways);
232+
233+
lint
234+
});
235+
}
236+
237+
None
238+
}
239+
240+
fn find_resolution(resolutions: &DocLinkResMap, path: &str) -> Option<Res<NodeId>> {
241+
[Namespace::TypeNS, Namespace::ValueNS, Namespace::MacroNS]
242+
.into_iter()
243+
.find_map(|ns| resolutions.get(&(Symbol::intern(path), ns)).copied().flatten())
244+
}
245+
246+
/// Collects all neccessary data of link.
247+
fn collect_link_data(offset_iter: &mut OffsetIter<'_, '_>) -> LinkData {
248+
let mut resolvable_link = None;
249+
let mut resolvable_link_range = None;
250+
let mut display_link = String::new();
251+
252+
while let Some((event, range)) = offset_iter.next() {
253+
match event {
254+
Event::Text(code) => {
255+
let code = code.to_string();
256+
display_link.push_str(&code);
257+
resolvable_link = Some(code);
258+
resolvable_link_range = Some(range);
259+
}
260+
Event::Code(code) => {
261+
let code = code.to_string();
262+
display_link.push('`');
263+
display_link.push_str(&code);
264+
display_link.push('`');
265+
resolvable_link = Some(code);
266+
resolvable_link_range = Some(range);
267+
}
268+
Event::End(_) => {
269+
break;
270+
}
271+
_ => {}
272+
}
273+
}
274+
275+
LinkData { resolvable_link, resolvable_link_range, display_link }
276+
}
277+
278+
fn offset_explicit_range(md: &str, link_range: Range<usize>, open: u8, close: u8) -> Range<usize> {
279+
let mut open_brace = !0;
280+
let mut close_brace = !0;
281+
for (i, b) in md.as_bytes()[link_range.clone()].iter().copied().enumerate().rev() {
282+
let i = i + link_range.start;
283+
if b == close {
284+
close_brace = i;
285+
break;
286+
}
287+
}
288+
289+
if close_brace < link_range.start || close_brace >= link_range.end {
290+
return link_range;
291+
}
292+
293+
let mut nesting = 1;
294+
295+
for (i, b) in md.as_bytes()[link_range.start..close_brace].iter().copied().enumerate().rev() {
296+
let i = i + link_range.start;
297+
if b == close {
298+
nesting += 1;
299+
}
300+
if b == open {
301+
nesting -= 1;
302+
}
303+
if nesting == 0 {
304+
open_brace = i;
305+
break;
306+
}
307+
}
308+
309+
assert!(open_brace != close_brace);
310+
311+
if open_brace < link_range.start || open_brace >= link_range.end {
312+
return link_range;
313+
}
314+
// do not actually include braces in the span
315+
(open_brace + 1)..close_brace
316+
}
317+
318+
fn offset_reference_def_range(
319+
md: &str,
320+
dest: &CowStr<'_>,
321+
link_range: Range<usize>,
322+
) -> Range<usize> {
323+
// For diagnostics, we want to underline the link's definition but `span` will point at
324+
// where the link is used. This is a problem for reference-style links, where the definition
325+
// is separate from the usage.
326+
327+
match dest {
328+
// `Borrowed` variant means the string (the link's destination) may come directly from
329+
// the markdown text and we can locate the original link destination.
330+
// NOTE: LinkReplacer also provides `Borrowed` but possibly from other sources,
331+
// so `locate()` can fall back to use `span`.
332+
CowStr::Borrowed(s) => {
333+
// FIXME: remove this function once pulldown_cmark can provide spans for link definitions.
334+
unsafe {
335+
let s_start = dest.as_ptr();
336+
let s_end = s_start.add(s.len());
337+
let md_start = md.as_ptr();
338+
let md_end = md_start.add(md.len());
339+
if md_start <= s_start && s_end <= md_end {
340+
let start = s_start.offset_from(md_start) as usize;
341+
let end = s_end.offset_from(md_start) as usize;
342+
start..end
343+
} else {
344+
link_range
345+
}
346+
}
347+
}
348+
349+
// For anything else, we can only use the provided range.
350+
CowStr::Boxed(_) | CowStr::Inlined(_) => link_range,
351+
}
352+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Regression test for <https://github.com/rust-lang/rust/pull/113167>
2+
3+
// check-pass
4+
#![deny(rustdoc::redundant_explicit_links)]
5+
6+
mod m {
7+
pub enum ValueEnum {}
8+
}
9+
mod m2 {
10+
/// [`ValueEnum`]
11+
pub use crate::m::ValueEnum;
12+
}
13+
pub use m2::ValueEnum;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// check-pass
2+
3+
#![deny(rustdoc::redundant_explicit_links)]
4+
5+
/// [Vec][std::vec::Vec#examples] should not warn, because it's not actually redundant!
6+
/// [This is just an `Option`][std::option::Option] has different display content to actual link!
7+
pub fn func() {}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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() {}
55+
56+
/// [dummy_target]
57+
//~^ ERROR redundant explicit link target
58+
/// [`dummy_target`]
59+
//~^ ERROR redundant explicit link target
60+
///
61+
/// [Vec]
62+
//~^ ERROR redundant explicit link target
63+
/// [`Vec`]
64+
//~^ ERROR redundant explicit link target
65+
/// [Vec]
66+
//~^ ERROR redundant explicit link target
67+
/// [`Vec`]
68+
//~^ ERROR redundant explicit link target
69+
/// [std::vec::Vec]
70+
//~^ ERROR redundant explicit link target
71+
/// [`std::vec::Vec`]
72+
//~^ ERROR redundant explicit link target
73+
/// [std::vec::Vec]
74+
//~^ ERROR redundant explicit link target
75+
/// [`std::vec::Vec`]
76+
//~^ ERROR redundant explicit link target
77+
///
78+
/// [usize]
79+
//~^ ERROR redundant explicit link target
80+
/// [`usize`]
81+
//~^ ERROR redundant explicit link target
82+
/// [usize]
83+
//~^ ERROR redundant explicit link target
84+
/// [`usize`]
85+
//~^ ERROR redundant explicit link target
86+
/// [std::primitive::usize]
87+
//~^ ERROR redundant explicit link target
88+
/// [`std::primitive::usize`]
89+
//~^ ERROR redundant explicit link target
90+
/// [std::primitive::usize]
91+
//~^ ERROR redundant explicit link target
92+
/// [`std::primitive::usize`]
93+
//~^ ERROR redundant explicit link target
94+
///
95+
/// [dummy_target] TEXT
96+
//~^ ERROR redundant explicit link target
97+
/// [`dummy_target`] TEXT
98+
//~^ ERROR redundant explicit link target
99+
pub fn should_warn_reference_unknown() {}
100+
101+
/// [`Vec<T>`][Vec]
102+
/// [`Vec<T>`][std::vec::Vec]
103+
pub fn should_not_warn_reference_unknown() {}
104+
105+
/// [dummy_target]
106+
//~^ ERROR redundant explicit link target
107+
/// [`dummy_target`]
108+
//~^ ERROR redundant explicit link target
109+
///
110+
/// [Vec]
111+
//~^ ERROR redundant explicit link target
112+
/// [`Vec`]
113+
//~^ ERROR redundant explicit link target
114+
/// [Vec]
115+
//~^ ERROR redundant explicit link target
116+
/// [`Vec`]
117+
//~^ ERROR redundant explicit link target
118+
/// [std::vec::Vec]
119+
//~^ ERROR redundant explicit link target
120+
/// [`std::vec::Vec`]
121+
//~^ ERROR redundant explicit link target
122+
/// [std::vec::Vec]
123+
//~^ ERROR redundant explicit link target
124+
/// [`std::vec::Vec`]
125+
//~^ ERROR redundant explicit link target
126+
///
127+
/// [usize]
128+
//~^ ERROR redundant explicit link target
129+
/// [`usize`]
130+
//~^ ERROR redundant explicit link target
131+
/// [usize]
132+
//~^ ERROR redundant explicit link target
133+
/// [`usize`]
134+
//~^ ERROR redundant explicit link target
135+
/// [std::primitive::usize]
136+
//~^ ERROR redundant explicit link target
137+
/// [`std::primitive::usize`]
138+
//~^ ERROR redundant explicit link target
139+
/// [std::primitive::usize]
140+
//~^ ERROR redundant explicit link target
141+
/// [`std::primitive::usize`]
142+
//~^ ERROR redundant explicit link target
143+
///
144+
/// [dummy_target] TEXT
145+
//~^ ERROR redundant explicit link target
146+
/// [`dummy_target`] TEXT
147+
//~^ ERROR redundant explicit link target
148+
///
149+
/// [dummy_target]: dummy_target
150+
/// [Vec]: Vec
151+
/// [std::vec::Vec]: Vec
152+
/// [usize]: usize
153+
/// [std::primitive::usize]: usize
154+
pub fn should_warn_reference() {}
155+
156+
/// [`Vec<T>`]: Vec
157+
/// [`Vec<T>`]: std::vec::Vec
158+
pub fn should_not_warn_reference() {}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// run-rustfix
2+
3+
#![deny(rustdoc::redundant_explicit_links)]
4+
5+
pub fn dummy_target() {}
6+
7+
/// [dummy_target](dummy_target)
8+
//~^ ERROR redundant explicit link target
9+
/// [`dummy_target`](dummy_target)
10+
//~^ ERROR redundant explicit link target
11+
///
12+
/// [Vec](Vec)
13+
//~^ ERROR redundant explicit link target
14+
/// [`Vec`](Vec)
15+
//~^ ERROR redundant explicit link target
16+
/// [Vec](std::vec::Vec)
17+
//~^ ERROR redundant explicit link target
18+
/// [`Vec`](std::vec::Vec)
19+
//~^ ERROR redundant explicit link target
20+
/// [std::vec::Vec](Vec)
21+
//~^ ERROR redundant explicit link target
22+
/// [`std::vec::Vec`](Vec)
23+
//~^ ERROR redundant explicit link target
24+
/// [std::vec::Vec](std::vec::Vec)
25+
//~^ ERROR redundant explicit link target
26+
/// [`std::vec::Vec`](std::vec::Vec)
27+
//~^ ERROR redundant explicit link target
28+
///
29+
/// [usize](usize)
30+
//~^ ERROR redundant explicit link target
31+
/// [`usize`](usize)
32+
//~^ ERROR redundant explicit link target
33+
/// [usize](std::primitive::usize)
34+
//~^ ERROR redundant explicit link target
35+
/// [`usize`](std::primitive::usize)
36+
//~^ ERROR redundant explicit link target
37+
/// [std::primitive::usize](usize)
38+
//~^ ERROR redundant explicit link target
39+
/// [`std::primitive::usize`](usize)
40+
//~^ ERROR redundant explicit link target
41+
/// [std::primitive::usize](std::primitive::usize)
42+
//~^ ERROR redundant explicit link target
43+
/// [`std::primitive::usize`](std::primitive::usize)
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() {}
51+
52+
/// [`Vec<T>`](Vec)
53+
/// [`Vec<T>`](std::vec::Vec)
54+
pub fn should_not_warn_inline() {}
55+
56+
/// [dummy_target][dummy_target]
57+
//~^ ERROR redundant explicit link target
58+
/// [`dummy_target`][dummy_target]
59+
//~^ ERROR redundant explicit link target
60+
///
61+
/// [Vec][Vec]
62+
//~^ ERROR redundant explicit link target
63+
/// [`Vec`][Vec]
64+
//~^ ERROR redundant explicit link target
65+
/// [Vec][std::vec::Vec]
66+
//~^ ERROR redundant explicit link target
67+
/// [`Vec`][std::vec::Vec]
68+
//~^ ERROR redundant explicit link target
69+
/// [std::vec::Vec][Vec]
70+
//~^ ERROR redundant explicit link target
71+
/// [`std::vec::Vec`][Vec]
72+
//~^ ERROR redundant explicit link target
73+
/// [std::vec::Vec][std::vec::Vec]
74+
//~^ ERROR redundant explicit link target
75+
/// [`std::vec::Vec`][std::vec::Vec]
76+
//~^ ERROR redundant explicit link target
77+
///
78+
/// [usize][usize]
79+
//~^ ERROR redundant explicit link target
80+
/// [`usize`][usize]
81+
//~^ ERROR redundant explicit link target
82+
/// [usize][std::primitive::usize]
83+
//~^ ERROR redundant explicit link target
84+
/// [`usize`][std::primitive::usize]
85+
//~^ ERROR redundant explicit link target
86+
/// [std::primitive::usize][usize]
87+
//~^ ERROR redundant explicit link target
88+
/// [`std::primitive::usize`][usize]
89+
//~^ ERROR redundant explicit link target
90+
/// [std::primitive::usize][std::primitive::usize]
91+
//~^ ERROR redundant explicit link target
92+
/// [`std::primitive::usize`][std::primitive::usize]
93+
//~^ ERROR redundant explicit link target
94+
///
95+
/// [dummy_target][dummy_target] TEXT
96+
//~^ ERROR redundant explicit link target
97+
/// [`dummy_target`][dummy_target] TEXT
98+
//~^ ERROR redundant explicit link target
99+
pub fn should_warn_reference_unknown() {}
100+
101+
/// [`Vec<T>`][Vec]
102+
/// [`Vec<T>`][std::vec::Vec]
103+
pub fn should_not_warn_reference_unknown() {}
104+
105+
/// [dummy_target][dummy_target]
106+
//~^ ERROR redundant explicit link target
107+
/// [`dummy_target`][dummy_target]
108+
//~^ ERROR redundant explicit link target
109+
///
110+
/// [Vec][Vec]
111+
//~^ ERROR redundant explicit link target
112+
/// [`Vec`][Vec]
113+
//~^ ERROR redundant explicit link target
114+
/// [Vec][std::vec::Vec]
115+
//~^ ERROR redundant explicit link target
116+
/// [`Vec`][std::vec::Vec]
117+
//~^ ERROR redundant explicit link target
118+
/// [std::vec::Vec][Vec]
119+
//~^ ERROR redundant explicit link target
120+
/// [`std::vec::Vec`][Vec]
121+
//~^ ERROR redundant explicit link target
122+
/// [std::vec::Vec][std::vec::Vec]
123+
//~^ ERROR redundant explicit link target
124+
/// [`std::vec::Vec`][std::vec::Vec]
125+
//~^ ERROR redundant explicit link target
126+
///
127+
/// [usize][usize]
128+
//~^ ERROR redundant explicit link target
129+
/// [`usize`][usize]
130+
//~^ ERROR redundant explicit link target
131+
/// [usize][std::primitive::usize]
132+
//~^ ERROR redundant explicit link target
133+
/// [`usize`][std::primitive::usize]
134+
//~^ ERROR redundant explicit link target
135+
/// [std::primitive::usize][usize]
136+
//~^ ERROR redundant explicit link target
137+
/// [`std::primitive::usize`][usize]
138+
//~^ ERROR redundant explicit link target
139+
/// [std::primitive::usize][std::primitive::usize]
140+
//~^ ERROR redundant explicit link target
141+
/// [`std::primitive::usize`][std::primitive::usize]
142+
//~^ ERROR redundant explicit link target
143+
///
144+
/// [dummy_target][dummy_target] TEXT
145+
//~^ ERROR redundant explicit link target
146+
/// [`dummy_target`][dummy_target] TEXT
147+
//~^ ERROR redundant explicit link target
148+
///
149+
/// [dummy_target]: dummy_target
150+
/// [Vec]: Vec
151+
/// [std::vec::Vec]: Vec
152+
/// [usize]: usize
153+
/// [std::primitive::usize]: usize
154+
pub fn should_warn_reference() {}
155+
156+
/// [`Vec<T>`]: Vec
157+
/// [`Vec<T>`]: std::vec::Vec
158+
pub fn should_not_warn_reference() {}

‎tests/rustdoc-ui/lints/redundant_explicit_links.stderr

Lines changed: 1007 additions & 0 deletions
Large diffs are not rendered by default.

‎tests/rustdoc-ui/unescaped_backticks.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![deny(rustdoc::unescaped_backticks)]
22
#![allow(rustdoc::broken_intra_doc_links)]
33
#![allow(rustdoc::invalid_html_tags)]
4+
#![allow(rustdoc::redundant_explicit_links)]
45

56
///
67
pub fn empty() {}

‎tests/rustdoc-ui/unescaped_backticks.stderr

Lines changed: 64 additions & 64 deletions
Large diffs are not rendered by default.

‎tests/rustdoc/description.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![crate_name = "foo"]
2+
#![allow(rustdoc::redundant_explicit_links)]
23
//! # Description test crate
34
//!
45
//! This is the contents of the test crate docstring.

‎tests/rustdoc/intra-doc/basic.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(rustdoc::redundant_explicit_links)]
2+
13
// @has basic/index.html
24
// @has - '//a/@href' 'struct.ThisType.html'
35
// @has - '//a/@title' 'struct basic::ThisType'

‎tests/rustdoc/intra-doc/generic-params.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// ignore-tidy-linelength
22

33
#![crate_name = "foo"]
4+
#![allow(rustdoc::redundant_explicit_links)]
45

56
//! Here's a link to [`Vec<T>`] and one to [`Box<Vec<Option<T>>>`].
67
//! Here's a link to [`Iterator<Box<T>>::Item`].

‎tests/rustdoc/intra-doc/issue-108459.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![deny(rustdoc::broken_intra_doc_links)]
2+
#![allow(rustdoc::redundant_explicit_links)]
23

34
pub struct S;
45
pub mod char {}

0 commit comments

Comments
 (0)
Please sign in to comment.