diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-02 18:36:06 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-02 18:36:06 -0600 |
| commit | 8cdfa445d6629ffef4cb84967ff7017654045bc2 (patch) | |
| tree | 22f0b0907c024c78d26a731e2e1f5219407d8102 /vendor/prettyplease/src/attr.rs | |
| parent | 4351c74c7c5f97156bc94d3a8549b9940ac80e3f (diff) | |
chore: add vendor directory
Diffstat (limited to 'vendor/prettyplease/src/attr.rs')
| -rw-r--r-- | vendor/prettyplease/src/attr.rs | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/vendor/prettyplease/src/attr.rs b/vendor/prettyplease/src/attr.rs new file mode 100644 index 00000000..b436283f --- /dev/null +++ b/vendor/prettyplease/src/attr.rs @@ -0,0 +1,288 @@ +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 && !value.ends_with('/') +} |
