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}