use darling::FromAttributes;
use proc_macro2::Span;
use syn::{
parse::Parse,
punctuated::{Pair, Punctuated},
spanned::Spanned,
};
use crate::err::RecordError;
#[derive(FromAttributes, Default)]
#[darling(attributes(props))]
pub struct PropsOptions {
}
pub struct PropsDefinition {
pub attrs: Vec<syn::Attribute>,
pub vis: syn::Visibility,
pub struct_token: syn::Token![struct],
pub ident: syn::Ident,
pub generics: syn::Generics,
pub fields: PropsFields,
}
pub struct PropsDefinitionWithOptions {
pub errors: Vec<darling::Error>,
pub options: PropsOptions,
pub definition: PropsDefinition,
}
impl PropsOptions {
pub fn parse_inner_attrs_record<R: RecordError<darling::Error>>(
input: syn::parse::ParseStream,
recorder: &mut R,
) -> Self {
match syn::Attribute::parse_inner(&input) {
Ok(attrs) => {
for attr in &attrs {
let ident = attr.path.get_ident().map(syn::Ident::to_string);
let ident = ident.as_ref().map_or("", |s| s.as_str());
if ident != "props" {
recorder.record_error(
darling::Error::custom(
"def_props inner attribute must be `props(options)`",
)
.with_span(&attr.bracket_token.span),
);
}
}
match PropsOptions::from_attributes(&attrs) {
Ok(v) => v,
Err(error) => {
recorder.record_error(error);
Default::default()
}
}
}
Err(error) => {
recorder.record_error(error.into());
PropsOptions::default()
}
}
}
}
impl Parse for PropsDefinition {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut attrs = input.call(syn::Attribute::parse_outer)?;
let vis = input.parse::<syn::Visibility>()?;
let struct_token = input.parse::<syn::Token![struct]>()?;
let ident = input.parse::<syn::Ident>()?;
let generics = input.parse::<syn::Generics>()?;
let (where_clause, fields) = data_struct(input, &mut attrs)?;
Ok(PropsDefinition {
attrs,
vis,
struct_token,
ident,
generics: syn::Generics {
where_clause,
..generics
},
fields,
})
}
}
pub fn data_struct(
input: syn::parse::ParseStream,
attrs: &mut Vec<syn::Attribute>,
) -> syn::Result<(Option<syn::WhereClause>, PropsFields)> {
let where_clause: Option<syn::WhereClause> = input.parse()?;
if input.peek(syn::token::Brace) {
let fields = parse_braced(input, attrs)?;
Ok((where_clause, fields))
} else {
Err(syn::Error::new(
input.span(),
"Expect braced fields { }. Props can only be a struct with named fields.",
))
}
}
pub fn parse_braced(
input: syn::parse::ParseStream,
attrs: &mut Vec<syn::Attribute>,
) -> syn::Result<PropsFields> {
let content;
let brace_token = syn::braced!(content in input);
attrs.extend(syn::Attribute::parse_inner(&content)?);
let named = content.parse_terminated(PropsField::parse_named)?;
Ok(PropsFields { brace_token, named })
}
pub struct PropsFields {
pub brace_token: syn::token::Brace,
pub named: Punctuated<PropsField, syn::Token![,]>,
}
pub struct PropsField {
pub attrs: Vec<syn::Attribute>,
pub vis: syn::Visibility,
pub ident: syn::Ident,
pub question: Option<syn::Token![?]>,
pub type_and_builder: PropsFieldTypeAndBuilder,
}
impl PropsField {
pub fn parse_named(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self {
attrs: input.call(syn::Attribute::parse_outer)?,
vis: input.parse()?,
ident: input.parse()?,
question: input.parse()?,
type_and_builder: input.parse()?,
})
}
}
pub enum PropsFieldTypeAndBuilder {
ImplicitBuilder {
colon: syn::Token![:],
ty: syn::Type,
},
Explicit(PropsFieldTypeAndBuilderExplicit),
ImplicitTypeAndBuilder,
}
pub struct PropsFieldTypeAndBuilderExplicit {
pub ty: syn::Type,
pub builder: PropsFieldBuilder,
}
#[derive(Clone)]
pub struct PropsFieldBuilder {
pub generics: syn::Generics,
pub paren: syn::token::Paren,
pub fn_arg: Pair<syn::PatType, syn::Token![,]>,
pub arrow: syn::Token![->],
pub fn_block: syn::Block,
}
impl PropsFieldBuilder {
pub fn new(span: Span, arg_ty: syn::Type) -> Self {
let id = syn::Ident::new("prop_value", span);
let fn_arg = syn::PatType {
attrs: vec![],
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: id.clone(),
subpat: None,
})),
colon_token: syn::Token,
ty: Box::new(arg_ty),
};
let fn_arg: Pair<syn::PatType, syn::Token![,]> = Pair::End(fn_arg);
let paren = syn::token::Paren { span };
Self {
generics: syn::Generics::default(),
paren,
fn_arg,
arrow: syn::Token,
fn_block: syn::Block {
brace_token: syn::token::Brace { span },
stmts: vec![syn::Stmt::Expr(syn::Expr::Path(syn::ExprPath {
attrs: vec![],
path: syn::Path {
leading_colon: None,
segments: Punctuated::from_iter([syn::PathSegment::from(id)]),
},
qself: None,
}))],
},
}
}
}
impl Parse for PropsFieldTypeAndBuilder {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let colon: Option<syn::Token![:]> = input.parse()?;
if let Some(colon) = colon {
let ty = input.parse()?;
Ok(Self::ImplicitBuilder { colon, ty })
} else if input.peek(syn::Token![<]) || input.peek(syn::token::Paren) {
input.parse().map(Self::Explicit)
} else {
Ok(Self::ImplicitTypeAndBuilder)
}
}
}
impl Parse for PropsFieldTypeAndBuilderExplicit {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let generics = input.parse::<syn::Generics>()?;
let args;
let paren = syn::parenthesized!(args in input);
let mut args: Punctuated<_, syn::Token![,]> = args.parse_terminated(syn::FnArg::parse)?;
if args.len() != 1 {
return Err(syn::Error::new(
paren.span,
"Props field's custom builder fn need exactly one argument",
));
}
let fn_arg = args.pop().unwrap();
let (fn_arg, comma) = fn_arg.into_tuple();
let fn_arg = match fn_arg {
syn::FnArg::Receiver(r) => {
return Err(syn::Error::new(
r.span(),
"Props field's custom builder fn can't have `self` argument",
))
}
syn::FnArg::Typed(fn_arg) => Pair::new(fn_arg, comma),
};
let arrow = input.parse()?;
let ty = input.parse()?;
let fn_block = input.parse()?;
Ok(Self {
ty,
builder: PropsFieldBuilder {
generics,
paren,
fn_arg,
arrow,
fn_block,
},
})
}
}
impl PropsDefinitionWithOptions {
pub fn parse_proc_macro_input(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut errors = vec![];
let options = PropsOptions::parse_inner_attrs_record(input, &mut errors);
let definition: PropsDefinition = input.parse()?;
Ok(Self {
errors,
options,
definition,
})
}
}