1 use crate::algorithm::Printer;
2 use crate::path::PathKind;
3 use crate::INDENT;
4 use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
5 use syn::{AttrStyle, Attribute, Expr, Lit, MacroDelimiter, Meta, MetaList, MetaNameValue};
6 
7 impl Printer {
outer_attrs(&mut self, attrs: &[Attribute])8     pub fn outer_attrs(&mut self, attrs: &[Attribute]) {
9         for attr in attrs {
10             if let AttrStyle::Outer = attr.style {
11                 self.attr(attr);
12             }
13         }
14     }
15 
inner_attrs(&mut self, attrs: &[Attribute])16     pub fn inner_attrs(&mut self, attrs: &[Attribute]) {
17         for attr in attrs {
18             if let AttrStyle::Inner(_) = attr.style {
19                 self.attr(attr);
20             }
21         }
22     }
23 
attr(&mut self, attr: &Attribute)24     fn attr(&mut self, attr: &Attribute) {
25         if let Some(mut doc) = value_of_attribute("doc", attr) {
26             if !doc.contains('\n')
27                 && match attr.style {
28                     AttrStyle::Outer => !doc.starts_with('/'),
29                     AttrStyle::Inner(_) => true,
30                 }
31             {
32                 trim_trailing_spaces(&mut doc);
33                 self.word(match attr.style {
34                     AttrStyle::Outer => "///",
35                     AttrStyle::Inner(_) => "//!",
36                 });
37                 self.word(doc);
38                 self.hardbreak();
39                 return;
40             } else if can_be_block_comment(&doc)
41                 && match attr.style {
42                     AttrStyle::Outer => !doc.starts_with(&['*', '/'][..]),
43                     AttrStyle::Inner(_) => true,
44                 }
45             {
46                 trim_interior_trailing_spaces(&mut doc);
47                 self.word(match attr.style {
48                     AttrStyle::Outer => "/**",
49                     AttrStyle::Inner(_) => "/*!",
50                 });
51                 self.word(doc);
52                 self.word("*/");
53                 self.hardbreak();
54                 return;
55             }
56         } else if let Some(mut comment) = value_of_attribute("comment", attr) {
57             if !comment.contains('\n') {
58                 trim_trailing_spaces(&mut comment);
59                 self.word("//");
60                 self.word(comment);
61                 self.hardbreak();
62                 return;
63             } else if can_be_block_comment(&comment) && !comment.starts_with(&['*', '!'][..]) {
64                 trim_interior_trailing_spaces(&mut comment);
65                 self.word("/*");
66                 self.word(comment);
67                 self.word("*/");
68                 self.hardbreak();
69                 return;
70             }
71         }
72 
73         self.word(match attr.style {
74             AttrStyle::Outer => "#",
75             AttrStyle::Inner(_) => "#!",
76         });
77         self.word("[");
78         self.meta(&attr.meta);
79         self.word("]");
80         self.space();
81     }
82 
meta(&mut self, meta: &Meta)83     fn meta(&mut self, meta: &Meta) {
84         match meta {
85             Meta::Path(path) => self.path(path, PathKind::Simple),
86             Meta::List(meta) => self.meta_list(meta),
87             Meta::NameValue(meta) => self.meta_name_value(meta),
88         }
89     }
90 
meta_list(&mut self, meta: &MetaList)91     fn meta_list(&mut self, meta: &MetaList) {
92         self.path(&meta.path, PathKind::Simple);
93         let delimiter = match meta.delimiter {
94             MacroDelimiter::Paren(_) => Delimiter::Parenthesis,
95             MacroDelimiter::Brace(_) => Delimiter::Brace,
96             MacroDelimiter::Bracket(_) => Delimiter::Bracket,
97         };
98         let group = Group::new(delimiter, meta.tokens.clone());
99         self.attr_tokens(TokenStream::from(TokenTree::Group(group)));
100     }
101 
meta_name_value(&mut self, meta: &MetaNameValue)102     fn meta_name_value(&mut self, meta: &MetaNameValue) {
103         self.path(&meta.path, PathKind::Simple);
104         self.word(" = ");
105         self.expr(&meta.value);
106     }
107 
attr_tokens(&mut self, tokens: TokenStream)108     fn attr_tokens(&mut self, tokens: TokenStream) {
109         let mut stack = Vec::new();
110         stack.push((tokens.into_iter().peekable(), Delimiter::None));
111         let mut space = Self::nbsp as fn(&mut Self);
112 
113         #[derive(PartialEq)]
114         enum State {
115             Word,
116             Punct,
117             TrailingComma,
118         }
119 
120         use State::*;
121         let mut state = Word;
122 
123         while let Some((tokens, delimiter)) = stack.last_mut() {
124             match tokens.next() {
125                 Some(TokenTree::Ident(ident)) => {
126                     if let Word = state {
127                         space(self);
128                     }
129                     self.ident(&ident);
130                     state = Word;
131                 }
132                 Some(TokenTree::Punct(punct)) => {
133                     let ch = punct.as_char();
134                     if let (Word, '=') = (state, ch) {
135                         self.nbsp();
136                     }
137                     if ch == ',' && tokens.peek().is_none() {
138                         self.trailing_comma(true);
139                         state = TrailingComma;
140                     } else {
141                         self.token_punct(ch);
142                         if ch == '=' {
143                             self.nbsp();
144                         } else if ch == ',' {
145                             space(self);
146                         }
147                         state = Punct;
148                     }
149                 }
150                 Some(TokenTree::Literal(literal)) => {
151                     if let Word = state {
152                         space(self);
153                     }
154                     self.token_literal(&literal);
155                     state = Word;
156                 }
157                 Some(TokenTree::Group(group)) => {
158                     let delimiter = group.delimiter();
159                     let stream = group.stream();
160                     match delimiter {
161                         Delimiter::Parenthesis => {
162                             self.word("(");
163                             self.cbox(INDENT);
164                             self.zerobreak();
165                             state = Punct;
166                         }
167                         Delimiter::Brace => {
168                             self.word("{");
169                             state = Punct;
170                         }
171                         Delimiter::Bracket => {
172                             self.word("[");
173                             state = Punct;
174                         }
175                         Delimiter::None => {}
176                     }
177                     stack.push((stream.into_iter().peekable(), delimiter));
178                     space = Self::space;
179                 }
180                 None => {
181                     match delimiter {
182                         Delimiter::Parenthesis => {
183                             if state != TrailingComma {
184                                 self.zerobreak();
185                             }
186                             self.offset(-INDENT);
187                             self.end();
188                             self.word(")");
189                             state = Punct;
190                         }
191                         Delimiter::Brace => {
192                             self.word("}");
193                             state = Punct;
194                         }
195                         Delimiter::Bracket => {
196                             self.word("]");
197                             state = Punct;
198                         }
199                         Delimiter::None => {}
200                     }
201                     stack.pop();
202                     if stack.is_empty() {
203                         space = Self::nbsp;
204                     }
205                 }
206             }
207         }
208     }
209 }
210 
value_of_attribute(requested: &str, attr: &Attribute) -> Option<String>211 fn value_of_attribute(requested: &str, attr: &Attribute) -> Option<String> {
212     let value = match &attr.meta {
213         Meta::NameValue(meta) if meta.path.is_ident(requested) => &meta.value,
214         _ => return None,
215     };
216     let lit = match value {
217         Expr::Lit(expr) if expr.attrs.is_empty() => &expr.lit,
218         _ => return None,
219     };
220     match lit {
221         Lit::Str(string) => Some(string.value()),
222         _ => None,
223     }
224 }
225 
has_outer(attrs: &[Attribute]) -> bool226 pub fn has_outer(attrs: &[Attribute]) -> bool {
227     for attr in attrs {
228         if let AttrStyle::Outer = attr.style {
229             return true;
230         }
231     }
232     false
233 }
234 
has_inner(attrs: &[Attribute]) -> bool235 pub fn has_inner(attrs: &[Attribute]) -> bool {
236     for attr in attrs {
237         if let AttrStyle::Inner(_) = attr.style {
238             return true;
239         }
240     }
241     false
242 }
243 
trim_trailing_spaces(doc: &mut String)244 fn trim_trailing_spaces(doc: &mut String) {
245     doc.truncate(doc.trim_end_matches(' ').len());
246 }
247 
trim_interior_trailing_spaces(doc: &mut String)248 fn trim_interior_trailing_spaces(doc: &mut String) {
249     if !doc.contains(" \n") {
250         return;
251     }
252     let mut trimmed = String::with_capacity(doc.len());
253     let mut lines = doc.split('\n').peekable();
254     while let Some(line) = lines.next() {
255         if lines.peek().is_some() {
256             trimmed.push_str(line.trim_end_matches(' '));
257             trimmed.push('\n');
258         } else {
259             trimmed.push_str(line);
260         }
261     }
262     *doc = trimmed;
263 }
264 
can_be_block_comment(value: &str) -> bool265 fn can_be_block_comment(value: &str) -> bool {
266     let mut depth = 0usize;
267     let bytes = value.as_bytes();
268     let mut i = 0usize;
269     let upper = bytes.len() - 1;
270 
271     while i < upper {
272         if bytes[i] == b'/' && bytes[i + 1] == b'*' {
273             depth += 1;
274             i += 2;
275         } else if bytes[i] == b'*' && bytes[i + 1] == b'/' {
276             if depth == 0 {
277                 return false;
278             }
279             depth -= 1;
280             i += 2;
281         } else {
282             i += 1;
283         }
284     }
285 
286     depth == 0
287 }
288