1pub use ferro_type_derive::TS;
18#[deprecated(since = "0.2.0", note = "use `TS` instead")]
19#[allow(deprecated)]
20pub use ferro_type_derive::TypeScript;
21pub use linkme;
22
23use std::collections::HashMap;
24
25#[linkme::distributed_slice]
46pub static TYPESCRIPT_TYPES: [fn() -> TypeDef];
47
48pub trait TS {
86 fn typescript() -> TypeDef;
88}
89
90#[deprecated(since = "0.2.0", note = "use `TS` instead")]
94pub trait TypeScript: TS {}
95
96#[derive(Debug, Clone, PartialEq)]
109pub enum TypeDef {
110 Primitive(Primitive),
112
113 Array(Box<TypeDef>),
115
116 Tuple(Vec<TypeDef>),
118
119 Object(Vec<Field>),
121
122 Union(Vec<TypeDef>),
124
125 Intersection(Vec<TypeDef>),
127
128 Record {
130 key: Box<TypeDef>,
131 value: Box<TypeDef>,
132 },
133
134 Named {
146 namespace: Vec<String>,
148 name: String,
150 def: Box<TypeDef>,
152 module: Option<String>,
154 wrapper: Option<String>,
156 },
157
158 Ref(String),
161
162 Literal(Literal),
164
165 Function {
167 params: Vec<Field>,
168 return_type: Box<TypeDef>,
169 },
170
171 Generic {
173 base: String,
174 args: Vec<TypeDef>,
175 },
176
177 IndexedAccess {
193 base: String,
195 key: String,
197 },
198
199 TemplateLiteral {
215 strings: Vec<String>,
219 types: Vec<Box<TypeDef>>,
221 },
222
223 GenericDef {
270 name: String,
272 type_params: Vec<TypeParam>,
274 def: Box<TypeDef>,
276 },
277
278 TypeParamRef(String),
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
287pub enum Primitive {
288 String,
290 Number,
292 Boolean,
294 Null,
296 Undefined,
298 Void,
300 Never,
302 Any,
304 Unknown,
306 BigInt,
308}
309
310#[derive(Debug, Clone, PartialEq)]
312pub struct Field {
313 pub name: String,
315 pub ty: TypeDef,
317 pub optional: bool,
319 pub readonly: bool,
321}
322
323#[derive(Debug, Clone, PartialEq)]
340pub struct TypeParam {
341 pub name: String,
343 pub constraint: Option<Box<TypeDef>>,
345 pub default: Option<Box<TypeDef>>,
347}
348
349impl TypeParam {
350 pub fn new(name: impl Into<String>) -> Self {
352 Self {
353 name: name.into(),
354 constraint: None,
355 default: None,
356 }
357 }
358
359 pub fn with_constraint(mut self, constraint: TypeDef) -> Self {
361 self.constraint = Some(Box::new(constraint));
362 self
363 }
364
365 pub fn with_default(mut self, default: TypeDef) -> Self {
367 self.default = Some(Box::new(default));
368 self
369 }
370
371 pub fn render(&self) -> String {
380 let mut result = self.name.clone();
381
382 if let Some(ref constraint) = self.constraint {
383 result.push_str(" extends ");
384 result.push_str(&constraint.render());
385 }
386
387 if let Some(ref default) = self.default {
388 result.push_str(" = ");
389 result.push_str(&default.render());
390 }
391
392 result
393 }
394}
395
396impl Field {
397 pub fn new(name: impl Into<String>, ty: TypeDef) -> Self {
399 Self {
400 name: name.into(),
401 ty,
402 optional: false,
403 readonly: false,
404 }
405 }
406
407 pub fn optional(name: impl Into<String>, ty: TypeDef) -> Self {
409 Self {
410 name: name.into(),
411 ty,
412 optional: true,
413 readonly: false,
414 }
415 }
416
417 pub fn readonly(mut self) -> Self {
419 self.readonly = true;
420 self
421 }
422}
423
424#[derive(Debug, Clone, PartialEq)]
426pub enum Literal {
427 String(String),
429 Number(f64),
431 Boolean(bool),
433}
434
435impl TypeDef {
436 pub fn render(&self) -> String {
438 match self {
439 TypeDef::Primitive(p) => p.render().to_string(),
440 TypeDef::Array(inner) => {
441 let inner_str = inner.render();
442 if matches!(inner.as_ref(), TypeDef::Union(_)) {
444 format!("({})[]", inner_str)
445 } else {
446 format!("{}[]", inner_str)
447 }
448 }
449 TypeDef::Tuple(items) => {
450 let items_str: Vec<_> = items.iter().map(|t| t.render()).collect();
451 format!("[{}]", items_str.join(", "))
452 }
453 TypeDef::Object(fields) => {
454 if fields.is_empty() {
455 "{}".to_string()
456 } else {
457 let fields_str: Vec<_> = fields
458 .iter()
459 .map(|f| {
460 let readonly = if f.readonly { "readonly " } else { "" };
461 let opt = if f.optional { "?" } else { "" };
462 format!("{}{}{}: {}", readonly, f.name, opt, f.ty.render())
463 })
464 .collect();
465 format!("{{ {} }}", fields_str.join("; "))
466 }
467 }
468 TypeDef::Union(variants) => {
469 let variants_str: Vec<_> = variants.iter().map(|t| t.render()).collect();
470 variants_str.join(" | ")
471 }
472 TypeDef::Intersection(types) => {
473 let types_str: Vec<_> = types.iter().map(|t| t.render()).collect();
474 types_str.join(" & ")
475 }
476 TypeDef::Record { key, value } => {
477 format!("Record<{}, {}>", key.render(), value.render())
478 }
479 TypeDef::Named { namespace, name, .. } => {
480 if namespace.is_empty() {
481 name.clone()
482 } else {
483 format!("{}.{}", namespace.join("."), name)
484 }
485 }
486 TypeDef::Ref(name) => name.clone(),
487 TypeDef::Literal(lit) => lit.render(),
488 TypeDef::Function {
489 params,
490 return_type,
491 } => {
492 let params_str: Vec<_> = params
493 .iter()
494 .map(|p| format!("{}: {}", p.name, p.ty.render()))
495 .collect();
496 format!("({}) => {}", params_str.join(", "), return_type.render())
497 }
498 TypeDef::Generic { base, args } => {
499 let args_str: Vec<_> = args.iter().map(|t| t.render()).collect();
500 format!("{}<{}>", base, args_str.join(", "))
501 }
502 TypeDef::IndexedAccess { base, key } => {
503 format!("{}[\"{}\"]", base, key.replace('\\', "\\\\").replace('"', "\\\""))
504 }
505 TypeDef::TemplateLiteral { strings, types } => {
506 let mut result = String::from("`");
507 for (i, s) in strings.iter().enumerate() {
508 let escaped = s.replace('\\', "\\\\").replace('`', "\\`");
510 result.push_str(&escaped);
511 if i < types.len() {
512 result.push_str("${");
513 result.push_str(&types[i].render());
514 result.push('}');
515 }
516 }
517 result.push('`');
518 result
519 }
520 TypeDef::GenericDef { name, .. } => {
521 name.clone()
524 }
525 TypeDef::TypeParamRef(name) => {
526 name.clone()
528 }
529 }
530 }
531
532 pub fn render_declaration(&self) -> String {
540 match self {
541 TypeDef::Named { namespace, name, def, wrapper, .. } => {
542 let def_rendered = def.render();
543 let wrapped = match wrapper {
544 Some(w) => Self::apply_wrapper(w, &def_rendered),
545 None => def_rendered,
546 };
547 let inner = format!("type {} = {};", name, wrapped);
548 Self::wrap_in_namespace(namespace, &inner)
549 }
550 TypeDef::GenericDef {
551 name,
552 type_params,
553 def,
554 } => {
555 let params_str: Vec<_> = type_params.iter().map(|p| p.render()).collect();
556 format!("type {}<{}> = {};", name, params_str.join(", "), def.render())
557 }
558 _ => self.render(),
559 }
560 }
561
562 fn apply_wrapper(wrapper: &str, definition: &str) -> String {
568 let open_count = wrapper.chars().filter(|c| *c == '<').count();
570 let close_count = wrapper.chars().filter(|c| *c == '>').count();
571 let needed_closes = open_count.saturating_sub(close_count);
572
573 if open_count == 0 {
574 format!("{}<{}>", wrapper, definition)
576 } else {
577 format!("{}{}{}", wrapper, definition, ">".repeat(needed_closes))
580 }
581 }
582
583 fn wrap_in_namespace(namespace: &[String], inner: &str) -> String {
594 if namespace.is_empty() {
595 return inner.to_string();
596 }
597
598 let mut result = String::new();
599 let indent = " ";
600
601 for (i, ns) in namespace.iter().enumerate() {
603 for _ in 0..i {
604 result.push_str(indent);
605 }
606 result.push_str("namespace ");
607 result.push_str(ns);
608 result.push_str(" {\n");
609 }
610
611 let depth = namespace.len();
613 for _ in 0..depth {
614 result.push_str(indent);
615 }
616 result.push_str(inner);
617 result.push('\n');
618
619 for i in (0..depth).rev() {
621 for _ in 0..i {
622 result.push_str(indent);
623 }
624 result.push('}');
625 if i > 0 {
626 result.push('\n');
627 }
628 }
629
630 result
631 }
632}
633
634impl Primitive {
635 pub fn render(&self) -> &'static str {
637 match self {
638 Primitive::String => "string",
639 Primitive::Number => "number",
640 Primitive::Boolean => "boolean",
641 Primitive::Null => "null",
642 Primitive::Undefined => "undefined",
643 Primitive::Void => "void",
644 Primitive::Never => "never",
645 Primitive::Any => "any",
646 Primitive::Unknown => "unknown",
647 Primitive::BigInt => "bigint",
648 }
649 }
650}
651
652impl Literal {
653 pub fn render(&self) -> String {
655 match self {
656 Literal::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
657 Literal::Number(n) => {
658 if n.fract() == 0.0 {
659 format!("{}", *n as i64)
660 } else {
661 format!("{}", n)
662 }
663 }
664 Literal::Boolean(b) => b.to_string(),
665 }
666 }
667}
668
669pub fn extract_object_fields(typedef: &TypeDef) -> Vec<Field> {
683 match typedef {
684 TypeDef::Object(fields) => fields.clone(),
685 TypeDef::Named { def, .. } => extract_object_fields(def),
686 other => panic!(
687 "#[ts(flatten)] can only be used on fields with object types, got: {:?}",
688 other
689 ),
690 }
691}
692
693pub fn inline_typedef(typedef: TypeDef) -> TypeDef {
699 match typedef {
700 TypeDef::Named { def, .. } => *def,
701 other => other,
702 }
703}
704
705use std::collections::{HashSet, VecDeque};
710
711#[derive(Debug, Default)]
732pub struct TypeRegistry {
733 types: HashMap<String, TypeDef>,
735 registration_order: Vec<String>,
737}
738
739impl TypeRegistry {
740 pub fn new() -> Self {
742 Self::default()
743 }
744
745 pub fn from_distributed() -> Self {
766 let mut registry = Self::new();
767 for type_fn in TYPESCRIPT_TYPES {
768 let typedef = type_fn();
769 registry.add_typedef(typedef);
770 }
771 registry
772 }
773
774 pub fn collect_all(&mut self) {
789 for type_fn in TYPESCRIPT_TYPES {
790 let typedef = type_fn();
791 self.add_typedef(typedef);
792 }
793 }
794
795 pub fn register<T: TS>(&mut self) {
800 let typedef = T::typescript();
801 self.add_typedef(typedef);
802 }
803
804 pub fn add_typedef(&mut self, typedef: TypeDef) {
806 self.extract_named_types(&typedef);
807 }
808
809 fn extract_named_types(&mut self, typedef: &TypeDef) {
811 match typedef {
812 TypeDef::Named { namespace, name, def, .. } => {
813 let qualified_name = if namespace.is_empty() {
815 name.clone()
816 } else {
817 format!("{}.{}", namespace.join("."), name)
818 };
819
820 if !self.types.contains_key(&qualified_name) {
821 self.types.insert(qualified_name.clone(), typedef.clone());
822 self.registration_order.push(qualified_name);
823 self.extract_named_types(def);
825 }
826 }
827 TypeDef::Array(inner) => self.extract_named_types(inner),
828 TypeDef::Tuple(items) => {
829 for item in items {
830 self.extract_named_types(item);
831 }
832 }
833 TypeDef::Object(fields) => {
834 for field in fields {
835 self.extract_named_types(&field.ty);
836 }
837 }
838 TypeDef::Union(items) | TypeDef::Intersection(items) => {
839 for item in items {
840 self.extract_named_types(item);
841 }
842 }
843 TypeDef::Record { key, value } => {
844 self.extract_named_types(key);
845 self.extract_named_types(value);
846 }
847 TypeDef::Function { params, return_type } => {
848 for param in params {
849 self.extract_named_types(¶m.ty);
850 }
851 self.extract_named_types(return_type);
852 }
853 TypeDef::Generic { args, .. } => {
854 for arg in args {
855 self.extract_named_types(arg);
856 }
857 }
858 TypeDef::TemplateLiteral { types, .. } => {
859 for ty in types {
860 self.extract_named_types(ty);
861 }
862 }
863 TypeDef::GenericDef { name, type_params, def } => {
864 if !self.types.contains_key(name) {
865 self.types.insert(name.clone(), typedef.clone());
866 self.registration_order.push(name.clone());
867 for param in type_params {
869 if let Some(ref constraint) = param.constraint {
870 self.extract_named_types(constraint);
871 }
872 if let Some(ref default) = param.default {
873 self.extract_named_types(default);
874 }
875 }
876 self.extract_named_types(def);
878 }
879 }
880 TypeDef::Primitive(_) | TypeDef::Ref(_) | TypeDef::Literal(_) | TypeDef::IndexedAccess { .. } | TypeDef::TypeParamRef(_) => {}
883 }
884 }
885
886 pub fn len(&self) -> usize {
888 self.types.len()
889 }
890
891 pub fn is_empty(&self) -> bool {
893 self.types.is_empty()
894 }
895
896 pub fn type_names(&self) -> impl Iterator<Item = &str> {
898 self.types.keys().map(|s| s.as_str())
899 }
900
901 pub fn get(&self, name: &str) -> Option<&TypeDef> {
903 self.types.get(name)
904 }
905
906 fn get_dependencies(&self, typedef: &TypeDef) -> HashSet<String> {
908 let mut deps = HashSet::new();
909 self.collect_dependencies(typedef, &mut deps);
910 deps
911 }
912
913 fn collect_dependencies(&self, typedef: &TypeDef, deps: &mut HashSet<String>) {
915 match typedef {
916 TypeDef::Named { def, .. } => {
917 self.collect_dependencies(def, deps);
919 }
920 TypeDef::Ref(name) => {
921 if self.types.contains_key(name) {
922 deps.insert(name.clone());
923 }
924 }
925 TypeDef::Array(inner) => self.collect_dependencies(inner, deps),
926 TypeDef::Tuple(items) => {
927 for item in items {
928 self.collect_dependencies(item, deps);
929 }
930 }
931 TypeDef::Object(fields) => {
932 for field in fields {
933 self.collect_dependencies(&field.ty, deps);
934 }
935 }
936 TypeDef::Union(variants) => {
937 for v in variants {
938 self.collect_dependencies(v, deps);
939 }
940 }
941 TypeDef::Intersection(types) => {
942 for t in types {
943 self.collect_dependencies(t, deps);
944 }
945 }
946 TypeDef::Record { key, value } => {
947 self.collect_dependencies(key, deps);
948 self.collect_dependencies(value, deps);
949 }
950 TypeDef::Function { params, return_type } => {
951 for param in params {
952 self.collect_dependencies(¶m.ty, deps);
953 }
954 self.collect_dependencies(return_type, deps);
955 }
956 TypeDef::Generic { base, args } => {
957 if self.types.contains_key(base) {
959 deps.insert(base.clone());
960 }
961 for arg in args {
962 self.collect_dependencies(arg, deps);
963 }
964 }
965 TypeDef::TemplateLiteral { types, .. } => {
966 for ty in types {
967 self.collect_dependencies(ty, deps);
968 }
969 }
970 TypeDef::IndexedAccess { base, .. } => {
971 if self.types.contains_key(base) {
973 deps.insert(base.clone());
974 }
975 }
976 TypeDef::GenericDef { type_params, def, .. } => {
977 for param in type_params {
979 if let Some(ref constraint) = param.constraint {
980 self.collect_dependencies(constraint, deps);
981 }
982 if let Some(ref default) = param.default {
983 self.collect_dependencies(default, deps);
984 }
985 }
986 self.collect_dependencies(def, deps);
987 }
988 TypeDef::Primitive(_) | TypeDef::Literal(_) | TypeDef::TypeParamRef(_) => {}
990 }
991 }
992
993 pub fn sorted_types(&self) -> Vec<&str> {
997 let mut in_degree: HashMap<&str, usize> = HashMap::new();
999 let mut dependents: HashMap<&str, Vec<&str>> = HashMap::new();
1000
1001 for name in self.types.keys() {
1003 in_degree.insert(name.as_str(), 0);
1004 dependents.insert(name.as_str(), Vec::new());
1005 }
1006
1007 for (name, typedef) in &self.types {
1009 let deps = self.get_dependencies(typedef);
1010 for dep in deps {
1011 if let Some(dep_name) = self.types.get_key_value(&dep) {
1012 *in_degree.get_mut(name.as_str()).unwrap() += 1;
1013 dependents.get_mut(dep_name.0.as_str()).unwrap().push(name.as_str());
1014 }
1015 }
1016 }
1017
1018 let mut queue: VecDeque<&str> = VecDeque::new();
1020 let mut result: Vec<&str> = Vec::new();
1021
1022 for (name, °ree) in &in_degree {
1024 if degree == 0 {
1025 queue.push_back(name);
1026 }
1027 }
1028
1029 let mut initial: Vec<_> = queue.drain(..).collect();
1031 initial.sort_by_key(|name| {
1032 self.registration_order.iter().position(|n| n == *name).unwrap_or(usize::MAX)
1033 });
1034 queue.extend(initial);
1035
1036 while let Some(name) = queue.pop_front() {
1037 result.push(name);
1038
1039 let mut deps: Vec<_> = dependents.get(name).map(|v| v.as_slice()).unwrap_or(&[]).to_vec();
1041 deps.sort_by_key(|n| {
1042 self.registration_order.iter().position(|name| name == *n).unwrap_or(usize::MAX)
1043 });
1044
1045 for dependent in deps {
1046 let degree = in_degree.get_mut(dependent).unwrap();
1047 *degree -= 1;
1048 if *degree == 0 {
1049 queue.push_back(dependent);
1050 }
1051 }
1052 }
1053
1054 if result.len() < self.types.len() {
1057 for name in &self.registration_order {
1058 if !result.contains(&name.as_str()) {
1059 result.push(name.as_str());
1060 }
1061 }
1062 }
1063
1064 result
1065 }
1066
1067 pub fn render(&self) -> String {
1071 let sorted = self.sorted_types();
1072 let mut output = String::new();
1073
1074 output.push_str("// Generated by ferrotype\n");
1076 output.push_str("// Do not edit manually\n\n");
1077
1078 for name in sorted {
1079 if let Some(typedef) = self.types.get(name) {
1080 output.push_str(&typedef.render_declaration());
1081 output.push_str("\n\n");
1082 }
1083 }
1084
1085 output.trim_end().to_string() + "\n"
1087 }
1088
1089 pub fn render_exported(&self) -> String {
1093 let sorted = self.sorted_types();
1094 let mut output = String::new();
1095
1096 output.push_str("// Generated by ferrotype\n");
1098 output.push_str("// Do not edit manually\n\n");
1099
1100 for name in sorted {
1101 if let Some(typedef) = self.types.get(name) {
1102 match typedef {
1103 TypeDef::Named { namespace, name, def, wrapper, .. } => {
1104 let def_rendered = def.render();
1105 let wrapped = match wrapper {
1106 Some(w) => TypeDef::apply_wrapper(w, &def_rendered),
1107 None => def_rendered,
1108 };
1109 let inner = format!("export type {} = {};", name, wrapped);
1110 if namespace.is_empty() {
1111 output.push_str(&inner);
1112 } else {
1113 output.push_str(&Self::wrap_in_export_namespace(namespace, &inner));
1115 }
1116 output.push_str("\n\n");
1117 }
1118 TypeDef::GenericDef { name, type_params, def } => {
1119 let params_str: Vec<_> = type_params.iter().map(|p| p.render()).collect();
1120 output.push_str(&format!(
1121 "export type {}<{}> = {};\n\n",
1122 name,
1123 params_str.join(", "),
1124 def.render()
1125 ));
1126 }
1127 _ => {}
1128 }
1129 }
1130 }
1131
1132 output.trim_end().to_string() + "\n"
1134 }
1135
1136 fn wrap_in_export_namespace(namespace: &[String], inner: &str) -> String {
1138 if namespace.is_empty() {
1139 return inner.to_string();
1140 }
1141
1142 let mut result = String::new();
1143 let indent = " ";
1144
1145 for (i, ns) in namespace.iter().enumerate() {
1147 for _ in 0..i {
1148 result.push_str(indent);
1149 }
1150 result.push_str("export namespace ");
1151 result.push_str(ns);
1152 result.push_str(" {\n");
1153 }
1154
1155 let depth = namespace.len();
1157 for _ in 0..depth {
1158 result.push_str(indent);
1159 }
1160 result.push_str(inner);
1161 result.push('\n');
1162
1163 for i in (0..depth).rev() {
1165 for _ in 0..i {
1166 result.push_str(indent);
1167 }
1168 result.push('}');
1169 if i > 0 {
1170 result.push('\n');
1171 }
1172 }
1173
1174 result
1175 }
1176
1177 pub fn clear(&mut self) {
1179 self.types.clear();
1180 self.registration_order.clear();
1181 }
1182}
1183
1184impl TS for () {
1189 fn typescript() -> TypeDef {
1190 TypeDef::Primitive(Primitive::Void)
1191 }
1192}
1193
1194impl TS for bool {
1195 fn typescript() -> TypeDef {
1196 TypeDef::Primitive(Primitive::Boolean)
1197 }
1198}
1199
1200impl TS for String {
1201 fn typescript() -> TypeDef {
1202 TypeDef::Primitive(Primitive::String)
1203 }
1204}
1205
1206impl TS for &str {
1207 fn typescript() -> TypeDef {
1208 TypeDef::Primitive(Primitive::String)
1209 }
1210}
1211
1212impl TS for char {
1213 fn typescript() -> TypeDef {
1214 TypeDef::Primitive(Primitive::String)
1215 }
1216}
1217
1218macro_rules! impl_ts_number {
1219 ($($t:ty),*) => {
1220 $(
1221 impl TS for $t {
1222 fn typescript() -> TypeDef {
1223 TypeDef::Primitive(Primitive::Number)
1224 }
1225 }
1226 )*
1227 };
1228}
1229
1230impl_ts_number!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64);
1231
1232impl TS for i128 {
1234 fn typescript() -> TypeDef {
1235 TypeDef::Primitive(Primitive::BigInt)
1236 }
1237}
1238
1239impl TS for u128 {
1240 fn typescript() -> TypeDef {
1241 TypeDef::Primitive(Primitive::BigInt)
1242 }
1243}
1244
1245impl<T: TS> TS for Option<T> {
1250 fn typescript() -> TypeDef {
1251 TypeDef::Union(vec![T::typescript(), TypeDef::Primitive(Primitive::Null)])
1252 }
1253}
1254
1255impl<T: TS> TS for Vec<T> {
1256 fn typescript() -> TypeDef {
1257 TypeDef::Array(Box::new(T::typescript()))
1258 }
1259}
1260
1261impl<T: TS> TS for Box<T> {
1262 fn typescript() -> TypeDef {
1263 T::typescript()
1264 }
1265}
1266
1267impl<T: TS> TS for std::rc::Rc<T> {
1268 fn typescript() -> TypeDef {
1269 T::typescript()
1270 }
1271}
1272
1273impl<T: TS> TS for std::sync::Arc<T> {
1274 fn typescript() -> TypeDef {
1275 T::typescript()
1276 }
1277}
1278
1279impl<T: TS> TS for std::cell::RefCell<T> {
1280 fn typescript() -> TypeDef {
1281 T::typescript()
1282 }
1283}
1284
1285impl<T: TS> TS for std::cell::Cell<T> {
1286 fn typescript() -> TypeDef {
1287 T::typescript()
1288 }
1289}
1290
1291impl<K: TS, V: TS> TS for HashMap<K, V> {
1292 fn typescript() -> TypeDef {
1293 TypeDef::Record {
1294 key: Box::new(K::typescript()),
1295 value: Box::new(V::typescript()),
1296 }
1297 }
1298}
1299
1300impl<K: TS, V: TS> TS for std::collections::BTreeMap<K, V> {
1301 fn typescript() -> TypeDef {
1302 TypeDef::Record {
1303 key: Box::new(K::typescript()),
1304 value: Box::new(V::typescript()),
1305 }
1306 }
1307}
1308
1309impl<T: TS, E: TS> TS for Result<T, E> {
1310 fn typescript() -> TypeDef {
1311 TypeDef::Union(vec![
1312 TypeDef::Object(vec![
1313 Field::new("ok", TypeDef::Literal(Literal::Boolean(true))),
1314 Field::new("value", T::typescript()),
1315 ]),
1316 TypeDef::Object(vec![
1317 Field::new("ok", TypeDef::Literal(Literal::Boolean(false))),
1318 Field::new("error", E::typescript()),
1319 ]),
1320 ])
1321 }
1322}
1323
1324impl<A: TS> TS for (A,) {
1329 fn typescript() -> TypeDef {
1330 TypeDef::Tuple(vec![A::typescript()])
1331 }
1332}
1333
1334impl<A: TS, B: TS> TS for (A, B) {
1335 fn typescript() -> TypeDef {
1336 TypeDef::Tuple(vec![A::typescript(), B::typescript()])
1337 }
1338}
1339
1340impl<A: TS, B: TS, C: TS> TS for (A, B, C) {
1341 fn typescript() -> TypeDef {
1342 TypeDef::Tuple(vec![A::typescript(), B::typescript(), C::typescript()])
1343 }
1344}
1345
1346impl<A: TS, B: TS, C: TS, D: TS> TS for (A, B, C, D) {
1347 fn typescript() -> TypeDef {
1348 TypeDef::Tuple(vec![
1349 A::typescript(),
1350 B::typescript(),
1351 C::typescript(),
1352 D::typescript(),
1353 ])
1354 }
1355}
1356
1357impl<A: TS, B: TS, C: TS, D: TS, E: TS> TS
1358 for (A, B, C, D, E)
1359{
1360 fn typescript() -> TypeDef {
1361 TypeDef::Tuple(vec![
1362 A::typescript(),
1363 B::typescript(),
1364 C::typescript(),
1365 D::typescript(),
1366 E::typescript(),
1367 ])
1368 }
1369}
1370
1371impl<A: TS, B: TS, C: TS, D: TS, E: TS, F: TS>
1372 TS for (A, B, C, D, E, F)
1373{
1374 fn typescript() -> TypeDef {
1375 TypeDef::Tuple(vec![
1376 A::typescript(),
1377 B::typescript(),
1378 C::typescript(),
1379 D::typescript(),
1380 E::typescript(),
1381 F::typescript(),
1382 ])
1383 }
1384}
1385
1386#[cfg(test)]
1391mod tests {
1392 use super::*;
1393
1394 #[test]
1399 fn test_typedef_primitive_render() {
1400 assert_eq!(TypeDef::Primitive(Primitive::String).render(), "string");
1401 assert_eq!(TypeDef::Primitive(Primitive::Number).render(), "number");
1402 assert_eq!(TypeDef::Primitive(Primitive::Boolean).render(), "boolean");
1403 assert_eq!(TypeDef::Primitive(Primitive::Null).render(), "null");
1404 assert_eq!(TypeDef::Primitive(Primitive::Undefined).render(), "undefined");
1405 assert_eq!(TypeDef::Primitive(Primitive::Void).render(), "void");
1406 assert_eq!(TypeDef::Primitive(Primitive::Never).render(), "never");
1407 assert_eq!(TypeDef::Primitive(Primitive::Any).render(), "any");
1408 assert_eq!(TypeDef::Primitive(Primitive::Unknown).render(), "unknown");
1409 assert_eq!(TypeDef::Primitive(Primitive::BigInt).render(), "bigint");
1410 }
1411
1412 #[test]
1413 fn test_typedef_array_render() {
1414 let arr = TypeDef::Array(Box::new(TypeDef::Primitive(Primitive::String)));
1415 assert_eq!(arr.render(), "string[]");
1416
1417 let union_arr = TypeDef::Array(Box::new(TypeDef::Union(vec![
1419 TypeDef::Primitive(Primitive::String),
1420 TypeDef::Primitive(Primitive::Number),
1421 ])));
1422 assert_eq!(union_arr.render(), "(string | number)[]");
1423 }
1424
1425 #[test]
1426 fn test_typedef_tuple_render() {
1427 let tuple = TypeDef::Tuple(vec![
1428 TypeDef::Primitive(Primitive::String),
1429 TypeDef::Primitive(Primitive::Number),
1430 ]);
1431 assert_eq!(tuple.render(), "[string, number]");
1432 }
1433
1434 #[test]
1435 fn test_typedef_object_render() {
1436 let obj = TypeDef::Object(vec![
1437 Field::new("name", TypeDef::Primitive(Primitive::String)),
1438 Field::optional("age", TypeDef::Primitive(Primitive::Number)),
1439 ]);
1440 assert_eq!(obj.render(), "{ name: string; age?: number }");
1441
1442 let empty_obj = TypeDef::Object(vec![]);
1443 assert_eq!(empty_obj.render(), "{}");
1444 }
1445
1446 #[test]
1447 fn test_typedef_object_readonly_field() {
1448 let obj = TypeDef::Object(vec![
1449 Field::new("id", TypeDef::Primitive(Primitive::String)).readonly(),
1450 ]);
1451 assert_eq!(obj.render(), "{ readonly id: string }");
1452 }
1453
1454 #[test]
1455 fn test_typedef_union_render() {
1456 let union = TypeDef::Union(vec![
1457 TypeDef::Primitive(Primitive::String),
1458 TypeDef::Primitive(Primitive::Number),
1459 TypeDef::Primitive(Primitive::Null),
1460 ]);
1461 assert_eq!(union.render(), "string | number | null");
1462 }
1463
1464 #[test]
1465 fn test_typedef_intersection_render() {
1466 let intersection = TypeDef::Intersection(vec![
1467 TypeDef::Ref("Base".into()),
1468 TypeDef::Object(vec![
1469 Field::new("extra", TypeDef::Primitive(Primitive::String)),
1470 ]),
1471 ]);
1472 assert_eq!(intersection.render(), "Base & { extra: string }");
1473 }
1474
1475 #[test]
1476 fn test_typedef_record_render() {
1477 let record = TypeDef::Record {
1478 key: Box::new(TypeDef::Primitive(Primitive::String)),
1479 value: Box::new(TypeDef::Primitive(Primitive::Number)),
1480 };
1481 assert_eq!(record.render(), "Record<string, number>");
1482 }
1483
1484 #[test]
1485 fn test_typedef_named_render() {
1486 let named = TypeDef::Named {
1487 namespace: vec![],
1488 name: "UserId".into(),
1489 def: Box::new(TypeDef::Primitive(Primitive::String)),
1490 module: None,
1491 wrapper: None,
1492 };
1493 assert_eq!(named.render(), "UserId");
1495 assert_eq!(named.render_declaration(), "type UserId = string;");
1497 }
1498
1499 #[test]
1500 fn test_typedef_namespaced_render() {
1501 let namespaced = TypeDef::Named {
1503 namespace: vec!["VM".into(), "Git".into()],
1504 name: "State".into(),
1505 def: Box::new(TypeDef::Union(vec![
1506 TypeDef::Literal(Literal::String("clean".into())),
1507 TypeDef::Literal(Literal::String("dirty".into())),
1508 TypeDef::Literal(Literal::String("unknown".into())),
1509 ])),
1510 module: None,
1511 wrapper: None,
1512 };
1513 assert_eq!(namespaced.render(), "VM.Git.State");
1515 let decl = namespaced.render_declaration();
1517 assert!(decl.contains("namespace VM {"));
1518 assert!(decl.contains("namespace Git {"));
1519 assert!(decl.contains(r#"type State = "clean" | "dirty" | "unknown";"#));
1520 }
1521
1522 #[test]
1523 fn test_typedef_single_namespace_render() {
1524 let namespaced = TypeDef::Named {
1525 namespace: vec!["API".into()],
1526 name: "Response".into(),
1527 def: Box::new(TypeDef::Primitive(Primitive::String)),
1528 module: None,
1529 wrapper: None,
1530 };
1531 assert_eq!(namespaced.render(), "API.Response");
1532 let decl = namespaced.render_declaration();
1533 assert!(decl.contains("namespace API {"));
1534 assert!(decl.contains("type Response = string;"));
1535 }
1536
1537 #[test]
1538 fn test_typedef_wrapper_simple() {
1539 let wrapped = TypeDef::Named {
1541 namespace: vec![],
1542 name: "User".into(),
1543 def: Box::new(TypeDef::Object(vec![
1544 Field::new("id", TypeDef::Primitive(Primitive::String)),
1545 Field::new("name", TypeDef::Primitive(Primitive::String)),
1546 ])),
1547 module: None,
1548 wrapper: Some("Prettify".to_string()),
1549 };
1550 let decl = wrapped.render_declaration();
1551 assert_eq!(decl, "type User = Prettify<{ id: string; name: string }>;");
1552 }
1553
1554 #[test]
1555 fn test_typedef_wrapper_chained() {
1556 let wrapped = TypeDef::Named {
1558 namespace: vec![],
1559 name: "Config".into(),
1560 def: Box::new(TypeDef::Object(vec![
1561 Field::new("theme", TypeDef::Primitive(Primitive::String)),
1562 ])),
1563 module: None,
1564 wrapper: Some("Prettify<Required<".to_string()),
1565 };
1566 let decl = wrapped.render_declaration();
1567 assert_eq!(decl, "type Config = Prettify<Required<{ theme: string }>>;");
1568 }
1569
1570 #[test]
1571 fn test_typedef_wrapper_with_namespace() {
1572 let wrapped = TypeDef::Named {
1574 namespace: vec!["API".into()],
1575 name: "Response".into(),
1576 def: Box::new(TypeDef::Primitive(Primitive::String)),
1577 module: None,
1578 wrapper: Some("Prettify".to_string()),
1579 };
1580 let decl = wrapped.render_declaration();
1581 assert!(decl.contains("namespace API {"));
1582 assert!(decl.contains("type Response = Prettify<string>;"));
1583 }
1584
1585 #[test]
1586 fn test_apply_wrapper_simple() {
1587 let result = TypeDef::apply_wrapper("Prettify", "{ id: string }");
1588 assert_eq!(result, "Prettify<{ id: string }>");
1589 }
1590
1591 #[test]
1592 fn test_apply_wrapper_chained() {
1593 let result = TypeDef::apply_wrapper("Prettify<Required<", "{ id: string }");
1594 assert_eq!(result, "Prettify<Required<{ id: string }>>");
1595 }
1596
1597 #[test]
1598 fn test_apply_wrapper_triple_chained() {
1599 let result = TypeDef::apply_wrapper("A<B<C<", "T");
1600 assert_eq!(result, "A<B<C<T>>>");
1601 }
1602
1603 #[test]
1604 fn test_typedef_ref_render() {
1605 let ref_type = TypeDef::Ref("User".into());
1606 assert_eq!(ref_type.render(), "User");
1607 }
1608
1609 #[test]
1610 fn test_typedef_literal_render() {
1611 assert_eq!(TypeDef::Literal(Literal::String("foo".into())).render(), "\"foo\"");
1612 assert_eq!(TypeDef::Literal(Literal::Number(42.0)).render(), "42");
1613 assert_eq!(TypeDef::Literal(Literal::Number(3.14)).render(), "3.14");
1614 assert_eq!(TypeDef::Literal(Literal::Boolean(true)).render(), "true");
1615 assert_eq!(TypeDef::Literal(Literal::Boolean(false)).render(), "false");
1616 }
1617
1618 #[test]
1619 fn test_typedef_literal_escaping() {
1620 let lit = Literal::String("say \"hello\"".into());
1621 assert_eq!(lit.render(), "\"say \\\"hello\\\"\"");
1622 }
1623
1624 #[test]
1625 fn test_typedef_function_render() {
1626 let func = TypeDef::Function {
1627 params: vec![
1628 Field::new("name", TypeDef::Primitive(Primitive::String)),
1629 Field::new("age", TypeDef::Primitive(Primitive::Number)),
1630 ],
1631 return_type: Box::new(TypeDef::Primitive(Primitive::Void)),
1632 };
1633 assert_eq!(func.render(), "(name: string, age: number) => void");
1634 }
1635
1636 #[test]
1637 fn test_typedef_generic_render() {
1638 let generic = TypeDef::Generic {
1639 base: "Promise".into(),
1640 args: vec![TypeDef::Primitive(Primitive::String)],
1641 };
1642 assert_eq!(generic.render(), "Promise<string>");
1643
1644 let multi_generic = TypeDef::Generic {
1645 base: "Map".into(),
1646 args: vec![
1647 TypeDef::Primitive(Primitive::String),
1648 TypeDef::Primitive(Primitive::Number),
1649 ],
1650 };
1651 assert_eq!(multi_generic.render(), "Map<string, number>");
1652 }
1653
1654 #[test]
1655 fn test_typedef_template_literal_render() {
1656 let vm_id = TypeDef::TemplateLiteral {
1658 strings: vec!["vm-".into(), "".into()],
1659 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1660 };
1661 assert_eq!(vm_id.render(), "`vm-${string}`");
1662
1663 let semver = TypeDef::TemplateLiteral {
1665 strings: vec!["v".into(), ".".into(), ".".into(), "".into()],
1666 types: vec![
1667 Box::new(TypeDef::Primitive(Primitive::Number)),
1668 Box::new(TypeDef::Primitive(Primitive::Number)),
1669 Box::new(TypeDef::Primitive(Primitive::Number)),
1670 ],
1671 };
1672 assert_eq!(semver.render(), "`v${number}.${number}.${number}`");
1673
1674 let api_route = TypeDef::TemplateLiteral {
1676 strings: vec!["/api/".into(), "".into()],
1677 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1678 };
1679 assert_eq!(api_route.render(), "`/api/${string}`");
1680
1681 let complex_id = TypeDef::TemplateLiteral {
1683 strings: vec!["user-".into(), "-id".into()],
1684 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1685 };
1686 assert_eq!(complex_id.render(), "`user-${string}-id`");
1687
1688 let static_str = TypeDef::TemplateLiteral {
1690 strings: vec!["static-value".into()],
1691 types: vec![],
1692 };
1693 assert_eq!(static_str.render(), "`static-value`");
1694 }
1695
1696 #[test]
1697 fn test_typedef_template_literal_escaping() {
1698 let with_backtick = TypeDef::TemplateLiteral {
1700 strings: vec!["code: `".into(), "`".into()],
1701 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1702 };
1703 assert_eq!(with_backtick.render(), "`code: \\`${string}\\``");
1704
1705 let with_backslash = TypeDef::TemplateLiteral {
1707 strings: vec!["path\\to\\".into(), "".into()],
1708 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1709 };
1710 assert_eq!(with_backslash.render(), "`path\\\\to\\\\${string}`");
1711 }
1712
1713 #[test]
1714 fn test_typedef_template_literal_with_refs() {
1715 let typed_id = TypeDef::TemplateLiteral {
1717 strings: vec!["vm-".into(), "".into()],
1718 types: vec![Box::new(TypeDef::Ref("VmIdSuffix".into()))],
1719 };
1720 assert_eq!(typed_id.render(), "`vm-${VmIdSuffix}`");
1721 }
1722
1723 #[test]
1724 fn test_typedef_indexed_access_render() {
1725 let indexed = TypeDef::IndexedAccess {
1727 base: "Profile".into(),
1728 key: "login".into(),
1729 };
1730 assert_eq!(indexed.render(), "Profile[\"login\"]");
1731
1732 let nested = TypeDef::IndexedAccess {
1734 base: "User.Settings".into(),
1735 key: "theme".into(),
1736 };
1737 assert_eq!(nested.render(), "User.Settings[\"theme\"]");
1738 }
1739
1740 #[test]
1741 fn test_typedef_indexed_access_escaping() {
1742 let with_quotes = TypeDef::IndexedAccess {
1744 base: "Config".into(),
1745 key: "key\"with\"quotes".into(),
1746 };
1747 assert_eq!(with_quotes.render(), "Config[\"key\\\"with\\\"quotes\"]");
1748
1749 let with_backslash = TypeDef::IndexedAccess {
1751 base: "Config".into(),
1752 key: "path\\to\\key".into(),
1753 };
1754 assert_eq!(with_backslash.render(), "Config[\"path\\\\to\\\\key\"]");
1755 }
1756
1757 #[test]
1758 fn test_typescript_trait_primitives() {
1759 assert_eq!(<()>::typescript().render(), "void");
1760 assert_eq!(bool::typescript().render(), "boolean");
1761 assert_eq!(String::typescript().render(), "string");
1762 assert_eq!(i32::typescript().render(), "number");
1763 assert_eq!(f64::typescript().render(), "number");
1764 assert_eq!(i128::typescript().render(), "bigint");
1765 assert_eq!(u128::typescript().render(), "bigint");
1766 }
1767
1768 #[test]
1769 fn test_typescript_trait_option() {
1770 let opt = <Option<String>>::typescript();
1771 assert_eq!(opt.render(), "string | null");
1772 }
1773
1774 #[test]
1775 fn test_typescript_trait_vec() {
1776 let vec_type = <Vec<i32>>::typescript();
1777 assert_eq!(vec_type.render(), "number[]");
1778 }
1779
1780 #[test]
1781 fn test_typescript_trait_hashmap() {
1782 let map = <HashMap<String, i32>>::typescript();
1783 assert_eq!(map.render(), "Record<string, number>");
1784 }
1785
1786 #[test]
1787 fn test_typescript_trait_result() {
1788 let result = <Result<String, String>>::typescript();
1789 assert_eq!(
1790 result.render(),
1791 "{ ok: true; value: string } | { ok: false; error: string }"
1792 );
1793 }
1794
1795 #[test]
1796 fn test_typescript_trait_tuples() {
1797 assert_eq!(<(String,)>::typescript().render(), "[string]");
1798 assert_eq!(<(String, i32)>::typescript().render(), "[string, number]");
1799 assert_eq!(
1800 <(String, i32, bool)>::typescript().render(),
1801 "[string, number, boolean]"
1802 );
1803 }
1804
1805 #[test]
1806 fn test_typescript_trait_box() {
1807 assert_eq!(<Box<String>>::typescript().render(), "string");
1809 }
1810
1811 #[test]
1812 fn test_typedef_equality() {
1813 let a = TypeDef::Primitive(Primitive::String);
1814 let b = TypeDef::Primitive(Primitive::String);
1815 let c = TypeDef::Primitive(Primitive::Number);
1816 assert_eq!(a, b);
1817 assert_ne!(a, c);
1818 }
1819
1820 #[test]
1821 fn test_field_builder() {
1822 let field = Field::new("name", TypeDef::Primitive(Primitive::String));
1823 assert!(!field.optional);
1824 assert!(!field.readonly);
1825
1826 let opt_field = Field::optional("name", TypeDef::Primitive(Primitive::String));
1827 assert!(opt_field.optional);
1828
1829 let readonly_field = Field::new("id", TypeDef::Primitive(Primitive::String)).readonly();
1830 assert!(readonly_field.readonly);
1831 }
1832
1833 #[test]
1838 fn test_registry_new() {
1839 let registry = TypeRegistry::new();
1840 assert!(registry.is_empty());
1841 assert_eq!(registry.len(), 0);
1842 }
1843
1844 #[test]
1845 fn test_registry_add_typedef() {
1846 let mut registry = TypeRegistry::new();
1847
1848 let user_type = TypeDef::Named {
1849 namespace: vec![],
1850 name: "User".to_string(),
1851 def: Box::new(TypeDef::Object(vec![
1852 Field::new("id", TypeDef::Primitive(Primitive::String)),
1853 Field::new("name", TypeDef::Primitive(Primitive::String)),
1854 ])),
1855 module: None,
1856 wrapper: None,
1857 };
1858
1859 registry.add_typedef(user_type);
1860
1861 assert_eq!(registry.len(), 1);
1862 assert!(registry.get("User").is_some());
1863 }
1864
1865 #[test]
1866 fn test_registry_deduplication() {
1867 let mut registry = TypeRegistry::new();
1868
1869 let user_type = TypeDef::Named {
1870 namespace: vec![],
1871 name: "User".to_string(),
1872 def: Box::new(TypeDef::Primitive(Primitive::String)),
1873 module: None,
1874 wrapper: None,
1875 };
1876
1877 registry.add_typedef(user_type.clone());
1878 registry.add_typedef(user_type);
1879
1880 assert_eq!(registry.len(), 1);
1882 }
1883
1884 #[test]
1885 fn test_registry_extracts_nested_types() {
1886 let mut registry = TypeRegistry::new();
1887
1888 let user_id = TypeDef::Named {
1890 namespace: vec![],
1891 name: "UserId".to_string(),
1892 def: Box::new(TypeDef::Primitive(Primitive::String)),
1893 module: None,
1894 wrapper: None,
1895 };
1896
1897 let user = TypeDef::Named {
1899 namespace: vec![],
1900 name: "User".to_string(),
1901 def: Box::new(TypeDef::Object(vec![
1902 Field::new("id", TypeDef::Ref("UserId".to_string())),
1903 Field::new("name", TypeDef::Primitive(Primitive::String)),
1904 ])),
1905 module: None,
1906 wrapper: None,
1907 };
1908
1909 let post_type = TypeDef::Named {
1911 namespace: vec![],
1912 name: "Post".to_string(),
1913 def: Box::new(TypeDef::Object(vec![
1914 Field::new("title", TypeDef::Primitive(Primitive::String)),
1915 Field::new("author", user),
1916 ])),
1917 module: None,
1918 wrapper: None,
1919 };
1920
1921 registry.add_typedef(post_type);
1922 registry.add_typedef(user_id);
1923
1924 assert_eq!(registry.len(), 3);
1926 assert!(registry.get("Post").is_some());
1927 assert!(registry.get("User").is_some());
1928 assert!(registry.get("UserId").is_some());
1929 }
1930
1931 #[test]
1932 fn test_registry_render() {
1933 let mut registry = TypeRegistry::new();
1934
1935 let user_type = TypeDef::Named {
1936 namespace: vec![],
1937 name: "User".to_string(),
1938 def: Box::new(TypeDef::Object(vec![
1939 Field::new("id", TypeDef::Primitive(Primitive::String)),
1940 Field::new("name", TypeDef::Primitive(Primitive::String)),
1941 ])),
1942 module: None,
1943 wrapper: None,
1944 };
1945
1946 registry.add_typedef(user_type);
1947
1948 let output = registry.render();
1949 assert!(output.contains("// Generated by ferrotype"));
1950 assert!(output.contains("type User = { id: string; name: string };"));
1951 }
1952
1953 #[test]
1954 fn test_registry_render_exported() {
1955 let mut registry = TypeRegistry::new();
1956
1957 let user_type = TypeDef::Named {
1958 namespace: vec![],
1959 name: "User".to_string(),
1960 def: Box::new(TypeDef::Primitive(Primitive::String)),
1961 module: None,
1962 wrapper: None,
1963 };
1964
1965 registry.add_typedef(user_type);
1966
1967 let output = registry.render_exported();
1968 assert!(output.contains("export type User = string;"));
1969 }
1970
1971 #[test]
1972 fn test_registry_dependency_order() {
1973 let mut registry = TypeRegistry::new();
1974
1975 let user_id = TypeDef::Named {
1977 namespace: vec![],
1978 name: "UserId".to_string(),
1979 def: Box::new(TypeDef::Primitive(Primitive::String)),
1980 module: None,
1981 wrapper: None,
1982 };
1983
1984 let user = TypeDef::Named {
1986 namespace: vec![],
1987 name: "User".to_string(),
1988 def: Box::new(TypeDef::Object(vec![
1989 Field::new("id", TypeDef::Ref("UserId".to_string())),
1990 Field::new("name", TypeDef::Primitive(Primitive::String)),
1991 ])),
1992 module: None,
1993 wrapper: None,
1994 };
1995
1996 registry.add_typedef(user);
1998 registry.add_typedef(user_id);
1999
2000 let sorted = registry.sorted_types();
2001
2002 let user_id_pos = sorted.iter().position(|&n| n == "UserId").unwrap();
2004 let user_pos = sorted.iter().position(|&n| n == "User").unwrap();
2005 assert!(user_id_pos < user_pos, "UserId should come before User");
2006 }
2007
2008 #[test]
2009 fn test_registry_clear() {
2010 let mut registry = TypeRegistry::new();
2011
2012 let user_type = TypeDef::Named {
2013 namespace: vec![],
2014 name: "User".to_string(),
2015 def: Box::new(TypeDef::Primitive(Primitive::String)),
2016 module: None,
2017 wrapper: None,
2018 };
2019
2020 registry.add_typedef(user_type);
2021 assert_eq!(registry.len(), 1);
2022
2023 registry.clear();
2024 assert!(registry.is_empty());
2025 }
2026
2027 #[test]
2028 fn test_registry_type_names() {
2029 let mut registry = TypeRegistry::new();
2030
2031 registry.add_typedef(TypeDef::Named {
2032 namespace: vec![],
2033 name: "Alpha".to_string(),
2034 def: Box::new(TypeDef::Primitive(Primitive::String)),
2035 module: None,
2036 wrapper: None,
2037 });
2038 registry.add_typedef(TypeDef::Named {
2039 namespace: vec![],
2040 name: "Beta".to_string(),
2041 def: Box::new(TypeDef::Primitive(Primitive::Number)),
2042 module: None,
2043 wrapper: None,
2044 });
2045
2046 let names: Vec<_> = registry.type_names().collect();
2047 assert_eq!(names.len(), 2);
2048 assert!(names.contains(&"Alpha"));
2049 assert!(names.contains(&"Beta"));
2050 }
2051
2052 #[test]
2053 fn test_registry_complex_dependencies() {
2054 let mut registry = TypeRegistry::new();
2055
2056 let c = TypeDef::Named {
2058 namespace: vec![],
2059 name: "C".to_string(),
2060 def: Box::new(TypeDef::Primitive(Primitive::String)),
2061 module: None,
2062 wrapper: None,
2063 };
2064
2065 let b = TypeDef::Named {
2066 namespace: vec![],
2067 name: "B".to_string(),
2068 def: Box::new(TypeDef::Object(vec![
2069 Field::new("c", TypeDef::Ref("C".to_string())),
2070 ])),
2071 module: None,
2072 wrapper: None,
2073 };
2074
2075 let a = TypeDef::Named {
2076 namespace: vec![],
2077 name: "A".to_string(),
2078 def: Box::new(TypeDef::Object(vec![
2079 Field::new("b", TypeDef::Ref("B".to_string())),
2080 ])),
2081 module: None,
2082 wrapper: None,
2083 };
2084
2085 registry.add_typedef(a);
2087 registry.add_typedef(b);
2088 registry.add_typedef(c);
2089
2090 let sorted = registry.sorted_types();
2091
2092 let c_pos = sorted.iter().position(|&n| n == "C").unwrap();
2093 let b_pos = sorted.iter().position(|&n| n == "B").unwrap();
2094 let a_pos = sorted.iter().position(|&n| n == "A").unwrap();
2095
2096 assert!(c_pos < b_pos, "C should come before B");
2097 assert!(b_pos < a_pos, "B should come before A");
2098 }
2099
2100 #[test]
2101 fn test_registry_indexed_access_dependency() {
2102 let mut registry = TypeRegistry::new();
2103
2104 let profile = TypeDef::Named {
2106 namespace: vec![],
2107 name: "Profile".to_string(),
2108 def: Box::new(TypeDef::Object(vec![
2109 Field::new("login", TypeDef::Primitive(Primitive::String)),
2110 Field::new("email", TypeDef::Primitive(Primitive::String)),
2111 ])),
2112 module: None,
2113 wrapper: None,
2114 };
2115
2116 let user_login = TypeDef::Named {
2118 namespace: vec![],
2119 name: "UserLogin".to_string(),
2120 def: Box::new(TypeDef::IndexedAccess {
2121 base: "Profile".to_string(),
2122 key: "login".to_string(),
2123 }),
2124 module: None,
2125 wrapper: None,
2126 };
2127
2128 registry.add_typedef(user_login);
2130 registry.add_typedef(profile);
2131
2132 let sorted = registry.sorted_types();
2133
2134 let profile_pos = sorted.iter().position(|&n| n == "Profile").unwrap();
2135 let user_login_pos = sorted.iter().position(|&n| n == "UserLogin").unwrap();
2136
2137 assert!(profile_pos < user_login_pos, "Profile should come before UserLogin");
2138 }
2139
2140 #[derive(Debug)]
2146 struct AutoRegTestUser {
2147 name: String,
2148 age: u32,
2149 }
2150
2151 impl TS for AutoRegTestUser {
2152 fn typescript() -> TypeDef {
2153 TypeDef::Named {
2154 namespace: vec![],
2155 name: "AutoRegTestUser".to_string(),
2156 def: Box::new(TypeDef::Object(vec![
2157 Field::new("name", TypeDef::Primitive(Primitive::String)),
2158 Field::new("age", TypeDef::Primitive(Primitive::Number)),
2159 ])),
2160 module: None,
2161 wrapper: None,
2162 }
2163 }
2164 }
2165
2166 #[linkme::distributed_slice(TYPESCRIPT_TYPES)]
2168 static __TEST_REGISTER_USER: fn() -> TypeDef = || AutoRegTestUser::typescript();
2169
2170 #[test]
2171 fn test_from_distributed_collects_types() {
2172 let registry = TypeRegistry::from_distributed();
2173
2174 assert!(registry.get("AutoRegTestUser").is_some(),
2176 "Registry should contain AutoRegTestUser");
2177 }
2178
2179 #[test]
2180 fn test_collect_all_adds_to_existing() {
2181 let mut registry = TypeRegistry::new();
2182
2183 let manual_type = TypeDef::Named {
2185 namespace: vec![],
2186 name: "ManualType".to_string(),
2187 def: Box::new(TypeDef::Primitive(Primitive::String)),
2188 module: None,
2189 wrapper: None,
2190 };
2191 registry.add_typedef(manual_type);
2192
2193 registry.collect_all();
2195
2196 assert!(registry.get("ManualType").is_some(),
2198 "Registry should contain ManualType");
2199 assert!(registry.get("AutoRegTestUser").is_some(),
2200 "Registry should contain AutoRegTestUser from distributed slice");
2201 }
2202
2203 #[test]
2204 fn test_distributed_slice_is_accessible() {
2205 let count = TYPESCRIPT_TYPES.len();
2207 assert!(count >= 1, "TYPESCRIPT_TYPES should have at least 1 entry");
2209 }
2210
2211 #[test]
2216 fn test_type_param_simple() {
2217 let param = TypeParam::new("T");
2218 assert_eq!(param.render(), "T");
2219 }
2220
2221 #[test]
2222 fn test_type_param_with_constraint() {
2223 let param = TypeParam::new("T").with_constraint(TypeDef::Primitive(Primitive::String));
2224 assert_eq!(param.render(), "T extends string");
2225 }
2226
2227 #[test]
2228 fn test_type_param_with_object_constraint() {
2229 let param = TypeParam::new("T").with_constraint(TypeDef::Object(vec![
2230 Field::new("type", TypeDef::Primitive(Primitive::String)),
2231 ]));
2232 assert_eq!(param.render(), "T extends { type: string }");
2233 }
2234
2235 #[test]
2236 fn test_type_param_with_default() {
2237 let param = TypeParam::new("T").with_default(TypeDef::Primitive(Primitive::Never));
2238 assert_eq!(param.render(), "T = never");
2239 }
2240
2241 #[test]
2242 fn test_type_param_with_constraint_and_default() {
2243 let param = TypeParam::new("T")
2244 .with_constraint(TypeDef::Primitive(Primitive::String))
2245 .with_default(TypeDef::Literal(Literal::String("default".into())));
2246 assert_eq!(param.render(), "T extends string = \"default\"");
2247 }
2248
2249 #[test]
2250 fn test_type_param_ref_render() {
2251 let param_ref = TypeDef::TypeParamRef("T".into());
2252 assert_eq!(param_ref.render(), "T");
2253 }
2254
2255 #[test]
2256 fn test_generic_def_simple() {
2257 let generic_def = TypeDef::GenericDef {
2259 name: "Identity".into(),
2260 type_params: vec![TypeParam::new("T")],
2261 def: Box::new(TypeDef::TypeParamRef("T".into())),
2262 };
2263 assert_eq!(generic_def.render(), "Identity");
2264 assert_eq!(generic_def.render_declaration(), "type Identity<T> = T;");
2265 }
2266
2267 #[test]
2268 fn test_generic_def_with_constraint() {
2269 let generic_def = TypeDef::GenericDef {
2271 name: "Wrapper".into(),
2272 type_params: vec![TypeParam::new("T").with_constraint(TypeDef::Object(vec![
2273 Field::new("type", TypeDef::Primitive(Primitive::String)),
2274 ]))],
2275 def: Box::new(TypeDef::Object(vec![
2276 Field::new("data", TypeDef::TypeParamRef("T".into())),
2277 ])),
2278 };
2279 assert_eq!(
2280 generic_def.render_declaration(),
2281 "type Wrapper<T extends { type: string }> = { data: T };"
2282 );
2283 }
2284
2285 #[test]
2286 fn test_generic_def_core_pattern() {
2287 let core_def = TypeDef::GenericDef {
2290 name: "Core".into(),
2291 type_params: vec![TypeParam::new("T").with_constraint(TypeDef::Object(vec![
2292 Field::new("type", TypeDef::Primitive(Primitive::String)),
2293 ]))],
2294 def: Box::new(TypeDef::Object(vec![
2295 Field::new("id", TypeDef::Primitive(Primitive::String)),
2296 Field::new("timestamp", TypeDef::Primitive(Primitive::Number)),
2297 Field::new("data", TypeDef::TypeParamRef("T".into())),
2298 ])),
2299 };
2300
2301 assert_eq!(
2302 core_def.render_declaration(),
2303 "type Core<T extends { type: string }> = { id: string; timestamp: number; data: T };"
2304 );
2305 }
2306
2307 #[test]
2308 fn test_generic_def_multiple_params() {
2309 let pair_def = TypeDef::GenericDef {
2311 name: "Pair".into(),
2312 type_params: vec![TypeParam::new("K"), TypeParam::new("V")],
2313 def: Box::new(TypeDef::Object(vec![
2314 Field::new("key", TypeDef::TypeParamRef("K".into())),
2315 Field::new("value", TypeDef::TypeParamRef("V".into())),
2316 ])),
2317 };
2318 assert_eq!(
2319 pair_def.render_declaration(),
2320 "type Pair<K, V> = { key: K; value: V };"
2321 );
2322 }
2323
2324 #[test]
2325 fn test_generic_def_with_default_params() {
2326 let result_def = TypeDef::GenericDef {
2328 name: "Result".into(),
2329 type_params: vec![
2330 TypeParam::new("T"),
2331 TypeParam::new("E").with_default(TypeDef::Ref("Error".into())),
2332 ],
2333 def: Box::new(TypeDef::Union(vec![
2334 TypeDef::Object(vec![
2335 Field::new("ok", TypeDef::Literal(Literal::Boolean(true))),
2336 Field::new("value", TypeDef::TypeParamRef("T".into())),
2337 ]),
2338 TypeDef::Object(vec![
2339 Field::new("ok", TypeDef::Literal(Literal::Boolean(false))),
2340 Field::new("error", TypeDef::TypeParamRef("E".into())),
2341 ]),
2342 ])),
2343 };
2344 assert_eq!(
2345 result_def.render_declaration(),
2346 "type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };"
2347 );
2348 }
2349
2350 #[test]
2351 fn test_generic_application_with_def() {
2352 let application = TypeDef::Generic {
2354 base: "Core".into(),
2355 args: vec![TypeDef::Object(vec![
2356 Field::new("type", TypeDef::Literal(Literal::String("text".into()))),
2357 Field::new("content", TypeDef::Primitive(Primitive::String)),
2358 ])],
2359 };
2360 assert_eq!(
2361 application.render(),
2362 "Core<{ type: \"text\"; content: string }>"
2363 );
2364 }
2365
2366 #[test]
2367 fn test_registry_with_generic_def() {
2368 let mut registry = TypeRegistry::new();
2369
2370 let core_def = TypeDef::GenericDef {
2372 name: "Core".into(),
2373 type_params: vec![TypeParam::new("T")],
2374 def: Box::new(TypeDef::Object(vec![
2375 Field::new("id", TypeDef::Primitive(Primitive::String)),
2376 Field::new("data", TypeDef::TypeParamRef("T".into())),
2377 ])),
2378 };
2379 registry.add_typedef(core_def);
2380
2381 assert_eq!(registry.len(), 1);
2382 assert!(registry.get("Core").is_some());
2383
2384 let output = registry.render();
2385 assert!(output.contains("type Core<T> = { id: string; data: T };"));
2386 }
2387
2388 #[test]
2389 fn test_registry_exported_with_generic_def() {
2390 let mut registry = TypeRegistry::new();
2391
2392 let core_def = TypeDef::GenericDef {
2393 name: "Core".into(),
2394 type_params: vec![TypeParam::new("T").with_constraint(TypeDef::Object(vec![
2395 Field::new("type", TypeDef::Primitive(Primitive::String)),
2396 ]))],
2397 def: Box::new(TypeDef::Object(vec![
2398 Field::new("data", TypeDef::TypeParamRef("T".into())),
2399 ])),
2400 };
2401 registry.add_typedef(core_def);
2402
2403 let output = registry.render_exported();
2404 assert!(output.contains("export type Core<T extends { type: string }> = { data: T };"));
2405 }
2406
2407 #[test]
2408 fn test_registry_generic_depends_on_constraint() {
2409 let mut registry = TypeRegistry::new();
2410
2411 let discriminant = TypeDef::Named {
2413 namespace: vec![],
2414 name: "Discriminant".into(),
2415 def: Box::new(TypeDef::Object(vec![Field::new(
2416 "type",
2417 TypeDef::Primitive(Primitive::String),
2418 )])),
2419 module: None,
2420 wrapper: None,
2421 };
2422
2423 let core_def = TypeDef::GenericDef {
2425 name: "Core".into(),
2426 type_params: vec![TypeParam::new("T").with_constraint(TypeDef::Ref("Discriminant".into()))],
2427 def: Box::new(TypeDef::Object(vec![
2428 Field::new("data", TypeDef::TypeParamRef("T".into())),
2429 ])),
2430 };
2431
2432 registry.add_typedef(core_def);
2433 registry.add_typedef(discriminant);
2434
2435 let sorted = registry.sorted_types();
2436
2437 let discrim_pos = sorted.iter().position(|&n| n == "Discriminant").unwrap();
2439 let core_pos = sorted.iter().position(|&n| n == "Core").unwrap();
2440 assert!(discrim_pos < core_pos, "Discriminant should come before Core");
2441 }
2442
2443 #[test]
2444 fn test_full_core_pattern_example() {
2445 let mut registry = TypeRegistry::new();
2447
2448 let core_def = TypeDef::GenericDef {
2450 name: "Core".into(),
2451 type_params: vec![TypeParam::new("T").with_constraint(TypeDef::Object(vec![
2452 Field::new("type", TypeDef::Primitive(Primitive::String)),
2453 ]))],
2454 def: Box::new(TypeDef::Object(vec![
2455 Field::new("id", TypeDef::Primitive(Primitive::String)),
2456 Field::new("timestamp", TypeDef::Primitive(Primitive::Number)),
2457 Field::new("data", TypeDef::TypeParamRef("T".into())),
2458 ])),
2459 };
2460
2461 let text_data = TypeDef::Named {
2463 namespace: vec![],
2464 name: "TextData".into(),
2465 def: Box::new(TypeDef::Object(vec![
2466 Field::new("type", TypeDef::Literal(Literal::String("text".into()))),
2467 Field::new("content", TypeDef::Primitive(Primitive::String)),
2468 ])),
2469 module: None,
2470 wrapper: None,
2471 };
2472
2473 let image_data = TypeDef::Named {
2474 namespace: vec![],
2475 name: "ImageData".into(),
2476 def: Box::new(TypeDef::Object(vec![
2477 Field::new("type", TypeDef::Literal(Literal::String("image".into()))),
2478 Field::new("url", TypeDef::Primitive(Primitive::String)),
2479 ])),
2480 module: None,
2481 wrapper: None,
2482 };
2483
2484 let text_message = TypeDef::Named {
2486 namespace: vec![],
2487 name: "TextMessage".into(),
2488 def: Box::new(TypeDef::Generic {
2489 base: "Core".into(),
2490 args: vec![TypeDef::Ref("TextData".into())],
2491 }),
2492 module: None,
2493 wrapper: None,
2494 };
2495
2496 let image_message = TypeDef::Named {
2497 namespace: vec![],
2498 name: "ImageMessage".into(),
2499 def: Box::new(TypeDef::Generic {
2500 base: "Core".into(),
2501 args: vec![TypeDef::Ref("ImageData".into())],
2502 }),
2503 module: None,
2504 wrapper: None,
2505 };
2506
2507 let message = TypeDef::Named {
2509 namespace: vec![],
2510 name: "Message".into(),
2511 def: Box::new(TypeDef::Union(vec![
2512 TypeDef::Ref("TextMessage".into()),
2513 TypeDef::Ref("ImageMessage".into()),
2514 ])),
2515 module: None,
2516 wrapper: None,
2517 };
2518
2519 registry.add_typedef(core_def);
2520 registry.add_typedef(text_data);
2521 registry.add_typedef(image_data);
2522 registry.add_typedef(text_message);
2523 registry.add_typedef(image_message);
2524 registry.add_typedef(message);
2525
2526 let output = registry.render_exported();
2527
2528 assert!(output.contains("export type Core<T extends { type: string }>"));
2530 assert!(output.contains("export type TextData ="));
2531 assert!(output.contains("export type ImageData ="));
2532 assert!(output.contains("export type TextMessage = Core<TextData>"));
2533 assert!(output.contains("export type ImageMessage = Core<ImageData>"));
2534 assert!(output.contains("export type Message = TextMessage | ImageMessage"));
2535 }
2536}