1use crate::algorithm::Printer;
2use crate::path::PathKind;
3use crate::INDENT;
4use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
5use syn::{AttrStyle, Attribute, Expr, Lit, MacroDelimiter, Meta, MetaList, MetaNameValue};
6
7impl Printer {
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
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
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
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
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
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
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
211fn value_of_attribute(requested: &str, attr: &Attribute) -> Option<String> {
212 let value: &Expr = match &attr.meta {
213 Meta::NameValue(meta: &MetaNameValue) if meta.path.is_ident(requested) => &meta.value,
214 _ => return None,
215 };
216 let lit: &Lit = match value {
217 Expr::Lit(expr: &ExprLit) if expr.attrs.is_empty() => &expr.lit,
218 _ => return None,
219 };
220 match lit {
221 Lit::Str(string: &LitStr) => Some(string.value()),
222 _ => None,
223 }
224}
225
226pub fn has_outer(attrs: &[Attribute]) -> bool {
227 for attr: &Attribute in attrs {
228 if let AttrStyle::Outer = attr.style {
229 return true;
230 }
231 }
232 false
233}
234
235pub fn has_inner(attrs: &[Attribute]) -> bool {
236 for attr: &Attribute in attrs {
237 if let AttrStyle::Inner(_) = attr.style {
238 return true;
239 }
240 }
241 false
242}
243
244fn trim_trailing_spaces(doc: &mut String) {
245 doc.truncate(new_len:doc.trim_end_matches(' ').len());
246}
247
248fn trim_interior_trailing_spaces(doc: &mut String) {
249 if !doc.contains(" \n") {
250 return;
251 }
252 let mut trimmed: String = String::with_capacity(doc.len());
253 let mut lines: impl Iterator = doc.split('\n').peekable();
254 while let Some(line: &str) = lines.next() {
255 if lines.peek().is_some() {
256 trimmed.push_str(string:line.trim_end_matches(' '));
257 trimmed.push(ch:'\n');
258 } else {
259 trimmed.push_str(string:line);
260 }
261 }
262 *doc = trimmed;
263}
264
265fn can_be_block_comment(value: &str) -> bool {
266 let mut depth: usize = 0usize;
267 let bytes: &[u8] = value.as_bytes();
268 let mut i: usize = 0usize;
269 let upper: usize = 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