use crate::ident::GetIdent;
#[cfg(feature = "parsing")]
use crate::meta::{self, MetaExt};
use syn::{parse_quote, Attribute, Ident, Meta, MetaList};
#[cfg(feature = "parsing")]
use syn::{punctuated::Punctuated, token::Paren, Result};
impl GetIdent for Attribute {
fn get_ident(&self) -> Option<&Ident> {
self.path.get_ident()
}
}
#[cfg(feature = "parsing")]
pub trait AttributeExt {
fn from_meta<M>(meta: M) -> Self
where
M: IntoAttribute;
fn try_meta_mut<F, R>(&mut self, f: F) -> Result<R>
where
F: FnOnce(&mut Meta) -> Result<R>;
fn promoted_list(&self) -> Result<MetaList>;
fn try_promoted_list_mut<F, R>(&mut self, paren: Paren, f: F) -> Result<R>
where
F: FnOnce(&mut MetaList) -> Result<R>;
}
#[cfg(feature = "parsing")]
impl AttributeExt for Attribute {
fn from_meta<M>(meta: M) -> Self
where
M: IntoAttribute,
{
meta.into_attribute()
}
fn try_meta_mut<F, R>(&mut self, f: F) -> Result<R>
where
F: FnOnce(&mut Meta) -> Result<R>,
{
let mut meta = self.parse_meta()?;
let result = f(&mut meta);
*self = Self::from_meta(meta);
result
}
fn promoted_list(&self) -> Result<MetaList> {
match self.parse_meta()? {
Meta::Path(path) => Ok(MetaList {
path,
paren_token: Default::default(),
nested: Punctuated::new(),
}),
Meta::List(metalist) => Ok(metalist),
other => Err(meta::err_promote_to_list(&other)),
}
}
fn try_promoted_list_mut<F, R>(&mut self, paren: Paren, f: F) -> Result<R>
where
F: FnOnce(&mut MetaList) -> Result<R>,
{
self.try_meta_mut(|meta| {
let metalist = meta.promote_to_list(paren)?;
f(metalist)
})
}
}
#[cfg(feature = "parsing")]
pub trait AttributeIteratorExt {
fn doc(self) -> Option<String>;
}
#[cfg(feature = "parsing")]
impl<'a, I> AttributeIteratorExt for I
where
I: std::iter::IntoIterator<Item = &'a Attribute>,
{
fn doc(self) -> Option<String> {
let items: Vec<_> = self
.into_iter()
.filter_map(|attr| attr.parse_meta().ok().and_then(|m| m.doc().ok()))
.collect();
if items.is_empty() {
None
} else {
Some(items.join("\n"))
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::assert_quote_eq;
use quote::quote;
#[cfg(feature = "parsing")]
fn test_meta_round_trip(attr: Attribute) -> Result<()> {
let meta = attr.parse_meta()?;
let created = Attribute::from_meta(meta);
assert_eq!(
quote! { #attr }.to_string(),
quote! { #created }.to_string()
);
Ok(())
}
#[cfg(feature = "parsing")]
#[test]
fn run_test_meta_round_trip() {
use syn::parse_quote;
test_meta_round_trip(parse_quote! { #[cfg(test)] }).unwrap();
test_meta_round_trip(parse_quote! { #[feature = "full"] }).unwrap();
test_meta_round_trip(parse_quote! { #[cfg(all(a,b,any(c,d)))] }).unwrap();
test_meta_round_trip(parse_quote! { #[a(b="1",d)] }).unwrap();
test_meta_round_trip(parse_quote! { #[abc::de::ef] }).unwrap();
}
#[cfg(feature = "parsing")]
#[test]
fn test_try_meta_mut() {
let mut attr: Attribute = parse_quote! { #[cfg(test)] };
attr.try_meta_mut(|meta| match meta {
Meta::List(metalist) => {
metalist.path = parse_quote! { newcfg };
Ok(())
}
_ => unreachable!(),
})
.unwrap();
let expected: Attribute = parse_quote! { #[newcfg(test)] };
assert_quote_eq!(attr, expected);
attr.try_meta_mut(|meta| match meta {
Meta::List(metalist) => {
metalist.nested.pop();
metalist.nested.push(parse_quote!(a));
metalist.nested.push(parse_quote!(b = "c"));
metalist.nested.push(parse_quote!("d"));
Ok(())
}
_ => unreachable!(),
})
.unwrap();
let expected: Attribute = parse_quote! { #[newcfg(a, b="c", "d")] };
assert_quote_eq!(attr, expected);
}
#[test]
#[cfg(feature = "parsing")]
fn test_promoted_list() {
let attr: Attribute = parse_quote! { #[derive] };
let list = attr.promoted_list().unwrap();
assert_quote_eq!(attr.path, list.path);
assert!(list.nested.is_empty());
}
#[cfg(all(feature = "parsing", feature = "full"))]
#[test]
fn test_doc() {
let func: syn::ItemFn = parse_quote! {
#[derive]
#[test]
#[doc = "doc line 2"]
#[cfg]
fn f() {}
};
let doc = func.attrs.doc().unwrap();
assert_eq!(doc, "doc line 1\ndoc line 2");
}
}
pub trait IntoAttribute {
fn into_attribute(self) -> Attribute;
}
impl IntoAttribute for Meta {
fn into_attribute(self) -> Attribute {
parse_quote!( #[ #self ] )
}
}
impl IntoAttribute for MetaList {
fn into_attribute(self) -> Attribute {
Meta::List(self).into_attribute()
}
}