1pub use ferro_type_derive::TypeScript;
18pub use linkme;
19
20use std::collections::HashMap;
21
22#[linkme::distributed_slice]
43pub static TYPESCRIPT_TYPES: [fn() -> TypeDef];
44
45pub trait TypeScript {
81 fn typescript() -> TypeDef;
83}
84
85#[derive(Debug, Clone, PartialEq)]
98pub enum TypeDef {
99 Primitive(Primitive),
101
102 Array(Box<TypeDef>),
104
105 Tuple(Vec<TypeDef>),
107
108 Object(Vec<Field>),
110
111 Union(Vec<TypeDef>),
113
114 Intersection(Vec<TypeDef>),
116
117 Record {
119 key: Box<TypeDef>,
120 value: Box<TypeDef>,
121 },
122
123 Named {
130 namespace: Vec<String>,
132 name: String,
134 def: Box<TypeDef>,
136 module: Option<String>,
138 },
139
140 Ref(String),
143
144 Literal(Literal),
146
147 Function {
149 params: Vec<Field>,
150 return_type: Box<TypeDef>,
151 },
152
153 Generic {
155 base: String,
156 args: Vec<TypeDef>,
157 },
158
159 IndexedAccess {
175 base: String,
177 key: String,
179 },
180
181 TemplateLiteral {
197 strings: Vec<String>,
201 types: Vec<Box<TypeDef>>,
203 },
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
208pub enum Primitive {
209 String,
211 Number,
213 Boolean,
215 Null,
217 Undefined,
219 Void,
221 Never,
223 Any,
225 Unknown,
227 BigInt,
229}
230
231#[derive(Debug, Clone, PartialEq)]
233pub struct Field {
234 pub name: String,
236 pub ty: TypeDef,
238 pub optional: bool,
240 pub readonly: bool,
242}
243
244impl Field {
245 pub fn new(name: impl Into<String>, ty: TypeDef) -> Self {
247 Self {
248 name: name.into(),
249 ty,
250 optional: false,
251 readonly: false,
252 }
253 }
254
255 pub fn optional(name: impl Into<String>, ty: TypeDef) -> Self {
257 Self {
258 name: name.into(),
259 ty,
260 optional: true,
261 readonly: false,
262 }
263 }
264
265 pub fn readonly(mut self) -> Self {
267 self.readonly = true;
268 self
269 }
270}
271
272#[derive(Debug, Clone, PartialEq)]
274pub enum Literal {
275 String(String),
277 Number(f64),
279 Boolean(bool),
281}
282
283impl TypeDef {
284 pub fn render(&self) -> String {
286 match self {
287 TypeDef::Primitive(p) => p.render().to_string(),
288 TypeDef::Array(inner) => {
289 let inner_str = inner.render();
290 if matches!(inner.as_ref(), TypeDef::Union(_)) {
292 format!("({})[]", inner_str)
293 } else {
294 format!("{}[]", inner_str)
295 }
296 }
297 TypeDef::Tuple(items) => {
298 let items_str: Vec<_> = items.iter().map(|t| t.render()).collect();
299 format!("[{}]", items_str.join(", "))
300 }
301 TypeDef::Object(fields) => {
302 if fields.is_empty() {
303 "{}".to_string()
304 } else {
305 let fields_str: Vec<_> = fields
306 .iter()
307 .map(|f| {
308 let readonly = if f.readonly { "readonly " } else { "" };
309 let opt = if f.optional { "?" } else { "" };
310 format!("{}{}{}: {}", readonly, f.name, opt, f.ty.render())
311 })
312 .collect();
313 format!("{{ {} }}", fields_str.join("; "))
314 }
315 }
316 TypeDef::Union(variants) => {
317 let variants_str: Vec<_> = variants.iter().map(|t| t.render()).collect();
318 variants_str.join(" | ")
319 }
320 TypeDef::Intersection(types) => {
321 let types_str: Vec<_> = types.iter().map(|t| t.render()).collect();
322 types_str.join(" & ")
323 }
324 TypeDef::Record { key, value } => {
325 format!("Record<{}, {}>", key.render(), value.render())
326 }
327 TypeDef::Named { namespace, name, .. } => {
328 if namespace.is_empty() {
329 name.clone()
330 } else {
331 format!("{}.{}", namespace.join("."), name)
332 }
333 }
334 TypeDef::Ref(name) => name.clone(),
335 TypeDef::Literal(lit) => lit.render(),
336 TypeDef::Function {
337 params,
338 return_type,
339 } => {
340 let params_str: Vec<_> = params
341 .iter()
342 .map(|p| format!("{}: {}", p.name, p.ty.render()))
343 .collect();
344 format!("({}) => {}", params_str.join(", "), return_type.render())
345 }
346 TypeDef::Generic { base, args } => {
347 let args_str: Vec<_> = args.iter().map(|t| t.render()).collect();
348 format!("{}<{}>", base, args_str.join(", "))
349 }
350 TypeDef::IndexedAccess { base, key } => {
351 format!("{}[\"{}\"]", base, key.replace('\\', "\\\\").replace('"', "\\\""))
352 }
353 TypeDef::TemplateLiteral { strings, types } => {
354 let mut result = String::from("`");
355 for (i, s) in strings.iter().enumerate() {
356 let escaped = s.replace('\\', "\\\\").replace('`', "\\`");
358 result.push_str(&escaped);
359 if i < types.len() {
360 result.push_str("${");
361 result.push_str(&types[i].render());
362 result.push('}');
363 }
364 }
365 result.push('`');
366 result
367 }
368 }
369 }
370
371 pub fn render_declaration(&self) -> String {
377 match self {
378 TypeDef::Named { namespace, name, def, .. } => {
379 let inner = format!("type {} = {};", name, def.render());
380 Self::wrap_in_namespace(namespace, &inner)
381 }
382 _ => self.render(),
383 }
384 }
385
386 fn wrap_in_namespace(namespace: &[String], inner: &str) -> String {
397 if namespace.is_empty() {
398 return inner.to_string();
399 }
400
401 let mut result = String::new();
402 let indent = " ";
403
404 for (i, ns) in namespace.iter().enumerate() {
406 for _ in 0..i {
407 result.push_str(indent);
408 }
409 result.push_str("namespace ");
410 result.push_str(ns);
411 result.push_str(" {\n");
412 }
413
414 let depth = namespace.len();
416 for _ in 0..depth {
417 result.push_str(indent);
418 }
419 result.push_str(inner);
420 result.push('\n');
421
422 for i in (0..depth).rev() {
424 for _ in 0..i {
425 result.push_str(indent);
426 }
427 result.push('}');
428 if i > 0 {
429 result.push('\n');
430 }
431 }
432
433 result
434 }
435}
436
437impl Primitive {
438 pub fn render(&self) -> &'static str {
440 match self {
441 Primitive::String => "string",
442 Primitive::Number => "number",
443 Primitive::Boolean => "boolean",
444 Primitive::Null => "null",
445 Primitive::Undefined => "undefined",
446 Primitive::Void => "void",
447 Primitive::Never => "never",
448 Primitive::Any => "any",
449 Primitive::Unknown => "unknown",
450 Primitive::BigInt => "bigint",
451 }
452 }
453}
454
455impl Literal {
456 pub fn render(&self) -> String {
458 match self {
459 Literal::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
460 Literal::Number(n) => {
461 if n.fract() == 0.0 {
462 format!("{}", *n as i64)
463 } else {
464 format!("{}", n)
465 }
466 }
467 Literal::Boolean(b) => b.to_string(),
468 }
469 }
470}
471
472pub fn extract_object_fields(typedef: &TypeDef) -> Vec<Field> {
486 match typedef {
487 TypeDef::Object(fields) => fields.clone(),
488 TypeDef::Named { def, .. } => extract_object_fields(def),
489 other => panic!(
490 "#[ts(flatten)] can only be used on fields with object types, got: {:?}",
491 other
492 ),
493 }
494}
495
496pub fn inline_typedef(typedef: TypeDef) -> TypeDef {
502 match typedef {
503 TypeDef::Named { def, .. } => *def,
504 other => other,
505 }
506}
507
508use std::collections::{HashSet, VecDeque};
513
514#[derive(Debug, Default)]
535pub struct TypeRegistry {
536 types: HashMap<String, TypeDef>,
538 registration_order: Vec<String>,
540}
541
542impl TypeRegistry {
543 pub fn new() -> Self {
545 Self::default()
546 }
547
548 pub fn from_distributed() -> Self {
569 let mut registry = Self::new();
570 for type_fn in TYPESCRIPT_TYPES {
571 let typedef = type_fn();
572 registry.add_typedef(typedef);
573 }
574 registry
575 }
576
577 pub fn collect_all(&mut self) {
592 for type_fn in TYPESCRIPT_TYPES {
593 let typedef = type_fn();
594 self.add_typedef(typedef);
595 }
596 }
597
598 pub fn register<T: TypeScript>(&mut self) {
603 let typedef = T::typescript();
604 self.add_typedef(typedef);
605 }
606
607 pub fn add_typedef(&mut self, typedef: TypeDef) {
609 self.extract_named_types(&typedef);
610 }
611
612 fn extract_named_types(&mut self, typedef: &TypeDef) {
614 match typedef {
615 TypeDef::Named { namespace, name, def, .. } => {
616 let qualified_name = if namespace.is_empty() {
618 name.clone()
619 } else {
620 format!("{}.{}", namespace.join("."), name)
621 };
622
623 if !self.types.contains_key(&qualified_name) {
624 self.types.insert(qualified_name.clone(), typedef.clone());
625 self.registration_order.push(qualified_name);
626 self.extract_named_types(def);
628 }
629 }
630 TypeDef::Array(inner) => self.extract_named_types(inner),
631 TypeDef::Tuple(items) => {
632 for item in items {
633 self.extract_named_types(item);
634 }
635 }
636 TypeDef::Object(fields) => {
637 for field in fields {
638 self.extract_named_types(&field.ty);
639 }
640 }
641 TypeDef::Union(items) | TypeDef::Intersection(items) => {
642 for item in items {
643 self.extract_named_types(item);
644 }
645 }
646 TypeDef::Record { key, value } => {
647 self.extract_named_types(key);
648 self.extract_named_types(value);
649 }
650 TypeDef::Function { params, return_type } => {
651 for param in params {
652 self.extract_named_types(¶m.ty);
653 }
654 self.extract_named_types(return_type);
655 }
656 TypeDef::Generic { args, .. } => {
657 for arg in args {
658 self.extract_named_types(arg);
659 }
660 }
661 TypeDef::TemplateLiteral { types, .. } => {
662 for ty in types {
663 self.extract_named_types(ty);
664 }
665 }
666 TypeDef::Primitive(_) | TypeDef::Ref(_) | TypeDef::Literal(_) | TypeDef::IndexedAccess { .. } => {}
669 }
670 }
671
672 pub fn len(&self) -> usize {
674 self.types.len()
675 }
676
677 pub fn is_empty(&self) -> bool {
679 self.types.is_empty()
680 }
681
682 pub fn type_names(&self) -> impl Iterator<Item = &str> {
684 self.types.keys().map(|s| s.as_str())
685 }
686
687 pub fn get(&self, name: &str) -> Option<&TypeDef> {
689 self.types.get(name)
690 }
691
692 fn get_dependencies(&self, typedef: &TypeDef) -> HashSet<String> {
694 let mut deps = HashSet::new();
695 self.collect_dependencies(typedef, &mut deps);
696 deps
697 }
698
699 fn collect_dependencies(&self, typedef: &TypeDef, deps: &mut HashSet<String>) {
701 match typedef {
702 TypeDef::Named { def, .. } => {
703 self.collect_dependencies(def, deps);
705 }
706 TypeDef::Ref(name) => {
707 if self.types.contains_key(name) {
708 deps.insert(name.clone());
709 }
710 }
711 TypeDef::Array(inner) => self.collect_dependencies(inner, deps),
712 TypeDef::Tuple(items) => {
713 for item in items {
714 self.collect_dependencies(item, deps);
715 }
716 }
717 TypeDef::Object(fields) => {
718 for field in fields {
719 self.collect_dependencies(&field.ty, deps);
720 }
721 }
722 TypeDef::Union(variants) => {
723 for v in variants {
724 self.collect_dependencies(v, deps);
725 }
726 }
727 TypeDef::Intersection(types) => {
728 for t in types {
729 self.collect_dependencies(t, deps);
730 }
731 }
732 TypeDef::Record { key, value } => {
733 self.collect_dependencies(key, deps);
734 self.collect_dependencies(value, deps);
735 }
736 TypeDef::Function { params, return_type } => {
737 for param in params {
738 self.collect_dependencies(¶m.ty, deps);
739 }
740 self.collect_dependencies(return_type, deps);
741 }
742 TypeDef::Generic { args, .. } => {
743 for arg in args {
744 self.collect_dependencies(arg, deps);
745 }
746 }
747 TypeDef::TemplateLiteral { types, .. } => {
748 for ty in types {
749 self.collect_dependencies(ty, deps);
750 }
751 }
752 TypeDef::IndexedAccess { base, .. } => {
753 if self.types.contains_key(base) {
755 deps.insert(base.clone());
756 }
757 }
758 TypeDef::Primitive(_) | TypeDef::Literal(_) => {}
759 }
760 }
761
762 pub fn sorted_types(&self) -> Vec<&str> {
766 let mut in_degree: HashMap<&str, usize> = HashMap::new();
768 let mut dependents: HashMap<&str, Vec<&str>> = HashMap::new();
769
770 for name in self.types.keys() {
772 in_degree.insert(name.as_str(), 0);
773 dependents.insert(name.as_str(), Vec::new());
774 }
775
776 for (name, typedef) in &self.types {
778 let deps = self.get_dependencies(typedef);
779 for dep in deps {
780 if let Some(dep_name) = self.types.get_key_value(&dep) {
781 *in_degree.get_mut(name.as_str()).unwrap() += 1;
782 dependents.get_mut(dep_name.0.as_str()).unwrap().push(name.as_str());
783 }
784 }
785 }
786
787 let mut queue: VecDeque<&str> = VecDeque::new();
789 let mut result: Vec<&str> = Vec::new();
790
791 for (name, °ree) in &in_degree {
793 if degree == 0 {
794 queue.push_back(name);
795 }
796 }
797
798 let mut initial: Vec<_> = queue.drain(..).collect();
800 initial.sort_by_key(|name| {
801 self.registration_order.iter().position(|n| n == *name).unwrap_or(usize::MAX)
802 });
803 queue.extend(initial);
804
805 while let Some(name) = queue.pop_front() {
806 result.push(name);
807
808 let mut deps: Vec<_> = dependents.get(name).map(|v| v.as_slice()).unwrap_or(&[]).to_vec();
810 deps.sort_by_key(|n| {
811 self.registration_order.iter().position(|name| name == *n).unwrap_or(usize::MAX)
812 });
813
814 for dependent in deps {
815 let degree = in_degree.get_mut(dependent).unwrap();
816 *degree -= 1;
817 if *degree == 0 {
818 queue.push_back(dependent);
819 }
820 }
821 }
822
823 if result.len() < self.types.len() {
826 for name in &self.registration_order {
827 if !result.contains(&name.as_str()) {
828 result.push(name.as_str());
829 }
830 }
831 }
832
833 result
834 }
835
836 pub fn render(&self) -> String {
840 let sorted = self.sorted_types();
841 let mut output = String::new();
842
843 output.push_str("// Generated by ferrotype\n");
845 output.push_str("// Do not edit manually\n\n");
846
847 for name in sorted {
848 if let Some(typedef) = self.types.get(name) {
849 output.push_str(&typedef.render_declaration());
850 output.push_str("\n\n");
851 }
852 }
853
854 output.trim_end().to_string() + "\n"
856 }
857
858 pub fn render_exported(&self) -> String {
862 let sorted = self.sorted_types();
863 let mut output = String::new();
864
865 output.push_str("// Generated by ferrotype\n");
867 output.push_str("// Do not edit manually\n\n");
868
869 for name in sorted {
870 if let Some(typedef) = self.types.get(name) {
871 if let TypeDef::Named { namespace, name, def, .. } = typedef {
872 let inner = format!("export type {} = {};", name, def.render());
873 if namespace.is_empty() {
874 output.push_str(&inner);
875 } else {
876 output.push_str(&Self::wrap_in_export_namespace(namespace, &inner));
878 }
879 output.push_str("\n\n");
880 }
881 }
882 }
883
884 output.trim_end().to_string() + "\n"
886 }
887
888 fn wrap_in_export_namespace(namespace: &[String], inner: &str) -> String {
890 if namespace.is_empty() {
891 return inner.to_string();
892 }
893
894 let mut result = String::new();
895 let indent = " ";
896
897 for (i, ns) in namespace.iter().enumerate() {
899 for _ in 0..i {
900 result.push_str(indent);
901 }
902 result.push_str("export namespace ");
903 result.push_str(ns);
904 result.push_str(" {\n");
905 }
906
907 let depth = namespace.len();
909 for _ in 0..depth {
910 result.push_str(indent);
911 }
912 result.push_str(inner);
913 result.push('\n');
914
915 for i in (0..depth).rev() {
917 for _ in 0..i {
918 result.push_str(indent);
919 }
920 result.push('}');
921 if i > 0 {
922 result.push('\n');
923 }
924 }
925
926 result
927 }
928
929 pub fn clear(&mut self) {
931 self.types.clear();
932 self.registration_order.clear();
933 }
934}
935
936impl TypeScript for () {
941 fn typescript() -> TypeDef {
942 TypeDef::Primitive(Primitive::Void)
943 }
944}
945
946impl TypeScript for bool {
947 fn typescript() -> TypeDef {
948 TypeDef::Primitive(Primitive::Boolean)
949 }
950}
951
952impl TypeScript for String {
953 fn typescript() -> TypeDef {
954 TypeDef::Primitive(Primitive::String)
955 }
956}
957
958impl TypeScript for &str {
959 fn typescript() -> TypeDef {
960 TypeDef::Primitive(Primitive::String)
961 }
962}
963
964impl TypeScript for char {
965 fn typescript() -> TypeDef {
966 TypeDef::Primitive(Primitive::String)
967 }
968}
969
970macro_rules! impl_typescript_number {
971 ($($t:ty),*) => {
972 $(
973 impl TypeScript for $t {
974 fn typescript() -> TypeDef {
975 TypeDef::Primitive(Primitive::Number)
976 }
977 }
978 )*
979 };
980}
981
982impl_typescript_number!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64);
983
984impl TypeScript for i128 {
986 fn typescript() -> TypeDef {
987 TypeDef::Primitive(Primitive::BigInt)
988 }
989}
990
991impl TypeScript for u128 {
992 fn typescript() -> TypeDef {
993 TypeDef::Primitive(Primitive::BigInt)
994 }
995}
996
997impl<T: TypeScript> TypeScript for Option<T> {
1002 fn typescript() -> TypeDef {
1003 TypeDef::Union(vec![T::typescript(), TypeDef::Primitive(Primitive::Null)])
1004 }
1005}
1006
1007impl<T: TypeScript> TypeScript for Vec<T> {
1008 fn typescript() -> TypeDef {
1009 TypeDef::Array(Box::new(T::typescript()))
1010 }
1011}
1012
1013impl<T: TypeScript> TypeScript for Box<T> {
1014 fn typescript() -> TypeDef {
1015 T::typescript()
1016 }
1017}
1018
1019impl<T: TypeScript> TypeScript for std::rc::Rc<T> {
1020 fn typescript() -> TypeDef {
1021 T::typescript()
1022 }
1023}
1024
1025impl<T: TypeScript> TypeScript for std::sync::Arc<T> {
1026 fn typescript() -> TypeDef {
1027 T::typescript()
1028 }
1029}
1030
1031impl<T: TypeScript> TypeScript for std::cell::RefCell<T> {
1032 fn typescript() -> TypeDef {
1033 T::typescript()
1034 }
1035}
1036
1037impl<T: TypeScript> TypeScript for std::cell::Cell<T> {
1038 fn typescript() -> TypeDef {
1039 T::typescript()
1040 }
1041}
1042
1043impl<K: TypeScript, V: TypeScript> TypeScript for HashMap<K, V> {
1044 fn typescript() -> TypeDef {
1045 TypeDef::Record {
1046 key: Box::new(K::typescript()),
1047 value: Box::new(V::typescript()),
1048 }
1049 }
1050}
1051
1052impl<K: TypeScript, V: TypeScript> TypeScript for std::collections::BTreeMap<K, V> {
1053 fn typescript() -> TypeDef {
1054 TypeDef::Record {
1055 key: Box::new(K::typescript()),
1056 value: Box::new(V::typescript()),
1057 }
1058 }
1059}
1060
1061impl<T: TypeScript, E: TypeScript> TypeScript for Result<T, E> {
1062 fn typescript() -> TypeDef {
1063 TypeDef::Union(vec![
1064 TypeDef::Object(vec![
1065 Field::new("ok", TypeDef::Literal(Literal::Boolean(true))),
1066 Field::new("value", T::typescript()),
1067 ]),
1068 TypeDef::Object(vec![
1069 Field::new("ok", TypeDef::Literal(Literal::Boolean(false))),
1070 Field::new("error", E::typescript()),
1071 ]),
1072 ])
1073 }
1074}
1075
1076impl<A: TypeScript> TypeScript for (A,) {
1081 fn typescript() -> TypeDef {
1082 TypeDef::Tuple(vec![A::typescript()])
1083 }
1084}
1085
1086impl<A: TypeScript, B: TypeScript> TypeScript for (A, B) {
1087 fn typescript() -> TypeDef {
1088 TypeDef::Tuple(vec![A::typescript(), B::typescript()])
1089 }
1090}
1091
1092impl<A: TypeScript, B: TypeScript, C: TypeScript> TypeScript for (A, B, C) {
1093 fn typescript() -> TypeDef {
1094 TypeDef::Tuple(vec![A::typescript(), B::typescript(), C::typescript()])
1095 }
1096}
1097
1098impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript> TypeScript for (A, B, C, D) {
1099 fn typescript() -> TypeDef {
1100 TypeDef::Tuple(vec![
1101 A::typescript(),
1102 B::typescript(),
1103 C::typescript(),
1104 D::typescript(),
1105 ])
1106 }
1107}
1108
1109impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript, E: TypeScript> TypeScript
1110 for (A, B, C, D, E)
1111{
1112 fn typescript() -> TypeDef {
1113 TypeDef::Tuple(vec![
1114 A::typescript(),
1115 B::typescript(),
1116 C::typescript(),
1117 D::typescript(),
1118 E::typescript(),
1119 ])
1120 }
1121}
1122
1123impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript, E: TypeScript, F: TypeScript>
1124 TypeScript for (A, B, C, D, E, F)
1125{
1126 fn typescript() -> TypeDef {
1127 TypeDef::Tuple(vec![
1128 A::typescript(),
1129 B::typescript(),
1130 C::typescript(),
1131 D::typescript(),
1132 E::typescript(),
1133 F::typescript(),
1134 ])
1135 }
1136}
1137
1138#[cfg(test)]
1143mod tests {
1144 use super::*;
1145
1146 #[test]
1151 fn test_typedef_primitive_render() {
1152 assert_eq!(TypeDef::Primitive(Primitive::String).render(), "string");
1153 assert_eq!(TypeDef::Primitive(Primitive::Number).render(), "number");
1154 assert_eq!(TypeDef::Primitive(Primitive::Boolean).render(), "boolean");
1155 assert_eq!(TypeDef::Primitive(Primitive::Null).render(), "null");
1156 assert_eq!(TypeDef::Primitive(Primitive::Undefined).render(), "undefined");
1157 assert_eq!(TypeDef::Primitive(Primitive::Void).render(), "void");
1158 assert_eq!(TypeDef::Primitive(Primitive::Never).render(), "never");
1159 assert_eq!(TypeDef::Primitive(Primitive::Any).render(), "any");
1160 assert_eq!(TypeDef::Primitive(Primitive::Unknown).render(), "unknown");
1161 assert_eq!(TypeDef::Primitive(Primitive::BigInt).render(), "bigint");
1162 }
1163
1164 #[test]
1165 fn test_typedef_array_render() {
1166 let arr = TypeDef::Array(Box::new(TypeDef::Primitive(Primitive::String)));
1167 assert_eq!(arr.render(), "string[]");
1168
1169 let union_arr = TypeDef::Array(Box::new(TypeDef::Union(vec![
1171 TypeDef::Primitive(Primitive::String),
1172 TypeDef::Primitive(Primitive::Number),
1173 ])));
1174 assert_eq!(union_arr.render(), "(string | number)[]");
1175 }
1176
1177 #[test]
1178 fn test_typedef_tuple_render() {
1179 let tuple = TypeDef::Tuple(vec![
1180 TypeDef::Primitive(Primitive::String),
1181 TypeDef::Primitive(Primitive::Number),
1182 ]);
1183 assert_eq!(tuple.render(), "[string, number]");
1184 }
1185
1186 #[test]
1187 fn test_typedef_object_render() {
1188 let obj = TypeDef::Object(vec![
1189 Field::new("name", TypeDef::Primitive(Primitive::String)),
1190 Field::optional("age", TypeDef::Primitive(Primitive::Number)),
1191 ]);
1192 assert_eq!(obj.render(), "{ name: string; age?: number }");
1193
1194 let empty_obj = TypeDef::Object(vec![]);
1195 assert_eq!(empty_obj.render(), "{}");
1196 }
1197
1198 #[test]
1199 fn test_typedef_object_readonly_field() {
1200 let obj = TypeDef::Object(vec![
1201 Field::new("id", TypeDef::Primitive(Primitive::String)).readonly(),
1202 ]);
1203 assert_eq!(obj.render(), "{ readonly id: string }");
1204 }
1205
1206 #[test]
1207 fn test_typedef_union_render() {
1208 let union = TypeDef::Union(vec![
1209 TypeDef::Primitive(Primitive::String),
1210 TypeDef::Primitive(Primitive::Number),
1211 TypeDef::Primitive(Primitive::Null),
1212 ]);
1213 assert_eq!(union.render(), "string | number | null");
1214 }
1215
1216 #[test]
1217 fn test_typedef_intersection_render() {
1218 let intersection = TypeDef::Intersection(vec![
1219 TypeDef::Ref("Base".into()),
1220 TypeDef::Object(vec![
1221 Field::new("extra", TypeDef::Primitive(Primitive::String)),
1222 ]),
1223 ]);
1224 assert_eq!(intersection.render(), "Base & { extra: string }");
1225 }
1226
1227 #[test]
1228 fn test_typedef_record_render() {
1229 let record = TypeDef::Record {
1230 key: Box::new(TypeDef::Primitive(Primitive::String)),
1231 value: Box::new(TypeDef::Primitive(Primitive::Number)),
1232 };
1233 assert_eq!(record.render(), "Record<string, number>");
1234 }
1235
1236 #[test]
1237 fn test_typedef_named_render() {
1238 let named = TypeDef::Named {
1239 namespace: vec![],
1240 name: "UserId".into(),
1241 def: Box::new(TypeDef::Primitive(Primitive::String)),
1242 module: None,
1243 };
1244 assert_eq!(named.render(), "UserId");
1246 assert_eq!(named.render_declaration(), "type UserId = string;");
1248 }
1249
1250 #[test]
1251 fn test_typedef_namespaced_render() {
1252 let namespaced = TypeDef::Named {
1254 namespace: vec!["VM".into(), "Git".into()],
1255 name: "State".into(),
1256 def: Box::new(TypeDef::Union(vec![
1257 TypeDef::Literal(Literal::String("clean".into())),
1258 TypeDef::Literal(Literal::String("dirty".into())),
1259 TypeDef::Literal(Literal::String("unknown".into())),
1260 ])),
1261 module: None,
1262 };
1263 assert_eq!(namespaced.render(), "VM.Git.State");
1265 let decl = namespaced.render_declaration();
1267 assert!(decl.contains("namespace VM {"));
1268 assert!(decl.contains("namespace Git {"));
1269 assert!(decl.contains(r#"type State = "clean" | "dirty" | "unknown";"#));
1270 }
1271
1272 #[test]
1273 fn test_typedef_single_namespace_render() {
1274 let namespaced = TypeDef::Named {
1275 namespace: vec!["API".into()],
1276 name: "Response".into(),
1277 def: Box::new(TypeDef::Primitive(Primitive::String)),
1278 module: None,
1279 };
1280 assert_eq!(namespaced.render(), "API.Response");
1281 let decl = namespaced.render_declaration();
1282 assert!(decl.contains("namespace API {"));
1283 assert!(decl.contains("type Response = string;"));
1284 }
1285
1286 #[test]
1287 fn test_typedef_ref_render() {
1288 let ref_type = TypeDef::Ref("User".into());
1289 assert_eq!(ref_type.render(), "User");
1290 }
1291
1292 #[test]
1293 fn test_typedef_literal_render() {
1294 assert_eq!(TypeDef::Literal(Literal::String("foo".into())).render(), "\"foo\"");
1295 assert_eq!(TypeDef::Literal(Literal::Number(42.0)).render(), "42");
1296 assert_eq!(TypeDef::Literal(Literal::Number(3.14)).render(), "3.14");
1297 assert_eq!(TypeDef::Literal(Literal::Boolean(true)).render(), "true");
1298 assert_eq!(TypeDef::Literal(Literal::Boolean(false)).render(), "false");
1299 }
1300
1301 #[test]
1302 fn test_typedef_literal_escaping() {
1303 let lit = Literal::String("say \"hello\"".into());
1304 assert_eq!(lit.render(), "\"say \\\"hello\\\"\"");
1305 }
1306
1307 #[test]
1308 fn test_typedef_function_render() {
1309 let func = TypeDef::Function {
1310 params: vec![
1311 Field::new("name", TypeDef::Primitive(Primitive::String)),
1312 Field::new("age", TypeDef::Primitive(Primitive::Number)),
1313 ],
1314 return_type: Box::new(TypeDef::Primitive(Primitive::Void)),
1315 };
1316 assert_eq!(func.render(), "(name: string, age: number) => void");
1317 }
1318
1319 #[test]
1320 fn test_typedef_generic_render() {
1321 let generic = TypeDef::Generic {
1322 base: "Promise".into(),
1323 args: vec![TypeDef::Primitive(Primitive::String)],
1324 };
1325 assert_eq!(generic.render(), "Promise<string>");
1326
1327 let multi_generic = TypeDef::Generic {
1328 base: "Map".into(),
1329 args: vec![
1330 TypeDef::Primitive(Primitive::String),
1331 TypeDef::Primitive(Primitive::Number),
1332 ],
1333 };
1334 assert_eq!(multi_generic.render(), "Map<string, number>");
1335 }
1336
1337 #[test]
1338 fn test_typedef_template_literal_render() {
1339 let vm_id = TypeDef::TemplateLiteral {
1341 strings: vec!["vm-".into(), "".into()],
1342 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1343 };
1344 assert_eq!(vm_id.render(), "`vm-${string}`");
1345
1346 let semver = TypeDef::TemplateLiteral {
1348 strings: vec!["v".into(), ".".into(), ".".into(), "".into()],
1349 types: vec![
1350 Box::new(TypeDef::Primitive(Primitive::Number)),
1351 Box::new(TypeDef::Primitive(Primitive::Number)),
1352 Box::new(TypeDef::Primitive(Primitive::Number)),
1353 ],
1354 };
1355 assert_eq!(semver.render(), "`v${number}.${number}.${number}`");
1356
1357 let api_route = TypeDef::TemplateLiteral {
1359 strings: vec!["/api/".into(), "".into()],
1360 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1361 };
1362 assert_eq!(api_route.render(), "`/api/${string}`");
1363
1364 let complex_id = TypeDef::TemplateLiteral {
1366 strings: vec!["user-".into(), "-id".into()],
1367 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1368 };
1369 assert_eq!(complex_id.render(), "`user-${string}-id`");
1370
1371 let static_str = TypeDef::TemplateLiteral {
1373 strings: vec!["static-value".into()],
1374 types: vec![],
1375 };
1376 assert_eq!(static_str.render(), "`static-value`");
1377 }
1378
1379 #[test]
1380 fn test_typedef_template_literal_escaping() {
1381 let with_backtick = TypeDef::TemplateLiteral {
1383 strings: vec!["code: `".into(), "`".into()],
1384 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1385 };
1386 assert_eq!(with_backtick.render(), "`code: \\`${string}\\``");
1387
1388 let with_backslash = TypeDef::TemplateLiteral {
1390 strings: vec!["path\\to\\".into(), "".into()],
1391 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1392 };
1393 assert_eq!(with_backslash.render(), "`path\\\\to\\\\${string}`");
1394 }
1395
1396 #[test]
1397 fn test_typedef_template_literal_with_refs() {
1398 let typed_id = TypeDef::TemplateLiteral {
1400 strings: vec!["vm-".into(), "".into()],
1401 types: vec![Box::new(TypeDef::Ref("VmIdSuffix".into()))],
1402 };
1403 assert_eq!(typed_id.render(), "`vm-${VmIdSuffix}`");
1404 }
1405
1406 #[test]
1407 fn test_typedef_indexed_access_render() {
1408 let indexed = TypeDef::IndexedAccess {
1410 base: "Profile".into(),
1411 key: "login".into(),
1412 };
1413 assert_eq!(indexed.render(), "Profile[\"login\"]");
1414
1415 let nested = TypeDef::IndexedAccess {
1417 base: "User.Settings".into(),
1418 key: "theme".into(),
1419 };
1420 assert_eq!(nested.render(), "User.Settings[\"theme\"]");
1421 }
1422
1423 #[test]
1424 fn test_typedef_indexed_access_escaping() {
1425 let with_quotes = TypeDef::IndexedAccess {
1427 base: "Config".into(),
1428 key: "key\"with\"quotes".into(),
1429 };
1430 assert_eq!(with_quotes.render(), "Config[\"key\\\"with\\\"quotes\"]");
1431
1432 let with_backslash = TypeDef::IndexedAccess {
1434 base: "Config".into(),
1435 key: "path\\to\\key".into(),
1436 };
1437 assert_eq!(with_backslash.render(), "Config[\"path\\\\to\\\\key\"]");
1438 }
1439
1440 #[test]
1441 fn test_typescript_trait_primitives() {
1442 assert_eq!(<()>::typescript().render(), "void");
1443 assert_eq!(bool::typescript().render(), "boolean");
1444 assert_eq!(String::typescript().render(), "string");
1445 assert_eq!(i32::typescript().render(), "number");
1446 assert_eq!(f64::typescript().render(), "number");
1447 assert_eq!(i128::typescript().render(), "bigint");
1448 assert_eq!(u128::typescript().render(), "bigint");
1449 }
1450
1451 #[test]
1452 fn test_typescript_trait_option() {
1453 let opt = <Option<String>>::typescript();
1454 assert_eq!(opt.render(), "string | null");
1455 }
1456
1457 #[test]
1458 fn test_typescript_trait_vec() {
1459 let vec_type = <Vec<i32>>::typescript();
1460 assert_eq!(vec_type.render(), "number[]");
1461 }
1462
1463 #[test]
1464 fn test_typescript_trait_hashmap() {
1465 let map = <HashMap<String, i32>>::typescript();
1466 assert_eq!(map.render(), "Record<string, number>");
1467 }
1468
1469 #[test]
1470 fn test_typescript_trait_result() {
1471 let result = <Result<String, String>>::typescript();
1472 assert_eq!(
1473 result.render(),
1474 "{ ok: true; value: string } | { ok: false; error: string }"
1475 );
1476 }
1477
1478 #[test]
1479 fn test_typescript_trait_tuples() {
1480 assert_eq!(<(String,)>::typescript().render(), "[string]");
1481 assert_eq!(<(String, i32)>::typescript().render(), "[string, number]");
1482 assert_eq!(
1483 <(String, i32, bool)>::typescript().render(),
1484 "[string, number, boolean]"
1485 );
1486 }
1487
1488 #[test]
1489 fn test_typescript_trait_box() {
1490 assert_eq!(<Box<String>>::typescript().render(), "string");
1492 }
1493
1494 #[test]
1495 fn test_typedef_equality() {
1496 let a = TypeDef::Primitive(Primitive::String);
1497 let b = TypeDef::Primitive(Primitive::String);
1498 let c = TypeDef::Primitive(Primitive::Number);
1499 assert_eq!(a, b);
1500 assert_ne!(a, c);
1501 }
1502
1503 #[test]
1504 fn test_field_builder() {
1505 let field = Field::new("name", TypeDef::Primitive(Primitive::String));
1506 assert!(!field.optional);
1507 assert!(!field.readonly);
1508
1509 let opt_field = Field::optional("name", TypeDef::Primitive(Primitive::String));
1510 assert!(opt_field.optional);
1511
1512 let readonly_field = Field::new("id", TypeDef::Primitive(Primitive::String)).readonly();
1513 assert!(readonly_field.readonly);
1514 }
1515
1516 #[test]
1521 fn test_registry_new() {
1522 let registry = TypeRegistry::new();
1523 assert!(registry.is_empty());
1524 assert_eq!(registry.len(), 0);
1525 }
1526
1527 #[test]
1528 fn test_registry_add_typedef() {
1529 let mut registry = TypeRegistry::new();
1530
1531 let user_type = TypeDef::Named {
1532 namespace: vec![],
1533 name: "User".to_string(),
1534 def: Box::new(TypeDef::Object(vec![
1535 Field::new("id", TypeDef::Primitive(Primitive::String)),
1536 Field::new("name", TypeDef::Primitive(Primitive::String)),
1537 ])),
1538 module: None,
1539 };
1540
1541 registry.add_typedef(user_type);
1542
1543 assert_eq!(registry.len(), 1);
1544 assert!(registry.get("User").is_some());
1545 }
1546
1547 #[test]
1548 fn test_registry_deduplication() {
1549 let mut registry = TypeRegistry::new();
1550
1551 let user_type = TypeDef::Named {
1552 namespace: vec![],
1553 name: "User".to_string(),
1554 def: Box::new(TypeDef::Primitive(Primitive::String)),
1555 module: None,
1556 };
1557
1558 registry.add_typedef(user_type.clone());
1559 registry.add_typedef(user_type);
1560
1561 assert_eq!(registry.len(), 1);
1563 }
1564
1565 #[test]
1566 fn test_registry_extracts_nested_types() {
1567 let mut registry = TypeRegistry::new();
1568
1569 let user_id = TypeDef::Named {
1571 namespace: vec![],
1572 name: "UserId".to_string(),
1573 def: Box::new(TypeDef::Primitive(Primitive::String)),
1574 module: None,
1575 };
1576
1577 let user = TypeDef::Named {
1579 namespace: vec![],
1580 name: "User".to_string(),
1581 def: Box::new(TypeDef::Object(vec![
1582 Field::new("id", TypeDef::Ref("UserId".to_string())),
1583 Field::new("name", TypeDef::Primitive(Primitive::String)),
1584 ])),
1585 module: None,
1586 };
1587
1588 let post_type = TypeDef::Named {
1590 namespace: vec![],
1591 name: "Post".to_string(),
1592 def: Box::new(TypeDef::Object(vec![
1593 Field::new("title", TypeDef::Primitive(Primitive::String)),
1594 Field::new("author", user),
1595 ])),
1596 module: None,
1597 };
1598
1599 registry.add_typedef(post_type);
1600 registry.add_typedef(user_id);
1601
1602 assert_eq!(registry.len(), 3);
1604 assert!(registry.get("Post").is_some());
1605 assert!(registry.get("User").is_some());
1606 assert!(registry.get("UserId").is_some());
1607 }
1608
1609 #[test]
1610 fn test_registry_render() {
1611 let mut registry = TypeRegistry::new();
1612
1613 let user_type = TypeDef::Named {
1614 namespace: vec![],
1615 name: "User".to_string(),
1616 def: Box::new(TypeDef::Object(vec![
1617 Field::new("id", TypeDef::Primitive(Primitive::String)),
1618 Field::new("name", TypeDef::Primitive(Primitive::String)),
1619 ])),
1620 module: None,
1621 };
1622
1623 registry.add_typedef(user_type);
1624
1625 let output = registry.render();
1626 assert!(output.contains("// Generated by ferrotype"));
1627 assert!(output.contains("type User = { id: string; name: string };"));
1628 }
1629
1630 #[test]
1631 fn test_registry_render_exported() {
1632 let mut registry = TypeRegistry::new();
1633
1634 let user_type = TypeDef::Named {
1635 namespace: vec![],
1636 name: "User".to_string(),
1637 def: Box::new(TypeDef::Primitive(Primitive::String)),
1638 module: None,
1639 };
1640
1641 registry.add_typedef(user_type);
1642
1643 let output = registry.render_exported();
1644 assert!(output.contains("export type User = string;"));
1645 }
1646
1647 #[test]
1648 fn test_registry_dependency_order() {
1649 let mut registry = TypeRegistry::new();
1650
1651 let user_id = TypeDef::Named {
1653 namespace: vec![],
1654 name: "UserId".to_string(),
1655 def: Box::new(TypeDef::Primitive(Primitive::String)),
1656 module: None,
1657 };
1658
1659 let user = TypeDef::Named {
1661 namespace: vec![],
1662 name: "User".to_string(),
1663 def: Box::new(TypeDef::Object(vec![
1664 Field::new("id", TypeDef::Ref("UserId".to_string())),
1665 Field::new("name", TypeDef::Primitive(Primitive::String)),
1666 ])),
1667 module: None,
1668 };
1669
1670 registry.add_typedef(user);
1672 registry.add_typedef(user_id);
1673
1674 let sorted = registry.sorted_types();
1675
1676 let user_id_pos = sorted.iter().position(|&n| n == "UserId").unwrap();
1678 let user_pos = sorted.iter().position(|&n| n == "User").unwrap();
1679 assert!(user_id_pos < user_pos, "UserId should come before User");
1680 }
1681
1682 #[test]
1683 fn test_registry_clear() {
1684 let mut registry = TypeRegistry::new();
1685
1686 let user_type = TypeDef::Named {
1687 namespace: vec![],
1688 name: "User".to_string(),
1689 def: Box::new(TypeDef::Primitive(Primitive::String)),
1690 module: None,
1691 };
1692
1693 registry.add_typedef(user_type);
1694 assert_eq!(registry.len(), 1);
1695
1696 registry.clear();
1697 assert!(registry.is_empty());
1698 }
1699
1700 #[test]
1701 fn test_registry_type_names() {
1702 let mut registry = TypeRegistry::new();
1703
1704 registry.add_typedef(TypeDef::Named {
1705 namespace: vec![],
1706 name: "Alpha".to_string(),
1707 def: Box::new(TypeDef::Primitive(Primitive::String)),
1708 module: None,
1709 });
1710 registry.add_typedef(TypeDef::Named {
1711 namespace: vec![],
1712 name: "Beta".to_string(),
1713 def: Box::new(TypeDef::Primitive(Primitive::Number)),
1714 module: None,
1715 });
1716
1717 let names: Vec<_> = registry.type_names().collect();
1718 assert_eq!(names.len(), 2);
1719 assert!(names.contains(&"Alpha"));
1720 assert!(names.contains(&"Beta"));
1721 }
1722
1723 #[test]
1724 fn test_registry_complex_dependencies() {
1725 let mut registry = TypeRegistry::new();
1726
1727 let c = TypeDef::Named {
1729 namespace: vec![],
1730 name: "C".to_string(),
1731 def: Box::new(TypeDef::Primitive(Primitive::String)),
1732 module: None,
1733 };
1734
1735 let b = TypeDef::Named {
1736 namespace: vec![],
1737 name: "B".to_string(),
1738 def: Box::new(TypeDef::Object(vec![
1739 Field::new("c", TypeDef::Ref("C".to_string())),
1740 ])),
1741 module: None,
1742 };
1743
1744 let a = TypeDef::Named {
1745 namespace: vec![],
1746 name: "A".to_string(),
1747 def: Box::new(TypeDef::Object(vec![
1748 Field::new("b", TypeDef::Ref("B".to_string())),
1749 ])),
1750 module: None,
1751 };
1752
1753 registry.add_typedef(a);
1755 registry.add_typedef(b);
1756 registry.add_typedef(c);
1757
1758 let sorted = registry.sorted_types();
1759
1760 let c_pos = sorted.iter().position(|&n| n == "C").unwrap();
1761 let b_pos = sorted.iter().position(|&n| n == "B").unwrap();
1762 let a_pos = sorted.iter().position(|&n| n == "A").unwrap();
1763
1764 assert!(c_pos < b_pos, "C should come before B");
1765 assert!(b_pos < a_pos, "B should come before A");
1766 }
1767
1768 #[test]
1769 fn test_registry_indexed_access_dependency() {
1770 let mut registry = TypeRegistry::new();
1771
1772 let profile = TypeDef::Named {
1774 namespace: vec![],
1775 name: "Profile".to_string(),
1776 def: Box::new(TypeDef::Object(vec![
1777 Field::new("login", TypeDef::Primitive(Primitive::String)),
1778 Field::new("email", TypeDef::Primitive(Primitive::String)),
1779 ])),
1780 module: None,
1781 };
1782
1783 let user_login = TypeDef::Named {
1785 namespace: vec![],
1786 name: "UserLogin".to_string(),
1787 def: Box::new(TypeDef::IndexedAccess {
1788 base: "Profile".to_string(),
1789 key: "login".to_string(),
1790 }),
1791 module: None,
1792 };
1793
1794 registry.add_typedef(user_login);
1796 registry.add_typedef(profile);
1797
1798 let sorted = registry.sorted_types();
1799
1800 let profile_pos = sorted.iter().position(|&n| n == "Profile").unwrap();
1801 let user_login_pos = sorted.iter().position(|&n| n == "UserLogin").unwrap();
1802
1803 assert!(profile_pos < user_login_pos, "Profile should come before UserLogin");
1804 }
1805
1806 #[derive(Debug)]
1812 struct AutoRegTestUser {
1813 name: String,
1814 age: u32,
1815 }
1816
1817 impl TypeScript for AutoRegTestUser {
1818 fn typescript() -> TypeDef {
1819 TypeDef::Named {
1820 namespace: vec![],
1821 name: "AutoRegTestUser".to_string(),
1822 def: Box::new(TypeDef::Object(vec![
1823 Field::new("name", TypeDef::Primitive(Primitive::String)),
1824 Field::new("age", TypeDef::Primitive(Primitive::Number)),
1825 ])),
1826 module: None,
1827 }
1828 }
1829 }
1830
1831 #[linkme::distributed_slice(TYPESCRIPT_TYPES)]
1833 static __TEST_REGISTER_USER: fn() -> TypeDef = || AutoRegTestUser::typescript();
1834
1835 #[test]
1836 fn test_from_distributed_collects_types() {
1837 let registry = TypeRegistry::from_distributed();
1838
1839 assert!(registry.get("AutoRegTestUser").is_some(),
1841 "Registry should contain AutoRegTestUser");
1842 }
1843
1844 #[test]
1845 fn test_collect_all_adds_to_existing() {
1846 let mut registry = TypeRegistry::new();
1847
1848 let manual_type = TypeDef::Named {
1850 namespace: vec![],
1851 name: "ManualType".to_string(),
1852 def: Box::new(TypeDef::Primitive(Primitive::String)),
1853 module: None,
1854 };
1855 registry.add_typedef(manual_type);
1856
1857 registry.collect_all();
1859
1860 assert!(registry.get("ManualType").is_some(),
1862 "Registry should contain ManualType");
1863 assert!(registry.get("AutoRegTestUser").is_some(),
1864 "Registry should contain AutoRegTestUser from distributed slice");
1865 }
1866
1867 #[test]
1868 fn test_distributed_slice_is_accessible() {
1869 let count = TYPESCRIPT_TYPES.len();
1871 assert!(count >= 1, "TYPESCRIPT_TYPES should have at least 1 entry");
1873 }
1874}