diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 09e7ed293d4..9bb20022cfd 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -1237,7 +1237,27 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin pub(crate) struct MarkdownLink { pub kind: LinkType, pub link: String, - pub range: Range, + pub range: MarkdownLinkRange, +} + +#[derive(Clone, Debug)] +pub(crate) enum MarkdownLinkRange { + /// Normally, markdown link warnings point only at the destination. + Destination(Range), + /// In some cases, it's not possible to point at the destination. + /// Usually, this happens because backslashes `\\` are used. + /// When that happens, point at the whole link, and don't provide structured suggestions. + WholeLink(Range), +} + +impl MarkdownLinkRange { + /// Extracts the inner range. + pub fn inner_range(&self) -> &Range { + match self { + MarkdownLinkRange::Destination(range) => range, + MarkdownLinkRange::WholeLink(range) => range, + } + } } pub(crate) fn markdown_links( @@ -1257,9 +1277,9 @@ pub(crate) fn markdown_links( if md_start <= s_start && s_end <= md_end { let start = s_start.offset_from(md_start) as usize; let end = s_end.offset_from(md_start) as usize; - start..end + MarkdownLinkRange::Destination(start..end) } else { - fallback + MarkdownLinkRange::WholeLink(fallback) } }; @@ -1267,6 +1287,7 @@ pub(crate) fn markdown_links( // For diagnostics, we want to underline the link's definition but `span` will point at // where the link is used. This is a problem for reference-style links, where the definition // is separate from the usage. + match link { // `Borrowed` variant means the string (the link's destination) may come directly from // the markdown text and we can locate the original link destination. @@ -1275,10 +1296,82 @@ pub(crate) fn markdown_links( CowStr::Borrowed(s) => locate(s, span), // For anything else, we can only use the provided range. - CowStr::Boxed(_) | CowStr::Inlined(_) => span, + CowStr::Boxed(_) | CowStr::Inlined(_) => MarkdownLinkRange::WholeLink(span), } }; + let span_for_offset_backward = |span: Range, open: u8, close: u8| { + let mut open_brace = !0; + let mut close_brace = !0; + for (i, b) in md.as_bytes()[span.clone()].iter().copied().enumerate().rev() { + let i = i + span.start; + if b == close { + close_brace = i; + break; + } + } + if close_brace < span.start || close_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + let mut nesting = 1; + for (i, b) in md.as_bytes()[span.start..close_brace].iter().copied().enumerate().rev() { + let i = i + span.start; + if b == close { + nesting += 1; + } + if b == open { + nesting -= 1; + } + if nesting == 0 { + open_brace = i; + break; + } + } + assert!(open_brace != close_brace); + if open_brace < span.start || open_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + // do not actually include braces in the span + let range = (open_brace + 1)..close_brace; + MarkdownLinkRange::Destination(range.clone()) + }; + + let span_for_offset_forward = |span: Range, open: u8, close: u8| { + let mut open_brace = !0; + let mut close_brace = !0; + for (i, b) in md.as_bytes()[span.clone()].iter().copied().enumerate() { + let i = i + span.start; + if b == open { + open_brace = i; + break; + } + } + if open_brace < span.start || open_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + let mut nesting = 0; + for (i, b) in md.as_bytes()[open_brace..span.end].iter().copied().enumerate() { + let i = i + open_brace; + if b == close { + nesting -= 1; + } + if b == open { + nesting += 1; + } + if nesting == 0 { + close_brace = i; + break; + } + } + assert!(open_brace != close_brace); + if open_brace < span.start || open_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + // do not actually include braces in the span + let range = (open_brace + 1)..close_brace; + MarkdownLinkRange::Destination(range.clone()) + }; + Parser::new_with_broken_link_callback( md, main_body_opts(), @@ -1287,11 +1380,20 @@ pub(crate) fn markdown_links( .into_offset_iter() .filter_map(|(event, span)| match event { Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => { - preprocess_link(MarkdownLink { - kind: link_type, - range: span_for_link(&dest, span), - link: dest.into_string(), - }) + let range = match link_type { + // Link is pulled from the link itself. + LinkType::ReferenceUnknown | LinkType::ShortcutUnknown => { + span_for_offset_backward(span, b'[', b']') + } + LinkType::CollapsedUnknown => span_for_offset_forward(span, b'[', b']'), + LinkType::Inline => span_for_offset_backward(span, b'(', b')'), + // Link is pulled from elsewhere in the document. + LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => { + span_for_link(&dest, span) + } + LinkType::Autolink | LinkType::Email => unreachable!(), + }; + preprocess_link(MarkdownLink { kind: link_type, range, link: dest.into_string() }) } _ => None, }) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 9e6894a77df..417bdd58ad4 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -31,7 +31,7 @@ use std::ops::Range; use crate::clean::{self, utils::find_nearest_parent_module}; use crate::clean::{Crate, Item, ItemLink, PrimitiveType}; use crate::core::DocContext; -use crate::html::markdown::{markdown_links, MarkdownLink}; +use crate::html::markdown::{markdown_links, MarkdownLink, MarkdownLinkRange}; use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS}; use crate::passes::Pass; use crate::visit::DocVisitor; @@ -248,7 +248,7 @@ struct DiagnosticInfo<'a> { item: &'a Item, dox: &'a str, ori_link: &'a str, - link_range: Range, + link_range: MarkdownLinkRange, } struct LinkCollector<'a, 'tcx> { @@ -833,7 +833,7 @@ impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> { enum PreprocessingError { /// User error: `[std#x#y]` is not valid MultipleAnchors, - Disambiguator(Range, String), + Disambiguator(MarkdownLinkRange, String), MalformedGenerics(MalformedGenerics, String), } @@ -873,6 +873,7 @@ pub(crate) struct PreprocessedMarkdownLink( /// `link_buffer` is needed for lifetime reasons; it will always be overwritten and the contents ignored. fn preprocess_link( ori_link: &MarkdownLink, + dox: &str, ) -> Option> { // [] is mostly likely not supposed to be a link if ori_link.link.is_empty() { @@ -906,9 +907,15 @@ fn preprocess_link( Err((err_msg, relative_range)) => { // Only report error if we would not have ignored this link. See issue #83859. if !should_ignore_link_with_disambiguators(link) { - let no_backticks_range = range_between_backticks(ori_link); - let disambiguator_range = (no_backticks_range.start + relative_range.start) - ..(no_backticks_range.start + relative_range.end); + let disambiguator_range = match range_between_backticks(&ori_link.range, dox) { + MarkdownLinkRange::Destination(no_backticks_range) => { + MarkdownLinkRange::Destination( + (no_backticks_range.start + relative_range.start) + ..(no_backticks_range.start + relative_range.end), + ) + } + mdlr @ MarkdownLinkRange::WholeLink(_) => mdlr, + }; return Some(Err(PreprocessingError::Disambiguator(disambiguator_range, err_msg))); } else { return None; @@ -947,7 +954,7 @@ fn preprocess_link( fn preprocessed_markdown_links(s: &str) -> Vec { markdown_links(s, |link| { - preprocess_link(&link).map(|pp_link| PreprocessedMarkdownLink(pp_link, link)) + preprocess_link(&link, s).map(|pp_link| PreprocessedMarkdownLink(pp_link, link)) }) } @@ -1060,22 +1067,12 @@ impl LinkCollector<'_, '_> { // valid omission. See https://github.com/rust-lang/rust/pull/80660#discussion_r551585677 // for discussion on the matter. let kind = self.cx.tcx.def_kind(id); - self.verify_disambiguator( - path_str, - ori_link, - kind, - id, - disambiguator, - item, - &diag_info, - )?; + self.verify_disambiguator(path_str, kind, id, disambiguator, item, &diag_info)?; } else { match disambiguator { Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {} Some(other) => { - self.report_disambiguator_mismatch( - path_str, ori_link, other, res, &diag_info, - ); + self.report_disambiguator_mismatch(path_str, other, res, &diag_info); return None; } } @@ -1096,7 +1093,6 @@ impl LinkCollector<'_, '_> { }; self.verify_disambiguator( path_str, - ori_link, kind_for_dis, id_for_dis, disambiguator, @@ -1118,7 +1114,6 @@ impl LinkCollector<'_, '_> { fn verify_disambiguator( &self, path_str: &str, - ori_link: &MarkdownLink, kind: DefKind, id: DefId, disambiguator: Option, @@ -1142,7 +1137,7 @@ impl LinkCollector<'_, '_> { => {} (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {} (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => { - self.report_disambiguator_mismatch(path_str,ori_link,specified, Res::Def(kind, id),diag_info); + self.report_disambiguator_mismatch(path_str, specified, Res::Def(kind, id), diag_info); return None; } } @@ -1164,14 +1159,13 @@ impl LinkCollector<'_, '_> { fn report_disambiguator_mismatch( &self, path_str: &str, - ori_link: &MarkdownLink, specified: Disambiguator, resolved: Res, diag_info: &DiagnosticInfo<'_>, ) { // The resolved item did not match the disambiguator; give a better error than 'not found' let msg = format!("incompatible link kind for `{}`", path_str); - let callback = |diag: &mut Diagnostic, sp: Option| { + let callback = |diag: &mut Diagnostic, sp: Option, link_range| { let note = format!( "this link resolved to {} {}, which is not {} {}", resolved.article(), @@ -1184,14 +1178,24 @@ impl LinkCollector<'_, '_> { } else { diag.note(note); } - suggest_disambiguator(resolved, diag, path_str, &ori_link.link, sp); + suggest_disambiguator(resolved, diag, path_str, link_range, sp, diag_info); }; report_diagnostic(self.cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, diag_info, callback); } - fn report_rawptr_assoc_feature_gate(&self, dox: &str, ori_link: &Range, item: &Item) { - let span = super::source_span_for_markdown_range(self.cx.tcx, dox, ori_link, &item.attrs) - .unwrap_or_else(|| item.attr_span(self.cx.tcx)); + fn report_rawptr_assoc_feature_gate( + &self, + dox: &str, + ori_link: &MarkdownLinkRange, + item: &Item, + ) { + let span = super::source_span_for_markdown_range( + self.cx.tcx, + dox, + ori_link.inner_range(), + &item.attrs, + ) + .unwrap_or_else(|| item.attr_span(self.cx.tcx)); rustc_session::parse::feature_err( &self.cx.tcx.sess.parse_sess, sym::intra_doc_pointers, @@ -1371,16 +1375,23 @@ impl LinkCollector<'_, '_> { /// [`Foo`] /// ^^^ /// ``` -fn range_between_backticks(ori_link: &MarkdownLink) -> Range { - let after_first_backtick_group = ori_link.link.bytes().position(|b| b != b'`').unwrap_or(0); - let before_second_backtick_group = ori_link - .link +/// +/// This function does nothing if `ori_link.range` is a `MarkdownLinkRange::WholeLink`. +fn range_between_backticks(ori_link_range: &MarkdownLinkRange, dox: &str) -> MarkdownLinkRange { + let range = match ori_link_range { + mdlr @ MarkdownLinkRange::WholeLink(_) => return mdlr.clone(), + MarkdownLinkRange::Destination(inner) => inner.clone(), + }; + let ori_link_text = &dox[range.clone()]; + let after_first_backtick_group = ori_link_text.bytes().position(|b| b != b'`').unwrap_or(0); + let before_second_backtick_group = ori_link_text .bytes() .skip(after_first_backtick_group) .position(|b| b == b'`') - .unwrap_or(ori_link.link.len()); - (ori_link.range.start + after_first_backtick_group) - ..(ori_link.range.start + before_second_backtick_group) + .unwrap_or(ori_link_text.len()); + MarkdownLinkRange::Destination( + (range.start + after_first_backtick_group)..(range.start + before_second_backtick_group), + ) } /// Returns true if we should ignore `link` due to it being unlikely @@ -1530,14 +1541,23 @@ impl Suggestion { sp: rustc_span::Span, ) -> Vec<(rustc_span::Span, String)> { let inner_sp = match ori_link.find('(') { + Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => { + sp.with_hi(sp.lo() + BytePos((index - 1) as _)) + } Some(index) => sp.with_hi(sp.lo() + BytePos(index as _)), None => sp, }; let inner_sp = match ori_link.find('!') { + Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => { + sp.with_hi(sp.lo() + BytePos((index - 1) as _)) + } Some(index) => inner_sp.with_hi(inner_sp.lo() + BytePos(index as _)), None => inner_sp, }; let inner_sp = match ori_link.find('@') { + Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => { + sp.with_hi(sp.lo() + BytePos((index - 1) as _)) + } Some(index) => inner_sp.with_lo(inner_sp.lo() + BytePos(index as u32 + 1)), None => inner_sp, }; @@ -1584,7 +1604,7 @@ fn report_diagnostic( lint: &'static Lint, msg: impl Into + Display, DiagnosticInfo { item, ori_link: _, dox, link_range }: &DiagnosticInfo<'_>, - decorate: impl FnOnce(&mut Diagnostic, Option), + decorate: impl FnOnce(&mut Diagnostic, Option, MarkdownLinkRange), ) { let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) else { @@ -1596,16 +1616,32 @@ fn report_diagnostic( let sp = item.attr_span(tcx); tcx.struct_span_lint_hir(lint, hir_id, sp, msg, |lint| { - let span = - super::source_span_for_markdown_range(tcx, dox, link_range, &item.attrs).map(|sp| { - if dox.as_bytes().get(link_range.start) == Some(&b'`') - && dox.as_bytes().get(link_range.end - 1) == Some(&b'`') - { - sp.with_lo(sp.lo() + BytePos(1)).with_hi(sp.hi() - BytePos(1)) - } else { - sp - } - }); + let (span, link_range) = match link_range { + MarkdownLinkRange::Destination(md_range) => { + let mut md_range = md_range.clone(); + let sp = super::source_span_for_markdown_range(tcx, dox, &md_range, &item.attrs) + .map(|mut sp| { + while dox.as_bytes().get(md_range.start) == Some(&b' ') + || dox.as_bytes().get(md_range.start) == Some(&b'`') + { + md_range.start += 1; + sp = sp.with_lo(sp.lo() + BytePos(1)); + } + while dox.as_bytes().get(md_range.end - 1) == Some(&b' ') + || dox.as_bytes().get(md_range.end - 1) == Some(&b'`') + { + md_range.end -= 1; + sp = sp.with_hi(sp.hi() - BytePos(1)); + } + sp + }); + (sp, MarkdownLinkRange::Destination(md_range)) + } + MarkdownLinkRange::WholeLink(md_range) => ( + super::source_span_for_markdown_range(tcx, dox, &md_range, &item.attrs), + link_range.clone(), + ), + }; if let Some(sp) = span { lint.set_span(sp); @@ -1614,21 +1650,22 @@ fn report_diagnostic( // ^ ~~~~ // | link_range // last_new_line_offset - let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); + let md_range = link_range.inner_range().clone(); + let last_new_line_offset = dox[..md_range.start].rfind('\n').map_or(0, |n| n + 1); let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); - // Print the line containing the `link_range` and manually mark it with '^'s. + // Print the line containing the `md_range` and manually mark it with '^'s. lint.note(format!( "the link appears in this line:\n\n{line}\n\ {indicator: unreachable!("handled above"), ResolutionFailure::WrongNamespace { res, expected_ns } => { - suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp); + suggest_disambiguator( + res, + diag, + path_str, + link_range.clone(), + sp, + &diag_info, + ); format!( "this link resolves to {}, which is not in the {} namespace", @@ -1882,7 +1926,7 @@ fn anchor_failure( msg: String, anchor_idx: usize, ) { - report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, sp| { + report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, sp, _link_range| { if let Some(mut sp) = sp { if let Some((fragment_offset, _)) = diag_info.ori_link.char_indices().filter(|(_, x)| *x == '#').nth(anchor_idx) @@ -1898,11 +1942,11 @@ fn anchor_failure( fn disambiguator_error( cx: &DocContext<'_>, mut diag_info: DiagnosticInfo<'_>, - disambiguator_range: Range, + disambiguator_range: MarkdownLinkRange, msg: impl Into + Display, ) { diag_info.link_range = disambiguator_range; - report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, _sp| { + report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, _sp, _link_range| { let msg = format!( "see {}/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators", crate::DOC_RUST_LANG_ORG_CHANNEL @@ -1922,7 +1966,7 @@ fn report_malformed_generics( BROKEN_INTRA_DOC_LINKS, format!("unresolved link to `{}`", path_str), &diag_info, - |diag, sp| { + |diag, sp, _link_range| { let note = match err { MalformedGenerics::UnbalancedAngleBrackets => "unbalanced angle brackets", MalformedGenerics::MissingType => "missing type for generic parameters", @@ -1995,7 +2039,7 @@ fn ambiguity_error( } } - report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, diag_info, |diag, sp| { + report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, diag_info, |diag, sp, link_range| { if let Some(sp) = sp { diag.span_label(sp, "ambiguous link"); } else { @@ -2003,7 +2047,7 @@ fn ambiguity_error( } for res in kinds { - suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp); + suggest_disambiguator(res, diag, path_str, link_range.clone(), sp, diag_info); } }); true @@ -2015,13 +2059,19 @@ fn suggest_disambiguator( res: Res, diag: &mut Diagnostic, path_str: &str, - ori_link: &str, + link_range: MarkdownLinkRange, sp: Option, + diag_info: &DiagnosticInfo<'_>, ) { let suggestion = res.disambiguator_suggestion(); let help = format!("to link to the {}, {}", res.descr(), suggestion.descr()); - if let Some(sp) = sp { + let ori_link = match link_range { + MarkdownLinkRange::Destination(range) => Some(&diag_info.dox[range]), + MarkdownLinkRange::WholeLink(_) => None, + }; + + if let (Some(sp), Some(ori_link)) = (sp, ori_link) { let mut spans = suggestion.as_help_span(path_str, ori_link, sp); if spans.len() > 1 { diag.multipart_suggestion(help, spans, Applicability::MaybeIncorrect); @@ -2047,7 +2097,7 @@ fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str: let msg = format!("public documentation for `{}` links to private item `{}`", item_name, path_str); - report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, msg, diag_info, |diag, sp| { + report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, msg, diag_info, |diag, sp, _link_range| { if let Some(sp) = sp { diag.span_label(sp, "this item is private"); } diff --git a/tests/rustdoc-ui/intra-doc/issue-110495-suffix-with-space.stderr b/tests/rustdoc-ui/intra-doc/issue-110495-suffix-with-space.stderr index 8669b0c2086..6c834fd0a1b 100644 --- a/tests/rustdoc-ui/intra-doc/issue-110495-suffix-with-space.stderr +++ b/tests/rustdoc-ui/intra-doc/issue-110495-suffix-with-space.stderr @@ -36,7 +36,7 @@ LL | //! [`Clone ()`]. help: to link to the trait, prefix with `trait@` | LL - //! [`Clone ()`]. -LL + //! [`trait@Clone (`]. +LL + //! [`trait@Clone `]. | error: incompatible link kind for `Clone` @@ -47,8 +47,9 @@ LL | //! [`Clone !`]. | help: to link to the derive macro, prefix with `derive@` | -LL | //! [`derive@Clone !`]. - | +++++++ +LL - //! [`Clone !`]. +LL + //! [`derive@Clone `]. + | error: aborting due to 4 previous errors diff --git a/tests/rustdoc-ui/intra-doc/weird-syntax.rs b/tests/rustdoc-ui/intra-doc/weird-syntax.rs new file mode 100644 index 00000000000..ca18842fb21 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/weird-syntax.rs @@ -0,0 +1,140 @@ +// Many examples are from +// https://github.com/rust-lang/rust/issues/110111#issuecomment-1517800781 +#![deny(rustdoc::broken_intra_doc_links)] + +//! This test case is closely linked to [raphlinus/pulldown-cmark#441], getting offsets of +//! link components. In particular, pulldown-cmark doesn't provide the offsets of the contents +//! of a link. +//! +//! To work around this, rustdoc parses parts of a link definition itself. This is basically a +//! test suite for that link syntax parser. +//! +//! [raphlinus/pulldown-cmark#441]: https://github.com/raphlinus/pulldown-cmark/issues/441 + +use std::clone::Clone; + +// Basic version // + +/// [`struct@Clone`] //~ERROR link +pub struct LinkToCloneWithBackquotes; + +/// [```struct@Clone```] //~ERROR link +pub struct LinkToCloneWithMultipleBackquotes; + +/// [ ` struct@Clone ` ] //~ERROR link +pub struct LinkToCloneWithSpacesAndBackquotes; + +/// [ `Clone ()` ] //~ERROR link +pub struct LinkToCloneWithSpacesBackquotesAndParens; + +/// [`Clone ()` ] //~ERROR link +pub struct LinkToCloneWithSpacesEndBackquotesAndParens; + +/// [ `Clone ()`] //~ERROR link +pub struct LinkToCloneWithSpacesStartBackquotesAndParens; + +/// [```Clone ()```] //~ERROR link +pub struct LinkToCloneWithMultipleBackquotesAndParens; + +/// [```Clone \(\)```] // not URL-shaped enough +pub struct LinkToCloneWithMultipleBackquotesAndEscapedParens; + +/// [ ``` Clone () ``` ] //~ERROR link +pub struct LinkToCloneWithSpacesMultipleBackquotesAndParens; + +/// [ x \] ] // not URL-shaped enough +pub struct LinkWithEscapedCloseBrace; + +/// [ x \[ ] // not URL-shaped enough +pub struct LinkWithEscapedOpenBrace; + +/// [ x \( ] // not URL-shaped enough +pub struct LinkWithEscapedCloseParen; + +/// [ x \) ] // not URL-shaped enough +pub struct LinkWithEscapedOpenParen; + +/// [ Clone \(\) ] // not URL-shaped enough +pub struct LinkWithEscapedParens; + +// [][] version // + +/// [x][ struct@Clone] //~ERROR link +pub struct XLinkToCloneWithStartSpace; + +/// [x][struct@Clone ] //~ERROR link +pub struct XLinkToCloneWithEndSpace; + +/// [x][Clone\(\)] not URL-shaped enough +pub struct XLinkToCloneWithEscapedParens; + +/// [x][`Clone`] not URL-shaped enough +pub struct XLinkToCloneWithBackquotes; + +/// [x][Clone()] //~ERROR link +pub struct XLinkToCloneWithUnescapedParens; + +/// [x][Clone ()] //~ERROR link +pub struct XLinkToCloneWithUnescapedParensAndDoubleSpace; + +/// [x][Clone [] //~ERROR unresolved link to `x` +pub struct XLinkToCloneWithUnmatchedOpenParenAndDoubleSpace; + +/// [x][Clone \[] // not URL-shaped enough +pub struct XLinkToCloneWithUnmatchedEscapedOpenParenAndDoubleSpace; + +/// [x][Clone \]] // not URL-shaped enough +pub struct XLinkToCloneWithUnmatchedEscapedCloseParenAndDoubleSpace; + +// []() version // + +/// [w]( struct@Clone) //~ERROR link +pub struct WLinkToCloneWithStartSpace; + +/// [w](struct@Clone ) //~ERROR link +pub struct WLinkToCloneWithEndSpace; + +/// [w](Clone\(\)) //~ERROR link +pub struct WLinkToCloneWithEscapedParens; + +/// [w](`Clone`) not URL-shaped enough +pub struct WLinkToCloneWithBackquotes; + +/// [w](Clone()) //~ERROR link +pub struct WLinkToCloneWithUnescapedParens; + +/// [w](Clone ()) not URL-shaped enough +pub struct WLinkToCloneWithUnescapedParensAndDoubleSpace; + +/// [w](Clone () //~ERROR unresolved link to `w` +pub struct WLinkToCloneWithUnmatchedOpenParenAndDoubleSpace; + +/// [w](Clone \() //~ERROR unresolved link to `w` +pub struct WLinkToCloneWithUnmatchedEscapedOpenParenAndDoubleSpace; + +/// [w](Clone \)) //~ERROR unresolved link to `w` +pub struct WLinkToCloneWithUnmatchedEscapedCloseParenAndDoubleSpace; + +// References + +/// The [cln][] link here is going to be unresolved, because `Clone()` gets rejected //~ERROR link +/// in Markdown for not being URL-shaped enough. +/// +/// [cln]: Clone() //~ERROR link +pub struct LinkToCloneWithParensInReference; + +/// The [cln][] link here is going to be unresolved, because `struct@Clone` gets //~ERROR link +/// rejected in Markdown for not being URL-shaped enough. +/// +/// [cln]: struct@Clone //~ERROR link +pub struct LinkToCloneWithWrongPrefix; + +/// The [cln][] link here will produce a plain text suggestion //~ERROR link +/// +/// [cln]: Clone\(\) +pub struct LinkToCloneWithEscapedParensInReference; + +/// The [cln][] link here will produce a plain text suggestion //~ERROR link +/// +/// [cln]: struct\@Clone +pub struct LinkToCloneWithEscapedAtsInReference; diff --git a/tests/rustdoc-ui/intra-doc/weird-syntax.stderr b/tests/rustdoc-ui/intra-doc/weird-syntax.stderr new file mode 100644 index 00000000000..f50feb57fcc --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/weird-syntax.stderr @@ -0,0 +1,272 @@ +error: incompatible link kind for `Clone` + --> $DIR/weird-syntax.rs:18:7 + | +LL | /// [`struct@Clone`] + | ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct + | +note: the lint level is defined here + --> $DIR/weird-syntax.rs:3:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: to link to the trait, prefix with `trait@` + | +LL | /// [`trait@Clone`] + | ~~~~~~ + +error: incompatible link kind for `Clone` + --> $DIR/weird-syntax.rs:21:9 + | +LL | /// [```struct@Clone```] + | ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct + | +help: to link to the trait, prefix with `trait@` + | +LL | /// [```trait@Clone```] + | ~~~~~~ + +error: incompatible link kind for `Clone` + --> $DIR/weird-syntax.rs:24:11 + | +LL | /// [ ` struct@Clone ` ] + | ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct + | +help: to link to the trait, prefix with `trait@` + | +LL | /// [ ` trait@Clone ` ] + | ~~~~~~ + +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:27:9 + | +LL | /// [ `Clone ()` ] + | ^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace + | +help: to link to the trait, prefix with `trait@` + | +LL - /// [ `Clone ()` ] +LL + /// [ `trait@Clone ` ] + | + +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:30:7 + | +LL | /// [`Clone ()` ] + | ^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace + | +help: to link to the trait, prefix with `trait@` + | +LL - /// [`Clone ()` ] +LL + /// [`trait@Clone ` ] + | + +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:33:9 + | +LL | /// [ `Clone ()`] + | ^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace + | +help: to link to the trait, prefix with `trait@` + | +LL - /// [ `Clone ()`] +LL + /// [ `trait@Clone `] + | + +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:36:9 + | +LL | /// [```Clone ()```] + | ^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace + | +help: to link to the trait, prefix with `trait@` + | +LL - /// [```Clone ()```] +LL + /// [```trait@Clone ```] + | + +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:42:13 + | +LL | /// [ ``` Clone () ``` ] + | ^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace + | +help: to link to the trait, prefix with `trait@` + | +LL - /// [ ``` Clone () ``` ] +LL + /// [ ``` trait@Clone ``` ] + | + +error: incompatible link kind for `Clone` + --> $DIR/weird-syntax.rs:62:10 + | +LL | /// [x][ struct@Clone] + | ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct + | +help: to link to the trait, prefix with `trait@` + | +LL | /// [x][ trait@Clone] + | ~~~~~~ + +error: incompatible link kind for `Clone` + --> $DIR/weird-syntax.rs:65:9 + | +LL | /// [x][struct@Clone ] + | ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct + | +help: to link to the trait, prefix with `trait@` + | +LL | /// [x][trait@Clone ] + | ~~~~~~ + +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:74:9 + | +LL | /// [x][Clone()] + | ^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace + | +help: to link to the trait, prefix with `trait@` + | +LL - /// [x][Clone()] +LL + /// [x][trait@Clone] + | + +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:77:9 + | +LL | /// [x][Clone ()] + | ^^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace + | +help: to link to the trait, prefix with `trait@` + | +LL - /// [x][Clone ()] +LL + /// [x][trait@Clone ] + | + +error: unresolved link to `x` + --> $DIR/weird-syntax.rs:80:6 + | +LL | /// [x][Clone [] + | ^ no item named `x` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: incompatible link kind for `Clone` + --> $DIR/weird-syntax.rs:91:10 + | +LL | /// [w]( struct@Clone) + | ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct + | +help: to link to the trait, prefix with `trait@` + | +LL | /// [w]( trait@Clone) + | ~~~~~~ + +error: incompatible link kind for `Clone` + --> $DIR/weird-syntax.rs:94:9 + | +LL | /// [w](struct@Clone ) + | ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct + | +help: to link to the trait, prefix with `trait@` + | +LL | /// [w](trait@Clone ) + | ~~~~~~ + +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:97:9 + | +LL | /// [w](Clone\(\)) + | ^^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace + | +help: to link to the trait, prefix with `trait@` + | +LL - /// [w](Clone\(\)) +LL + /// [w](trait@Clone) + | + +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:103:9 + | +LL | /// [w](Clone()) + | ^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace + | +help: to link to the trait, prefix with `trait@` + | +LL - /// [w](Clone()) +LL + /// [w](trait@Clone) + | + +error: unresolved link to `w` + --> $DIR/weird-syntax.rs:109:6 + | +LL | /// [w](Clone () + | ^ no item named `w` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: unresolved link to `w` + --> $DIR/weird-syntax.rs:112:6 + | +LL | /// [w](Clone \() + | ^ no item named `w` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: unresolved link to `w` + --> $DIR/weird-syntax.rs:115:6 + | +LL | /// [w](Clone \)) + | ^ no item named `w` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: unresolved link to `cln` + --> $DIR/weird-syntax.rs:120:10 + | +LL | /// The [cln][] link here is going to be unresolved, because `Clone()` gets rejected + | ^^^ no item named `cln` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: unresolved link to `cln` + --> $DIR/weird-syntax.rs:123:6 + | +LL | /// [cln]: Clone() + | ^^^ no item named `cln` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: unresolved link to `cln` + --> $DIR/weird-syntax.rs:126:10 + | +LL | /// The [cln][] link here is going to be unresolved, because `struct@Clone` gets + | ^^^ no item named `cln` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: unresolved link to `cln` + --> $DIR/weird-syntax.rs:129:6 + | +LL | /// [cln]: struct@Clone + | ^^^ no item named `cln` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:132:9 + | +LL | /// The [cln][] link here will produce a plain text suggestion + | ^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace + | + = help: to link to the trait, prefix with `trait@`: trait@Clone + +error: incompatible link kind for `Clone` + --> $DIR/weird-syntax.rs:137:9 + | +LL | /// The [cln][] link here will produce a plain text suggestion + | ^^^^^ this link resolved to a trait, which is not a struct + | + = help: to link to the trait, prefix with `trait@`: trait@Clone + +error: aborting due to 26 previous errors +