typescript_type_def_derive/
lib.rs

1//! This crate defines a procedural derive macro for implementing the `TypeDef`
2//! trait from the `typescript_type_def` crate.
3//!
4//! See the documentation of that crate for more information.
5#![warn(rust_2018_idioms, clippy::all)]
6#![deny(clippy::correctness)]
7#![allow(clippy::match_like_matches_macro)]
8
9use darling::{
10    ast,
11    util::{Ignored, SpannedValue},
12    FromDeriveInput, FromField, FromMeta, FromVariant,
13};
14use proc_macro2::Span;
15use proc_macro_error2::{abort, proc_macro_error};
16use quote::{format_ident, quote};
17use std::{ops::Deref, str::FromStr};
18use syn::{
19    ext::IdentExt,
20    parse::Parser,
21    parse_quote, parse_str,
22    punctuated::Punctuated,
23    visit_mut::{self, VisitMut},
24    AngleBracketedGenericArguments, Attribute, DeriveInput, Expr, ExprLit,
25    GenericArgument, GenericParam, Generics, Ident, Item, ItemImpl, ItemStruct,
26    Lifetime, Lit, LitStr, Meta, MetaNameValue, Path, PathArguments,
27    PathSegment, PredicateLifetime, PredicateType, Token, TraitBound,
28    TraitBoundModifier, Type, TypeParam, TypeParamBound, TypePath, WhereClause,
29    WherePredicate,
30};
31
32#[proc_macro_error]
33#[proc_macro_derive(TypeDef, attributes(type_def, serde))]
34pub fn derive_type_def(
35    input: proc_macro::TokenStream,
36) -> proc_macro::TokenStream {
37    let input = match syn::parse2::<DeriveInput>(input.into()) {
38        Ok(data) => data,
39        Err(err) => return err.to_compile_error().into(),
40    };
41    let mut input = match TypeDefInput::from_derive_input(&input) {
42        Ok(input) => input,
43        Err(error) => return error.write_errors().into(),
44    };
45
46    remove_skipped(&mut input.data);
47
48    let generics: &mut Generics = &mut input.generics;
49    if generics.params.iter().any(|param| match param {
50        GenericParam::Type(_) | GenericParam::Lifetime(_) => true,
51        _ => false,
52    }) {
53        // add generic bounds
54        generics
55            .where_clause
56            .get_or_insert_with(|| WhereClause {
57                where_token: <Token![where]>::default(),
58                predicates: Punctuated::new(),
59            })
60            .predicates
61            .extend(generics.params.iter().filter_map(|param| {
62                match param {
63                    GenericParam::Type(param) => {
64                        // all type params should impl TypeDef
65                        Some(WherePredicate::Type(PredicateType {
66                            lifetimes: None,
67                            bounded_ty: Type::Path(TypePath {
68                                qself: None,
69                                path: ident_path(param.ident.clone()),
70                            }),
71                            colon_token: <Token![:]>::default(),
72                            bounds: IntoIterator::into_iter([
73                                TypeParamBound::Trait(TraitBound {
74                                    paren_token: None,
75                                    modifier: TraitBoundModifier::None,
76                                    lifetimes: None,
77                                    path: Path::parse_mod_style
78                                        .parse_str(
79                                            "::typescript_type_def::TypeDef",
80                                        )
81                                        .unwrap(),
82                                }),
83                            ])
84                            .collect(),
85                        }))
86                    }
87                    GenericParam::Lifetime(param) => {
88                        // all lifetime params should be static
89                        Some(WherePredicate::Lifetime(PredicateLifetime {
90                            lifetime: param.lifetime.clone(),
91                            colon_token: <Token![:]>::default(),
92                            bounds: std::iter::once(Lifetime::new(
93                                "'static",
94                                Span::call_site(),
95                            ))
96                            .collect(),
97                        }))
98                    }
99                    GenericParam::Const(_) => None,
100                }
101            }));
102    }
103
104    let ty_name = &input.ident;
105
106    let (impl_generics, ty_generics, where_clause) =
107        input.generics.split_for_impl();
108
109    let info_def = make_info_def(&input);
110
111    (quote! {
112        impl #impl_generics ::typescript_type_def::TypeDef for
113            #ty_name #ty_generics
114        #where_clause
115        {
116            const INFO: ::typescript_type_def::type_expr::TypeInfo = #info_def;
117        }
118    })
119    .into()
120}
121
122#[derive(FromDeriveInput)]
123#[darling(attributes(type_def, serde), forward_attrs)]
124struct TypeDefInput {
125    attrs: Vec<Attribute>,
126    ident: Ident,
127    generics: Generics,
128    data: ast::Data<TypeDefVariant, TypeDefField>,
129
130    // type_def
131    #[darling(default)]
132    namespace: Namespace,
133
134    // serde
135    #[darling(default)]
136    tag: Option<SpannedValue<String>>,
137    #[darling(default)]
138    content: Option<SpannedValue<String>>,
139    #[darling(default)]
140    untagged: SpannedValue<Flag>,
141    #[darling(default)]
142    rename_all: Option<SpannedValue<String>>,
143    #[darling(default)]
144    rename_all_fields: Option<SpannedValue<String>>,
145    #[darling(default)]
146    rename: Option<SpannedValue<String>>,
147    #[darling(default)]
148    #[allow(dead_code)] // doesn't affect JSON
149    transparent: Ignored,
150    #[darling(default)]
151    #[allow(dead_code)]
152    deny_unknown_fields: Ignored,
153    #[darling(default)]
154    #[allow(dead_code)]
155    bound: Ignored,
156    #[darling(default)]
157    #[allow(dead_code)]
158    default: Ignored,
159    #[darling(default)]
160    #[allow(dead_code)]
161    remote: Ignored,
162    #[darling(default)]
163    #[allow(dead_code)]
164    from: Ignored,
165    #[darling(default)]
166    #[allow(dead_code)]
167    try_from: Ignored,
168    #[darling(default)]
169    #[allow(dead_code)]
170    into: Ignored,
171    #[darling(default, rename = "crate")]
172    #[allow(dead_code)]
173    crate_: Ignored,
174}
175
176#[derive(FromField)]
177#[darling(attributes(type_def, serde), forward_attrs)]
178struct TypeDefField {
179    attrs: Vec<Attribute>,
180    ident: Option<Ident>,
181    ty: Type,
182
183    // type_def
184    #[darling(default)]
185    type_of: Option<SpannedValue<TypeFromMeta>>,
186
187    // serde
188    #[darling(default)]
189    flatten: SpannedValue<Flag>,
190    #[darling(default)]
191    skip_serializing_if: Option<SpannedValue<String>>,
192    #[darling(default)]
193    default: SpannedValue<FieldDefault>,
194    #[darling(default)]
195    skip: SpannedValue<Flag>,
196    #[darling(default)]
197    rename: Option<SpannedValue<String>>,
198    #[darling(default)]
199    #[allow(dead_code)]
200    alias: Ignored,
201    #[darling(default)]
202    #[allow(dead_code)]
203    skip_serializing: Ignored,
204    #[darling(default)]
205    #[allow(dead_code)]
206    skip_deserializing: Ignored,
207    #[darling(default)]
208    #[allow(dead_code)]
209    serialize_with: Ignored,
210    #[darling(default)]
211    #[allow(dead_code)]
212    deserialize_with: Ignored,
213    #[darling(default)]
214    #[allow(dead_code)]
215    with: Ignored,
216    #[darling(default)]
217    #[allow(dead_code)]
218    borrow: Ignored,
219    #[darling(default)]
220    #[allow(dead_code)]
221    bound: Ignored,
222    #[darling(default)]
223    #[allow(dead_code)]
224    getter: Ignored,
225}
226
227#[derive(FromVariant)]
228#[darling(attributes(serde), forward_attrs)]
229struct TypeDefVariant {
230    attrs: Vec<Attribute>,
231    ident: Ident,
232    fields: ast::Fields<TypeDefField>,
233
234    // serde
235    #[darling(default)]
236    rename_all: Option<SpannedValue<String>>,
237    #[darling(default)]
238    skip: SpannedValue<Flag>,
239    #[darling(default)]
240    rename: Option<SpannedValue<String>>,
241    #[darling(default)]
242    #[allow(dead_code)]
243    alias: Ignored,
244    #[darling(default)]
245    #[allow(dead_code)]
246    skip_serializing: Ignored,
247    #[darling(default)]
248    #[allow(dead_code)]
249    skip_deserializing: Ignored,
250    #[darling(default)]
251    #[allow(dead_code)]
252    serialize_with: Ignored,
253    #[darling(default)]
254    #[allow(dead_code)]
255    deserialize_with: Ignored,
256    #[darling(default)]
257    #[allow(dead_code)]
258    with: Ignored,
259    #[darling(default)]
260    #[allow(dead_code)]
261    bound: Ignored,
262    #[darling(default)]
263    #[allow(dead_code)]
264    borrow: Ignored,
265    #[darling(default)]
266    #[allow(dead_code)]
267    other: Ignored,
268}
269
270#[derive(Default)]
271struct Flag(bool);
272
273#[derive(Default)]
274struct Namespace {
275    parts: Vec<Ident>,
276}
277
278#[derive(Default)]
279struct FieldDefault(bool);
280
281struct TypeFromMeta(Type);
282
283fn make_info_def(
284    TypeDefInput {
285        attrs,
286        ident: ty_name,
287        generics,
288        data,
289        namespace,
290        tag,
291        content,
292        untagged,
293        rename_all,
294        rename_all_fields,
295        rename,
296        ..
297    }: &TypeDefInput,
298) -> Expr {
299    let type_param_decls =
300        generics.type_params().flat_map(|TypeParam { ident, .. }| {
301            let struct_name = format_ident!("__TypeParam_{}", ident);
302            let struct_decl: ItemStruct = parse_quote! {
303                #[allow(non_camel_case_types)]
304                struct #struct_name;
305            };
306            let r#ref = type_expr_ident(&ident.to_string());
307            let type_def_impl: ItemImpl = parse_quote! {
308                impl ::typescript_type_def::TypeDef for #struct_name {
309                    const INFO: ::typescript_type_def::type_expr::TypeInfo =
310                        ::typescript_type_def::type_expr::TypeInfo::Native(
311                            ::typescript_type_def::type_expr::NativeTypeInfo {
312                                r#ref: #r#ref,
313                            },
314                        );
315                }
316            };
317            [Item::Struct(struct_decl), Item::Impl(type_def_impl)]
318        });
319    let type_info = type_info(
320        namespace
321            .parts
322            .iter()
323            .map(|part| type_ident(&part.to_string())),
324        &match rename {
325            Some(rename) => type_ident(rename.as_str()),
326            None => type_ident(&ty_name.unraw().to_string()),
327        },
328        &match data {
329            ast::Data::Struct(ast::Fields { fields, style, .. }) => {
330                if let Some(tag) = tag {
331                    abort!(tag.span(), "`tag` option is only valid for enums");
332                }
333                if let Some(content) = content {
334                    abort!(
335                        content.span(),
336                        "`content` option is only valid for enums"
337                    );
338                }
339                if ***untagged {
340                    abort!(
341                        untagged.span(),
342                        "`untagged` option is only valid for enums"
343                    );
344                }
345
346                match style {
347                    ast::Style::Unit => type_expr_ident("null"),
348                    ast::Style::Tuple => fields_to_type_expr(
349                        fields,
350                        false,
351                        rename_all.as_ref(),
352                        generics,
353                        None,
354                    ),
355                    ast::Style::Struct => {
356                        if fields.is_empty() {
357                            type_expr_object([], None)
358                        } else {
359                            fields_to_type_expr(
360                                fields,
361                                true,
362                                rename_all.as_ref(),
363                                generics,
364                                None,
365                            )
366                        }
367                    }
368                }
369            }
370            ast::Data::Enum(variants) => variants_to_type_expr(
371                variants,
372                tag,
373                content,
374                untagged,
375                rename_all,
376                rename_all_fields,
377                generics,
378            ),
379        },
380        generics
381            .type_params()
382            .map(|TypeParam { ident, .. }| type_ident(&ident.to_string())),
383        generics.type_params().map(|TypeParam { ident, .. }| {
384            type_expr_ref(
385                &Type::Path(TypePath {
386                    qself: None,
387                    path: ident_path(ident.clone()),
388                }),
389                None,
390            )
391        }),
392        extract_type_docs(attrs).as_ref(),
393    );
394    parse_quote! {{
395        #(#type_param_decls)*
396        #type_info
397    }}
398}
399
400fn fields_to_type_expr(
401    fields: &[TypeDefField],
402    named: bool,
403    rename_all: Option<&SpannedValue<String>>,
404    generics: &Generics,
405    docs: Option<&Expr>,
406) -> Expr {
407    if fields.is_empty() {
408        return if named {
409            type_expr_object(std::iter::empty(), docs)
410        } else {
411            type_expr_tuple(std::iter::empty(), docs)
412        };
413    }
414    let all_flatten = fields.iter().all(|TypeDefField { flatten, .. }| {
415        if ***flatten && !named {
416            abort!(flatten.span(), "tuple fields cannot be flattened");
417        }
418        ***flatten
419    });
420    let flatten_exprs = fields
421        .iter()
422        .filter(|&TypeDefField { flatten, .. }| ***flatten)
423        .map(|TypeDefField { ty, type_of, .. }| {
424            let ty = if let Some(type_of) = type_of {
425                &***type_of
426            } else {
427                ty
428            };
429            type_expr_ref(ty, Some(generics))
430        });
431    // always put flatten exprs first
432    let exprs = flatten_exprs.chain((!all_flatten).then(|| {
433        // if there are some non-flattened fields, make an expr out of them
434        let fields = fields.iter().filter_map(
435            |TypeDefField {
436                 attrs,
437                 ident: field_name,
438                 ty,
439                 type_of,
440                 flatten,
441                 skip_serializing_if,
442                 default,
443                 rename,
444                 ..
445             }| {
446                if ***flatten {
447                    if !named {
448                        abort!(
449                            flatten.span(),
450                            "tuple fields cannot be flattened"
451                        );
452                    }
453                    return None;
454                }
455                let ty = if let Some(type_of) = type_of {
456                    &***type_of
457                } else {
458                    ty
459                };
460                if let Some(field_name) = field_name {
461                    let name = type_string(
462                        &serde_rename_ident(
463                            field_name, rename, rename_all, true,
464                        )
465                        .value(),
466                        None,
467                    );
468                    let mut ty = ty;
469                    let optional = if let Some(skip_serializing_if) =
470                        skip_serializing_if
471                    {
472                        if let Some(inner_ty) = is_option(ty) {
473                            if parse_str::<Path>(skip_serializing_if).unwrap()
474                                == parse_str::<Path>("Option::is_none").unwrap()
475                            {
476                                ty = inner_ty;
477                            }
478                        }
479                        true
480                    } else {
481                        ***default
482                    };
483                    let r#type = type_expr_ref(ty, Some(generics));
484                    Some(type_object_field(
485                        &name,
486                        optional,
487                        &r#type,
488                        extract_type_docs(attrs).as_ref(),
489                    ))
490                } else {
491                    Some(type_expr_ref(ty, Some(generics)))
492                }
493            },
494        );
495        if named {
496            type_expr_object(fields, docs)
497        } else {
498            type_expr_tuple(fields, docs)
499        }
500    }));
501    type_expr_intersection(exprs, None)
502}
503
504fn variants_to_type_expr(
505    variants: &[TypeDefVariant],
506    tag: &Option<SpannedValue<String>>,
507    content: &Option<SpannedValue<String>>,
508    untagged: &SpannedValue<Flag>,
509    variant_rename_all: &Option<SpannedValue<String>>,
510    fields_rename_all: &Option<SpannedValue<String>>,
511    generics: &Generics,
512) -> Expr {
513    type_expr_union(
514        variants.iter().map(
515            |TypeDefVariant {
516                 attrs,
517                 ident: variant_name,
518                 fields: ast::Fields { style, fields, .. },
519                 rename_all: field_rename_all,
520                 rename: variant_rename,
521                 ..
522             }| {
523                let variant_name = serde_rename_ident(
524                    variant_name,
525                    variant_rename,
526                    variant_rename_all.as_ref(),
527                    false,
528                );
529                let field_rename_all =
530                    field_rename_all.as_ref().or(fields_rename_all.as_ref());
531                match (tag, content, ***untagged) {
532                    (None, None, false) => match style {
533                        ast::Style::Unit => type_expr_string(
534                            &variant_name.value(),
535                            extract_type_docs(attrs).as_ref(),
536                        ),
537                        ast::Style::Tuple | ast::Style::Struct => {
538                            type_expr_object(
539                                [type_object_field(
540                                    &type_string(&variant_name.value(), None),
541                                    false,
542                                    &fields_to_type_expr(
543                                        fields,
544                                        matches!(style, ast::Style::Struct),
545                                        field_rename_all,
546                                        generics,
547                                        None,
548                                    ),
549                                    extract_type_docs(attrs).as_ref(),
550                                )],
551                                None,
552                            )
553                        }
554                    },
555                    (None, None, true) => match style {
556                        ast::Style::Unit => type_expr_ident("null"),
557                        ast::Style::Tuple | ast::Style::Struct => {
558                            fields_to_type_expr(
559                                fields,
560                                matches!(style, ast::Style::Struct),
561                                field_rename_all,
562                                generics,
563                                extract_type_docs(attrs).as_ref(),
564                            )
565                        }
566                    },
567                    (Some(tag), None, false) => match style {
568                        ast::Style::Unit => type_expr_object(
569                            [type_object_field(
570                                &type_string(tag, None),
571                                false,
572                                &type_expr_string(&variant_name.value(), None),
573                                extract_type_docs(attrs).as_ref(),
574                            )],
575                            None,
576                        ),
577                        ast::Style::Tuple | ast::Style::Struct => {
578                            if matches!(style, ast::Style::Tuple)
579                                && fields.len() != 1
580                            {
581                                abort!(
582                                    tag.span(),
583                                    "cannot tag enums with tuple variants"
584                                );
585                            }
586                            type_expr_intersection(
587                                [
588                                    type_expr_object(
589                                        [type_object_field(
590                                            &type_string(tag, None),
591                                            false,
592                                            &type_expr_string(
593                                                &variant_name.value(),
594                                                None,
595                                            ),
596                                            extract_type_docs(attrs).as_ref(),
597                                        )],
598                                        None,
599                                    ),
600                                    fields_to_type_expr(
601                                        fields,
602                                        matches!(style, ast::Style::Struct),
603                                        field_rename_all,
604                                        generics,
605                                        None,
606                                    ),
607                                ],
608                                None,
609                            )
610                        }
611                    },
612                    (Some(tag), Some(content), false) => match style {
613                        ast::Style::Unit => type_expr_object(
614                            [type_object_field(
615                                &type_string(tag, None),
616                                false,
617                                &type_expr_string(&variant_name.value(), None),
618                                extract_type_docs(attrs).as_ref(),
619                            )],
620                            None,
621                        ),
622                        ast::Style::Tuple | ast::Style::Struct => {
623                            type_expr_object(
624                                [
625                                    type_object_field(
626                                        &type_string(tag, None),
627                                        false,
628                                        &type_expr_string(
629                                            &variant_name.value(),
630                                            None,
631                                        ),
632                                        extract_type_docs(attrs).as_ref(),
633                                    ),
634                                    type_object_field(
635                                        &type_string(content, None),
636                                        false,
637                                        &fields_to_type_expr(
638                                            fields,
639                                            matches!(style, ast::Style::Struct),
640                                            field_rename_all,
641                                            generics,
642                                            None,
643                                        ),
644                                        None,
645                                    ),
646                                ],
647                                None,
648                            )
649                        }
650                    },
651                    (Some(tag), _, true) => {
652                        abort!(
653                            tag.span(),
654                            "cannot give both `tag` and `untagged` options"
655                        );
656                    }
657                    (None, Some(content), _) => {
658                        abort!(
659                            content.span(),
660                            "`content` option requires `tag` option"
661                        );
662                    }
663                }
664            },
665        ),
666        None,
667    )
668}
669
670fn type_ident(ident: &str) -> Expr {
671    parse_quote! {
672        ::typescript_type_def::type_expr::Ident(
673            #ident,
674        )
675    }
676}
677
678fn type_string(value: &str, docs: Option<&Expr>) -> Expr {
679    let docs = wrap_optional_docs(docs);
680    parse_quote! {
681        ::typescript_type_def::type_expr::TypeString {
682            docs: #docs,
683            value: #value,
684        }
685    }
686}
687
688fn type_expr_ident(ident: &str) -> Expr {
689    parse_quote! {
690        ::typescript_type_def::type_expr::TypeExpr::ident(
691            ::typescript_type_def::type_expr::Ident(
692                #ident,
693            ),
694        )
695    }
696}
697
698fn type_expr_ref(ty: &Type, generics: Option<&Generics>) -> Expr {
699    let mut ty = ty.clone();
700
701    if let Some(generics) = generics {
702        struct TypeParamReplace<'a> {
703            generics: &'a Generics,
704        }
705
706        impl VisitMut for TypeParamReplace<'_> {
707            fn visit_type_path_mut(&mut self, type_path: &mut TypePath) {
708                let TypePath { path, .. } = type_path;
709                if let Some(TypeParam { ident, .. }) = self
710                    .generics
711                    .type_params()
712                    .find(|TypeParam { ident, .. }| path.is_ident(ident))
713                {
714                    *path = ident_path(format_ident!("__TypeParam_{}", ident));
715                }
716
717                visit_mut::visit_type_path_mut(self, type_path);
718            }
719        }
720
721        visit_mut::visit_type_mut(&mut TypeParamReplace { generics }, &mut ty);
722    }
723
724    parse_quote! {
725        ::typescript_type_def::type_expr::TypeExpr::Ref(
726            &<#ty as ::typescript_type_def::TypeDef>::INFO,
727        )
728    }
729}
730
731fn type_expr_string(value: &str, docs: Option<&Expr>) -> Expr {
732    let docs = wrap_optional_docs(docs);
733    parse_quote! {
734        ::typescript_type_def::type_expr::TypeExpr::String(
735            ::typescript_type_def::type_expr::TypeString {
736                docs: #docs,
737                value: #value,
738            },
739        )
740    }
741}
742
743fn type_expr_tuple(
744    exprs: impl IntoIterator<Item = Expr>,
745    docs: Option<&Expr>,
746) -> Expr {
747    let docs = wrap_optional_docs(docs);
748    let exprs = exprs.into_iter().collect::<Vec<_>>();
749    if exprs.len() == 1 {
750        exprs.into_iter().next().unwrap()
751    } else {
752        parse_quote! {
753            ::typescript_type_def::type_expr::TypeExpr::Tuple(
754                ::typescript_type_def::type_expr::TypeTuple {
755                    docs: #docs,
756                    elements: &[#(#exprs,)*],
757                },
758            )
759        }
760    }
761}
762
763fn type_object_field(
764    name: &Expr,
765    optional: bool,
766    r#type: &Expr,
767    docs: Option<&Expr>,
768) -> Expr {
769    let docs = wrap_optional_docs(docs);
770    parse_quote! {
771        ::typescript_type_def::type_expr::ObjectField {
772            docs: #docs,
773            name: #name,
774            optional: #optional,
775            r#type: #r#type,
776        }
777    }
778}
779
780fn type_expr_object(
781    exprs: impl IntoIterator<Item = Expr>,
782    docs: Option<&Expr>,
783) -> Expr {
784    let docs = wrap_optional_docs(docs);
785    let exprs = exprs.into_iter();
786    parse_quote! {
787        ::typescript_type_def::type_expr::TypeExpr::Object(
788            ::typescript_type_def::type_expr::TypeObject {
789                docs: #docs,
790                index_signature: ::core::option::Option::None,
791                fields: &[#(#exprs,)*],
792            },
793        )
794    }
795}
796
797fn type_expr_union(
798    exprs: impl IntoIterator<Item = Expr>,
799    docs: Option<&Expr>,
800) -> Expr {
801    let docs = wrap_optional_docs(docs);
802    let exprs = exprs.into_iter().collect::<Vec<_>>();
803    if exprs.len() == 1 {
804        exprs.into_iter().next().unwrap()
805    } else {
806        parse_quote! {
807            ::typescript_type_def::type_expr::TypeExpr::Union(
808                ::typescript_type_def::type_expr::TypeUnion {
809                    docs: #docs,
810                    members: &[#(#exprs,)*],
811                },
812            )
813        }
814    }
815}
816
817fn type_expr_intersection(
818    exprs: impl IntoIterator<Item = Expr>,
819    docs: Option<&Expr>,
820) -> Expr {
821    let docs = wrap_optional_docs(docs);
822    let exprs = exprs.into_iter().collect::<Vec<_>>();
823    if exprs.len() == 1 {
824        exprs.into_iter().next().unwrap()
825    } else {
826        parse_quote! {
827            ::typescript_type_def::type_expr::TypeExpr::Intersection(
828                ::typescript_type_def::type_expr::TypeIntersection {
829                    docs: #docs,
830                    members: &[#(#exprs,)*],
831                },
832            )
833        }
834    }
835}
836
837fn type_info(
838    path_parts: impl IntoIterator<Item = Expr>,
839    name: &Expr,
840    def: &Expr,
841    generic_vars: impl IntoIterator<Item = Expr>,
842    generic_args: impl IntoIterator<Item = Expr>,
843    docs: Option<&Expr>,
844) -> Expr {
845    let docs = wrap_optional_docs(docs);
846    let path_parts = path_parts.into_iter();
847    let generic_vars = generic_vars.into_iter();
848    let generic_args = generic_args.into_iter();
849    parse_quote! {
850        ::typescript_type_def::type_expr::TypeInfo::Defined(
851            ::typescript_type_def::type_expr::DefinedTypeInfo {
852                def: ::typescript_type_def::type_expr::TypeDefinition {
853                    docs: #docs,
854                    path: &[#(#path_parts,)*],
855                    name: #name,
856                    generic_vars: &[#(#generic_vars,)*],
857                    def: #def,
858                },
859                generic_args: &[#(#generic_args,)*],
860            },
861        )
862    }
863}
864
865fn extract_type_docs(attrs: &[Attribute]) -> Option<Expr> {
866    let mut lines = attrs
867        .iter()
868        .filter_map(|attr| {
869            if let Meta::NameValue(MetaNameValue {
870                path,
871                eq_token: _,
872                value,
873            }) = &attr.meta
874            {
875                if path.is_ident("doc") {
876                    if let Expr::Lit(ExprLit {
877                        attrs: _,
878                        lit: Lit::Str(lit_str),
879                    }) = value
880                    {
881                        return Some(lit_str.value());
882                    }
883                }
884            }
885            None
886        })
887        .collect::<Vec<_>>();
888    let min_indent = lines
889        .iter()
890        .filter_map(|line| {
891            if line.is_empty() {
892                None
893            } else {
894                Some(
895                    line.find(|c: char| !c.is_whitespace())
896                        .unwrap_or(line.len()),
897                )
898            }
899        })
900        .min()?;
901    if min_indent > 0 {
902        for line in &mut lines {
903            if !line.is_empty() {
904                *line = line.split_off(min_indent);
905            }
906        }
907    }
908    let docs = lines.join("\n");
909    Some(parse_quote! {
910        ::typescript_type_def::type_expr::Docs(
911            #docs,
912        )
913    })
914}
915
916fn wrap_optional_docs(docs: Option<&Expr>) -> Expr {
917    match docs {
918        Some(docs) => parse_quote! {
919            ::core::option::Option::Some(
920                #docs,
921            )
922        },
923        None => parse_quote! {
924            ::core::option::Option::None
925        },
926    }
927}
928
929fn serde_rename_ident(
930    ident: &Ident,
931    rename: &Option<SpannedValue<String>>,
932    rename_all: Option<&SpannedValue<String>>,
933    is_field: bool,
934) -> LitStr {
935    let span = ident.span();
936    if let Some(rename) = rename {
937        LitStr::new(rename.as_str(), span)
938    } else {
939        let ident = ident.unraw().to_string();
940        let ident = if let Some(rename_all) = rename_all {
941            match rename_all.as_str() {
942                "lowercase" => ident.to_lowercase(),
943                "UPPERCASE" => ident.to_uppercase(),
944                _ => match ident_case::RenameRule::from_str(rename_all) {
945                    Ok(rename_all) => match is_field {
946                        true => rename_all.apply_to_field(ident),
947                        false => rename_all.apply_to_variant(ident),
948                    },
949                    Err(()) => {
950                        abort!(rename_all.span(), "unknown case conversion")
951                    }
952                },
953            }
954        } else {
955            ident
956        };
957        LitStr::new(&ident, span)
958    }
959}
960
961fn remove_skipped(data: &mut ast::Data<TypeDefVariant, TypeDefField>) {
962    match data {
963        ast::Data::Struct(ast::Fields { fields, .. }) => {
964            remove_if(fields, |TypeDefField { skip, .. }| ***skip);
965        }
966        ast::Data::Enum(variants) => {
967            remove_if(
968                variants,
969                |TypeDefVariant {
970                     fields: ast::Fields { fields, .. },
971                     skip,
972                     ..
973                 }| {
974                    if ***skip {
975                        return true;
976                    }
977                    remove_if(fields, |TypeDefField { skip, .. }| ***skip);
978                    false
979                },
980            );
981        }
982    }
983}
984
985impl Deref for Flag {
986    type Target = bool;
987
988    fn deref(&self) -> &Self::Target {
989        &self.0
990    }
991}
992
993impl FromMeta for Flag {
994    fn from_word() -> Result<Self, darling::Error> {
995        Ok(Self(true))
996    }
997}
998
999impl FromMeta for Namespace {
1000    fn from_value(value: &Lit) -> Result<Self, darling::Error> {
1001        match value {
1002            Lit::Str(lit_str) => Ok(Self {
1003                parts: lit_str
1004                    .value()
1005                    .split('.')
1006                    .map(|part| format_ident!("{}", part))
1007                    .collect(),
1008            }),
1009            _ => Err(darling::Error::custom("expected string literal")),
1010        }
1011    }
1012}
1013
1014impl Deref for FieldDefault {
1015    type Target = bool;
1016
1017    fn deref(&self) -> &Self::Target {
1018        &self.0
1019    }
1020}
1021
1022impl FromMeta for FieldDefault {
1023    fn from_word() -> Result<Self, darling::Error> {
1024        Ok(Self(true))
1025    }
1026
1027    fn from_string(_value: &str) -> Result<Self, darling::Error> {
1028        Ok(Self(true))
1029    }
1030}
1031
1032impl Deref for TypeFromMeta {
1033    type Target = Type;
1034
1035    fn deref(&self) -> &Self::Target {
1036        &self.0
1037    }
1038}
1039
1040impl FromMeta for TypeFromMeta {
1041    fn from_string(value: &str) -> Result<Self, darling::Error> {
1042        parse_str(value).map(Self).map_err(Into::into)
1043    }
1044}
1045
1046fn is_option(ty: &Type) -> Option<&Type> {
1047    if let Type::Path(TypePath {
1048        qself: None,
1049        path:
1050            Path {
1051                leading_colon: None,
1052                segments,
1053            },
1054    }) = ty
1055    {
1056        if segments.len() == 1 {
1057            let PathSegment { ident, arguments } = &segments[0];
1058            if ident == "Option" {
1059                if let PathArguments::AngleBracketed(
1060                    AngleBracketedGenericArguments { args, .. },
1061                ) = arguments
1062                {
1063                    if args.len() == 1 {
1064                        if let GenericArgument::Type(ty) = &args[0] {
1065                            return Some(ty);
1066                        }
1067                    }
1068                }
1069            }
1070        }
1071    }
1072    None
1073}
1074
1075fn ident_path(ident: Ident) -> Path {
1076    let mut segments = Punctuated::new();
1077    segments.push_value(PathSegment {
1078        ident,
1079        arguments: PathArguments::None,
1080    });
1081    Path {
1082        leading_colon: None,
1083        segments,
1084    }
1085}
1086
1087fn remove_if<T, F>(vec: &mut Vec<T>, mut filter: F)
1088where
1089    F: FnMut(&mut T) -> bool,
1090{
1091    let mut i = 0;
1092    while i < vec.len() {
1093        if filter(&mut vec[i]) {
1094            vec.remove(i);
1095        } else {
1096            i += 1;
1097        }
1098    }
1099}