prettyplease/
attr.rs

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 = 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
226pub 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
235pub 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
244fn trim_trailing_spaces(doc: &mut String) {
245    doc.truncate(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::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
265fn 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}