]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/mod.rs
Rename automatic_links to url_improvements
[rust.git] / src / librustdoc / passes / mod.rs
1 //! Contains information about "passes", used to modify crate information during the documentation
2 //! process.
3
4 use rustc_span::{InnerSpan, Span, DUMMY_SP};
5 use std::ops::Range;
6
7 use self::Condition::*;
8 use crate::clean::{self, DocFragmentKind};
9 use crate::core::DocContext;
10
11 mod stripper;
12 pub use stripper::*;
13
14 mod url_improvements;
15 pub use self::url_improvements::CHECK_URL_IMPROVEMENTS;
16
17 mod collapse_docs;
18 pub use self::collapse_docs::COLLAPSE_DOCS;
19
20 mod strip_hidden;
21 pub use self::strip_hidden::STRIP_HIDDEN;
22
23 mod strip_private;
24 pub use self::strip_private::STRIP_PRIVATE;
25
26 mod strip_priv_imports;
27 pub use self::strip_priv_imports::STRIP_PRIV_IMPORTS;
28
29 mod unindent_comments;
30 pub use self::unindent_comments::UNINDENT_COMMENTS;
31
32 mod propagate_doc_cfg;
33 pub use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
34
35 mod collect_intra_doc_links;
36 pub use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;
37
38 mod doc_test_lints;
39 pub use self::doc_test_lints::CHECK_PRIVATE_ITEMS_DOC_TESTS;
40
41 mod collect_trait_impls;
42 pub use self::collect_trait_impls::COLLECT_TRAIT_IMPLS;
43
44 mod check_code_block_syntax;
45 pub use self::check_code_block_syntax::CHECK_CODE_BLOCK_SYNTAX;
46
47 mod calculate_doc_coverage;
48 pub use self::calculate_doc_coverage::CALCULATE_DOC_COVERAGE;
49
50 mod html_tags;
51 pub use self::html_tags::CHECK_INVALID_HTML_TAGS;
52
53 /// A single pass over the cleaned documentation.
54 ///
55 /// Runs in the compiler context, so it has access to types and traits and the like.
56 #[derive(Copy, Clone)]
57 pub struct Pass {
58     pub name: &'static str,
59     pub run: fn(clean::Crate, &DocContext<'_>) -> clean::Crate,
60     pub description: &'static str,
61 }
62
63 /// In a list of passes, a pass that may or may not need to be run depending on options.
64 #[derive(Copy, Clone)]
65 pub struct ConditionalPass {
66     pub pass: Pass,
67     pub condition: Condition,
68 }
69
70 /// How to decide whether to run a conditional pass.
71 #[derive(Copy, Clone)]
72 pub enum Condition {
73     Always,
74     /// When `--document-private-items` is passed.
75     WhenDocumentPrivate,
76     /// When `--document-private-items` is not passed.
77     WhenNotDocumentPrivate,
78     /// When `--document-hidden-items` is not passed.
79     WhenNotDocumentHidden,
80 }
81
82 /// The full list of passes.
83 pub const PASSES: &[Pass] = &[
84     CHECK_PRIVATE_ITEMS_DOC_TESTS,
85     STRIP_HIDDEN,
86     UNINDENT_COMMENTS,
87     COLLAPSE_DOCS,
88     STRIP_PRIVATE,
89     STRIP_PRIV_IMPORTS,
90     PROPAGATE_DOC_CFG,
91     COLLECT_INTRA_DOC_LINKS,
92     CHECK_CODE_BLOCK_SYNTAX,
93     COLLECT_TRAIT_IMPLS,
94     CALCULATE_DOC_COVERAGE,
95     CHECK_INVALID_HTML_TAGS,
96     CHECK_URL_IMPROVEMENTS,
97 ];
98
99 /// The list of passes run by default.
100 pub const DEFAULT_PASSES: &[ConditionalPass] = &[
101     ConditionalPass::always(COLLECT_TRAIT_IMPLS),
102     ConditionalPass::always(COLLAPSE_DOCS),
103     ConditionalPass::always(UNINDENT_COMMENTS),
104     ConditionalPass::always(CHECK_PRIVATE_ITEMS_DOC_TESTS),
105     ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
106     ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
107     ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate),
108     ConditionalPass::always(COLLECT_INTRA_DOC_LINKS),
109     ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX),
110     ConditionalPass::always(CHECK_INVALID_HTML_TAGS),
111     ConditionalPass::always(PROPAGATE_DOC_CFG),
112     ConditionalPass::always(CHECK_URL_IMPROVEMENTS),
113 ];
114
115 /// The list of default passes run when `--doc-coverage` is passed to rustdoc.
116 pub const COVERAGE_PASSES: &[ConditionalPass] = &[
117     ConditionalPass::always(COLLECT_TRAIT_IMPLS),
118     ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
119     ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
120     ConditionalPass::always(CALCULATE_DOC_COVERAGE),
121 ];
122
123 impl ConditionalPass {
124     pub const fn always(pass: Pass) -> Self {
125         Self::new(pass, Always)
126     }
127
128     pub const fn new(pass: Pass, condition: Condition) -> Self {
129         ConditionalPass { pass, condition }
130     }
131 }
132
133 /// A shorthand way to refer to which set of passes to use, based on the presence of
134 /// `--no-defaults` and `--show-coverage`.
135 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
136 pub enum DefaultPassOption {
137     Default,
138     Coverage,
139     None,
140 }
141
142 /// Returns the given default set of passes.
143 pub fn defaults(default_set: DefaultPassOption) -> &'static [ConditionalPass] {
144     match default_set {
145         DefaultPassOption::Default => DEFAULT_PASSES,
146         DefaultPassOption::Coverage => COVERAGE_PASSES,
147         DefaultPassOption::None => &[],
148     }
149 }
150
151 /// If the given name matches a known pass, returns its information.
152 pub fn find_pass(pass_name: &str) -> Option<Pass> {
153     PASSES.iter().find(|p| p.name == pass_name).copied()
154 }
155
156 /// Returns a span encompassing all the given attributes.
157 crate fn span_of_attrs(attrs: &clean::Attributes) -> Option<Span> {
158     if attrs.doc_strings.is_empty() {
159         return None;
160     }
161     let start = attrs.doc_strings[0].span;
162     if start == DUMMY_SP {
163         return None;
164     }
165     let end = attrs.doc_strings.last().expect("no doc strings provided").span;
166     Some(start.to(end))
167 }
168
169 /// Attempts to match a range of bytes from parsed markdown to a `Span` in the source code.
170 ///
171 /// This method will return `None` if we cannot construct a span from the source map or if the
172 /// attributes are not all sugared doc comments. It's difficult to calculate the correct span in
173 /// that case due to escaping and other source features.
174 crate fn source_span_for_markdown_range(
175     cx: &DocContext<'_>,
176     markdown: &str,
177     md_range: &Range<usize>,
178     attrs: &clean::Attributes,
179 ) -> Option<Span> {
180     let is_all_sugared_doc =
181         attrs.doc_strings.iter().all(|frag| frag.kind == DocFragmentKind::SugaredDoc);
182
183     if !is_all_sugared_doc {
184         return None;
185     }
186
187     let snippet = cx.sess().source_map().span_to_snippet(span_of_attrs(attrs)?).ok()?;
188
189     let starting_line = markdown[..md_range.start].matches('\n').count();
190     let ending_line = starting_line + markdown[md_range.start..md_range.end].matches('\n').count();
191
192     // We use `split_terminator('\n')` instead of `lines()` when counting bytes so that we treat
193     // CRLF and LF line endings the same way.
194     let mut src_lines = snippet.split_terminator('\n');
195     let md_lines = markdown.split_terminator('\n');
196
197     // The number of bytes from the source span to the markdown span that are not part
198     // of the markdown, like comment markers.
199     let mut start_bytes = 0;
200     let mut end_bytes = 0;
201
202     'outer: for (line_no, md_line) in md_lines.enumerate() {
203         loop {
204             let source_line = src_lines.next().expect("could not find markdown in source");
205             match source_line.find(md_line) {
206                 Some(offset) => {
207                     if line_no == starting_line {
208                         start_bytes += offset;
209
210                         if starting_line == ending_line {
211                             break 'outer;
212                         }
213                     } else if line_no == ending_line {
214                         end_bytes += offset;
215                         break 'outer;
216                     } else if line_no < starting_line {
217                         start_bytes += source_line.len() - md_line.len();
218                     } else {
219                         end_bytes += source_line.len() - md_line.len();
220                     }
221                     break;
222                 }
223                 None => {
224                     // Since this is a source line that doesn't include a markdown line,
225                     // we have to count the newline that we split from earlier.
226                     if line_no <= starting_line {
227                         start_bytes += source_line.len() + 1;
228                     } else {
229                         end_bytes += source_line.len() + 1;
230                     }
231                 }
232             }
233         }
234     }
235
236     Some(span_of_attrs(attrs)?.from_inner(InnerSpan::new(
237         md_range.start + start_bytes,
238         md_range.end + start_bytes + end_bytes,
239     )))
240 }