| use crate::algorithm::Printer; |
| use crate::fixup::FixupContext; |
| use crate::path::PathKind; |
| use crate::INDENT; |
| use proc_macro2::{Delimiter, Group, TokenStream, TokenTree}; |
| use syn::{AttrStyle, Attribute, Expr, Lit, MacroDelimiter, Meta, MetaList, MetaNameValue}; |
| |
| impl Printer { |
| pub fn outer_attrs(&mut self, attrs: &[Attribute]) { |
| for attr in attrs { |
| if let AttrStyle::Outer = attr.style { |
| self.attr(attr); |
| } |
| } |
| } |
| |
| pub fn inner_attrs(&mut self, attrs: &[Attribute]) { |
| for attr in attrs { |
| if let AttrStyle::Inner(_) = attr.style { |
| self.attr(attr); |
| } |
| } |
| } |
| |
| fn attr(&mut self, attr: &Attribute) { |
| if let Some(mut doc) = value_of_attribute("doc", attr) { |
| if !doc.contains('\n') |
| && match attr.style { |
| AttrStyle::Outer => !doc.starts_with('/'), |
| AttrStyle::Inner(_) => true, |
| } |
| { |
| trim_trailing_spaces(&mut doc); |
| self.word(match attr.style { |
| AttrStyle::Outer => "///", |
| AttrStyle::Inner(_) => "//!", |
| }); |
| self.word(doc); |
| self.hardbreak(); |
| return; |
| } else if can_be_block_comment(&doc) |
| && match attr.style { |
| AttrStyle::Outer => !doc.starts_with(&['*', '/'][..]), |
| AttrStyle::Inner(_) => true, |
| } |
| { |
| trim_interior_trailing_spaces(&mut doc); |
| self.word(match attr.style { |
| AttrStyle::Outer => "/**", |
| AttrStyle::Inner(_) => "/*!", |
| }); |
| self.word(doc); |
| self.word("*/"); |
| self.hardbreak(); |
| return; |
| } |
| } else if let Some(mut comment) = value_of_attribute("comment", attr) { |
| if !comment.contains('\n') { |
| trim_trailing_spaces(&mut comment); |
| self.word("//"); |
| self.word(comment); |
| self.hardbreak(); |
| return; |
| } else if can_be_block_comment(&comment) && !comment.starts_with(&['*', '!'][..]) { |
| trim_interior_trailing_spaces(&mut comment); |
| self.word("/*"); |
| self.word(comment); |
| self.word("*/"); |
| self.hardbreak(); |
| return; |
| } |
| } |
| |
| self.word(match attr.style { |
| AttrStyle::Outer => "#", |
| AttrStyle::Inner(_) => "#!", |
| }); |
| self.word("["); |
| self.meta(&attr.meta); |
| self.word("]"); |
| self.space(); |
| } |
| |
| fn meta(&mut self, meta: &Meta) { |
| match meta { |
| Meta::Path(path) => self.path(path, PathKind::Simple), |
| Meta::List(meta) => self.meta_list(meta), |
| Meta::NameValue(meta) => self.meta_name_value(meta), |
| } |
| } |
| |
| fn meta_list(&mut self, meta: &MetaList) { |
| self.path(&meta.path, PathKind::Simple); |
| let delimiter = match meta.delimiter { |
| MacroDelimiter::Paren(_) => Delimiter::Parenthesis, |
| MacroDelimiter::Brace(_) => Delimiter::Brace, |
| MacroDelimiter::Bracket(_) => Delimiter::Bracket, |
| }; |
| let group = Group::new(delimiter, meta.tokens.clone()); |
| self.attr_tokens(TokenStream::from(TokenTree::Group(group))); |
| } |
| |
| fn meta_name_value(&mut self, meta: &MetaNameValue) { |
| self.path(&meta.path, PathKind::Simple); |
| self.word(" = "); |
| self.expr(&meta.value, FixupContext::NONE); |
| } |
| |
| fn attr_tokens(&mut self, tokens: TokenStream) { |
| let mut stack = Vec::new(); |
| stack.push((tokens.into_iter().peekable(), Delimiter::None)); |
| let mut space = Self::nbsp as fn(&mut Self); |
| |
| #[derive(PartialEq)] |
| enum State { |
| Word, |
| Punct, |
| TrailingComma, |
| } |
| |
| use State::*; |
| let mut state = Word; |
| |
| while let Some((tokens, delimiter)) = stack.last_mut() { |
| match tokens.next() { |
| Some(TokenTree::Ident(ident)) => { |
| if let Word = state { |
| space(self); |
| } |
| self.ident(&ident); |
| state = Word; |
| } |
| Some(TokenTree::Punct(punct)) => { |
| let ch = punct.as_char(); |
| if let (Word, '=') = (state, ch) { |
| self.nbsp(); |
| } |
| if ch == ',' && tokens.peek().is_none() { |
| self.trailing_comma(true); |
| state = TrailingComma; |
| } else { |
| self.token_punct(ch); |
| if ch == '=' { |
| self.nbsp(); |
| } else if ch == ',' { |
| space(self); |
| } |
| state = Punct; |
| } |
| } |
| Some(TokenTree::Literal(literal)) => { |
| if let Word = state { |
| space(self); |
| } |
| self.token_literal(&literal); |
| state = Word; |
| } |
| Some(TokenTree::Group(group)) => { |
| let delimiter = group.delimiter(); |
| let stream = group.stream(); |
| match delimiter { |
| Delimiter::Parenthesis => { |
| self.word("("); |
| self.cbox(INDENT); |
| self.zerobreak(); |
| state = Punct; |
| } |
| Delimiter::Brace => { |
| self.word("{"); |
| state = Punct; |
| } |
| Delimiter::Bracket => { |
| self.word("["); |
| state = Punct; |
| } |
| Delimiter::None => {} |
| } |
| stack.push((stream.into_iter().peekable(), delimiter)); |
| space = Self::space; |
| } |
| None => { |
| match delimiter { |
| Delimiter::Parenthesis => { |
| if state != TrailingComma { |
| self.zerobreak(); |
| } |
| self.offset(-INDENT); |
| self.end(); |
| self.word(")"); |
| state = Punct; |
| } |
| Delimiter::Brace => { |
| self.word("}"); |
| state = Punct; |
| } |
| Delimiter::Bracket => { |
| self.word("]"); |
| state = Punct; |
| } |
| Delimiter::None => {} |
| } |
| stack.pop(); |
| if stack.is_empty() { |
| space = Self::nbsp; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| fn value_of_attribute(requested: &str, attr: &Attribute) -> Option<String> { |
| let value = match &attr.meta { |
| Meta::NameValue(meta) if meta.path.is_ident(requested) => &meta.value, |
| _ => return None, |
| }; |
| let lit = match value { |
| Expr::Lit(expr) if expr.attrs.is_empty() => &expr.lit, |
| _ => return None, |
| }; |
| match lit { |
| Lit::Str(string) => Some(string.value()), |
| _ => None, |
| } |
| } |
| |
| pub fn has_outer(attrs: &[Attribute]) -> bool { |
| for attr in attrs { |
| if let AttrStyle::Outer = attr.style { |
| return true; |
| } |
| } |
| false |
| } |
| |
| pub fn has_inner(attrs: &[Attribute]) -> bool { |
| for attr in attrs { |
| if let AttrStyle::Inner(_) = attr.style { |
| return true; |
| } |
| } |
| false |
| } |
| |
| fn trim_trailing_spaces(doc: &mut String) { |
| doc.truncate(doc.trim_end_matches(' ').len()); |
| } |
| |
| fn trim_interior_trailing_spaces(doc: &mut String) { |
| if !doc.contains(" \n") { |
| return; |
| } |
| let mut trimmed = String::with_capacity(doc.len()); |
| let mut lines = doc.split('\n').peekable(); |
| while let Some(line) = lines.next() { |
| if lines.peek().is_some() { |
| trimmed.push_str(line.trim_end_matches(' ')); |
| trimmed.push('\n'); |
| } else { |
| trimmed.push_str(line); |
| } |
| } |
| *doc = trimmed; |
| } |
| |
| fn can_be_block_comment(value: &str) -> bool { |
| let mut depth = 0usize; |
| let bytes = value.as_bytes(); |
| let mut i = 0usize; |
| let upper = bytes.len() - 1; |
| |
| while i < upper { |
| if bytes[i] == b'/' && bytes[i + 1] == b'*' { |
| depth += 1; |
| i += 2; |
| } else if bytes[i] == b'*' && bytes[i + 1] == b'/' { |
| if depth == 0 { |
| return false; |
| } |
| depth -= 1; |
| i += 2; |
| } else { |
| i += 1; |
| } |
| } |
| |
| depth == 0 |
| } |