]>
Commit | Line | Data |
---|---|---|
9e0c209e | 1 | use std::cmp; |
9e0c209e | 2 | |
29967ef6 | 3 | use crate::clean::{self, DocFragment, DocFragmentKind, Item}; |
532ac7d7 | 4 | use crate::core::DocContext; |
9fa01778 XL |
5 | use crate::fold::{self, DocFolder}; |
6 | use crate::passes::Pass; | |
b7449926 | 7 | |
416331ca XL |
8 | #[cfg(test)] |
9 | mod tests; | |
10 | ||
fc512014 | 11 | crate const UNINDENT_COMMENTS: Pass = Pass { |
532ac7d7 | 12 | name: "unindent-comments", |
60c5eb7d | 13 | run: unindent_comments, |
532ac7d7 XL |
14 | description: "removes excess indentation on comments in order for markdown to like it", |
15 | }; | |
9e0c209e | 16 | |
6a06907d | 17 | crate fn unindent_comments(krate: clean::Crate, _: &mut DocContext<'_>) -> clean::Crate { |
476ff2be | 18 | CommentCleaner.fold_crate(krate) |
9e0c209e SL |
19 | } |
20 | ||
21 | struct CommentCleaner; | |
22 | ||
23 | impl fold::DocFolder for CommentCleaner { | |
24 | fn fold_item(&mut self, mut i: Item) -> Option<Item> { | |
476ff2be | 25 | i.attrs.unindent_doc_comments(); |
fc512014 | 26 | Some(self.fold_item_recur(i)) |
9e0c209e SL |
27 | } |
28 | } | |
29 | ||
476ff2be | 30 | impl clean::Attributes { |
fc512014 | 31 | crate fn unindent_doc_comments(&mut self) { |
ff7c6d11 XL |
32 | unindent_fragments(&mut self.doc_strings); |
33 | } | |
34 | } | |
35 | ||
36 | fn unindent_fragments(docs: &mut Vec<DocFragment>) { | |
29967ef6 XL |
37 | // `add` is used in case the most common sugared doc syntax is used ("/// "). The other |
38 | // fragments kind's lines are never starting with a whitespace unless they are using some | |
39 | // markdown formatting requiring it. Therefore, if the doc block have a mix between the two, | |
40 | // we need to take into account the fact that the minimum indent minus one (to take this | |
41 | // whitespace into account). | |
42 | // | |
43 | // For example: | |
44 | // | |
45 | // /// hello! | |
46 | // #[doc = "another"] | |
47 | // | |
48 | // In this case, you want "hello! another" and not "hello! another". | |
49 | let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind) | |
50 | && docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc) | |
51 | { | |
52 | // In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to | |
53 | // "decide" how much the minimum indent will be. | |
54 | 1 | |
55 | } else { | |
56 | 0 | |
57 | }; | |
9e0c209e | 58 | |
29967ef6 XL |
59 | // `min_indent` is used to know how much whitespaces from the start of each lines must be |
60 | // removed. Example: | |
61 | // | |
62 | // /// hello! | |
63 | // #[doc = "another"] | |
64 | // | |
65 | // In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum | |
66 | // 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4 | |
67 | // (5 - 1) whitespaces. | |
68 | let min_indent = match docs | |
69 | .iter() | |
70 | .map(|fragment| { | |
5869c6ff | 71 | fragment.doc.as_str().lines().fold(usize::MAX, |min_indent, line| { |
29967ef6 XL |
72 | if line.chars().all(|c| c.is_whitespace()) { |
73 | min_indent | |
74 | } else { | |
75 | // Compare against either space or tab, ignoring whether they are | |
76 | // mixed or not. | |
77 | let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count(); | |
78 | cmp::min(min_indent, whitespace) | |
79 | + if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add } | |
80 | } | |
81 | }) | |
82 | }) | |
83 | .min() | |
84 | { | |
85 | Some(x) => x, | |
86 | None => return, | |
87 | }; | |
9e0c209e | 88 | |
29967ef6 | 89 | for fragment in docs { |
5869c6ff | 90 | if fragment.doc.as_str().lines().count() == 0 { |
29967ef6 | 91 | continue; |
9e0c209e SL |
92 | } |
93 | ||
29967ef6 XL |
94 | let min_indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 { |
95 | min_indent - add | |
9e0c209e | 96 | } else { |
29967ef6 XL |
97 | min_indent |
98 | }; | |
99 | ||
5869c6ff | 100 | fragment.indent = min_indent; |
9e0c209e SL |
101 | } |
102 | } |