Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions src/librustdoc/passes/lint/bare_urls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
//! Suggests wrapping the link with angle brackets: `Go to <https://example.com/>.` to linkify it.

use core::ops::Range;
use std::mem;
use std::sync::LazyLock;

use regex::Regex;
use rustc_errors::{Applicability, DiagDecorator};
use rustc_hir::HirId;
use rustc_resolve::rustdoc::pulldown_cmark::{Event, Parser, Tag};
use rustc_resolve::rustdoc::pulldown_cmark::{
DefaultBrokenLinkCallback, Event, Tag, TextMergeWithOffset,
};
use rustc_resolve::rustdoc::source_span_for_markdown_range;
use tracing::trace;

Expand Down Expand Up @@ -55,21 +56,20 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &
);
};

let mut p = Parser::new_ext(dox, main_body_opts()).into_offset_iter();
// pulldown-cmark can split a URL into multiple `Text` events while processing
// characters such as `_` according to CommonMark's emphasis rules.
// `TextMergeWithOffset` merges these events so we can check the complete URL.
let mut p = TextMergeWithOffset::<DefaultBrokenLinkCallback>::new_ext(dox, main_body_opts());

while let Some((event, range)) = p.next() {
match event {
Event::Text(s) => find_raw_urls(cx, dox, &s, range, &report_diag),
// We don't want to check the text inside code blocks or links.
Event::Start(tag @ (Tag::CodeBlock(_) | Tag::Link { .. })) => {
let end = tag.to_end();
for (event, _) in p.by_ref() {
match event {
Event::End(end)
if mem::discriminant(&end) == mem::discriminant(&tag.to_end()) =>
{
break;
}
_ => {}
if matches!(event, Event::End(tag) if tag == end) {
break;
}
}
}
Expand All @@ -83,7 +83,12 @@ static URL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
r"https?://", // url scheme
r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", // one or more subdomains
r"[a-zA-Z]{2,63}", // root domain
r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)", // optional query or url fragments
// Match URL characters and balanced parenthesized segments, without
// consuming a trailing `)` that belongs to the surrounding prose.
r"\b(?:",
r"[-a-zA-Z0-9@:%_\+.~#?&/=]",
r"|\([-a-zA-Z0-9@:%_\+.~#?&/=]*\)",
r")*",
))
.expect("failed to build regex")
});
Expand Down
4 changes: 4 additions & 0 deletions tests/rustdoc-ui/lints/bare-urls.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,7 @@ pub fn trailing_period() {}
/// <https://bloob.blob>]
//~^ ERROR this URL is not a hyperlink
pub fn lint_with_brackets() {}

/// See <https://en.wikipedia.org/wiki/Rust_(programming_language)>
//~^ ERROR this URL is not a hyperlink
pub fn hippo() {}
4 changes: 4 additions & 0 deletions tests/rustdoc-ui/lints/bare-urls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,7 @@ pub fn trailing_period() {}
/// https://bloob.blob]
//~^ ERROR this URL is not a hyperlink
pub fn lint_with_brackets() {}

/// See https://en.wikipedia.org/wiki/Rust_(programming_language)
//~^ ERROR this URL is not a hyperlink
pub fn hippo() {}
14 changes: 13 additions & 1 deletion tests/rustdoc-ui/lints/bare-urls.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -364,5 +364,17 @@ help: use an automatic link instead
LL | /// <https://bloob.blob>]
| + +

error: aborting due to 30 previous errors
error: this URL is not a hyperlink
--> $DIR/bare-urls.rs:96:9
|
LL | /// See https://en.wikipedia.org/wiki/Rust_(programming_language)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: bare URLs are not automatically turned into clickable links
help: use an automatic link instead
|
LL | /// See <https://en.wikipedia.org/wiki/Rust_(programming_language)>
| + +

error: aborting due to 31 previous errors

Loading