use syn::token;
use super::helpers::{ident, try_parse_path};
use super::{ToSyn, ToSynError};
use crate::pure::ast::{PureAttrMeta, PureAttribute, PureVis};
impl ToSyn for PureAttribute {
type Output = syn::Attribute;
fn to_syn(&self) -> Result<syn::Attribute, ToSynError> {
let meta = match &self.meta {
PureAttrMeta::Path => syn::Meta::Path(try_parse_path(&self.path)?),
PureAttrMeta::List(args) => {
let tokens: proc_macro2::TokenStream =
args.parse()
.map_err(|e: proc_macro2::LexError| ToSynError::Other {
message: format!("Failed to parse attribute args '{}': {}", args, e),
})?;
syn::Meta::List(syn::MetaList {
path: try_parse_path(&self.path)?,
delimiter: syn::MacroDelimiter::Paren(token::Paren::default()),
tokens,
})
}
PureAttrMeta::NameValue(value) => {
let value_expr: syn::Expr =
syn::parse_str(value).map_err(|e| ToSynError::Other {
message: format!("Failed to parse attribute value '{}': {}", value, e),
})?;
syn::Meta::NameValue(syn::MetaNameValue {
path: try_parse_path(&self.path)?,
eq_token: token::Eq::default(),
value: value_expr,
})
}
};
Ok(syn::Attribute {
pound_token: token::Pound::default(),
style: if self.is_inner {
syn::AttrStyle::Inner(token::Not::default())
} else {
syn::AttrStyle::Outer
},
bracket_token: token::Bracket::default(),
meta,
})
}
}
impl ToSyn for PureVis {
type Output = syn::Visibility;
fn to_syn(&self) -> Result<syn::Visibility, ToSynError> {
match self {
PureVis::Private => Ok(syn::Visibility::Inherited),
PureVis::Public => Ok(syn::Visibility::Public(token::Pub::default())),
PureVis::Crate => Ok(syn::Visibility::Restricted(syn::VisRestricted {
pub_token: token::Pub::default(),
paren_token: token::Paren::default(),
in_token: None,
path: Box::new(syn::Path {
leading_colon: None,
segments: std::iter::once(syn::PathSegment {
ident: ident("crate"),
arguments: syn::PathArguments::None,
})
.collect(),
}),
})),
PureVis::Super => Ok(syn::Visibility::Restricted(syn::VisRestricted {
pub_token: token::Pub::default(),
paren_token: token::Paren::default(),
in_token: None,
path: Box::new(syn::Path {
leading_colon: None,
segments: std::iter::once(syn::PathSegment {
ident: ident("super"),
arguments: syn::PathArguments::None,
})
.collect(),
}),
})),
PureVis::In(path) => Ok(syn::Visibility::Restricted(syn::VisRestricted {
pub_token: token::Pub::default(),
paren_token: token::Paren::default(),
in_token: Some(token::In::default()),
path: Box::new(try_parse_path(path)?),
})),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::ToTokens;
#[test]
fn test_pure_attribute_path() {
let attr = PureAttribute {
path: "test".to_string(),
meta: PureAttrMeta::Path,
is_inner: false,
};
let syn_attr = attr.to_syn().unwrap();
let output = syn_attr.to_token_stream().to_string();
assert!(output.contains("test"), "Output: {}", output);
assert!(!output.contains("("), "Should not have parens: {}", output);
}
#[test]
fn test_pure_attribute_list() {
let attr = PureAttribute {
path: "derive".to_string(),
meta: PureAttrMeta::List("Debug, Clone".to_string()),
is_inner: false,
};
let syn_attr = attr.to_syn().unwrap();
let output = syn_attr.to_token_stream().to_string();
assert!(output.contains("derive"), "Output: {}", output);
assert!(output.contains("Debug"), "Output: {}", output);
assert!(output.contains("Clone"), "Output: {}", output);
}
#[test]
fn test_pure_attribute_name_value() {
let attr = PureAttribute {
path: "doc".to_string(),
meta: PureAttrMeta::NameValue("\"This is a comment\"".to_string()),
is_inner: false,
};
let syn_attr = attr.to_syn().unwrap();
let output = syn_attr.to_token_stream().to_string();
assert!(output.contains("doc"), "Output: {}", output);
assert!(output.contains("="), "Output: {}", output);
assert!(output.contains("comment"), "Output: {}", output);
}
#[test]
fn test_pure_attribute_inner() {
let attr = PureAttribute {
path: "allow".to_string(),
meta: PureAttrMeta::List("unused".to_string()),
is_inner: true,
};
let syn_attr = attr.to_syn().unwrap();
let output = syn_attr.to_token_stream().to_string();
assert!(
output.contains("# !") || output.contains("#!"),
"Should be inner: {}",
output
);
}
#[test]
fn test_pure_vis_public() {
let vis = PureVis::Public;
let syn_vis = vis.to_syn().unwrap();
let output = syn_vis.to_token_stream().to_string();
assert_eq!(output.trim(), "pub");
}
#[test]
fn test_pure_vis_crate() {
let vis = PureVis::Crate;
let syn_vis = vis.to_syn().unwrap();
let output = syn_vis.to_token_stream().to_string();
assert!(output.contains("crate"), "Output: {}", output);
}
}