Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework attribute parsing #290

Merged
merged 10 commits into from
Apr 3, 2024
138 changes: 94 additions & 44 deletions macros/src/attr/enum.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::collections::HashMap;

use syn::{parse_quote, Attribute, Ident, Path, Result, Type, WherePredicate};
use syn::{parse_quote, Attribute, Ident, ItemEnum, Path, Result, Type, WherePredicate};

use super::{parse_assign_from_str, parse_bound};
use super::{parse_assign_from_str, parse_bound, Attr, ContainerAttr};
use crate::{
attr::{parse_assign_inflection, parse_assign_str, parse_concrete, Inflection},
utils::{parse_attrs, parse_docs},
Expand Down Expand Up @@ -51,14 +51,14 @@ impl EnumAttr {
}

pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut result = Self::default();
parse_attrs(attrs)?.for_each(|a| result.merge(a));
let mut result = parse_attrs(attrs)?.fold(Self::default(), |acc, cur| acc.merge(cur));

let docs = parse_docs(attrs)?;
result.docs = docs;

#[cfg(feature = "serde-compat")]
crate::utils::parse_serde_attrs::<SerdeEnumAttr>(attrs).for_each(|a| result.merge(a.0));
let result = crate::utils::parse_serde_attrs::<SerdeEnumAttr>(attrs)
.fold(result, |acc, cur| acc.merge(cur.0));
Ok(result)
}

Expand All @@ -67,46 +67,96 @@ impl EnumAttr {
.clone()
.unwrap_or_else(|| parse_quote!(::ts_rs))
}
}

impl Attr for EnumAttr {
type Item = ItemEnum;

fn merge(self, other: Self) -> Self {
Self {
crate_rename: self.crate_rename.or(other.crate_rename),
type_override: self.type_override.or(other.type_override),
rename: self.rename.or(other.rename),
rename_all: self.rename_all.or(other.rename_all),
rename_all_fields: self.rename_all_fields.or(other.rename_all_fields),
tag: self.tag.or(other.tag),
untagged: self.untagged || other.untagged,
content: self.content.or(other.content),
export: self.export || other.export,
export_to: self.export_to.or(other.export_to),
docs: other.docs,
concrete: self.concrete.into_iter().chain(other.concrete).collect(),
bound: match (self.bound, other.bound) {
(Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()),
(Some(bound), None) | (None, Some(bound)) => Some(bound),
(None, None) => None,
},
}
}

fn merge(
&mut self,
EnumAttr {
crate_rename,
type_override,
rename_all,
rename_all_fields,
rename,
tag,
content,
untagged,
export_to,
export,
docs,
concrete,
bound,
}: EnumAttr,
) {
self.crate_rename = self.crate_rename.take().or(crate_rename);
self.type_override = self.type_override.take().or(type_override);
self.rename = self.rename.take().or(rename);
self.rename_all = self.rename_all.take().or(rename_all);
self.rename_all_fields = self.rename_all_fields.take().or(rename_all_fields);
self.tag = self.tag.take().or(tag);
self.untagged = self.untagged || untagged;
self.content = self.content.take().or(content);
self.export = self.export || export;
self.export_to = self.export_to.take().or(export_to);
self.docs = docs;
self.concrete.extend(concrete);
self.bound = self
.bound
.take()
.map(|b| {
b.into_iter()
.chain(bound.clone().unwrap_or_default())
.collect()
})
.or(bound);
fn assert_validity(&self, item: &Self::Item) -> Result<()> {
if self.type_override.is_some() {
if self.rename_all.is_some() {
syn_err_spanned!(
item;
"`rename_all` is not compatible with `type`"
);
}

if self.rename_all_fields.is_some() {
syn_err_spanned!(
item;
"`rename_all_fields` is not compatible with `type`"
);
}

if self.tag.is_some() {
syn_err_spanned!(
item;
"`tag` is not compatible with `type`"
);
}

if self.content.is_some() {
syn_err_spanned!(
item;
"`content` is not compatible with `type`"
);
}

if self.untagged {
syn_err_spanned!(
item;
"`untagged` is not compatible with `type`"
);
}
}

match (self.untagged, &self.tag, &self.content) {
(true, Some(_), None) => syn_err_spanned!(
item;
"untagged cannot be used with tag"
),
(true, _, Some(_)) => syn_err_spanned!(
item;
"untagged cannot be used with content"
),
(false, None, Some(_)) => syn_err_spanned!(
item;
"content cannot be used without tag"
),
_ => (),
};

Ok(())
}
}

impl ContainerAttr for EnumAttr {
fn crate_rename(&self) -> Path {
self.crate_rename
.clone()
.unwrap_or_else(|| parse_quote!(::ts_rs))
}
}

Expand Down
138 changes: 108 additions & 30 deletions macros/src/attr/field.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use syn::{Attribute, Ident, Result, Type};
use syn::{Attribute, Field, Ident, Result, Type};

use super::{parse_assign_from_str, parse_assign_str};
use super::{parse_assign_from_str, parse_assign_str, Attr};
use crate::utils::{parse_attrs, parse_docs};

#[derive(Default)]
Expand Down Expand Up @@ -30,41 +30,119 @@ pub struct SerdeFieldAttr(FieldAttr);

impl FieldAttr {
pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut result = Self::default();
parse_attrs(attrs)?.for_each(|a| result.merge(a));
let mut result = parse_attrs(attrs)?.fold(Self::default(), |acc, cur| acc.merge(cur));

result.docs = parse_docs(attrs)?;

#[cfg(feature = "serde-compat")]
if !result.skip {
crate::utils::parse_serde_attrs::<SerdeFieldAttr>(attrs)
.for_each(|a| result.merge(a.0));
result = crate::utils::parse_serde_attrs::<SerdeFieldAttr>(attrs)
.fold(result, |acc, cur| acc.merge(cur.0));
}

Ok(result)
}
}

