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