Skip to content

Commit 9c699a4

Browse files
committed
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

24 files changed

+2066
-118
lines changed

Diff for: compiler/rustc_borrowck/src/consumers.rs

+1-1
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
///

Diff for: compiler/rustc_errors/src/lib.rs

+2-2
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

Diff for: compiler/rustc_resolve/src/rustdoc.rs

+66-9
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
}

Diff for: library/alloc/src/lib.rs

+1
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

Diff for: library/alloc/src/sync.rs

+2-4
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
///

Diff for: library/core/src/lib.rs

+2
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

Diff for: library/std/src/lib.rs

+1
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

Diff for: src/doc/rustdoc/src/lints.md

+34
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+
```

Diff for: src/librustdoc/html/markdown.rs

+83-24
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)]

Diff for: src/librustdoc/lint.rs

+12
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

0 commit comments

Comments
 (0)