1use proc_macro::TokenStream;
7use proc_macro2::TokenStream as TokenStream2;
8use quote::quote;
9use syn::{
10 parse_macro_input, Attribute, Data, DeriveInput, Fields, GenericParam, Generics, Ident, Type,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq)]
19enum RenameAll {
20 CamelCase,
22 PascalCase,
24 SnakeCase,
26 ScreamingSnakeCase,
28 KebabCase,
30 ScreamingKebabCase,
32}
33
34impl RenameAll {
35 fn from_str(s: &str) -> Option<Self> {
36 match s {
37 "camelCase" => Some(RenameAll::CamelCase),
38 "PascalCase" => Some(RenameAll::PascalCase),
39 "snake_case" => Some(RenameAll::SnakeCase),
40 "SCREAMING_SNAKE_CASE" => Some(RenameAll::ScreamingSnakeCase),
41 "kebab-case" => Some(RenameAll::KebabCase),
42 "SCREAMING-KEBAB-CASE" => Some(RenameAll::ScreamingKebabCase),
43 _ => None,
44 }
45 }
46
47 fn apply(&self, name: &str) -> String {
48 match self {
49 RenameAll::CamelCase => to_camel_case(name),
50 RenameAll::PascalCase => to_pascal_case(name),
51 RenameAll::SnakeCase => to_snake_case(name),
52 RenameAll::ScreamingSnakeCase => to_snake_case(name).to_uppercase(),
53 RenameAll::KebabCase => to_snake_case(name).replace('_', "-"),
54 RenameAll::ScreamingKebabCase => to_snake_case(name).replace('_', "-").to_uppercase(),
55 }
56 }
57}
58
59fn to_camel_case(name: &str) -> String {
61 let mut result = String::new();
62 let mut capitalize_next = false;
63
64 for (i, c) in name.chars().enumerate() {
65 if c == '_' {
66 capitalize_next = true;
67 } else if capitalize_next {
68 result.push(c.to_ascii_uppercase());
69 capitalize_next = false;
70 } else if i == 0 {
71 result.push(c.to_ascii_lowercase());
72 } else {
73 result.push(c);
74 }
75 }
76
77 result
78}
79
80fn to_pascal_case(name: &str) -> String {
82 let mut result = String::new();
83 let mut capitalize_next = true;
84
85 for c in name.chars() {
86 if c == '_' {
87 capitalize_next = true;
88 } else if capitalize_next {
89 result.push(c.to_ascii_uppercase());
90 capitalize_next = false;
91 } else {
92 result.push(c);
93 }
94 }
95
96 result
97}
98
99fn to_snake_case(name: &str) -> String {
101 let mut result = String::new();
102
103 for (i, c) in name.chars().enumerate() {
104 if c.is_uppercase() && i > 0 {
105 result.push('_');
106 }
107 result.push(c.to_ascii_lowercase());
108 }
109
110 result
111}
112
113#[derive(Default)]
115struct ContainerAttrs {
116 rename: Option<String>,
118 rename_all: Option<RenameAll>,
120 transparent: bool,
122 tag: Option<String>,
124 content: Option<String>,
126 untagged: bool,
128 pattern: Option<String>,
130 namespace: Vec<String>,
132}
133
134impl ContainerAttrs {
135 fn from_attrs(attrs: &[Attribute]) -> syn::Result<Self> {
136 let mut result = ContainerAttrs::default();
137
138 for attr in attrs {
139 if !attr.path().is_ident("ts") {
140 continue;
141 }
142
143 attr.parse_nested_meta(|meta| {
144 if meta.path.is_ident("rename") {
145 let value: syn::LitStr = meta.value()?.parse()?;
146 result.rename = Some(value.value());
147 } else if meta.path.is_ident("rename_all") {
148 let value: syn::LitStr = meta.value()?.parse()?;
149 let s = value.value();
150 result.rename_all = RenameAll::from_str(&s);
151 if result.rename_all.is_none() {
152 return Err(syn::Error::new_spanned(
153 value,
154 format!(
155 "unknown rename_all value: '{}'. Expected one of: \
156 camelCase, PascalCase, snake_case, \
157 SCREAMING_SNAKE_CASE, kebab-case, SCREAMING-KEBAB-CASE",
158 s
159 ),
160 ));
161 }
162 } else if meta.path.is_ident("transparent") {
163 result.transparent = true;
164 } else if meta.path.is_ident("tag") {
165 let value: syn::LitStr = meta.value()?.parse()?;
166 result.tag = Some(value.value());
167 } else if meta.path.is_ident("content") {
168 let value: syn::LitStr = meta.value()?.parse()?;
169 result.content = Some(value.value());
170 } else if meta.path.is_ident("untagged") {
171 result.untagged = true;
172 } else if meta.path.is_ident("pattern") {
173 let value: syn::LitStr = meta.value()?.parse()?;
174 result.pattern = Some(value.value());
175 } else if meta.path.is_ident("namespace") {
176 let value: syn::LitStr = meta.value()?.parse()?;
177 let ns_str = value.value();
179 result.namespace = ns_str
180 .split(|c| c == ':' || c == '.')
181 .filter(|s| !s.is_empty())
182 .map(|s| s.to_string())
183 .collect();
184 }
185 Ok(())
186 })?;
187 }
188
189 Ok(result)
190 }
191}
192
193#[derive(Default)]
195struct FieldAttrs {
196 rename: Option<String>,
198 skip: bool,
200 flatten: bool,
202 type_override: Option<String>,
204 default: bool,
206 inline: bool,
208}
209
210impl FieldAttrs {
211 fn from_attrs(attrs: &[Attribute]) -> syn::Result<Self> {
212 let mut result = FieldAttrs::default();
213
214 for attr in attrs {
215 if !attr.path().is_ident("ts") {
216 continue;
217 }
218
219 attr.parse_nested_meta(|meta| {
220 if meta.path.is_ident("rename") {
221 let value: syn::LitStr = meta.value()?.parse()?;
222 result.rename = Some(value.value());
223 } else if meta.path.is_ident("skip") {
224 result.skip = true;
225 } else if meta.path.is_ident("flatten") {
226 result.flatten = true;
227 } else if meta.path.is_ident("type") {
228 let value: syn::LitStr = meta.value()?.parse()?;
229 result.type_override = Some(value.value());
230 } else if meta.path.is_ident("default") {
231 result.default = true;
232 } else if meta.path.is_ident("inline") {
233 result.inline = true;
234 }
235 Ok(())
236 })?;
237 }
238
239 Ok(result)
240 }
241}
242
243fn get_field_name(
245 original: &str,
246 field_attrs: &FieldAttrs,
247 container_attrs: &ContainerAttrs,
248) -> String {
249 if let Some(ref renamed) = field_attrs.rename {
251 return renamed.clone();
252 }
253
254 if let Some(rename_all) = container_attrs.rename_all {
256 return rename_all.apply(original);
257 }
258
259 original.to_string()
261}
262
263fn parse_template_pattern(pattern: &str) -> syn::Result<(Vec<String>, Vec<String>)> {
273 let mut strings = Vec::new();
274 let mut types = Vec::new();
275 let mut current = String::new();
276 let mut chars = pattern.chars().peekable();
277
278 while let Some(c) = chars.next() {
279 if c == '$' && chars.peek() == Some(&'{') {
280 strings.push(std::mem::take(&mut current));
282 chars.next(); let mut type_name = String::new();
285 let mut depth = 1;
286 while let Some(tc) = chars.next() {
287 if tc == '{' {
288 depth += 1;
289 type_name.push(tc);
290 } else if tc == '}' {
291 depth -= 1;
292 if depth == 0 {
293 break;
294 }
295 type_name.push(tc);
296 } else {
297 type_name.push(tc);
298 }
299 }
300
301 if type_name.is_empty() {
302 return Err(syn::Error::new(
303 proc_macro2::Span::call_site(),
304 "Empty type placeholder ${} in pattern",
305 ));
306 }
307 types.push(type_name);
308 } else {
309 current.push(c);
310 }
311 }
312
313 strings.push(current);
315
316 Ok((strings, types))
317}
318
319fn type_name_to_typedef(name: &str) -> TokenStream2 {
322 match name.trim() {
323 "string" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::String) },
324 "number" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Number) },
325 "boolean" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Boolean) },
326 "bigint" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::BigInt) },
327 "any" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Any) },
328 "unknown" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Unknown) },
329 other => {
331 let type_ref = other.trim();
332 quote! { ferro_type::TypeDef::Ref(#type_ref.to_string()) }
333 }
334 }
335}
336
337fn generate_template_literal_expr(strings: &[String], types: &[String]) -> TokenStream2 {
339 let string_literals: Vec<_> = strings.iter().map(|s| quote! { #s.to_string() }).collect();
340 let type_exprs: Vec<_> = types.iter().map(|t| {
341 let typedef = type_name_to_typedef(t);
342 quote! { Box::new(#typedef) }
343 }).collect();
344
345 quote! {
346 ferro_type::TypeDef::TemplateLiteral {
347 strings: vec![#(#string_literals),*],
348 types: vec![#(#type_exprs),*],
349 }
350 }
351}
352
353#[proc_macro_derive(TypeScript, attributes(ts))]
399pub fn derive_typescript(input: TokenStream) -> TokenStream {
400 let input = parse_macro_input!(input as DeriveInput);
401
402 match expand_derive_typescript(&input) {
403 Ok(tokens) => tokens.into(),
404 Err(err) => err.to_compile_error().into(),
405 }
406}
407
408fn expand_derive_typescript(input: &DeriveInput) -> syn::Result<TokenStream2> {
409 let name = &input.ident;
410 let generics = &input.generics;
411
412 let container_attrs = ContainerAttrs::from_attrs(&input.attrs)?;
414
415 let type_name = container_attrs
417 .rename
418 .clone()
419 .unwrap_or_else(|| name.to_string());
420
421 match &input.data {
422 Data::Enum(data) => {
423 let typedef = generate_enum_typedef(&data.variants, &container_attrs)?;
424 generate_impl(name, &type_name, &container_attrs.namespace, generics, typedef)
425 }
426 Data::Struct(data) => {
427 if container_attrs.transparent {
429 if let syn::Fields::Unnamed(fields) = &data.fields {
430 if fields.unnamed.len() == 1 {
431 let inner_type = &fields.unnamed.first().unwrap().ty;
432 return generate_transparent_impl(name, inner_type, generics);
433 }
434 }
435 return Err(syn::Error::new_spanned(
436 input,
437 "#[ts(transparent)] can only be used on newtype structs (single unnamed field)",
438 ));
439 }
440
441 if let Some(ref pattern) = container_attrs.pattern {
443 let (strings, types) = parse_template_pattern(pattern)?;
444 let typedef = generate_template_literal_expr(&strings, &types);
445 return generate_impl(name, &type_name, &[], generics, typedef);
446 }
447
448 let typedef = generate_struct_typedef(&data.fields, &container_attrs)?;
449 generate_impl(name, &type_name, &container_attrs.namespace, generics, typedef)
450 }
451 Data::Union(_) => {
452 Err(syn::Error::new_spanned(
453 input,
454 "TypeScript derive is not supported for unions",
455 ))
456 }
457 }
458}
459
460fn generate_enum_typedef(
461 variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
462 container_attrs: &ContainerAttrs,
463) -> syn::Result<TokenStream2> {
464 if variants.is_empty() {
465 return Err(syn::Error::new(
466 proc_macro2::Span::call_site(),
467 "Cannot derive TypeScript for empty enum",
468 ));
469 }
470
471 let all_unit = variants.iter().all(|v| matches!(v.fields, Fields::Unit));
473
474 if container_attrs.untagged {
476 return generate_untagged_enum(variants, container_attrs);
477 }
478
479 let tag_name = container_attrs.tag.as_deref().unwrap_or("type");
481
482 let content_name = container_attrs.content.as_deref();
484
485 if all_unit {
486 let mut variant_exprs: Vec<TokenStream2> = Vec::new();
488 for v in variants.iter() {
489 let variant_attrs = FieldAttrs::from_attrs(&v.attrs)?;
490 let name = get_field_name(&v.ident.to_string(), &variant_attrs, container_attrs);
491 variant_exprs.push(
492 quote! { ferro_type::TypeDef::Literal(ferro_type::Literal::String(#name.to_string())) }
493 );
494 }
495
496 Ok(quote! {
497 ferro_type::TypeDef::Union(vec![#(#variant_exprs),*])
498 })
499 } else {
500 let mut variant_exprs: Vec<TokenStream2> = Vec::new();
502
503 for variant in variants.iter() {
504 let variant_attrs = FieldAttrs::from_attrs(&variant.attrs)?;
505 let variant_name_str = get_field_name(
506 &variant.ident.to_string(),
507 &variant_attrs,
508 container_attrs,
509 );
510
511 let expr = match &variant.fields {
512 Fields::Unit => {
513 quote! {
515 ferro_type::TypeDef::Object(vec![
516 ferro_type::Field::new(
517 #tag_name,
518 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
519 )
520 ])
521 }
522 }
523 Fields::Unnamed(fields) => {
524 if let Some(content) = content_name {
525 let content_type = if fields.unnamed.len() == 1 {
527 let field_type = &fields.unnamed.first().unwrap().ty;
528 type_to_typedef(field_type)
529 } else {
530 let field_exprs: Vec<TokenStream2> = fields
531 .unnamed
532 .iter()
533 .map(|f| type_to_typedef(&f.ty))
534 .collect();
535 quote! { ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*]) }
536 };
537 quote! {
538 ferro_type::TypeDef::Object(vec![
539 ferro_type::Field::new(
540 #tag_name,
541 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
542 ),
543 ferro_type::Field::new(#content, #content_type)
544 ])
545 }
546 } else if fields.unnamed.len() == 1 {
547 let field_type = &fields.unnamed.first().unwrap().ty;
549 let type_expr = type_to_typedef(field_type);
550 quote! {
551 ferro_type::TypeDef::Object(vec![
552 ferro_type::Field::new(
553 #tag_name,
554 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
555 ),
556 ferro_type::Field::new("value", #type_expr)
557 ])
558 }
559 } else {
560 let field_exprs: Vec<TokenStream2> = fields
562 .unnamed
563 .iter()
564 .map(|f| type_to_typedef(&f.ty))
565 .collect();
566 quote! {
567 ferro_type::TypeDef::Object(vec![
568 ferro_type::Field::new(
569 #tag_name,
570 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
571 ),
572 ferro_type::Field::new(
573 "value",
574 ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*])
575 )
576 ])
577 }
578 }
579 }
580 Fields::Named(fields) => {
581 let mut field_exprs: Vec<TokenStream2> = Vec::new();
582 for f in fields.named.iter() {
583 let field_attrs = FieldAttrs::from_attrs(&f.attrs)?;
584 if field_attrs.skip {
585 continue;
586 }
587 let original_name = f.ident.as_ref().unwrap().to_string();
588 let field_name = field_attrs.rename.clone().unwrap_or(original_name);
589 let field_type = &f.ty;
590 let type_expr = type_to_typedef(field_type);
591 field_exprs.push(quote! {
592 ferro_type::Field::new(#field_name, #type_expr)
593 });
594 }
595
596 if let Some(content) = content_name {
597 quote! {
599 ferro_type::TypeDef::Object(vec![
600 ferro_type::Field::new(
601 #tag_name,
602 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
603 ),
604 ferro_type::Field::new(
605 #content,
606 ferro_type::TypeDef::Object(vec![#(#field_exprs),*])
607 )
608 ])
609 }
610 } else {
611 quote! {
613 ferro_type::TypeDef::Object({
614 let mut fields = vec![
615 ferro_type::Field::new(
616 #tag_name,
617 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
618 )
619 ];
620 fields.extend(vec![#(#field_exprs),*]);
621 fields
622 })
623 }
624 }
625 }
626 };
627 variant_exprs.push(expr);
628 }
629
630 Ok(quote! {
631 ferro_type::TypeDef::Union(vec![#(#variant_exprs),*])
632 })
633 }
634}
635
636fn generate_untagged_enum(
638 variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
639 container_attrs: &ContainerAttrs,
640) -> syn::Result<TokenStream2> {
641 let mut variant_exprs: Vec<TokenStream2> = Vec::new();
642
643 for variant in variants.iter() {
644 let variant_attrs = FieldAttrs::from_attrs(&variant.attrs)?;
645 let variant_name_str = get_field_name(
646 &variant.ident.to_string(),
647 &variant_attrs,
648 container_attrs,
649 );
650
651 let expr = match &variant.fields {
652 Fields::Unit => {
653 quote! {
655 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
656 }
657 }
658 Fields::Unnamed(fields) => {
659 if fields.unnamed.len() == 1 {
660 let field_type = &fields.unnamed.first().unwrap().ty;
662 type_to_typedef(field_type)
663 } else {
664 let field_exprs: Vec<TokenStream2> = fields
666 .unnamed
667 .iter()
668 .map(|f| type_to_typedef(&f.ty))
669 .collect();
670 quote! {
671 ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*])
672 }
673 }
674 }
675 Fields::Named(fields) => {
676 let mut field_exprs: Vec<TokenStream2> = Vec::new();
678 for f in fields.named.iter() {
679 let field_attrs = FieldAttrs::from_attrs(&f.attrs)?;
680 if field_attrs.skip {
681 continue;
682 }
683 let original_name = f.ident.as_ref().unwrap().to_string();
684 let field_name = field_attrs.rename.clone().unwrap_or(original_name);
685 let field_type = &f.ty;
686 let type_expr = type_to_typedef(field_type);
687 field_exprs.push(quote! {
688 ferro_type::Field::new(#field_name, #type_expr)
689 });
690 }
691 quote! {
692 ferro_type::TypeDef::Object(vec![#(#field_exprs),*])
693 }
694 }
695 };
696 variant_exprs.push(expr);
697 }
698
699 Ok(quote! {
700 ferro_type::TypeDef::Union(vec![#(#variant_exprs),*])
701 })
702}
703
704fn generate_struct_typedef(
705 fields: &syn::Fields,
706 container_attrs: &ContainerAttrs,
707) -> syn::Result<TokenStream2> {
708 match fields {
709 syn::Fields::Named(fields) => {
710 if fields.named.is_empty() {
712 return Ok(quote! { ferro_type::TypeDef::Object(vec![]) });
714 }
715
716 let mut regular_field_exprs: Vec<TokenStream2> = Vec::new();
718 let mut flatten_exprs: Vec<TokenStream2> = Vec::new();
719
720 for f in fields.named.iter() {
721 let field_attrs = FieldAttrs::from_attrs(&f.attrs)?;
722 if field_attrs.skip {
724 continue;
725 }
726
727 let field_type = &f.ty;
728
729 if field_attrs.flatten {
730 flatten_exprs.push(quote! {
732 {
733 let inner_td = <#field_type as ferro_type::TypeScript>::typescript();
734 ferro_type::extract_object_fields(&inner_td)
735 }
736 });
737 } else {
738 let original_name = f.ident.as_ref().unwrap().to_string();
739 let field_name = get_field_name(&original_name, &field_attrs, container_attrs);
740
741 let type_expr = if let Some(ref type_override) = field_attrs.type_override {
743 quote! { ferro_type::TypeDef::Ref(#type_override.to_string()) }
744 } else {
745 let base_expr = type_to_typedef(field_type);
746 if field_attrs.inline {
747 quote! { ferro_type::inline_typedef(#base_expr) }
748 } else {
749 base_expr
750 }
751 };
752
753 if field_attrs.default {
755 regular_field_exprs.push(quote! {
756 ferro_type::Field::optional(#field_name, #type_expr)
757 });
758 } else {
759 regular_field_exprs.push(quote! {
760 ferro_type::Field::new(#field_name, #type_expr)
761 });
762 }
763 }
764 }
765
766 if flatten_exprs.is_empty() {
768 Ok(quote! {
769 ferro_type::TypeDef::Object(vec![#(#regular_field_exprs),*])
770 })
771 } else {
772 Ok(quote! {
773 {
774 let mut fields = vec![#(#regular_field_exprs),*];
775 #(fields.extend(#flatten_exprs);)*
776 ferro_type::TypeDef::Object(fields)
777 }
778 })
779 }
780 }
781 syn::Fields::Unnamed(fields) => {
782 if fields.unnamed.len() == 1 {
784 let field_type = &fields.unnamed.first().unwrap().ty;
786 let type_expr = type_to_typedef(field_type);
787 Ok(quote! { #type_expr })
788 } else {
789 let field_exprs: Vec<TokenStream2> = fields
791 .unnamed
792 .iter()
793 .map(|f| type_to_typedef(&f.ty))
794 .collect();
795
796 Ok(quote! {
797 ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*])
798 })
799 }
800 }
801 syn::Fields::Unit => {
802 Ok(quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Null) })
804 }
805 }
806}
807
808fn type_to_typedef(ty: &Type) -> TokenStream2 {
811 quote! { <#ty as ferro_type::TypeScript>::typescript() }
812}
813
814fn generate_transparent_impl(
817 name: &Ident,
818 inner_type: &Type,
819 generics: &Generics,
820) -> syn::Result<TokenStream2> {
821 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
822
823 let where_clause = if generics.params.is_empty() {
825 where_clause.cloned()
826 } else {
827 let type_params: Vec<_> = generics.params.iter().filter_map(|p| {
828 if let GenericParam::Type(tp) = p {
829 Some(&tp.ident)
830 } else {
831 None
832 }
833 }).collect();
834
835 if type_params.is_empty() {
836 where_clause.cloned()
837 } else {
838 let bounds = type_params.iter().map(|p| {
839 quote! { #p: ferro_type::TypeScript }
840 });
841
842 if let Some(existing_where) = where_clause {
843 let existing_predicates = &existing_where.predicates;
844 Some(syn::parse_quote! { where #(#bounds,)* #existing_predicates })
845 } else {
846 Some(syn::parse_quote! { where #(#bounds),* })
847 }
848 }
849 };
850
851 Ok(quote! {
852 impl #impl_generics ferro_type::TypeScript for #name #ty_generics #where_clause {
853 fn typescript() -> ferro_type::TypeDef {
854 <#inner_type as ferro_type::TypeScript>::typescript()
855 }
856 }
857 })
858}
859
860fn generate_impl(
861 name: &Ident,
862 name_str: &str,
863 namespace: &[String],
864 generics: &Generics,
865 typedef_expr: TokenStream2,
866) -> syn::Result<TokenStream2> {
867 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
868
869 let where_clause = if generics.params.is_empty() {
871 where_clause.cloned()
872 } else {
873 let type_params: Vec<_> = generics.params.iter().filter_map(|p| {
874 if let GenericParam::Type(tp) = p {
875 Some(&tp.ident)
876 } else {
877 None
878 }
879 }).collect();
880
881 if type_params.is_empty() {
882 where_clause.cloned()
883 } else {
884 let bounds = type_params.iter().map(|p| {
885 quote! { #p: ferro_type::TypeScript }
886 });
887
888 if let Some(existing_where) = where_clause {
889 let existing_predicates = &existing_where.predicates;
890 Some(syn::parse_quote! { where #(#bounds,)* #existing_predicates })
891 } else {
892 Some(syn::parse_quote! { where #(#bounds),* })
893 }
894 }
895 };
896
897 let registration = if generics.params.is_empty() {
900 let register_name = syn::Ident::new(
901 &format!("__FERRO_TYPE_REGISTER_{}", name.to_string().to_uppercase()),
902 name.span(),
903 );
904 quote! {
905 #[ferro_type::linkme::distributed_slice(ferro_type::TYPESCRIPT_TYPES)]
906 #[linkme(crate = ferro_type::linkme)]
907 static #register_name: fn() -> ferro_type::TypeDef = || <#name as ferro_type::TypeScript>::typescript();
908 }
909 } else {
910 quote! {}
911 };
912
913 let namespace_expr = if namespace.is_empty() {
915 quote! { vec![] }
916 } else {
917 let ns_strings = namespace.iter().map(|s| quote! { #s.to_string() });
918 quote! { vec![#(#ns_strings),*] }
919 };
920
921 Ok(quote! {
922 impl #impl_generics ferro_type::TypeScript for #name #ty_generics #where_clause {
923 fn typescript() -> ferro_type::TypeDef {
924 ferro_type::TypeDef::Named {
925 namespace: #namespace_expr,
926 name: #name_str.to_string(),
927 def: Box::new(#typedef_expr),
928 module: Some(module_path!().to_string()),
929 }
930 }
931 }
932
933 #registration
934 })
935}