use std::collections::HashSet;
use serde_derive_internals::{
ast::{Data, Field, Style, Variant},
attr::TagType,
};
use crate::{
attrs::TsifyFieldAttrs,
container::Container,
decl::{Decl, TsEnumDecl, TsInterfaceDecl, TsStringEnumDecl, TsTypeAliasDecl},
typescript::{TsType, TsTypeElement, TsTypeLit},
};
enum ParsedFields {
Named(Vec<TsTypeElement>, Vec<TsType>),
Unnamed(Vec<TsType>),
Transparent(TsType),
}
impl From<ParsedFields> for TsType {
fn from(fields: ParsedFields) -> Self {
match fields {
ParsedFields::Named(members, extends) => {
let type_lit = TsType::from(TsTypeLit { members });
if extends.is_empty() {
type_lit
} else {
type_lit.and(TsType::Intersection(extends))
}
}
ParsedFields::Unnamed(elems) => TsType::Tuple(elems),
ParsedFields::Transparent(ty) => ty,
}
}
}
enum FieldsStyle {
Named,
Unnamed,
}
#[derive(Clone)]
pub struct Parser<'a> {
pub container: &'a Container<'a>,
}
impl<'a> Parser<'a> {
pub fn new(container: &'a Container<'a>) -> Self {
Self { container }
}
pub fn parse(&self) -> Decl {
match self.container.serde_data() {
Data::Struct(style, ref fields) => self.parse_struct(*style, fields),
Data::Enum(ref variants) => {
if self.container.attrs.string_enum {
self.parse_string_enum(variants)
} else {
self.parse_enum(variants)
}
}
}
}
fn create_relevant_type_params(&self, type_ref_names: HashSet<&String>) -> Vec<String> {
self.container
.generics()
.type_params()
.map(|p| p.ident.to_string())
.filter(|t| type_ref_names.contains(t))
.collect()
}
fn create_type_alias_decl(&self, type_ann: TsType) -> Decl {
Decl::TsTypeAlias(TsTypeAliasDecl {
id: self.container.name(),
export: true,
type_params: self.create_relevant_type_params(type_ann.type_ref_names()),
type_ann,
})
}
fn create_decl(&self, members: Vec<TsTypeElement>, extends: Vec<TsType>) -> Decl {
if extends.iter().all(|ty| ty.is_ref()) {
let mut type_ref_names: HashSet<&String> = HashSet::new();
members.iter().for_each(|member| {
type_ref_names.extend(member.type_ann.type_ref_names());
});
extends.iter().for_each(|ty| {
type_ref_names.extend(ty.type_ref_names());
});
let type_params = self.create_relevant_type_params(type_ref_names);
Decl::TsInterface(TsInterfaceDecl {
id: self.container.name(),
type_params,
extends,
body: members,
})
} else {
let extra = TsType::Intersection(
extends
.into_iter()
.map(|ty| match ty {
TsType::Option(ty) => TsType::Union(vec![*ty, TsType::empty_type_lit()]),
_ => ty,
})
.collect(),
);
let type_ann = TsType::TypeLit(TsTypeLit { members }).and(extra);
self.create_type_alias_decl(type_ann)
}
}
fn parse_struct(&self, style: Style, fields: &[Field]) -> Decl {
let parsed_fields = self.parse_fields(style, fields);
let tag_type = self.container.serde_attrs().tag();
match (tag_type, parsed_fields) {
(TagType::Internal { tag }, ParsedFields::Named(members, extends)) => {
let name = self.container.name();
let tag_field = TsTypeElement {
key: tag.clone(),
type_ann: TsType::Lit(name),
optional: false,
};
let mut vec = Vec::with_capacity(members.len() + 1);
vec.push(tag_field);
vec.extend(members);
self.create_decl(vec, extends)
}
(_, ParsedFields::Named(members, extends)) => self.create_decl(members, extends),
(_, parsed_fields) => self.create_type_alias_decl(parsed_fields.into()),
}
}
fn parse_fields(&self, style: Style, fields: &[Field]) -> ParsedFields {
let style = match style {
Style::Struct => FieldsStyle::Named,
Style::Newtype => return ParsedFields::Transparent(self.parse_field(&fields[0]).0),
Style::Tuple => FieldsStyle::Unnamed,
Style::Unit => return ParsedFields::Transparent(TsType::nullish()),
};
let fields = fields
.iter()
.filter(|field| {
!field.attrs.skip_serializing()
&& !field.attrs.skip_deserializing()
&& !is_phantom(field.ty)
})
.collect::<Vec<_>>();
if fields.len() == 1 && self.container.transparent() {
return ParsedFields::Transparent(self.parse_field(fields[0]).0);
}
match style {
FieldsStyle::Named => {
let (members, flatten_fields) = self.parse_named_fields(fields);
ParsedFields::Named(members, flatten_fields)
}
FieldsStyle::Unnamed => {
let elems = fields
.into_iter()
.map(|field| self.parse_field(field).0)
.collect();
ParsedFields::Unnamed(elems)
}
}
}
fn parse_field(&self, field: &Field) -> (TsType, Option<TsifyFieldAttrs>) {
let ts_attrs = match TsifyFieldAttrs::from_serde_field(field) {
Ok(attrs) => attrs,
Err(err) => {
self.container.syn_error(err);
return (TsType::NEVER, None);
}
};
let type_ann = TsType::from_syn_type(field.ty, self.container.attrs.maps_as_objects);
if let Some(t) = &ts_attrs.type_override {
let type_ref_names = type_ann.type_ref_names();
let type_params = self.create_relevant_type_params(type_ref_names);
(
TsType::Override {
type_override: t.clone(),
type_params,
},
Some(ts_attrs),
)
} else {
(type_ann, Some(ts_attrs))
}
}
fn parse_named_fields(&self, fields: Vec<&Field>) -> (Vec<TsTypeElement>, Vec<TsType>) {
let (flatten_fields, members): (Vec<_>, Vec<_>) =
fields.into_iter().partition(|field| field.attrs.flatten());
let members = members
.into_iter()
.map(|field| {
let key = field.attrs.name().serialize_name();
let (type_ann, field_attrs) = self.parse_field(field);
let optional = field_attrs.is_some_and(|attrs| attrs.optional);
let default_is_none = self.container.serde_attrs().default().is_none()
&& field.attrs.default().is_none();
let type_ann = if optional {
match type_ann {
TsType::Option(t) => *t,
_ => type_ann,
}
} else {
type_ann
};
TsTypeElement {
key,
type_ann,
optional: optional || !default_is_none,
}
})
.collect();
let flatten_fields = flatten_fields
.into_iter()
.map(|field| self.parse_field(field).0)
.collect();
(members, flatten_fields)
}
fn parse_enum(&self, variants: &[Variant]) -> Decl {
let members = variants
.iter()
.filter(|v| !v.attrs.skip_serializing() && !v.attrs.skip_deserializing())
.map(|variant| {
let decl = self.create_type_alias_decl(self.parse_variant(variant));
if let Decl::TsTypeAlias(mut type_alias) = decl {
type_alias.id = variant.attrs.name().serialize_name();
type_alias
} else {
panic!();
}
})
.collect::<Vec<_>>();
let type_ref_names = members
.iter()
.flat_map(|type_alias| type_alias.type_ann.type_ref_names())
.collect::<HashSet<_>>();
let relevant_type_params = self.create_relevant_type_params(type_ref_names);
Decl::TsEnum(TsEnumDecl {
id: self.container.name(),
type_params: relevant_type_params,
members,
namespace: self.container.attrs.namespace,
})
}
fn parse_string_enum(&self, variants: &[Variant]) -> Decl {
let variant_names = self
.container
.enum_variant_names
.as_ref()
.expect("Expected enum variant names when parsing enum");
if variant_names.len() != variants.len() {
panic!("Expected lengths of variant_names and variants to be equal when parsing enum");
}
let variants: Vec<_> = variants
.iter()
.zip(variant_names)
.filter(|(v, _)| !v.attrs.skip_serializing() && !v.attrs.skip_deserializing())
.map(|(variant, name)| (name.to_string(), variant.attrs.name().serialize_name()))
.collect();
Decl::TsStringEnum(TsStringEnumDecl {
id: self.container.name(),
export: true,
variants,
})
}
fn parse_variant(&self, variant: &Variant) -> TsType {
let tag_type = self.container.serde_attrs().tag();
let name = variant.attrs.name().serialize_name();
let style = variant.style;
let type_ann: TsType = self.parse_fields(style, &variant.fields).into();
type_ann.with_tag_type(name, style, tag_type)
}
}
fn is_phantom(ty: &syn::Type) -> bool {
if let syn::Type::Path(syn::TypePath { path, .. }) = ty {
path.segments
.last()
.is_some_and(|path| path.ident == "PhantomData")
} else {
false
}
}