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 GenericDef {
252 name: String,
254 type_params: Vec<TypeParam>,
256 def: Box<TypeDef>,
258 },
259
260 TypeParamRef(String),
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
269pub enum Primitive {
270 String,
272 Number,
274 Boolean,
276 Null,
278 Undefined,
280 Void,
282 Never,
284 Any,
286 Unknown,
288 BigInt,
290}
291
292#[derive(Debug, Clone, PartialEq)]
294pub struct Field {
295 pub name: String,
297 pub ty: TypeDef,
299 pub optional: bool,
301 pub readonly: bool,
303}
304
305#[derive(Debug, Clone, PartialEq)]
322pub struct TypeParam {
323 pub name: String,
325 pub constraint: Option<Box<TypeDef>>,
327 pub default: Option<Box<TypeDef>>,
329}
330
331impl TypeParam {
332 pub fn new(name: impl Into<String>) -> Self {
334 Self {
335 name: name.into(),
336 constraint: None,
337 default: None,
338 }
339 }
340
341 pub fn with_constraint(mut self, constraint: TypeDef) -> Self {
343 self.constraint = Some(Box::new(constraint));
344 self
345 }
346
347 pub fn with_default(mut self, default: TypeDef) -> Self {
349 self.default = Some(Box::new(default));
350 self
351 }
352
353 pub fn render(&self) -> String {
362 let mut result = self.name.clone();
363
364 if let Some(ref constraint) = self.constraint {
365 result.push_str(" extends ");
366 result.push_str(&constraint.render());
367 }
368
369 if let Some(ref default) = self.default {
370 result.push_str(" = ");
371 result.push_str(&default.render());
372 }
373
374 result
375 }
376}
377
378impl Field {
379 pub fn new(name: impl Into<String>, ty: TypeDef) -> Self {
381 Self {
382 name: name.into(),
383 ty,
384 optional: false,
385 readonly: false,
386 }
387 }
388
389 pub fn optional(name: impl Into<String>, ty: TypeDef) -> Self {
391 Self {
392 name: name.into(),
393 ty,
394 optional: true,
395 readonly: false,
396 }
397 }
398
399 pub fn readonly(mut self) -> Self {
401 self.readonly = true;
402 self
403 }
404}
405
406#[derive(Debug, Clone, PartialEq)]
408pub enum Literal {
409 String(String),
411 Number(f64),
413 Boolean(bool),
415}
416
417impl TypeDef {
418 pub fn render(&self) -> String {
420 match self {
421 TypeDef::Primitive(p) => p.render().to_string(),
422 TypeDef::Array(inner) => {
423 let inner_str = inner.render();
424 if matches!(inner.as_ref(), TypeDef::Union(_)) {
426 format!("({})[]", inner_str)
427 } else {
428 format!("{}[]", inner_str)
429 }
430 }
431 TypeDef::Tuple(items) => {
432 let items_str: Vec<_> = items.iter().map(|t| t.render()).collect();
433 format!("[{}]", items_str.join(", "))
434 }
435 TypeDef::Object(fields) => {
436 if fields.is_empty() {
437 "{}".to_string()
438 } else {
439 let fields_str: Vec<_> = fields
440 .iter()
441 .map(|f| {
442 let readonly = if f.readonly { "readonly " } else { "" };
443 let opt = if f.optional { "?" } else { "" };
444 format!("{}{}{}: {}", readonly, f.name, opt, f.ty.render())
445 })
446 .collect();
447 format!("{{ {} }}", fields_str.join("; "))
448 }
449 }
450 TypeDef::Union(variants) => {
451 let variants_str: Vec<_> = variants.iter().map(|t| t.render()).collect();
452 variants_str.join(" | ")
453 }
454 TypeDef::Intersection(types) => {
455 let types_str: Vec<_> = types.iter().map(|t| t.render()).collect();
456 types_str.join(" & ")
457 }
458 TypeDef::Record { key, value } => {
459 format!("Record<{}, {}>", key.render(), value.render())
460 }
461 TypeDef::Named { namespace, name, .. } => {
462 if namespace.is_empty() {
463 name.clone()
464 } else {
465 format!("{}.{}", namespace.join("."), name)
466 }
467 }
468 TypeDef::Ref(name) => name.clone(),
469 TypeDef::Literal(lit) => lit.render(),
470 TypeDef::Function {
471 params,
472 return_type,
473 } => {
474 let params_str: Vec<_> = params
475 .iter()
476 .map(|p| format!("{}: {}", p.name, p.ty.render()))
477 .collect();
478 format!("({}) => {}", params_str.join(", "), return_type.render())
479 }
480 TypeDef::Generic { base, args } => {
481 let args_str: Vec<_> = args.iter().map(|t| t.render()).collect();
482 format!("{}<{}>", base, args_str.join(", "))
483 }
484 TypeDef::IndexedAccess { base, key } => {
485 format!("{}[\"{}\"]", base, key.replace('\\', "\\\\").replace('"', "\\\""))
486 }
487 TypeDef::TemplateLiteral { strings, types } => {
488 let mut result = String::from("`");
489 for (i, s) in strings.iter().enumerate() {
490 let escaped = s.replace('\\', "\\\\").replace('`', "\\`");
492 result.push_str(&escaped);
493 if i < types.len() {
494 result.push_str("${");
495 result.push_str(&types[i].render());
496 result.push('}');
497 }
498 }
499 result.push('`');
500 result
501 }
502 TypeDef::GenericDef { name, .. } => {
503 name.clone()
506 }
507 TypeDef::TypeParamRef(name) => {
508 name.clone()
510 }
511 }
512 }
513
514 pub fn render_declaration(&self) -> String {
521 match self {
522 TypeDef::Named { namespace, name, def, .. } => {
523 let inner = format!("type {} = {};", name, def.render());
524 Self::wrap_in_namespace(namespace, &inner)
525 }
526 TypeDef::GenericDef {
527 name,
528 type_params,
529 def,
530 } => {
531 let params_str: Vec<_> = type_params.iter().map(|p| p.render()).collect();
532 format!("type {}<{}> = {};", name, params_str.join(", "), def.render())
533 }
534 _ => self.render(),
535 }
536 }
537
538 fn wrap_in_namespace(namespace: &[String], inner: &str) -> String {
549 if namespace.is_empty() {
550 return inner.to_string();
551 }
552
553 let mut result = String::new();
554 let indent = " ";
555
556 for (i, ns) in namespace.iter().enumerate() {
558 for _ in 0..i {
559 result.push_str(indent);
560 }
561 result.push_str("namespace ");
562 result.push_str(ns);
563 result.push_str(" {\n");
564 }
565
566 let depth = namespace.len();
568 for _ in 0..depth {
569 result.push_str(indent);
570 }
571 result.push_str(inner);
572 result.push('\n');
573
574 for i in (0..depth).rev() {
576 for _ in 0..i {
577 result.push_str(indent);
578 }
579 result.push('}');
580 if i > 0 {
581 result.push('\n');
582 }
583 }
584
585 result
586 }
587}
588
589impl Primitive {
590 pub fn render(&self) -> &'static str {
592 match self {
593 Primitive::String => "string",
594 Primitive::Number => "number",
595 Primitive::Boolean => "boolean",
596 Primitive::Null => "null",
597 Primitive::Undefined => "undefined",
598 Primitive::Void => "void",
599 Primitive::Never => "never",
600 Primitive::Any => "any",
601 Primitive::Unknown => "unknown",
602 Primitive::BigInt => "bigint",
603 }
604 }
605}
606
607impl Literal {
608 pub fn render(&self) -> String {
610 match self {
611 Literal::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
612 Literal::Number(n) => {
613 if n.fract() == 0.0 {
614 format!("{}", *n as i64)
615 } else {
616 format!("{}", n)
617 }
618 }
619 Literal::Boolean(b) => b.to_string(),
620 }
621 }
622}
623
624pub fn extract_object_fields(typedef: &TypeDef) -> Vec<Field> {
638 match typedef {
639 TypeDef::Object(fields) => fields.clone(),
640 TypeDef::Named { def, .. } => extract_object_fields(def),
641 other => panic!(
642 "#[ts(flatten)] can only be used on fields with object types, got: {:?}",
643 other
644 ),
645 }
646}
647
648pub fn inline_typedef(typedef: TypeDef) -> TypeDef {
654 match typedef {
655 TypeDef::Named { def, .. } => *def,
656 other => other,
657 }
658}
659
660use std::collections::{HashSet, VecDeque};
665
666#[derive(Debug, Default)]
687pub struct TypeRegistry {
688 types: HashMap<String, TypeDef>,
690 registration_order: Vec<String>,
692}
693
694impl TypeRegistry {
695 pub fn new() -> Self {
697 Self::default()
698 }
699
700 pub fn from_distributed() -> Self {
721 let mut registry = Self::new();
722 for type_fn in TYPESCRIPT_TYPES {
723 let typedef = type_fn();
724 registry.add_typedef(typedef);
725 }
726 registry
727 }
728
729 pub fn collect_all(&mut self) {
744 for type_fn in TYPESCRIPT_TYPES {
745 let typedef = type_fn();
746 self.add_typedef(typedef);
747 }
748 }
749
750 pub fn register<T: TypeScript>(&mut self) {
755 let typedef = T::typescript();
756 self.add_typedef(typedef);
757 }
758
759 pub fn add_typedef(&mut self, typedef: TypeDef) {
761 self.extract_named_types(&typedef);
762 }
763
764 fn extract_named_types(&mut self, typedef: &TypeDef) {
766 match typedef {
767 TypeDef::Named { namespace, name, def, .. } => {
768 let qualified_name = if namespace.is_empty() {
770 name.clone()
771 } else {
772 format!("{}.{}", namespace.join("."), name)
773 };
774
775 if !self.types.contains_key(&qualified_name) {
776 self.types.insert(qualified_name.clone(), typedef.clone());
777 self.registration_order.push(qualified_name);
778 self.extract_named_types(def);
780 }
781 }
782 TypeDef::Array(inner) => self.extract_named_types(inner),
783 TypeDef::Tuple(items) => {
784 for item in items {
785 self.extract_named_types(item);
786 }
787 }
788 TypeDef::Object(fields) => {
789 for field in fields {
790 self.extract_named_types(&field.ty);
791 }
792 }
793 TypeDef::Union(items) | TypeDef::Intersection(items) => {
794 for item in items {
795 self.extract_named_types(item);
796 }
797 }
798 TypeDef::Record { key, value } => {
799 self.extract_named_types(key);
800 self.extract_named_types(value);
801 }
802 TypeDef::Function { params, return_type } => {
803 for param in params {
804 self.extract_named_types(¶m.ty);
805 }
806 self.extract_named_types(return_type);
807 }
808 TypeDef::Generic { args, .. } => {
809 for arg in args {
810 self.extract_named_types(arg);
811 }
812 }
813 TypeDef::TemplateLiteral { types, .. } => {
814 for ty in types {
815 self.extract_named_types(ty);
816 }
817 }
818 TypeDef::GenericDef { name, type_params, def } => {
819 if !self.types.contains_key(name) {
820 self.types.insert(name.clone(), typedef.clone());
821 self.registration_order.push(name.clone());
822 for param in type_params {
824 if let Some(ref constraint) = param.constraint {
825 self.extract_named_types(constraint);
826 }
827 if let Some(ref default) = param.default {
828 self.extract_named_types(default);
829 }
830 }
831 self.extract_named_types(def);
833 }
834 }
835 TypeDef::Primitive(_) | TypeDef::Ref(_) | TypeDef::Literal(_) | TypeDef::IndexedAccess { .. } | TypeDef::TypeParamRef(_) => {}
838 }
839 }
840
841 pub fn len(&self) -> usize {
843 self.types.len()
844 }
845
846 pub fn is_empty(&self) -> bool {
848 self.types.is_empty()
849 }
850
851 pub fn type_names(&self) -> impl Iterator<Item = &str> {
853 self.types.keys().map(|s| s.as_str())
854 }
855
856 pub fn get(&self, name: &str) -> Option<&TypeDef> {
858 self.types.get(name)
859 }
860
861 fn get_dependencies(&self, typedef: &TypeDef) -> HashSet<String> {
863 let mut deps = HashSet::new();
864 self.collect_dependencies(typedef, &mut deps);
865 deps
866 }
867
868 fn collect_dependencies(&self, typedef: &TypeDef, deps: &mut HashSet<String>) {
870 match typedef {
871 TypeDef::Named { def, .. } => {
872 self.collect_dependencies(def, deps);
874 }
875 TypeDef::Ref(name) => {
876 if self.types.contains_key(name) {
877 deps.insert(name.clone());
878 }
879 }
880 TypeDef::Array(inner) => self.collect_dependencies(inner, deps),
881 TypeDef::Tuple(items) => {
882 for item in items {
883 self.collect_dependencies(item, deps);
884 }
885 }
886 TypeDef::Object(fields) => {
887 for field in fields {
888 self.collect_dependencies(&field.ty, deps);
889 }
890 }
891 TypeDef::Union(variants) => {
892 for v in variants {
893 self.collect_dependencies(v, deps);
894 }
895 }
896 TypeDef::Intersection(types) => {
897 for t in types {
898 self.collect_dependencies(t, deps);
899 }
900 }
901 TypeDef::Record { key, value } => {
902 self.collect_dependencies(key, deps);
903 self.collect_dependencies(value, deps);
904 }
905 TypeDef::Function { params, return_type } => {
906 for param in params {
907 self.collect_dependencies(¶m.ty, deps);
908 }
909 self.collect_dependencies(return_type, deps);
910 }
911 TypeDef::Generic { base, args } => {
912 if self.types.contains_key(base) {
914 deps.insert(base.clone());
915 }
916 for arg in args {
917 self.collect_dependencies(arg, deps);
918 }
919 }
920 TypeDef::TemplateLiteral { types, .. } => {
921 for ty in types {
922 self.collect_dependencies(ty, deps);
923 }
924 }
925 TypeDef::IndexedAccess { base, .. } => {
926 if self.types.contains_key(base) {
928 deps.insert(base.clone());
929 }
930 }
931 TypeDef::GenericDef { type_params, def, .. } => {
932 for param in type_params {
934 if let Some(ref constraint) = param.constraint {
935 self.collect_dependencies(constraint, deps);
936 }
937 if let Some(ref default) = param.default {
938 self.collect_dependencies(default, deps);
939 }
940 }
941 self.collect_dependencies(def, deps);
942 }
943 TypeDef::Primitive(_) | TypeDef::Literal(_) | TypeDef::TypeParamRef(_) => {}
945 }
946 }
947
948 pub fn sorted_types(&self) -> Vec<&str> {
952 let mut in_degree: HashMap<&str, usize> = HashMap::new();
954 let mut dependents: HashMap<&str, Vec<&str>> = HashMap::new();
955
956 for name in self.types.keys() {
958 in_degree.insert(name.as_str(), 0);
959 dependents.insert(name.as_str(), Vec::new());
960 }
961
962 for (name, typedef) in &self.types {
964 let deps = self.get_dependencies(typedef);
965 for dep in deps {
966 if let Some(dep_name) = self.types.get_key_value(&dep) {
967 *in_degree.get_mut(name.as_str()).unwrap() += 1;
968 dependents.get_mut(dep_name.0.as_str()).unwrap().push(name.as_str());
969 }
970 }
971 }
972
973 let mut queue: VecDeque<&str> = VecDeque::new();
975 let mut result: Vec<&str> = Vec::new();
976
977 for (name, °ree) in &in_degree {
979 if degree == 0 {
980 queue.push_back(name);
981 }
982 }
983
984 let mut initial: Vec<_> = queue.drain(..).collect();
986 initial.sort_by_key(|name| {
987 self.registration_order.iter().position(|n| n == *name).unwrap_or(usize::MAX)
988 });
989 queue.extend(initial);
990
991 while let Some(name) = queue.pop_front() {
992 result.push(name);
993
994 let mut deps: Vec<_> = dependents.get(name).map(|v| v.as_slice()).unwrap_or(&[]).to_vec();
996 deps.sort_by_key(|n| {
997 self.registration_order.iter().position(|name| name == *n).unwrap_or(usize::MAX)
998 });
999
1000 for dependent in deps {
1001 let degree = in_degree.get_mut(dependent).unwrap();
1002 *degree -= 1;
1003 if *degree == 0 {
1004 queue.push_back(dependent);
1005 }
1006 }
1007 }
1008
1009 if result.len() < self.types.len() {
1012 for name in &self.registration_order {
1013 if !result.contains(&name.as_str()) {
1014 result.push(name.as_str());
1015 }
1016 }
1017 }
1018
1019 result
1020 }
1021
1022 pub fn render(&self) -> String {
1026 let sorted = self.sorted_types();
1027 let mut output = String::new();
1028
1029 output.push_str("// Generated by ferrotype\n");
1031 output.push_str("// Do not edit manually\n\n");
1032
1033 for name in sorted {
1034 if let Some(typedef) = self.types.get(name) {
1035 output.push_str(&typedef.render_declaration());
1036 output.push_str("\n\n");
1037 }
1038 }
1039
1040 output.trim_end().to_string() + "\n"
1042 }
1043
1044 pub fn render_exported(&self) -> String {
1048 let sorted = self.sorted_types();
1049 let mut output = String::new();
1050
1051 output.push_str("// Generated by ferrotype\n");
1053 output.push_str("// Do not edit manually\n\n");
1054
1055 for name in sorted {
1056 if let Some(typedef) = self.types.get(name) {
1057 match typedef {
1058 TypeDef::Named { namespace, name, def, .. } => {
1059 let inner = format!("export type {} = {};", name, def.render());
1060 if namespace.is_empty() {
1061 output.push_str(&inner);
1062 } else {
1063 output.push_str(&Self::wrap_in_export_namespace(namespace, &inner));
1065 }
1066 output.push_str("\n\n");
1067 }
1068 TypeDef::GenericDef { name, type_params, def } => {
1069 let params_str: Vec<_> = type_params.iter().map(|p| p.render()).collect();
1070 output.push_str(&format!(
1071 "export type {}<{}> = {};\n\n",
1072 name,
1073 params_str.join(", "),
1074 def.render()
1075 ));
1076 }
1077 _ => {}
1078 }
1079 }
1080 }
1081
1082 output.trim_end().to_string() + "\n"
1084 }
1085
1086 fn wrap_in_export_namespace(namespace: &[String], inner: &str) -> String {
1088 if namespace.is_empty() {
1089 return inner.to_string();
1090 }
1091
1092 let mut result = String::new();
1093 let indent = " ";
1094
1095 for (i, ns) in namespace.iter().enumerate() {
1097 for _ in 0..i {
1098 result.push_str(indent);
1099 }
1100 result.push_str("export namespace ");
1101 result.push_str(ns);
1102 result.push_str(" {\n");
1103 }
1104
1105 let depth = namespace.len();
1107 for _ in 0..depth {
1108 result.push_str(indent);
1109 }
1110 result.push_str(inner);
1111 result.push('\n');
1112
1113 for i in (0..depth).rev() {
1115 for _ in 0..i {
1116 result.push_str(indent);
1117 }
1118 result.push('}');
1119 if i > 0 {
1120 result.push('\n');
1121 }
1122 }
1123
1124 result
1125 }
1126
1127 pub fn clear(&mut self) {
1129 self.types.clear();
1130 self.registration_order.clear();
1131 }
1132}
1133
1134impl TypeScript for () {
1139 fn typescript() -> TypeDef {
1140 TypeDef::Primitive(Primitive::Void)
1141 }
1142}
1143
1144impl TypeScript for bool {
1145 fn typescript() -> TypeDef {
1146 TypeDef::Primitive(Primitive::Boolean)
1147 }
1148}
1149
1150impl TypeScript for String {
1151 fn typescript() -> TypeDef {
1152 TypeDef::Primitive(Primitive::String)
1153 }
1154}
1155
1156impl TypeScript for &str {
1157 fn typescript() -> TypeDef {
1158 TypeDef::Primitive(Primitive::String)
1159 }
1160}
1161
1162impl TypeScript for char {
1163 fn typescript() -> TypeDef {
1164 TypeDef::Primitive(Primitive::String)
1165 }
1166}
1167
1168macro_rules! impl_typescript_number {
1169 ($($t:ty),*) => {
1170 $(
1171 impl TypeScript for $t {
1172 fn typescript() -> TypeDef {
1173 TypeDef::Primitive(Primitive::Number)
1174 }
1175 }
1176 )*
1177 };
1178}
1179
1180impl_typescript_number!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64);
1181
1182impl TypeScript for i128 {
1184 fn typescript() -> TypeDef {
1185 TypeDef::Primitive(Primitive::BigInt)
1186 }
1187}
1188
1189impl TypeScript for u128 {
1190 fn typescript() -> TypeDef {
1191 TypeDef::Primitive(Primitive::BigInt)
1192 }
1193}
1194
1195impl<T: TypeScript> TypeScript for Option<T> {
1200 fn typescript() -> TypeDef {
1201 TypeDef::Union(vec![T::typescript(), TypeDef::Primitive(Primitive::Null)])
1202 }
1203}
1204
1205impl<T: TypeScript> TypeScript for Vec<T> {
1206 fn typescript() -> TypeDef {
1207 TypeDef::Array(Box::new(T::typescript()))
1208 }
1209}
1210
1211impl<T: TypeScript> TypeScript for Box<T> {
1212 fn typescript() -> TypeDef {
1213 T::typescript()
1214 }
1215}
1216
1217impl<T: TypeScript> TypeScript for std::rc::Rc<T> {
1218 fn typescript() -> TypeDef {
1219 T::typescript()
1220 }
1221}
1222
1223impl<T: TypeScript> TypeScript for std::sync::Arc<T> {
1224 fn typescript() -> TypeDef {
1225 T::typescript()
1226 }
1227}
1228
1229impl<T: TypeScript> TypeScript for std::cell::RefCell<T> {
1230 fn typescript() -> TypeDef {
1231 T::typescript()
1232 }
1233}
1234
1235impl<T: TypeScript> TypeScript for std::cell::Cell<T> {
1236 fn typescript() -> TypeDef {
1237 T::typescript()
1238 }
1239}
1240
1241impl<K: TypeScript, V: TypeScript> TypeScript for HashMap<K, V> {
1242 fn typescript() -> TypeDef {
1243 TypeDef::Record {
1244 key: Box::new(K::typescript()),
1245 value: Box::new(V::typescript()),
1246 }
1247 }
1248}
1249
1250impl<K: TypeScript, V: TypeScript> TypeScript for std::collections::BTreeMap<K, V> {
1251 fn typescript() -> TypeDef {
1252 TypeDef::Record {
1253 key: Box::new(K::typescript()),
1254 value: Box::new(V::typescript()),
1255 }
1256 }
1257}
1258
1259impl<T: TypeScript, E: TypeScript> TypeScript for Result<T, E> {
1260 fn typescript() -> TypeDef {
1261 TypeDef::Union(vec![
1262 TypeDef::Object(vec![
1263 Field::new("ok", TypeDef::Literal(Literal::Boolean(true))),
1264 Field::new("value", T::typescript()),
1265 ]),
1266 TypeDef::Object(vec![
1267 Field::new("ok", TypeDef::Literal(Literal::Boolean(false))),
1268 Field::new("error", E::typescript()),
1269 ]),
1270 ])
1271 }
1272}
1273
1274impl<A: TypeScript> TypeScript for (A,) {
1279 fn typescript() -> TypeDef {
1280 TypeDef::Tuple(vec![A::typescript()])
1281 }
1282}
1283
1284impl<A: TypeScript, B: TypeScript> TypeScript for (A, B) {
1285 fn typescript() -> TypeDef {
1286 TypeDef::Tuple(vec![A::typescript(), B::typescript()])
1287 }
1288}
1289
1290impl<A: TypeScript, B: TypeScript, C: TypeScript> TypeScript for (A, B, C) {
1291 fn typescript() -> TypeDef {
1292 TypeDef::Tuple(vec![A::typescript(), B::typescript(), C::typescript()])
1293 }
1294}
1295
1296impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript> TypeScript for (A, B, C, D) {
1297 fn typescript() -> TypeDef {
1298 TypeDef::Tuple(vec![
1299 A::typescript(),
1300 B::typescript(),
1301 C::typescript(),
1302 D::typescript(),
1303 ])
1304 }
1305}
1306
1307impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript, E: TypeScript> TypeScript
1308 for (A, B, C, D, E)
1309{
1310 fn typescript() -> TypeDef {
1311 TypeDef::Tuple(vec![
1312 A::typescript(),
1313 B::typescript(),
1314 C::typescript(),
1315 D::typescript(),
1316 E::typescript(),
1317 ])
1318 }
1319}
1320
1321impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript, E: TypeScript, F: TypeScript>
1322 TypeScript for (A, B, C, D, E, F)
1323{
1324 fn typescript() -> TypeDef {
1325 TypeDef::Tuple(vec![
1326 A::typescript(),
1327 B::typescript(),
1328 C::typescript(),
1329 D::typescript(),
1330 E::typescript(),
1331 F::typescript(),
1332 ])
1333 }
1334}
1335
1336#[cfg(test)]
1341mod tests {
1342 use super::*;
1343
1344 #[test]
1349 fn test_typedef_primitive_render() {
1350 assert_eq!(TypeDef::Primitive(Primitive::String).render(), "string");
1351 assert_eq!(TypeDef::Primitive(Primitive::Number).render(), "number");
1352 assert_eq!(TypeDef::Primitive(Primitive::Boolean).render(), "boolean");
1353 assert_eq!(TypeDef::Primitive(Primitive::Null).render(), "null");
1354 assert_eq!(TypeDef::Primitive(Primitive::Undefined).render(), "undefined");
1355 assert_eq!(TypeDef::Primitive(Primitive::Void).render(), "void");
1356 assert_eq!(TypeDef::Primitive(Primitive::Never).render(), "never");
1357 assert_eq!(TypeDef::Primitive(Primitive::Any).render(), "any");
1358 assert_eq!(TypeDef::Primitive(Primitive::Unknown).render(), "unknown");
1359 assert_eq!(TypeDef::Primitive(Primitive::BigInt).render(), "bigint");
1360 }
1361
1362 #[test]
1363 fn test_typedef_array_render() {
1364 let arr = TypeDef::Array(Box::new(TypeDef::Primitive(Primitive::String)));
1365 assert_eq!(arr.render(), "string[]");
1366
1367 let union_arr = TypeDef::Array(Box::new(TypeDef::Union(vec![
1369 TypeDef::Primitive(Primitive::String),
1370 TypeDef::Primitive(Primitive::Number),
1371 ])));
1372 assert_eq!(union_arr.render(), "(string | number)[]");
1373 }
1374
1375 #[test]
1376 fn test_typedef_tuple_render() {
1377 let tuple = TypeDef::Tuple(vec![
1378 TypeDef::Primitive(Primitive::String),
1379 TypeDef::Primitive(Primitive::Number),
1380 ]);
1381 assert_eq!(tuple.render(), "[string, number]");
1382 }
1383
1384 #[test]
1385 fn test_typedef_object_render() {
1386 let obj = TypeDef::Object(vec![
1387 Field::new("name", TypeDef::Primitive(Primitive::String)),
1388 Field::optional("age", TypeDef::Primitive(Primitive::Number)),
1389 ]);
1390 assert_eq!(obj.render(), "{ name: string; age?: number }");
1391
1392 let empty_obj = TypeDef::Object(vec![]);
1393 assert_eq!(empty_obj.render(), "{}");
1394 }
1395
1396 #[test]
1397 fn test_typedef_object_readonly_field() {
1398 let obj = TypeDef::Object(vec![
1399 Field::new("id", TypeDef::Primitive(Primitive::String)).readonly(),
1400 ]);
1401 assert_eq!(obj.render(), "{ readonly id: string }");
1402 }
1403
1404 #[test]
1405 fn test_typedef_union_render() {
1406 let union = TypeDef::Union(vec![
1407 TypeDef::Primitive(Primitive::String),
1408 TypeDef::Primitive(Primitive::Number),
1409 TypeDef::Primitive(Primitive::Null),
1410 ]);
1411 assert_eq!(union.render(), "string | number | null");
1412 }
1413
1414 #[test]
1415 fn test_typedef_intersection_render() {
1416 let intersection = TypeDef::Intersection(vec![
1417 TypeDef::Ref("Base".into()),
1418 TypeDef::Object(vec![
1419 Field::new("extra", TypeDef::Primitive(Primitive::String)),
1420 ]),
1421 ]);
1422 assert_eq!(intersection.render(), "Base & { extra: string }");
1423 }
1424
1425 #[test]
1426 fn test_typedef_record_render() {
1427 let record = TypeDef::Record {
1428 key: Box::new(TypeDef::Primitive(Primitive::String)),
1429 value: Box::new(TypeDef::Primitive(Primitive::Number)),
1430 };
1431 assert_eq!(record.render(), "Record<string, number>");
1432 }
1433
1434 #[test]
1435 fn test_typedef_named_render() {
1436 let named = TypeDef::Named {
1437 namespace: vec![],
1438 name: "UserId".into(),
1439 def: Box::new(TypeDef::Primitive(Primitive::String)),
1440 module: None,
1441 };
1442 assert_eq!(named.render(), "UserId");
1444 assert_eq!(named.render_declaration(), "type UserId = string;");
1446 }
1447
1448 #[test]
1449 fn test_typedef_namespaced_render() {
1450 let namespaced = TypeDef::Named {
1452 namespace: vec!["VM".into(), "Git".into()],
1453 name: "State".into(),
1454 def: Box::new(TypeDef::Union(vec![
1455 TypeDef::Literal(Literal::String("clean".into())),
1456 TypeDef::Literal(Literal::String("dirty".into())),
1457 TypeDef::Literal(Literal::String("unknown".into())),
1458 ])),
1459 module: None,
1460 };
1461 assert_eq!(namespaced.render(), "VM.Git.State");
1463 let decl = namespaced.render_declaration();
1465 assert!(decl.contains("namespace VM {"));
1466 assert!(decl.contains("namespace Git {"));
1467 assert!(decl.contains(r#"type State = "clean" | "dirty" | "unknown";"#));
1468 }
1469
1470 #[test]
1471 fn test_typedef_single_namespace_render() {
1472 let namespaced = TypeDef::Named {
1473 namespace: vec!["API".into()],
1474 name: "Response".into(),
1475 def: Box::new(TypeDef::Primitive(Primitive::String)),
1476 module: None,
1477 };
1478 assert_eq!(namespaced.render(), "API.Response");
1479 let decl = namespaced.render_declaration();
1480 assert!(decl.contains("namespace API {"));
1481 assert!(decl.contains("type Response = string;"));
1482 }
1483
1484 #[test]
1485 fn test_typedef_ref_render() {
1486 let ref_type = TypeDef::Ref("User".into());
1487 assert_eq!(ref_type.render(), "User");
1488 }
1489
1490 #[test]
1491 fn test_typedef_literal_render() {
1492 assert_eq!(TypeDef::Literal(Literal::String("foo".into())).render(), "\"foo\"");
1493 assert_eq!(TypeDef::Literal(Literal::Number(42.0)).render(), "42");
1494 assert_eq!(TypeDef::Literal(Literal::Number(3.14)).render(), "3.14");
1495 assert_eq!(TypeDef::Literal(Literal::Boolean(true)).render(), "true");
1496 assert_eq!(TypeDef::Literal(Literal::Boolean(false)).render(), "false");
1497 }
1498
1499 #[test]
1500 fn test_typedef_literal_escaping() {
1501 let lit = Literal::String("say \"hello\"".into());
1502 assert_eq!(lit.render(), "\"say \\\"hello\\\"\"");
1503 }
1504
1505 #[test]
1506 fn test_typedef_function_render() {
1507 let func = TypeDef::Function {
1508 params: vec![
1509 Field::new("name", TypeDef::Primitive(Primitive::String)),
1510 Field::new("age", TypeDef::Primitive(Primitive::Number)),
1511 ],
1512 return_type: Box::new(TypeDef::Primitive(Primitive::Void)),
1513 };
1514 assert_eq!(func.render(), "(name: string, age: number) => void");
1515 }
1516
1517 #[test]
1518 fn test_typedef_generic_render() {
1519 let generic = TypeDef::Generic {
1520 base: "Promise".into(),
1521 args: vec![TypeDef::Primitive(Primitive::String)],
1522 };
1523 assert_eq!(generic.render(), "Promise<string>");
1524
1525 let multi_generic = TypeDef::Generic {
1526 base: "Map".into(),
1527 args: vec![
1528 TypeDef::Primitive(Primitive::String),
1529 TypeDef::Primitive(Primitive::Number),
1530 ],
1531 };
1532 assert_eq!(multi_generic.render(), "Map<string, number>");
1533 }
1534
1535 #[test]
1536 fn test_typedef_template_literal_render() {
1537 let vm_id = TypeDef::TemplateLiteral {
1539 strings: vec!["vm-".into(), "".into()],
1540 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1541 };
1542 assert_eq!(vm_id.render(), "`vm-${string}`");
1543
1544 let semver = TypeDef::TemplateLiteral {
1546 strings: vec!["v".into(), ".".into(), ".".into(), "".into()],
1547 types: vec![
1548 Box::new(TypeDef::Primitive(Primitive::Number)),
1549 Box::new(TypeDef::Primitive(Primitive::Number)),
1550 Box::new(TypeDef::Primitive(Primitive::Number)),
1551 ],
1552 };
1553 assert_eq!(semver.render(), "`v${number}.${number}.${number}`");
1554
1555 let api_route = TypeDef::TemplateLiteral {
1557 strings: vec!["/api/".into(), "".into()],
1558 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1559 };
1560 assert_eq!(api_route.render(), "`/api/${string}`");
1561
1562 let complex_id = TypeDef::TemplateLiteral {
1564 strings: vec!["user-".into(), "-id".into()],
1565 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1566 };
1567 assert_eq!(complex_id.render(), "`user-${string}-id`");
1568
1569 let static_str = TypeDef::TemplateLiteral {
1571 strings: vec!["static-value".into()],
1572 types: vec![],
1573 };
1574 assert_eq!(static_str.render(), "`static-value`");
1575 }
1576
1577 #[test]
1578 fn test_typedef_template_literal_escaping() {
1579 let with_backtick = TypeDef::TemplateLiteral {
1581 strings: vec!["code: `".into(), "`".into()],
1582 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1583 };
1584 assert_eq!(with_backtick.render(), "`code: \\`${string}\\``");
1585
1586 let with_backslash = TypeDef::TemplateLiteral {
1588 strings: vec!["path\\to\\".into(), "".into()],
1589 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1590 };
1591 assert_eq!(with_backslash.render(), "`path\\\\to\\\\${string}`");
1592 }
1593
1594 #[test]
1595 fn test_typedef_template_literal_with_refs() {
1596 let typed_id = TypeDef::TemplateLiteral {
1598 strings: vec!["vm-".into(), "".into()],
1599 types: vec![Box::new(TypeDef::Ref("VmIdSuffix".into()))],
1600 };
1601 assert_eq!(typed_id.render(), "`vm-${VmIdSuffix}`");
1602 }
1603
1604 #[test]
1605 fn test_typedef_indexed_access_render() {
1606 let indexed = TypeDef::IndexedAccess {
1608 base: "Profile".into(),
1609 key: "login".into(),
1610 };
1611 assert_eq!(indexed.render(), "Profile[\"login\"]");
1612
1613 let nested = TypeDef::IndexedAccess {
1615 base: "User.Settings".into(),
1616 key: "theme".into(),
1617 };
1618 assert_eq!(nested.render(), "User.Settings[\"theme\"]");
1619 }
1620
1621 #[test]
1622 fn test_typedef_indexed_access_escaping() {
1623 let with_quotes = TypeDef::IndexedAccess {
1625 base: "Config".into(),
1626 key: "key\"with\"quotes".into(),
1627 };
1628 assert_eq!(with_quotes.render(), "Config[\"key\\\"with\\\"quotes\"]");
1629
1630 let with_backslash = TypeDef::IndexedAccess {
1632 base: "Config".into(),
1633 key: "path\\to\\key".into(),
1634 };
1635 assert_eq!(with_backslash.render(), "Config[\"path\\\\to\\\\key\"]");
1636 }
1637
1638 #[test]
1639 fn test_typescript_trait_primitives() {
1640 assert_eq!(<()>::typescript().render(), "void");
1641 assert_eq!(bool::typescript().render(), "boolean");
1642 assert_eq!(String::typescript().render(), "string");
1643 assert_eq!(i32::typescript().render(), "number");
1644 assert_eq!(f64::typescript().render(), "number");
1645 assert_eq!(i128::typescript().render(), "bigint");
1646 assert_eq!(u128::typescript().render(), "bigint");
1647 }
1648
1649 #[test]
1650 fn test_typescript_trait_option() {
1651 let opt = <Option<String>>::typescript();
1652 assert_eq!(opt.render(), "string | null");
1653 }
1654
1655 #[test]
1656 fn test_typescript_trait_vec() {
1657 let vec_type = <Vec<i32>>::typescript();
1658 assert_eq!(vec_type.render(), "number[]");
1659 }
1660
1661 #[test]
1662 fn test_typescript_trait_hashmap() {
1663 let map = <HashMap<String, i32>>::typescript();
1664 assert_eq!(map.render(), "Record<string, number>");
1665 }
1666
1667 #[test]
1668 fn test_typescript_trait_result() {
1669 let result = <Result<String, String>>::typescript();
1670 assert_eq!(
1671 result.render(),
1672 "{ ok: true; value: string } | { ok: false; error: string }"
1673 );
1674 }
1675
1676 #[test]
1677 fn test_typescript_trait_tuples() {
1678 assert_eq!(<(String,)>::typescript().render(), "[string]");
1679 assert_eq!(<(String, i32)>::typescript().render(), "[string, number]");
1680 assert_eq!(
1681 <(String, i32, bool)>::typescript().render(),
1682 "[string, number, boolean]"
1683 );
1684 }
1685
1686 #[test]
1687 fn test_typescript_trait_box() {
1688 assert_eq!(<Box<String>>::typescript().render(), "string");
1690 }
1691
1692 #[test]
1693 fn test_typedef_equality() {
1694 let a = TypeDef::Primitive(Primitive::String);
1695 let b = TypeDef::Primitive(Primitive::String);
1696 let c = TypeDef::Primitive(Primitive::Number);
1697 assert_eq!(a, b);
1698 assert_ne!(a, c);
1699 }
1700
1701 #[test]
1702 fn test_field_builder() {
1703 let field = Field::new("name", TypeDef::Primitive(Primitive::String));
1704 assert!(!field.optional);
1705 assert!(!field.readonly);
1706
1707 let opt_field = Field::optional("name", TypeDef::Primitive(Primitive::String));
1708 assert!(opt_field.optional);
1709
1710 let readonly_field = Field::new("id", TypeDef::Primitive(Primitive::String)).readonly();
1711 assert!(readonly_field.readonly);
1712 }
1713
1714 #[test]
1719 fn test_registry_new() {
1720 let registry = TypeRegistry::new();
1721 assert!(registry.is_empty());
1722 assert_eq!(registry.len(), 0);
1723 }
1724
1725 #[test]
1726 fn test_registry_add_typedef() {
1727 let mut registry = TypeRegistry::new();
1728
1729 let user_type = TypeDef::Named {
1730 namespace: vec![],
1731 name: "User".to_string(),
1732 def: Box::new(TypeDef::Object(vec![
1733 Field::new("id", TypeDef::Primitive(Primitive::String)),
1734 Field::new("name", TypeDef::Primitive(Primitive::String)),
1735 ])),
1736 module: None,
1737 };
1738
1739 registry.add_typedef(user_type);
1740
1741 assert_eq!(registry.len(), 1);
1742 assert!(registry.get("User").is_some());
1743 }
1744
1745 #[test]
1746 fn test_registry_deduplication() {
1747 let mut registry = TypeRegistry::new();
1748
1749 let user_type = TypeDef::Named {
1750 namespace: vec![],
1751 name: "User".to_string(),
1752 def: Box::new(TypeDef::Primitive(Primitive::String)),
1753 module: None,
1754 };
1755
1756 registry.add_typedef(user_type.clone());
1757 registry.add_typedef(user_type);
1758
1759 assert_eq!(registry.len(), 1);
1761 }
1762
1763 #[test]
1764 fn test_registry_extracts_nested_types() {
1765 let mut registry = TypeRegistry::new();
1766
1767 let user_id = TypeDef::Named {
1769 namespace: vec![],
1770 name: "UserId".to_string(),
1771 def: Box::new(TypeDef::Primitive(Primitive::String)),
1772 module: None,
1773 };
1774
1775 let user = TypeDef::Named {
1777 namespace: vec![],
1778 name: "User".to_string(),
1779 def: Box::new(TypeDef::Object(vec![
1780 Field::new("id", TypeDef::Ref("UserId".to_string())),
1781 Field::new("name", TypeDef::Primitive(Primitive::String)),
1782 ])),
1783 module: None,
1784 };
1785
1786 let post_type = TypeDef::Named {
1788 namespace: vec![],
1789 name: "Post".to_string(),
1790 def: Box::new(TypeDef::Object(vec![
1791 Field::new("title", TypeDef::Primitive(Primitive::String)),
1792 Field::new("author", user),
1793 ])),
1794 module: None,
1795 };
1796
1797 registry.add_typedef(post_type);
1798 registry.add_typedef(user_id);
1799
1800 assert_eq!(registry.len(), 3);
1802 assert!(registry.get("Post").is_some());
1803 assert!(registry.get("User").is_some());
1804 assert!(registry.get("UserId").is_some());
1805 }
1806
1807 #[test]
1808 fn test_registry_render() {
1809 let mut registry = TypeRegistry::new();
1810
1811 let user_type = TypeDef::Named {
1812 namespace: vec![],
1813 name: "User".to_string(),
1814 def: Box::new(TypeDef::Object(vec![
1815 Field::new("id", TypeDef::Primitive(Primitive::String)),
1816 Field::new("name", TypeDef::Primitive(Primitive::String)),
1817 ])),
1818 module: None,
1819 };
1820
1821 registry.add_typedef(user_type);
1822
1823 let output = registry.render();
1824 assert!(output.contains("// Generated by ferrotype"));
1825 assert!(output.contains("type User = { id: string; name: string };"));
1826 }
1827
1828 #[test]
1829 fn test_registry_render_exported() {
1830 let mut registry = TypeRegistry::new();
1831
1832 let user_type = TypeDef::Named {
1833 namespace: vec![],
1834 name: "User".to_string(),
1835 def: Box::new(TypeDef::Primitive(Primitive::String)),
1836 module: None,
1837 };
1838
1839 registry.add_typedef(user_type);
1840
1841 let output = registry.render_exported();
1842 assert!(output.contains("export type User = string;"));
1843 }
1844
1845 #[test]
1846 fn test_registry_dependency_order() {
1847 let mut registry = TypeRegistry::new();
1848
1849 let user_id = TypeDef::Named {
1851 namespace: vec![],
1852 name: "UserId".to_string(),
1853 def: Box::new(TypeDef::Primitive(Primitive::String)),
1854 module: None,
1855 };
1856
1857 let user = TypeDef::Named {
1859 namespace: vec![],
1860 name: "User".to_string(),
1861 def: Box::new(TypeDef::Object(vec![
1862 Field::new("id", TypeDef::Ref("UserId".to_string())),
1863 Field::new("name", TypeDef::Primitive(Primitive::String)),
1864 ])),
1865 module: None,
1866 };
1867
1868 registry.add_typedef(user);
1870 registry.add_typedef(user_id);
1871
1872 let sorted = registry.sorted_types();
1873
1874 let user_id_pos = sorted.iter().position(|&n| n == "UserId").unwrap();
1876 let user_pos = sorted.iter().position(|&n| n == "User").unwrap();
1877 assert!(user_id_pos < user_pos, "UserId should come before User");
1878 }
1879
1880 #[test]
1881 fn test_registry_clear() {
1882 let mut registry = TypeRegistry::new();
1883
1884 let user_type = TypeDef::Named {
1885 namespace: vec![],
1886 name: "User".to_string(),
1887 def: Box::new(TypeDef::Primitive(Primitive::String)),
1888 module: None,
1889 };
1890
1891 registry.add_typedef(user_type);
1892 assert_eq!(registry.len(), 1);
1893
1894 registry.clear();
1895 assert!(registry.is_empty());
1896 }
1897
1898 #[test]
1899 fn test_registry_type_names() {
1900 let mut registry = TypeRegistry::new();
1901
1902 registry.add_typedef(TypeDef::Named {
1903 namespace: vec![],
1904 name: "Alpha".to_string(),
1905 def: Box::new(TypeDef::Primitive(Primitive::String)),
1906 module: None,
1907 });
1908 registry.add_typedef(TypeDef::Named {
1909 namespace: vec![],
1910 name: "Beta".to_string(),
1911 def: Box::new(TypeDef::Primitive(Primitive::Number)),
1912 module: None,
1913 });
1914
1915 let names: Vec<_> = registry.type_names().collect();
1916 assert_eq!(names.len(), 2);
1917 assert!(names.contains(&"Alpha"));
1918 assert!(names.contains(&"Beta"));
1919 }
1920
1921 #[test]
1922 fn test_registry_complex_dependencies() {
1923 let mut registry = TypeRegistry::new();
1924
1925 let c = TypeDef::Named {
1927 namespace: vec![],
1928 name: "C".to_string(),
1929 def: Box::new(TypeDef::Primitive(Primitive::String)),
1930 module: None,
1931 };
1932
1933 let b = TypeDef::Named {
1934 namespace: vec![],
1935 name: "B".to_string(),
1936 def: Box::new(TypeDef::Object(vec![
1937 Field::new("c", TypeDef::Ref("C".to_string())),
1938 ])),
1939 module: None,
1940 };
1941
1942 let a = TypeDef::Named {
1943 namespace: vec![],
1944 name: "A".to_string(),
1945 def: Box::new(TypeDef::Object(vec![
1946 Field::new("b", TypeDef::Ref("B".to_string())),
1947 ])),
1948 module: None,
1949 };
1950
1951 registry.add_typedef(a);
1953 registry.add_typedef(b);
1954 registry.add_typedef(c);
1955
1956 let sorted = registry.sorted_types();
1957
1958 let c_pos = sorted.iter().position(|&n| n == "C").unwrap();
1959 let b_pos = sorted.iter().position(|&n| n == "B").unwrap();
1960 let a_pos = sorted.iter().position(|&n| n == "A").unwrap();
1961
1962 assert!(c_pos < b_pos, "C should come before B");
1963 assert!(b_pos < a_pos, "B should come before A");
1964 }
1965
1966 #[test]
1967 fn test_registry_indexed_access_dependency() {
1968 let mut registry = TypeRegistry::new();
1969
1970 let profile = TypeDef::Named {
1972 namespace: vec![],
1973 name: "Profile".to_string(),
1974 def: Box::new(TypeDef::Object(vec![
1975 Field::new("login", TypeDef::Primitive(Primitive::String)),
1976 Field::new("email", TypeDef::Primitive(Primitive::String)),
1977 ])),
1978 module: None,
1979 };
1980
1981 let user_login = TypeDef::Named {
1983 namespace: vec![],
1984 name: "UserLogin".to_string(),
1985 def: Box::new(TypeDef::IndexedAccess {
1986 base: "Profile".to_string(),
1987 key: "login".to_string(),
1988 }),
1989 module: None,
1990 };
1991
1992 registry.add_typedef(user_login);
1994 registry.add_typedef(profile);
1995
1996 let sorted = registry.sorted_types();
1997
1998 let profile_pos = sorted.iter().position(|&n| n == "Profile").unwrap();
1999 let user_login_pos = sorted.iter().position(|&n| n == "UserLogin").unwrap();
2000
2001 assert!(profile_pos < user_login_pos, "Profile should come before UserLogin");
2002 }
2003
2004 #[derive(Debug)]
2010 struct AutoRegTestUser {
2011 name: String,
2012 age: u32,
2013 }
2014
2015 impl TypeScript for AutoRegTestUser {
2016 fn typescript() -> TypeDef {
2017 TypeDef::Named {
2018 namespace: vec![],
2019 name: "AutoRegTestUser".to_string(),
2020 def: Box::new(TypeDef::Object(vec![
2021 Field::new("name", TypeDef::Primitive(Primitive::String)),
2022 Field::new("age", TypeDef::Primitive(Primitive::Number)),
2023 ])),
2024 module: None,
2025 }
2026 }
2027 }
2028
2029 #[linkme::distributed_slice(TYPESCRIPT_TYPES)]
2031 static __TEST_REGISTER_USER: fn() -> TypeDef = || AutoRegTestUser::typescript();
2032
2033 #[test]
2034 fn test_from_distributed_collects_types() {
2035 let registry = TypeRegistry::from_distributed();
2036
2037 assert!(registry.get("AutoRegTestUser").is_some(),
2039 "Registry should contain AutoRegTestUser");
2040 }
2041
2042 #[test]
2043 fn test_collect_all_adds_to_existing() {
2044 let mut registry = TypeRegistry::new();
2045
2046 let manual_type = TypeDef::Named {
2048 namespace: vec![],
2049 name: "ManualType".to_string(),
2050 def: Box::new(TypeDef::Primitive(Primitive::String)),
2051 module: None,
2052 };
2053 registry.add_typedef(manual_type);
2054
2055 registry.collect_all();
2057
2058 assert!(registry.get("ManualType").is_some(),
2060 "Registry should contain ManualType");
2061 assert!(registry.get("AutoRegTestUser").is_some(),
2062 "Registry should contain AutoRegTestUser from distributed slice");
2063 }
2064
2065 #[test]
2066 fn test_distributed_slice_is_accessible() {
2067 let count = TYPESCRIPT_TYPES.len();
2069 assert!(count >= 1, "TYPESCRIPT_TYPES should have at least 1 entry");
2071 }
2072
2073 #[test]
2078 fn test_type_param_simple() {
2079 let param = TypeParam::new("T");
2080 assert_eq!(param.render(), "T");
2081 }
2082
2083 #[test]
2084 fn test_type_param_with_constraint() {
2085 let param = TypeParam::new("T").with_constraint(TypeDef::Primitive(Primitive::String));
2086 assert_eq!(param.render(), "T extends string");
2087 }
2088
2089 #[test]
2090 fn test_type_param_with_object_constraint() {
2091 let param = TypeParam::new("T").with_constraint(TypeDef::Object(vec![
2092 Field::new("type", TypeDef::Primitive(Primitive::String)),
2093 ]));
2094 assert_eq!(param.render(), "T extends { type: string }");
2095 }
2096
2097 #[test]
2098 fn test_type_param_with_default() {
2099 let param = TypeParam::new("T").with_default(TypeDef::Primitive(Primitive::Never));
2100 assert_eq!(param.render(), "T = never");
2101 }
2102
2103 #[test]
2104 fn test_type_param_with_constraint_and_default() {
2105 let param = TypeParam::new("T")
2106 .with_constraint(TypeDef::Primitive(Primitive::String))
2107 .with_default(TypeDef::Literal(Literal::String("default".into())));
2108 assert_eq!(param.render(), "T extends string = \"default\"");
2109 }
2110
2111 #[test]
2112 fn test_type_param_ref_render() {
2113 let param_ref = TypeDef::TypeParamRef("T".into());
2114 assert_eq!(param_ref.render(), "T");
2115 }
2116
2117 #[test]
2118 fn test_generic_def_simple() {
2119 let generic_def = TypeDef::GenericDef {
2121 name: "Identity".into(),
2122 type_params: vec![TypeParam::new("T")],
2123 def: Box::new(TypeDef::TypeParamRef("T".into())),
2124 };
2125 assert_eq!(generic_def.render(), "Identity");
2126 assert_eq!(generic_def.render_declaration(), "type Identity<T> = T;");
2127 }
2128
2129 #[test]
2130 fn test_generic_def_with_constraint() {
2131 let generic_def = TypeDef::GenericDef {
2133 name: "Wrapper".into(),
2134 type_params: vec![TypeParam::new("T").with_constraint(TypeDef::Object(vec![
2135 Field::new("type", TypeDef::Primitive(Primitive::String)),
2136 ]))],
2137 def: Box::new(TypeDef::Object(vec![
2138 Field::new("data", TypeDef::TypeParamRef("T".into())),
2139 ])),
2140 };
2141 assert_eq!(
2142 generic_def.render_declaration(),
2143 "type Wrapper<T extends { type: string }> = { data: T };"
2144 );
2145 }
2146
2147 #[test]
2148 fn test_generic_def_core_pattern() {
2149 let core_def = TypeDef::GenericDef {
2152 name: "Core".into(),
2153 type_params: vec![TypeParam::new("T").with_constraint(TypeDef::Object(vec![
2154 Field::new("type", TypeDef::Primitive(Primitive::String)),
2155 ]))],
2156 def: Box::new(TypeDef::Object(vec![
2157 Field::new("id", TypeDef::Primitive(Primitive::String)),
2158 Field::new("timestamp", TypeDef::Primitive(Primitive::Number)),
2159 Field::new("data", TypeDef::TypeParamRef("T".into())),
2160 ])),
2161 };
2162
2163 assert_eq!(
2164 core_def.render_declaration(),
2165 "type Core<T extends { type: string }> = { id: string; timestamp: number; data: T };"
2166 );
2167 }
2168
2169 #[test]
2170 fn test_generic_def_multiple_params() {
2171 let pair_def = TypeDef::GenericDef {
2173 name: "Pair".into(),
2174 type_params: vec![TypeParam::new("K"), TypeParam::new("V")],
2175 def: Box::new(TypeDef::Object(vec![
2176 Field::new("key", TypeDef::TypeParamRef("K".into())),
2177 Field::new("value", TypeDef::TypeParamRef("V".into())),
2178 ])),
2179 };
2180 assert_eq!(
2181 pair_def.render_declaration(),
2182 "type Pair<K, V> = { key: K; value: V };"
2183 );
2184 }
2185
2186 #[test]
2187 fn test_generic_def_with_default_params() {
2188 let result_def = TypeDef::GenericDef {
2190 name: "Result".into(),
2191 type_params: vec![
2192 TypeParam::new("T"),
2193 TypeParam::new("E").with_default(TypeDef::Ref("Error".into())),
2194 ],
2195 def: Box::new(TypeDef::Union(vec![
2196 TypeDef::Object(vec![
2197 Field::new("ok", TypeDef::Literal(Literal::Boolean(true))),
2198 Field::new("value", TypeDef::TypeParamRef("T".into())),
2199 ]),
2200 TypeDef::Object(vec![
2201 Field::new("ok", TypeDef::Literal(Literal::Boolean(false))),
2202 Field::new("error", TypeDef::TypeParamRef("E".into())),
2203 ]),
2204 ])),
2205 };
2206 assert_eq!(
2207 result_def.render_declaration(),
2208 "type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };"
2209 );
2210 }
2211
2212 #[test]
2213 fn test_generic_application_with_def() {
2214 let application = TypeDef::Generic {
2216 base: "Core".into(),
2217 args: vec![TypeDef::Object(vec![
2218 Field::new("type", TypeDef::Literal(Literal::String("text".into()))),
2219 Field::new("content", TypeDef::Primitive(Primitive::String)),
2220 ])],
2221 };
2222 assert_eq!(
2223 application.render(),
2224 "Core<{ type: \"text\"; content: string }>"
2225 );
2226 }
2227
2228 #[test]
2229 fn test_registry_with_generic_def() {
2230 let mut registry = TypeRegistry::new();
2231
2232 let core_def = TypeDef::GenericDef {
2234 name: "Core".into(),
2235 type_params: vec![TypeParam::new("T")],
2236 def: Box::new(TypeDef::Object(vec![
2237 Field::new("id", TypeDef::Primitive(Primitive::String)),
2238 Field::new("data", TypeDef::TypeParamRef("T".into())),
2239 ])),
2240 };
2241 registry.add_typedef(core_def);
2242
2243 assert_eq!(registry.len(), 1);
2244 assert!(registry.get("Core").is_some());
2245
2246 let output = registry.render();
2247 assert!(output.contains("type Core<T> = { id: string; data: T };"));
2248 }
2249
2250 #[test]
2251 fn test_registry_exported_with_generic_def() {
2252 let mut registry = TypeRegistry::new();
2253
2254 let core_def = TypeDef::GenericDef {
2255 name: "Core".into(),
2256 type_params: vec![TypeParam::new("T").with_constraint(TypeDef::Object(vec![
2257 Field::new("type", TypeDef::Primitive(Primitive::String)),
2258 ]))],
2259 def: Box::new(TypeDef::Object(vec![
2260 Field::new("data", TypeDef::TypeParamRef("T".into())),
2261 ])),
2262 };
2263 registry.add_typedef(core_def);
2264
2265 let output = registry.render_exported();
2266 assert!(output.contains("export type Core<T extends { type: string }> = { data: T };"));
2267 }
2268
2269 #[test]
2270 fn test_registry_generic_depends_on_constraint() {
2271 let mut registry = TypeRegistry::new();
2272
2273 let discriminant = TypeDef::Named {
2275 namespace: vec![],
2276 name: "Discriminant".into(),
2277 def: Box::new(TypeDef::Object(vec![Field::new(
2278 "type",
2279 TypeDef::Primitive(Primitive::String),
2280 )])),
2281 module: None,
2282 };
2283
2284 let core_def = TypeDef::GenericDef {
2286 name: "Core".into(),
2287 type_params: vec![TypeParam::new("T").with_constraint(TypeDef::Ref("Discriminant".into()))],
2288 def: Box::new(TypeDef::Object(vec![
2289 Field::new("data", TypeDef::TypeParamRef("T".into())),
2290 ])),
2291 };
2292
2293 registry.add_typedef(core_def);
2294 registry.add_typedef(discriminant);
2295
2296 let sorted = registry.sorted_types();
2297
2298 let discrim_pos = sorted.iter().position(|&n| n == "Discriminant").unwrap();
2300 let core_pos = sorted.iter().position(|&n| n == "Core").unwrap();
2301 assert!(discrim_pos < core_pos, "Discriminant should come before Core");
2302 }
2303
2304 #[test]
2305 fn test_full_core_pattern_example() {
2306 let mut registry = TypeRegistry::new();
2308
2309 let core_def = TypeDef::GenericDef {
2311 name: "Core".into(),
2312 type_params: vec![TypeParam::new("T").with_constraint(TypeDef::Object(vec![
2313 Field::new("type", TypeDef::Primitive(Primitive::String)),
2314 ]))],
2315 def: Box::new(TypeDef::Object(vec![
2316 Field::new("id", TypeDef::Primitive(Primitive::String)),
2317 Field::new("timestamp", TypeDef::Primitive(Primitive::Number)),
2318 Field::new("data", TypeDef::TypeParamRef("T".into())),
2319 ])),
2320 };
2321
2322 let text_data = TypeDef::Named {
2324 namespace: vec![],
2325 name: "TextData".into(),
2326 def: Box::new(TypeDef::Object(vec![
2327 Field::new("type", TypeDef::Literal(Literal::String("text".into()))),
2328 Field::new("content", TypeDef::Primitive(Primitive::String)),
2329 ])),
2330 module: None,
2331 };
2332
2333 let image_data = TypeDef::Named {
2334 namespace: vec![],
2335 name: "ImageData".into(),
2336 def: Box::new(TypeDef::Object(vec![
2337 Field::new("type", TypeDef::Literal(Literal::String("image".into()))),
2338 Field::new("url", TypeDef::Primitive(Primitive::String)),
2339 ])),
2340 module: None,
2341 };
2342
2343 let text_message = TypeDef::Named {
2345 namespace: vec![],
2346 name: "TextMessage".into(),
2347 def: Box::new(TypeDef::Generic {
2348 base: "Core".into(),
2349 args: vec![TypeDef::Ref("TextData".into())],
2350 }),
2351 module: None,
2352 };
2353
2354 let image_message = TypeDef::Named {
2355 namespace: vec![],
2356 name: "ImageMessage".into(),
2357 def: Box::new(TypeDef::Generic {
2358 base: "Core".into(),
2359 args: vec![TypeDef::Ref("ImageData".into())],
2360 }),
2361 module: None,
2362 };
2363
2364 let message = TypeDef::Named {
2366 namespace: vec![],
2367 name: "Message".into(),
2368 def: Box::new(TypeDef::Union(vec![
2369 TypeDef::Ref("TextMessage".into()),
2370 TypeDef::Ref("ImageMessage".into()),
2371 ])),
2372 module: None,
2373 };
2374
2375 registry.add_typedef(core_def);
2376 registry.add_typedef(text_data);
2377 registry.add_typedef(image_data);
2378 registry.add_typedef(text_message);
2379 registry.add_typedef(image_message);
2380 registry.add_typedef(message);
2381
2382 let output = registry.render_exported();
2383
2384 assert!(output.contains("export type Core<T extends { type: string }>"));
2386 assert!(output.contains("export type TextData ="));
2387 assert!(output.contains("export type ImageData ="));
2388 assert!(output.contains("export type TextMessage = Core<TextData>"));
2389 assert!(output.contains("export type ImageMessage = Core<ImageData>"));
2390 assert!(output.contains("export type Message = TextMessage | ImageMessage"));
2391 }
2392}