impl Attr for FieldAttr {
type Item = Field;

fn merge(self, other: Self) -> Self {
Self {
type_as: self.type_as.or(other.type_as),
type_override: self.type_override.or(other.type_override),
rename: self.rename.or(other.rename),
inline: self.inline || other.inline,
skip: self.skip || other.skip,
optional: Optional {
optional: self.optional.optional || other.optional.optional,
nullable: self.optional.nullable || other.optional.nullable,
},
flatten: self.flatten || other.flatten,

// We can't emit TSDoc for a flattened field
// and we cant make this invalid in assert_validity because
// this documentation is totally valid in Rust
docs: if self.flatten || other.flatten {
String::new()
} else {
self.docs + &other.docs
},
}
}

fn assert_validity(&self, field: &Self::Item) -> Result<()> {
if self.type_override.is_some() {
if self.type_as.is_some() {
syn_err_spanned!(field; "`type` is not compatible with `as`")
}

if self.inline {
syn_err_spanned!(field; "`type` is not compatible with `inline`")
}

if self.flatten {
syn_err_spanned!(
field;
"`type` is not compatible with `flatten`"
);
}
}

if self.flatten {
if self.type_as.is_some() {
syn_err_spanned!(
field;
"`as` is not compatible with `flatten`"
);
}

if self.rename.is_some() {
syn_err_spanned!(
field;
"`rename` is not compatible with `flatten`"
);
}

if self.inline {
syn_err_spanned!(
field;
"`inline` is not compatible with `flatten`"
);
}

if self.optional.optional {
syn_err_spanned!(
field;
"`optional` is not compatible with `flatten`"
);
}
}

if field.ident.is_none() {
if self.flatten {
syn_err_spanned!(
field;
"`flatten` cannot with tuple struct fields"
);
}

if self.rename.is_some() {
syn_err_spanned!(
field;
"`flatten` cannot with tuple struct fields"
);
}

if self.optional.optional {
syn_err_spanned!(
field;
"`optional` cannot with tuple struct fields"
);
}
}

fn merge(
&mut self,
FieldAttr {
type_as,
type_override,
rename,
inline,
skip,
optional: Optional { optional, nullable },
flatten,
docs,
}: FieldAttr,
) {
self.rename = self.rename.take().or(rename);
self.type_as = self.type_as.take().or(type_as);
self.type_override = self.type_override.take().or(type_override);
self.inline = self.inline || inline;
self.skip = self.skip || skip;
self.optional = Optional {
optional: self.optional.optional || optional,
nullable: self.optional.nullable || nullable,
};
self.flatten |= flatten;
self.docs.push_str(&docs);
Ok(())
}
}

Expand Down
13 changes: 12 additions & 1 deletion macros/src/attr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use r#struct::*;
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Error, Lit, Result, Token, WherePredicate,
Error, Lit, Path, Result, Token, WherePredicate,
};
pub use variant::*;

Expand All @@ -26,6 +26,17 @@ pub enum Inflection {
Kebab,
}

pub(super) trait Attr {
type Item;

fn merge(self, other: Self) -> Self;
fn assert_validity(&self, item: &Self::Item) -> Result<()>;
}

pub(super) trait ContainerAttr: Attr {
fn crate_rename(&self) -> Path;
}

impl Inflection {
pub fn apply(self, string: &str) -> String {
use inflector::Inflector;
Expand Down
Loading
Loading