1use std::mem;
2use std::ops::Range;
34use itertools::Itertools;
5/// Re-export the markdown parser used by rustdoc.
6pub use pulldown_cmark;
7use pulldown_cmark::{
8BrokenLink, BrokenLinkCallback, CowStr, Event, LinkType, Options, Parser, Tag,
9};
10use rustc_astas ast;
11use rustc_ast::attr::AttributeExt;
12use rustc_ast::token::DocFragmentKind;
13use rustc_ast::util::comments::beautify_doc_string;
14use rustc_ast::{AttrStyle, join_path_syms};
15use rustc_data_structures::fx::FxIndexMap;
16use rustc_data_structures::unord::UnordSet;
17use rustc_middle::ty::TyCtxt;
18use rustc_span::def_id::DefId;
19use rustc_span::source_map::SourceMap;
20use rustc_span::{DUMMY_SP, InnerSpan, Span, Symbol, sym};
21use thin_vec::ThinVec;
22use tracing::{debug, trace};
2324#[cfg(test)]
25mod tests;
2627/// A portion of documentation, extracted from a `#[doc]` attribute.
28///
29/// Each variant contains the line number within the complete doc-comment where the fragment
30/// starts, as well as the Span where the corresponding doc comment or attribute is located.
31///
32/// Included files are kept separate from inline doc comments so that proper line-number
33/// information can be given when a doctest fails. Sugared doc comments and "raw" doc comments are
34/// kept separate because of issue #42760.
35#[derive(#[automatically_derived]
impl ::core::clone::Clone for DocFragment {
#[inline]
fn clone(&self) -> Self {
DocFragment {
span: ::core::clone::Clone::clone(&self.span),
item_id: ::core::clone::Clone::clone(&self.item_id),
doc: ::core::clone::Clone::clone(&self.doc),
kind: ::core::clone::Clone::clone(&self.kind),
indent: ::core::clone::Clone::clone(&self.indent),
from_expansion: ::core::clone::Clone::clone(&self.from_expansion),
style: ::core::clone::Clone::clone(&self.style),
}
}
}Clone, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for DocFragment { }
#[automatically_derived]
impl ::core::cmp::PartialEq for DocFragment {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.from_expansion == other.from_expansion && self.span == other.span
&& self.item_id == other.item_id && self.doc == other.doc &&
self.kind == other.kind && self.indent == other.indent &&
self.style == other.style
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for DocFragment {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<Span>;
let _: ::core::cmp::AssertParamIsEq<Option<DefId>>;
let _: ::core::cmp::AssertParamIsEq<Symbol>;
let _: ::core::cmp::AssertParamIsEq<DocFragmentKind>;
let _: ::core::cmp::AssertParamIsEq<usize>;
let _: ::core::cmp::AssertParamIsEq<bool>;
let _: ::core::cmp::AssertParamIsEq<AttrStyle>;
}
}Eq, #[automatically_derived]
impl ::core::fmt::Debug for DocFragment {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["span", "item_id", "doc", "kind", "indent", "from_expansion",
"style"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.span, &self.item_id, &self.doc, &self.kind, &self.indent,
&self.from_expansion, &&self.style];
::core::fmt::Formatter::debug_struct_fields_finish(f, "DocFragment",
names, values)
}
}Debug)]
36pub struct DocFragment {
37pub span: Span,
38/// The item this doc-comment came from.
39 /// Used to determine the scope in which doc links in this fragment are resolved.
40 /// Typically filled for reexport docs when they are merged into the docs of the
41 /// original reexported item.
42 /// If the id is not filled, which happens for the original reexported item, then
43 /// it has to be taken from somewhere else during doc link resolution.
44pub item_id: Option<DefId>,
45pub doc: Symbol,
46pub kind: DocFragmentKind,
47pub indent: usize,
48/// Because we tamper with the spans context, this information cannot be correctly retrieved
49 /// later on. So instead, we compute it and store it here.
50pub from_expansion: bool,
51pub style: AttrStyle,
52}
5354#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for MalformedGenerics { }
#[automatically_derived]
impl ::core::clone::Clone for MalformedGenerics {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for MalformedGenerics { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for MalformedGenerics {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
MalformedGenerics::UnbalancedAngleBrackets =>
"UnbalancedAngleBrackets",
MalformedGenerics::MissingType => "MissingType",
MalformedGenerics::HasFullyQualifiedSyntax =>
"HasFullyQualifiedSyntax",
MalformedGenerics::InvalidPathSeparator =>
"InvalidPathSeparator",
MalformedGenerics::TooManyAngleBrackets =>
"TooManyAngleBrackets",
MalformedGenerics::EmptyAngleBrackets => "EmptyAngleBrackets",
})
}
}Debug)]
55pub enum MalformedGenerics {
56/// This link has unbalanced angle brackets.
57 ///
58 /// For example, `Vec<T` should trigger this, as should `Vec<T>>`.
59UnbalancedAngleBrackets,
60/// The generics are not attached to a type.
61 ///
62 /// For example, `<T>` should trigger this.
63 ///
64 /// This is detected by checking if the path is empty after the generics are stripped.
65MissingType,
66/// The link uses fully-qualified syntax, which is currently unsupported.
67 ///
68 /// For example, `<Vec as IntoIterator>::into_iter` should trigger this.
69 ///
70 /// This is detected by checking if ` as ` (the keyword `as` with spaces around it) is inside
71 /// angle brackets.
72HasFullyQualifiedSyntax,
73/// The link has an invalid path separator.
74 ///
75 /// For example, `Vec:<T>:new()` should trigger this. Note that `Vec:new()` will **not**
76 /// trigger this because it has no generics and thus [`strip_generics_from_path`] will not be
77 /// called.
78 ///
79 /// Note that this will also **not** be triggered if the invalid path separator is inside angle
80 /// brackets because rustdoc mostly ignores what's inside angle brackets (except for
81 /// [`HasFullyQualifiedSyntax`](MalformedGenerics::HasFullyQualifiedSyntax)).
82 ///
83 /// This is detected by checking if there is a colon followed by a non-colon in the link.
84InvalidPathSeparator,
85/// The link has too many angle brackets.
86 ///
87 /// For example, `Vec<<T>>` should trigger this.
88TooManyAngleBrackets,
89/// The link has empty angle brackets.
90 ///
91 /// For example, `Vec<>` should trigger this.
92EmptyAngleBrackets,
93}
9495/// Removes excess indentation on comments in order for the Markdown
96/// to be parsed correctly. This is necessary because the convention for
97/// writing documentation is to provide a space between the /// or //! marker
98/// and the doc text, but Markdown is whitespace-sensitive. For example,
99/// a block of text with four-space indentation is parsed as a code block,
100/// so if we didn't unindent comments, these list items
101///
102/// /// A list:
103/// ///
104/// /// - Foo
105/// /// - Bar
106///
107/// would be parsed as if they were in a code block, which is likely not what the user intended.
108pub fn unindent_doc_fragments(docs: &mut [DocFragment]) {
109// `add` is used in case the most common sugared doc syntax is used ("/// "). The other
110 // fragments kind's lines are never starting with a whitespace unless they are using some
111 // markdown formatting requiring it. Therefore, if the doc block have a mix between the two,
112 // we need to take into account the fact that the minimum indent minus one (to take this
113 // whitespace into account).
114 //
115 // For example:
116 //
117 // /// hello!
118 // #[doc = "another"]
119 //
120 // In this case, you want "hello! another" and not "hello! another".
121let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind)
122 && docs.iter().any(|d| d.kind.is_sugared())
123 {
124// In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to
125 // "decide" how much the minimum indent will be.
1261
127} else {
1280
129};
130131// `min_indent` is used to know how much whitespaces from the start of each lines must be
132 // removed. Example:
133 //
134 // /// hello!
135 // #[doc = "another"]
136 //
137 // In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum
138 // 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4
139 // (5 - 1) whitespaces.
140let Some(min_indent) = docs141 .iter()
142 .map(|fragment| {
143fragment144 .doc
145 .as_str()
146 .lines()
147 .filter(|line| line.chars().any(|c| !c.is_whitespace()))
148 .map(|line| {
149// Compare against either space or tab, ignoring whether they are
150 // mixed or not.
151let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count();
152whitespace + (if fragment.kind.is_sugared() { 0 } else { add })
153 })
154 .min()
155 .unwrap_or(usize::MAX)
156 })
157 .min()
158else {
159return;
160 };
161162for fragment in docs {
163if fragment.doc == sym::empty {
164continue;
165 }
166167let indent = if !fragment.kind.is_sugared() && min_indent > 0 {
168 min_indent - add
169 } else {
170 min_indent
171 };
172173 fragment.indent = indent;
174 }
175}
176177/// The goal of this function is to apply the `DocFragment` transformation that is required when
178/// transforming into the final Markdown, which is applying the computed indent to each line in
179/// each doc fragment (a `DocFragment` can contain multiple lines in case of `#[doc = ""]`).
180///
181/// Note: remove the trailing newline where appropriate
182pub fn add_doc_fragment(out: &mut String, frag: &DocFragment) {
183if frag.doc == sym::empty {
184out.push('\n');
185return;
186 }
187let s = frag.doc.as_str();
188let mut iter = s.lines();
189190while let Some(line) = iter.next() {
191if line.chars().any(|c| !c.is_whitespace()) {
192if !(line.len() >= frag.indent) {
::core::panicking::panic("assertion failed: line.len() >= frag.indent")
};assert!(line.len() >= frag.indent);
193 out.push_str(&line[frag.indent..]);
194 } else {
195 out.push_str(line);
196 }
197 out.push('\n');
198 }
199}
200201pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>(
202 attrs: impl Iterator<Item = (&'a A, Option<DefId>)>,
203 doc_only: bool,
204) -> (Vec<DocFragment>, ThinVec<A>) {
205let (min_size, max_size) = attrs.size_hint();
206let size_hint = max_size.unwrap_or(min_size);
207let mut doc_fragments = Vec::with_capacity(size_hint);
208let mut other_attrs = ThinVec::<A>::with_capacity(if doc_only { 0 } else { size_hint });
209for (attr, item_id) in attrs {
210if let Some((doc_str, fragment_kind)) = attr.doc_str_and_fragment_kind() {
211let doc = beautify_doc_string(doc_str, fragment_kind.comment_kind());
212let attr_span = attr.span();
213let (span, from_expansion) = match fragment_kind {
214 DocFragmentKind::Sugared(_) => (attr_span, attr_span.from_expansion()),
215 DocFragmentKind::Raw(value_span) => {
216 (value_span.with_ctxt(attr_span.ctxt()), value_span.from_expansion())
217 }
218 };
219let fragment = DocFragment {
220 span,
221 doc,
222 kind: fragment_kind,
223 item_id,
224 indent: 0,
225 from_expansion,
226 style: attr.doc_resolution_scope().unwrap(),
227 };
228 doc_fragments.push(fragment);
229 } else if !doc_only {
230 other_attrs.push(attr.clone());
231 }
232 }
233234doc_fragments.shrink_to_fit();
235other_attrs.shrink_to_fit();
236237unindent_doc_fragments(&mut doc_fragments);
238239 (doc_fragments, other_attrs)
240}
241242/// Represents a doc comment, split in two parts, outer and inner attributes. It's needed for
243/// the intra-doc link resolution context.
244#[derive(#[automatically_derived]
impl ::core::default::Default for DocStrings {
#[inline]
fn default() -> Self {
DocStrings {
outer: ::core::default::Default::default(),
inner: ::core::default::Default::default(),
}
}
}Default)]
245pub struct DocStrings {
246pub outer: String,
247pub inner: String,
248}
249250/// Return the doc-comments on this item, grouped by the module they came from.
251/// The module can be different if this is a re-export with added documentation.
252///
253/// The last newline is not trimmed so the produced strings are reusable between
254/// early and late doc link resolution regardless of their position.
255pub fn prepare_to_doc_link_resolution(
256 doc_fragments: &[DocFragment],
257) -> FxIndexMap<Option<DefId>, DocStrings> {
258let mut res: FxIndexMap<Option<DefId>, DocStrings> = FxIndexMap::default();
259for fragment in doc_fragments {
260let out_strs = res.entry(fragment.item_id).or_default();
261let out = if fragment.style == AttrStyle::Inner {
262&mut out_strs.inner
263 } else {
264&mut out_strs.outer
265 };
266 add_doc_fragment(out, fragment);
267 }
268res269}
270271/// Options for rendering Markdown in the main body of documentation.
272pub fn main_body_opts() -> Options {
273Options::ENABLE_TABLES274 | Options::ENABLE_FOOTNOTES275 | Options::ENABLE_STRIKETHROUGH276 | Options::ENABLE_TASKLISTS277 | Options::ENABLE_SMART_PUNCTUATION278}
279280fn strip_generics_from_path_segment(segment: Vec<char>) -> Result<Symbol, MalformedGenerics> {
281let mut stripped_segment = String::new();
282let mut param_depth = 0;
283284let mut latest_generics_chunk = String::new();
285286for c in segment {
287if c == '<' {
288 param_depth += 1;
289 latest_generics_chunk.clear();
290 } else if c == '>' {
291 param_depth -= 1;
292if latest_generics_chunk.contains(" as ") {
293// The segment tries to use fully-qualified syntax, which is currently unsupported.
294 // Give a helpful error message instead of completely ignoring the angle brackets.
295return Err(MalformedGenerics::HasFullyQualifiedSyntax);
296 }
297 } else if param_depth == 0 {
298 stripped_segment.push(c);
299 } else {
300 latest_generics_chunk.push(c);
301 }
302 }
303304if param_depth == 0 {
305Ok(Symbol::intern(&stripped_segment))
306 } else {
307// The segment has unbalanced angle brackets, e.g. `Vec<T` or `Vec<T>>`
308Err(MalformedGenerics::UnbalancedAngleBrackets)
309 }
310}
311312pub fn strip_generics_from_path(path_str: &str) -> Result<Box<str>, MalformedGenerics> {
313if !path_str.contains(['<', '>']) {
314return Ok(path_str.into());
315 }
316let mut stripped_segments = ::alloc::vec::Vec::new()vec![];
317let mut path = path_str.chars().peekable();
318let mut segment = Vec::new();
319320while let Some(chr) = path.next() {
321match chr {
322':' => {
323if path.next_if_eq(&':').is_some() {
324let stripped_segment =
325 strip_generics_from_path_segment(mem::take(&mut segment))?;
326if !stripped_segment.is_empty() {
327 stripped_segments.push(stripped_segment);
328 }
329 } else {
330return Err(MalformedGenerics::InvalidPathSeparator);
331 }
332 }
333'<' => {
334 segment.push(chr);
335336match path.next() {
337Some('<') => {
338return Err(MalformedGenerics::TooManyAngleBrackets);
339 }
340Some('>') => {
341return Err(MalformedGenerics::EmptyAngleBrackets);
342 }
343Some(chr) => {
344 segment.push(chr);
345346while let Some(chr) = path.next_if(|c| *c != '>') {
347 segment.push(chr);
348 }
349 }
350None => break,
351 }
352 }
353_ => segment.push(chr),
354 }
355{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_resolve/src/rustdoc.rs:355",
"rustc_resolve::rustdoc", ::tracing::Level::TRACE,
::tracing_core::__macro_support::Option::Some("/rustc-dev/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_resolve/src/rustdoc.rs"),
::tracing_core::__macro_support::Option::Some(355u32),
::tracing_core::__macro_support::Option::Some("rustc_resolve::rustdoc"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("raw segment: {0:?}",
segment) as &dyn ::tracing::field::Value))])
});
} else { ; }
};trace!("raw segment: {:?}", segment);
356 }
357358if !segment.is_empty() {
359let stripped_segment = strip_generics_from_path_segment(segment)?;
360if !stripped_segment.is_empty() {
361stripped_segments.push(stripped_segment);
362 }
363 }
364365{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_resolve/src/rustdoc.rs:365",
"rustc_resolve::rustdoc", ::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("/rustc-dev/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_resolve/src/rustdoc.rs"),
::tracing_core::__macro_support::Option::Some(365u32),
::tracing_core::__macro_support::Option::Some("rustc_resolve::rustdoc"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("path_str: {0:?}\nstripped segments: {1:?}",
path_str, stripped_segments) as
&dyn ::tracing::field::Value))])
});
} else { ; }
};debug!("path_str: {path_str:?}\nstripped segments: {stripped_segments:?}");
366367if !stripped_segments.is_empty() {
368let stripped_path = join_path_syms(stripped_segments);
369Ok(stripped_path.into())
370 } else {
371Err(MalformedGenerics::MissingType)
372 }
373}
374375/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`.
376pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool {
377for attr in attrs {
378if attr.is_rustc_doc_primitive() || attr.is_doc_keyword_or_attribute() {
379return true;
380 }
381 }
382false
383}
384385/// Simplified version of the corresponding function in rustdoc.
386fn preprocess_link(link: &str) -> Box<str> {
387// IMPORTANT: To be kept in sync with the corresponding function in rustdoc.
388 // Namely, whenever the rustdoc function returns a successful result for a given input,
389 // this function *MUST* return a link that's equal to `PreprocessingInfo.path_str`!
390391let link = link.replace('`', "");
392let link = link.split('#').next().unwrap();
393let link = link.trim();
394let link = link.split_once('@').map_or(link, |(_, rhs)| rhs);
395let link = link.trim_suffix("()");
396let link = link.trim_suffix("{}");
397let link = link.trim_suffix("[]");
398let link = if link != "!" { link.trim_suffix('!') } else { link };
399let link = link.trim();
400strip_generics_from_path(link).unwrap_or_else(|_| link.into())
401}
402403/// Keep inline and reference links `[]`,
404/// but skip autolinks `<>` which we never consider to be intra-doc links.
405pub fn may_be_doc_link(link_type: LinkType) -> bool {
406match link_type {
407 LinkType::Inline408 | LinkType::Reference409 | LinkType::ReferenceUnknown410 | LinkType::Collapsed411 | LinkType::CollapsedUnknown412 | LinkType::Shortcut413 | LinkType::ShortcutUnknown => true,
414 LinkType::Autolink | LinkType::Email => false,
415 }
416}
417418/// Simplified version of `preprocessed_markdown_links` from rustdoc.
419/// Must return at least the same links as it, but may add some more links on top of that.
420pub(crate) fn attrs_to_preprocessed_links(attrs: &[ast::Attribute]) -> Vec<Box<str>> {
421let (doc_fragments, other_attrs) =
422attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), false);
423let doc = prepare_to_doc_link_resolution(&doc_fragments).into_values().next();
424let mut links = doc425 .as_ref()
426 .map(|doc| parse_links(&::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}{1}", doc.outer, doc.inner))
})format!("{}{}", doc.outer, doc.inner)))
427 .unwrap_or_default();
428429for attr in other_attrs {
430if let Some(note) = attr.deprecation_note() {
431 links.extend(parse_links(note.as_str()));
432 }
433 }
434435links436}
437438/// Similar version of `markdown_links` from rustdoc.
439/// This will collect destination links and display text if exists.
440fn parse_links<'md>(doc: &'md str) -> Vec<Box<str>> {
441let mut broken_link_callback = |link: BrokenLink<'md>| Some((link.reference, "".into()));
442let mut event_iter = Parser::new_with_broken_link_callback(
443doc,
444main_body_opts(),
445Some(&mut broken_link_callback),
446 );
447let mut links = Vec::new();
448449let mut refids = UnordSet::default();
450451while let Some(event) = event_iter.next() {
452match event {
453 Event::Start(Tag::Link { link_type, dest_url, title: _, id })
454if may_be_doc_link(link_type) =>
455 {
456if #[allow(non_exhaustive_omitted_patterns)] match link_type {
LinkType::Inline | LinkType::ReferenceUnknown | LinkType::Reference |
LinkType::Shortcut | LinkType::ShortcutUnknown => true,
_ => false,
}matches!(
457 link_type,
458 LinkType::Inline
459 | LinkType::ReferenceUnknown
460 | LinkType::Reference
461 | LinkType::Shortcut
462 | LinkType::ShortcutUnknown
463 ) {
464if let Some(display_text) = collect_link_data(&mut event_iter) {
465 links.push(display_text);
466 }
467 }
468if #[allow(non_exhaustive_omitted_patterns)] match link_type {
LinkType::Reference | LinkType::Shortcut | LinkType::Collapsed => true,
_ => false,
}matches!(
469 link_type,
470 LinkType::Reference | LinkType::Shortcut | LinkType::Collapsed
471 ) {
472 refids.insert(id);
473 }
474475 links.push(preprocess_link(&dest_url));
476 }
477_ => {}
478 }
479 }
480481for (label, refdef) in event_iter.reference_definitions().iter().sorted_by_key(|x| x.0) {
482if !refids.contains(label) {
483 links.push(preprocess_link(&refdef.dest));
484 }
485 }
486487links488}
489490/// Collects additional data of link.
491fn collect_link_data<'input, F: BrokenLinkCallback<'input>>(
492 event_iter: &mut Parser<'input, F>,
493) -> Option<Box<str>> {
494let mut display_text: Option<String> = None;
495let mut append_text = |text: CowStr<'_>| {
496if let Some(display_text) = &mut display_text {
497display_text.push_str(&text);
498 } else {
499display_text = Some(text.to_string());
500 }
501 };
502503while let Some(event) = event_iter.next() {
504match event {
505 Event::Text(text) => {
506 append_text(text);
507 }
508 Event::Code(code) => {
509 append_text(code);
510 }
511 Event::End(_) => {
512break;
513 }
514_ => {}
515 }
516 }
517518display_text.map(String::into_boxed_str)
519}
520521/// Returns a span encompassing all the document fragments.
522pub fn span_of_fragments(fragments: &[DocFragment]) -> Option<Span> {
523let (first_fragment, last_fragment) = match fragments {
524 [] => return None,
525 [first, .., last] => (first, last),
526 [first] => (first, first),
527 };
528if first_fragment.span == DUMMY_SP {
529return None;
530 }
531Some(first_fragment.span.to(last_fragment.span))
532}
533534/// Attempts to match a range of bytes from parsed markdown to a `Span` in the source code.
535///
536/// This method does not always work, because markdown bytes don't necessarily match source bytes,
537/// like if escapes are used in the string. In this case, it returns `None`.
538///
539/// `markdown` is typically the entire documentation for an item,
540/// after combining fragments.
541///
542/// This method will return `Some` only if one of the following is true:
543///
544/// - The doc is made entirely from sugared doc comments, which cannot contain escapes
545/// - The doc is entirely from a single doc fragment with a string literal exactly equal to
546/// `markdown`.
547/// - The doc comes from `include_str!`
548/// - The doc includes exactly one substring matching `markdown[md_range]` which is contained in a
549/// single doc fragment.
550///
551/// This function is defined in the compiler so it can be used by both `rustdoc` and `clippy`.
552///
553/// It returns a tuple containing a span encompassing all the document fragments and a boolean that
554/// is `true` if any of the *matched* fragments are from a macro expansion.
555pub fn source_span_for_markdown_range(
556 tcx: TyCtxt<'_>,
557 markdown: &str,
558 md_range: &Range<usize>,
559 fragments: &[DocFragment],
560) -> Option<(Span, bool)> {
561let map = tcx.sess.source_map();
562source_span_for_markdown_range_inner(map, markdown, md_range, fragments)
563}
564565// inner function used for unit testing
566pub fn source_span_for_markdown_range_inner(
567 map: &SourceMap,
568 markdown: &str,
569 md_range: &Range<usize>,
570 fragments: &[DocFragment],
571) -> Option<(Span, bool)> {
572use rustc_span::BytePos;
573574if let &[fragment] = &fragments575 && !fragment.kind.is_sugared()
576 && let Ok(snippet) = map.span_to_snippet(fragment.span)
577 && snippet.trim_end() == markdown.trim_end()
578 && let Ok(md_range_lo) = u32::try_from(md_range.start)
579 && let Ok(md_range_hi) = u32::try_from(md_range.end)
580 {
581// Single fragment with string that contains same bytes as doc.
582return Some((
583Span::new(
584fragment.span.lo() + rustc_span::BytePos(md_range_lo),
585fragment.span.lo() + rustc_span::BytePos(md_range_hi),
586fragment.span.ctxt(),
587fragment.span.parent(),
588 ),
589fragment.from_expansion,
590 ));
591 }
592593let is_all_sugared_doc = fragments.iter().all(|frag| frag.kind.is_sugared());
594595if !is_all_sugared_doc {
596// This case ignores the markdown outside of the range so that it can
597 // work in cases where the markdown is made from several different
598 // doc fragments, but the target range does not span across multiple
599 // fragments.
600let mut match_data = None;
601let pat = &markdown[md_range.clone()];
602// This heirustic doesn't make sense with a zero-sized range.
603if pat.is_empty() {
604return None;
605 }
606for (i, fragment) in fragments.iter().enumerate() {
607if let Ok(snippet) = map.span_to_snippet(fragment.span)
608 && let Some(match_start) = snippet.find(pat)
609 {
610// If there is either a match in a previous fragment, or
611 // multiple matches in this fragment, there is ambiguity.
612 // the snippet cannot be zero-sized, because it matches
613 // the pattern, which is checked to not be zero sized.
614if match_data.is_none()
615 && !snippet.as_bytes()[match_start + 1..]
616 .windows(pat.len())
617 .any(|s| s == pat.as_bytes())
618 {
619 match_data = Some((i, match_start));
620 } else {
621// Heuristic produced ambiguity, return nothing.
622return None;
623 }
624 }
625 }
626if let Some((i, match_start)) = match_data {
627let fragment = &fragments[i];
628let sp = fragment.span;
629// we need to calculate the span start,
630 // then use that in our calculations for the span end
631let lo = sp.lo() + BytePos(match_startas u32);
632return Some((
633sp.with_lo(lo).with_hi(lo + BytePos((md_range.end - md_range.start) as u32)),
634fragment.from_expansion,
635 ));
636 }
637return None;
638 }
639640let snippet = map.span_to_snippet(span_of_fragments(fragments)?).ok()?;
641642let starting_line = markdown[..md_range.start].matches('\n').count();
643let ending_line = starting_line + markdown[md_range.start..md_range.end].matches('\n').count();
644645// We use `split_terminator('\n')` instead of `lines()` when counting bytes so that we treat
646 // CRLF and LF line endings the same way.
647let mut src_lines = snippet.split_terminator('\n');
648let md_lines = markdown.split_terminator('\n');
649650// The number of bytes from the source span to the markdown span that are not part
651 // of the markdown, like comment markers.
652let mut start_bytes = 0;
653let mut end_bytes = 0;
654655let span_of_all_fragments: Span = span_of_fragments(fragments)?;
656657let mut prev_lines_bytes = 0;
658'outer: for (line_no, md_line) in md_lines.enumerate() {
659loop {
660let source_line = src_lines.next()?;
661let source_line_len = u32::try_from(source_line.len()).unwrap();
662let source_line_span =
663 span_of_all_fragments.split_at(prev_lines_bytes).1.split_at(source_line_len).0;
664let fragment = fragments
665 .iter()
666// `source_line_span` might contain indentation that `fragment.span` doesn't contain
667.find(|fragment| fragment.span.overlaps(source_line_span));
668// Since we're counting bytes, `prev_line_bytes` includes the "\n".
669prev_lines_bytes += source_line_len + 1;
670if let Some(fragment) = fragment
671 && let Some(offset) = source_line.find(md_line)
672 {
673if fragment.span.lo() > source_line_span.lo()
674 && source_line
675 [..usize::try_from(fragment.span.lo().0 - source_line_span.lo().0).unwrap()]
676 .chars()
677 .any(|c| !c.is_whitespace())
678 {
679// Make sure anything between the start of this line and the fragment itself is just indentation.
680 // Because source_line is built by splitting the span that covers all fragments, this only finds
681 // characters *between* doc comments, not characters before or after doc comments.
682 //
683 // 1| /** doc */
684 // 2| #[inline] /** doc2 */
685 // ^^^^^^^^^
686 // | this
687 //
688 // 3| fn foo() {}
689return None;
690 }
691if fragment.span.hi() < source_line_span.hi()
692 && source_line
693 [usize::try_from(fragment.span.hi().0 - source_line_span.lo().0).unwrap()..]
694 .chars()
695 .any(|c| !c.is_whitespace())
696 {
697// Make sure anything between the start of this line and the fragment itself is just indentation.
698 // Because source_line is built by splitting the span that covers all fragments, this only finds
699 // characters *between* doc comments, not characters before or after doc comments.
700 // 1| /** doc */ #[inline]
701 // ^^^^^^^^^
702 // | this
703 //
704 // 2| /** doc2 */
705 // 3| fn foo() {}
706return None;
707 }
708if line_no == starting_line {
709 start_bytes += offset;
710711if starting_line == ending_line {
712break 'outer;
713 }
714 } else if line_no == ending_line {
715 end_bytes += offset;
716break 'outer;
717 } else if line_no < starting_line {
718 start_bytes += source_line.len() - md_line.len();
719 } else {
720 end_bytes += source_line.len() - md_line.len();
721 }
722break;
723 } else {
724// Since this is a source line that doesn't include a markdown line,
725 // we have to count it and its newline as non-markdown bytes.
726if line_no <= starting_line {
727 start_bytes += source_line.len() + 1;
728 } else if source_line.chars().any(|c| !c.is_whitespace()) {
729// We're past the first line, but haven't found the last line,
730 // but we found a non-empty non-markdown line.
731 // This could be an attribute, and we don't want a diagnostic
732 // suggesting to delete that attribute, so we return None to be safe.
733 // 1| /** doc */
734 // 2 | #[inline]
735 // ^^^^^^^^^
736 // | this
737 // 3| /** doc2 */
738 // 4| fn foo() {}
739return None;
740 } else {
741 end_bytes += source_line.len() + 1;
742 }
743 }
744 }
745 }
746747let src_span = span_of_all_fragments.from_inner(InnerSpan::new(
748md_range.start + start_bytes,
749md_range.end + start_bytes + end_bytes,
750 ));
751Some((
752src_span,
753fragments.iter().any(|frag| frag.span.overlaps(src_span) && frag.from_expansion),
754 ))
755}