1pub use ferro_type_derive::TypeScript;
17pub use linkme;
18
19use std::collections::HashMap;
20
21#[linkme::distributed_slice]
42pub static TYPESCRIPT_TYPES: [fn() -> TypeDef];
43
44pub trait TypeScript {
80 fn typescript() -> TypeDef;
82}
83
84#[derive(Debug, Clone, PartialEq)]
97pub enum TypeDef {
98 Primitive(Primitive),
100
101 Array(Box<TypeDef>),
103
104 Tuple(Vec<TypeDef>),
106
107 Object(Vec<Field>),
109
110 Union(Vec<TypeDef>),
112
113 Intersection(Vec<TypeDef>),
115
116 Record {
118 key: Box<TypeDef>,
119 value: Box<TypeDef>,
120 },
121
122 Named {
129 namespace: Vec<String>,
131 name: String,
133 def: Box<TypeDef>,
135 module: Option<String>,
137 },
138
139 Ref(String),
142
143 Literal(Literal),
145
146 Function {
148 params: Vec<Field>,
149 return_type: Box<TypeDef>,
150 },
151
152 Generic {
154 base: String,
155 args: Vec<TypeDef>,
156 },
157
158 TemplateLiteral {
174 strings: Vec<String>,
178 types: Vec<Box<TypeDef>>,
180 },
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
185pub enum Primitive {
186 String,
188 Number,
190 Boolean,
192 Null,
194 Undefined,
196 Void,
198 Never,
200 Any,
202 Unknown,
204 BigInt,
206}
207
208#[derive(Debug, Clone, PartialEq)]
210pub struct Field {
211 pub name: String,
213 pub ty: TypeDef,
215 pub optional: bool,
217 pub readonly: bool,
219}
220
221impl Field {
222 pub fn new(name: impl Into<String>, ty: TypeDef) -> Self {
224 Self {
225 name: name.into(),
226 ty,
227 optional: false,
228 readonly: false,
229 }
230 }
231
232 pub fn optional(name: impl Into<String>, ty: TypeDef) -> Self {
234 Self {
235 name: name.into(),
236 ty,
237 optional: true,
238 readonly: false,
239 }
240 }
241
242 pub fn readonly(mut self) -> Self {
244 self.readonly = true;
245 self
246 }
247}
248
249#[derive(Debug, Clone, PartialEq)]
251pub enum Literal {
252 String(String),
254 Number(f64),
256 Boolean(bool),
258}
259
260impl TypeDef {
261 pub fn render(&self) -> String {
263 match self {
264 TypeDef::Primitive(p) => p.render().to_string(),
265 TypeDef::Array(inner) => {
266 let inner_str = inner.render();
267 if matches!(inner.as_ref(), TypeDef::Union(_)) {
269 format!("({})[]", inner_str)
270 } else {
271 format!("{}[]", inner_str)
272 }
273 }
274 TypeDef::Tuple(items) => {
275 let items_str: Vec<_> = items.iter().map(|t| t.render()).collect();
276 format!("[{}]", items_str.join(", "))
277 }
278 TypeDef::Object(fields) => {
279 if fields.is_empty() {
280 "{}".to_string()
281 } else {
282 let fields_str: Vec<_> = fields
283 .iter()
284 .map(|f| {
285 let readonly = if f.readonly { "readonly " } else { "" };
286 let opt = if f.optional { "?" } else { "" };
287 format!("{}{}{}: {}", readonly, f.name, opt, f.ty.render())
288 })
289 .collect();
290 format!("{{ {} }}", fields_str.join("; "))
291 }
292 }
293 TypeDef::Union(variants) => {
294 let variants_str: Vec<_> = variants.iter().map(|t| t.render()).collect();
295 variants_str.join(" | ")
296 }
297 TypeDef::Intersection(types) => {
298 let types_str: Vec<_> = types.iter().map(|t| t.render()).collect();
299 types_str.join(" & ")
300 }
301 TypeDef::Record { key, value } => {
302 format!("Record<{}, {}>", key.render(), value.render())
303 }
304 TypeDef::Named { namespace, name, .. } => {
305 if namespace.is_empty() {
306 name.clone()
307 } else {
308 format!("{}.{}", namespace.join("."), name)
309 }
310 }
311 TypeDef::Ref(name) => name.clone(),
312 TypeDef::Literal(lit) => lit.render(),
313 TypeDef::Function {
314 params,
315 return_type,
316 } => {
317 let params_str: Vec<_> = params
318 .iter()
319 .map(|p| format!("{}: {}", p.name, p.ty.render()))
320 .collect();
321 format!("({}) => {}", params_str.join(", "), return_type.render())
322 }
323 TypeDef::Generic { base, args } => {
324 let args_str: Vec<_> = args.iter().map(|t| t.render()).collect();
325 format!("{}<{}>", base, args_str.join(", "))
326 }
327 TypeDef::TemplateLiteral { strings, types } => {
328 let mut result = String::from("`");
329 for (i, s) in strings.iter().enumerate() {
330 let escaped = s.replace('\\', "\\\\").replace('`', "\\`");
332 result.push_str(&escaped);
333 if i < types.len() {
334 result.push_str("${");
335 result.push_str(&types[i].render());
336 result.push('}');
337 }
338 }
339 result.push('`');
340 result
341 }
342 }
343 }
344
345 pub fn render_declaration(&self) -> String {
351 match self {
352 TypeDef::Named { namespace, name, def, .. } => {
353 let inner = format!("type {} = {};", name, def.render());
354 Self::wrap_in_namespace(namespace, &inner)
355 }
356 _ => self.render(),
357 }
358 }
359
360 fn wrap_in_namespace(namespace: &[String], inner: &str) -> String {
371 if namespace.is_empty() {
372 return inner.to_string();
373 }
374
375 let mut result = String::new();
376 let indent = " ";
377
378 for (i, ns) in namespace.iter().enumerate() {
380 for _ in 0..i {
381 result.push_str(indent);
382 }
383 result.push_str("namespace ");
384 result.push_str(ns);
385 result.push_str(" {\n");
386 }
387
388 let depth = namespace.len();
390 for _ in 0..depth {
391 result.push_str(indent);
392 }
393 result.push_str(inner);
394 result.push('\n');
395
396 for i in (0..depth).rev() {
398 for _ in 0..i {
399 result.push_str(indent);
400 }
401 result.push('}');
402 if i > 0 {
403 result.push('\n');
404 }
405 }
406
407 result
408 }
409}
410
411impl Primitive {
412 pub fn render(&self) -> &'static str {
414 match self {
415 Primitive::String => "string",
416 Primitive::Number => "number",
417 Primitive::Boolean => "boolean",
418 Primitive::Null => "null",
419 Primitive::Undefined => "undefined",
420 Primitive::Void => "void",
421 Primitive::Never => "never",
422 Primitive::Any => "any",
423 Primitive::Unknown => "unknown",
424 Primitive::BigInt => "bigint",
425 }
426 }
427}
428
429impl Literal {
430 pub fn render(&self) -> String {
432 match self {
433 Literal::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
434 Literal::Number(n) => {
435 if n.fract() == 0.0 {
436 format!("{}", *n as i64)
437 } else {
438 format!("{}", n)
439 }
440 }
441 Literal::Boolean(b) => b.to_string(),
442 }
443 }
444}
445
446pub fn extract_object_fields(typedef: &TypeDef) -> Vec<Field> {
460 match typedef {
461 TypeDef::Object(fields) => fields.clone(),
462 TypeDef::Named { def, .. } => extract_object_fields(def),
463 other => panic!(
464 "#[ts(flatten)] can only be used on fields with object types, got: {:?}",
465 other
466 ),
467 }
468}
469
470pub fn inline_typedef(typedef: TypeDef) -> TypeDef {
476 match typedef {
477 TypeDef::Named { def, .. } => *def,
478 other => other,
479 }
480}
481
482use std::collections::{HashSet, VecDeque};
487
488#[derive(Debug, Default)]
509pub struct TypeRegistry {
510 types: HashMap<String, TypeDef>,
512 registration_order: Vec<String>,
514}
515
516impl TypeRegistry {
517 pub fn new() -> Self {
519 Self::default()
520 }
521
522 pub fn from_distributed() -> Self {
543 let mut registry = Self::new();
544 for type_fn in TYPESCRIPT_TYPES {
545 let typedef = type_fn();
546 registry.add_typedef(typedef);
547 }
548 registry
549 }
550
551 pub fn collect_all(&mut self) {
566 for type_fn in TYPESCRIPT_TYPES {
567 let typedef = type_fn();
568 self.add_typedef(typedef);
569 }
570 }
571
572 pub fn register<T: TypeScript>(&mut self) {
577 let typedef = T::typescript();
578 self.add_typedef(typedef);
579 }
580
581 pub fn add_typedef(&mut self, typedef: TypeDef) {
583 self.extract_named_types(&typedef);
584 }
585
586 fn extract_named_types(&mut self, typedef: &TypeDef) {
588 match typedef {
589 TypeDef::Named { namespace, name, def, .. } => {
590 let qualified_name = if namespace.is_empty() {
592 name.clone()
593 } else {
594 format!("{}.{}", namespace.join("."), name)
595 };
596
597 if !self.types.contains_key(&qualified_name) {
598 self.types.insert(qualified_name.clone(), typedef.clone());
599 self.registration_order.push(qualified_name);
600 self.extract_named_types(def);
602 }
603 }
604 TypeDef::Array(inner) => self.extract_named_types(inner),
605 TypeDef::Tuple(items) => {
606 for item in items {
607 self.extract_named_types(item);
608 }
609 }
610 TypeDef::Object(fields) => {
611 for field in fields {
612 self.extract_named_types(&field.ty);
613 }
614 }
615 TypeDef::Union(items) | TypeDef::Intersection(items) => {
616 for item in items {
617 self.extract_named_types(item);
618 }
619 }
620 TypeDef::Record { key, value } => {
621 self.extract_named_types(key);
622 self.extract_named_types(value);
623 }
624 TypeDef::Function { params, return_type } => {
625 for param in params {
626 self.extract_named_types(¶m.ty);
627 }
628 self.extract_named_types(return_type);
629 }
630 TypeDef::Generic { args, .. } => {
631 for arg in args {
632 self.extract_named_types(arg);
633 }
634 }
635 TypeDef::TemplateLiteral { types, .. } => {
636 for ty in types {
637 self.extract_named_types(ty);
638 }
639 }
640 TypeDef::Primitive(_) | TypeDef::Ref(_) | TypeDef::Literal(_) => {}
642 }
643 }
644
645 pub fn len(&self) -> usize {
647 self.types.len()
648 }
649
650 pub fn is_empty(&self) -> bool {
652 self.types.is_empty()
653 }
654
655 pub fn type_names(&self) -> impl Iterator<Item = &str> {
657 self.types.keys().map(|s| s.as_str())
658 }
659
660 pub fn get(&self, name: &str) -> Option<&TypeDef> {
662 self.types.get(name)
663 }
664
665 fn get_dependencies(&self, typedef: &TypeDef) -> HashSet<String> {
667 let mut deps = HashSet::new();
668 self.collect_dependencies(typedef, &mut deps);
669 deps
670 }
671
672 fn collect_dependencies(&self, typedef: &TypeDef, deps: &mut HashSet<String>) {
674 match typedef {
675 TypeDef::Named { def, .. } => {
676 self.collect_dependencies(def, deps);
678 }
679 TypeDef::Ref(name) => {
680 if self.types.contains_key(name) {
681 deps.insert(name.clone());
682 }
683 }
684 TypeDef::Array(inner) => self.collect_dependencies(inner, deps),
685 TypeDef::Tuple(items) => {
686 for item in items {
687 self.collect_dependencies(item, deps);
688 }
689 }
690 TypeDef::Object(fields) => {
691 for field in fields {
692 self.collect_dependencies(&field.ty, deps);
693 }
694 }
695 TypeDef::Union(variants) => {
696 for v in variants {
697 self.collect_dependencies(v, deps);
698 }
699 }
700 TypeDef::Intersection(types) => {
701 for t in types {
702 self.collect_dependencies(t, deps);
703 }
704 }
705 TypeDef::Record { key, value } => {
706 self.collect_dependencies(key, deps);
707 self.collect_dependencies(value, deps);
708 }
709 TypeDef::Function { params, return_type } => {
710 for param in params {
711 self.collect_dependencies(¶m.ty, deps);
712 }
713 self.collect_dependencies(return_type, deps);
714 }
715 TypeDef::Generic { args, .. } => {
716 for arg in args {
717 self.collect_dependencies(arg, deps);
718 }
719 }
720 TypeDef::TemplateLiteral { types, .. } => {
721 for ty in types {
722 self.collect_dependencies(ty, deps);
723 }
724 }
725 TypeDef::Primitive(_) | TypeDef::Literal(_) => {}
726 }
727 }
728
729 pub fn sorted_types(&self) -> Vec<&str> {
733 let mut in_degree: HashMap<&str, usize> = HashMap::new();
735 let mut dependents: HashMap<&str, Vec<&str>> = HashMap::new();
736
737 for name in self.types.keys() {
739 in_degree.insert(name.as_str(), 0);
740 dependents.insert(name.as_str(), Vec::new());
741 }
742
743 for (name, typedef) in &self.types {
745 let deps = self.get_dependencies(typedef);
746 for dep in deps {
747 if let Some(dep_name) = self.types.get_key_value(&dep) {
748 *in_degree.get_mut(name.as_str()).unwrap() += 1;
749 dependents.get_mut(dep_name.0.as_str()).unwrap().push(name.as_str());
750 }
751 }
752 }
753
754 let mut queue: VecDeque<&str> = VecDeque::new();
756 let mut result: Vec<&str> = Vec::new();
757
758 for (name, °ree) in &in_degree {
760 if degree == 0 {
761 queue.push_back(name);
762 }
763 }
764
765 let mut initial: Vec<_> = queue.drain(..).collect();
767 initial.sort_by_key(|name| {
768 self.registration_order.iter().position(|n| n == *name).unwrap_or(usize::MAX)
769 });
770 queue.extend(initial);
771
772 while let Some(name) = queue.pop_front() {
773 result.push(name);
774
775 let mut deps: Vec<_> = dependents.get(name).map(|v| v.as_slice()).unwrap_or(&[]).to_vec();
777 deps.sort_by_key(|n| {
778 self.registration_order.iter().position(|name| name == *n).unwrap_or(usize::MAX)
779 });
780
781 for dependent in deps {
782 let degree = in_degree.get_mut(dependent).unwrap();
783 *degree -= 1;
784 if *degree == 0 {
785 queue.push_back(dependent);
786 }
787 }
788 }
789
790 if result.len() < self.types.len() {
793 for name in &self.registration_order {
794 if !result.contains(&name.as_str()) {
795 result.push(name.as_str());
796 }
797 }
798 }
799
800 result
801 }
802
803 pub fn render(&self) -> String {
807 let sorted = self.sorted_types();
808 let mut output = String::new();
809
810 output.push_str("// Generated by ferrotype\n");
812 output.push_str("// Do not edit manually\n\n");
813
814 for name in sorted {
815 if let Some(typedef) = self.types.get(name) {
816 output.push_str(&typedef.render_declaration());
817 output.push_str("\n\n");
818 }
819 }
820
821 output.trim_end().to_string() + "\n"
823 }
824
825 pub fn render_exported(&self) -> String {
829 let sorted = self.sorted_types();
830 let mut output = String::new();
831
832 output.push_str("// Generated by ferrotype\n");
834 output.push_str("// Do not edit manually\n\n");
835
836 for name in sorted {
837 if let Some(typedef) = self.types.get(name) {
838 if let TypeDef::Named { namespace, name, def, .. } = typedef {
839 let inner = format!("export type {} = {};", name, def.render());
840 if namespace.is_empty() {
841 output.push_str(&inner);
842 } else {
843 output.push_str(&Self::wrap_in_export_namespace(namespace, &inner));
845 }
846 output.push_str("\n\n");
847 }
848 }
849 }
850
851 output.trim_end().to_string() + "\n"
853 }
854
855 fn wrap_in_export_namespace(namespace: &[String], inner: &str) -> String {
857 if namespace.is_empty() {
858 return inner.to_string();
859 }
860
861 let mut result = String::new();
862 let indent = " ";
863
864 for (i, ns) in namespace.iter().enumerate() {
866 for _ in 0..i {
867 result.push_str(indent);
868 }
869 result.push_str("export namespace ");
870 result.push_str(ns);
871 result.push_str(" {\n");
872 }
873
874 let depth = namespace.len();
876 for _ in 0..depth {
877 result.push_str(indent);
878 }
879 result.push_str(inner);
880 result.push('\n');
881
882 for i in (0..depth).rev() {
884 for _ in 0..i {
885 result.push_str(indent);
886 }
887 result.push('}');
888 if i > 0 {
889 result.push('\n');
890 }
891 }
892
893 result
894 }
895
896 pub fn clear(&mut self) {
898 self.types.clear();
899 self.registration_order.clear();
900 }
901}
902
903impl TypeScript for () {
908 fn typescript() -> TypeDef {
909 TypeDef::Primitive(Primitive::Void)
910 }
911}
912
913impl TypeScript for bool {
914 fn typescript() -> TypeDef {
915 TypeDef::Primitive(Primitive::Boolean)
916 }
917}
918
919impl TypeScript for String {
920 fn typescript() -> TypeDef {
921 TypeDef::Primitive(Primitive::String)
922 }
923}
924
925impl TypeScript for &str {
926 fn typescript() -> TypeDef {
927 TypeDef::Primitive(Primitive::String)
928 }
929}
930
931impl TypeScript for char {
932 fn typescript() -> TypeDef {
933 TypeDef::Primitive(Primitive::String)
934 }
935}
936
937macro_rules! impl_typescript_number {
938 ($($t:ty),*) => {
939 $(
940 impl TypeScript for $t {
941 fn typescript() -> TypeDef {
942 TypeDef::Primitive(Primitive::Number)
943 }
944 }
945 )*
946 };
947}
948
949impl_typescript_number!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64);
950
951impl TypeScript for i128 {
953 fn typescript() -> TypeDef {
954 TypeDef::Primitive(Primitive::BigInt)
955 }
956}
957
958impl TypeScript for u128 {
959 fn typescript() -> TypeDef {
960 TypeDef::Primitive(Primitive::BigInt)
961 }
962}
963
964impl<T: TypeScript> TypeScript for Option<T> {
969 fn typescript() -> TypeDef {
970 TypeDef::Union(vec![T::typescript(), TypeDef::Primitive(Primitive::Null)])
971 }
972}
973
974impl<T: TypeScript> TypeScript for Vec<T> {
975 fn typescript() -> TypeDef {
976 TypeDef::Array(Box::new(T::typescript()))
977 }
978}
979
980impl<T: TypeScript> TypeScript for Box<T> {
981 fn typescript() -> TypeDef {
982 T::typescript()
983 }
984}
985
986impl<T: TypeScript> TypeScript for std::rc::Rc<T> {
987 fn typescript() -> TypeDef {
988 T::typescript()
989 }
990}
991
992impl<T: TypeScript> TypeScript for std::sync::Arc<T> {
993 fn typescript() -> TypeDef {
994 T::typescript()
995 }
996}
997
998impl<T: TypeScript> TypeScript for std::cell::RefCell<T> {
999 fn typescript() -> TypeDef {
1000 T::typescript()
1001 }
1002}
1003
1004impl<T: TypeScript> TypeScript for std::cell::Cell<T> {
1005 fn typescript() -> TypeDef {
1006 T::typescript()
1007 }
1008}
1009
1010impl<K: TypeScript, V: TypeScript> TypeScript for HashMap<K, V> {
1011 fn typescript() -> TypeDef {
1012 TypeDef::Record {
1013 key: Box::new(K::typescript()),
1014 value: Box::new(V::typescript()),
1015 }
1016 }
1017}
1018
1019impl<K: TypeScript, V: TypeScript> TypeScript for std::collections::BTreeMap<K, V> {
1020 fn typescript() -> TypeDef {
1021 TypeDef::Record {
1022 key: Box::new(K::typescript()),
1023 value: Box::new(V::typescript()),
1024 }
1025 }
1026}
1027
1028impl<T: TypeScript, E: TypeScript> TypeScript for Result<T, E> {
1029 fn typescript() -> TypeDef {
1030 TypeDef::Union(vec![
1031 TypeDef::Object(vec![
1032 Field::new("ok", TypeDef::Literal(Literal::Boolean(true))),
1033 Field::new("value", T::typescript()),
1034 ]),
1035 TypeDef::Object(vec![
1036 Field::new("ok", TypeDef::Literal(Literal::Boolean(false))),
1037 Field::new("error", E::typescript()),
1038 ]),
1039 ])
1040 }
1041}
1042
1043impl<A: TypeScript> TypeScript for (A,) {
1048 fn typescript() -> TypeDef {
1049 TypeDef::Tuple(vec![A::typescript()])
1050 }
1051}
1052
1053impl<A: TypeScript, B: TypeScript> TypeScript for (A, B) {
1054 fn typescript() -> TypeDef {
1055 TypeDef::Tuple(vec![A::typescript(), B::typescript()])
1056 }
1057}
1058
1059impl<A: TypeScript, B: TypeScript, C: TypeScript> TypeScript for (A, B, C) {
1060 fn typescript() -> TypeDef {
1061 TypeDef::Tuple(vec![A::typescript(), B::typescript(), C::typescript()])
1062 }
1063}
1064
1065impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript> TypeScript for (A, B, C, D) {
1066 fn typescript() -> TypeDef {
1067 TypeDef::Tuple(vec![
1068 A::typescript(),
1069 B::typescript(),
1070 C::typescript(),
1071 D::typescript(),
1072 ])
1073 }
1074}
1075
1076impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript, E: TypeScript> TypeScript
1077 for (A, B, C, D, E)
1078{
1079 fn typescript() -> TypeDef {
1080 TypeDef::Tuple(vec![
1081 A::typescript(),
1082 B::typescript(),
1083 C::typescript(),
1084 D::typescript(),
1085 E::typescript(),
1086 ])
1087 }
1088}
1089
1090impl<A: TypeScript, B: TypeScript, C: TypeScript, D: TypeScript, E: TypeScript, F: TypeScript>
1091 TypeScript for (A, B, C, D, E, F)
1092{
1093 fn typescript() -> TypeDef {
1094 TypeDef::Tuple(vec![
1095 A::typescript(),
1096 B::typescript(),
1097 C::typescript(),
1098 D::typescript(),
1099 E::typescript(),
1100 F::typescript(),
1101 ])
1102 }
1103}
1104
1105#[cfg(test)]
1110mod tests {
1111 use super::*;
1112
1113 #[test]
1118 fn test_typedef_primitive_render() {
1119 assert_eq!(TypeDef::Primitive(Primitive::String).render(), "string");
1120 assert_eq!(TypeDef::Primitive(Primitive::Number).render(), "number");
1121 assert_eq!(TypeDef::Primitive(Primitive::Boolean).render(), "boolean");
1122 assert_eq!(TypeDef::Primitive(Primitive::Null).render(), "null");
1123 assert_eq!(TypeDef::Primitive(Primitive::Undefined).render(), "undefined");
1124 assert_eq!(TypeDef::Primitive(Primitive::Void).render(), "void");
1125 assert_eq!(TypeDef::Primitive(Primitive::Never).render(), "never");
1126 assert_eq!(TypeDef::Primitive(Primitive::Any).render(), "any");
1127 assert_eq!(TypeDef::Primitive(Primitive::Unknown).render(), "unknown");
1128 assert_eq!(TypeDef::Primitive(Primitive::BigInt).render(), "bigint");
1129 }
1130
1131 #[test]
1132 fn test_typedef_array_render() {
1133 let arr = TypeDef::Array(Box::new(TypeDef::Primitive(Primitive::String)));
1134 assert_eq!(arr.render(), "string[]");
1135
1136 let union_arr = TypeDef::Array(Box::new(TypeDef::Union(vec![
1138 TypeDef::Primitive(Primitive::String),
1139 TypeDef::Primitive(Primitive::Number),
1140 ])));
1141 assert_eq!(union_arr.render(), "(string | number)[]");
1142 }
1143
1144 #[test]
1145 fn test_typedef_tuple_render() {
1146 let tuple = TypeDef::Tuple(vec![
1147 TypeDef::Primitive(Primitive::String),
1148 TypeDef::Primitive(Primitive::Number),
1149 ]);
1150 assert_eq!(tuple.render(), "[string, number]");
1151 }
1152
1153 #[test]
1154 fn test_typedef_object_render() {
1155 let obj = TypeDef::Object(vec![
1156 Field::new("name", TypeDef::Primitive(Primitive::String)),
1157 Field::optional("age", TypeDef::Primitive(Primitive::Number)),
1158 ]);
1159 assert_eq!(obj.render(), "{ name: string; age?: number }");
1160
1161 let empty_obj = TypeDef::Object(vec![]);
1162 assert_eq!(empty_obj.render(), "{}");
1163 }
1164
1165 #[test]
1166 fn test_typedef_object_readonly_field() {
1167 let obj = TypeDef::Object(vec![
1168 Field::new("id", TypeDef::Primitive(Primitive::String)).readonly(),
1169 ]);
1170 assert_eq!(obj.render(), "{ readonly id: string }");
1171 }
1172
1173 #[test]
1174 fn test_typedef_union_render() {
1175 let union = TypeDef::Union(vec![
1176 TypeDef::Primitive(Primitive::String),
1177 TypeDef::Primitive(Primitive::Number),
1178 TypeDef::Primitive(Primitive::Null),
1179 ]);
1180 assert_eq!(union.render(), "string | number | null");
1181 }
1182
1183 #[test]
1184 fn test_typedef_intersection_render() {
1185 let intersection = TypeDef::Intersection(vec![
1186 TypeDef::Ref("Base".into()),
1187 TypeDef::Object(vec![
1188 Field::new("extra", TypeDef::Primitive(Primitive::String)),
1189 ]),
1190 ]);
1191 assert_eq!(intersection.render(), "Base & { extra: string }");
1192 }
1193
1194 #[test]
1195 fn test_typedef_record_render() {
1196 let record = TypeDef::Record {
1197 key: Box::new(TypeDef::Primitive(Primitive::String)),
1198 value: Box::new(TypeDef::Primitive(Primitive::Number)),
1199 };
1200 assert_eq!(record.render(), "Record<string, number>");
1201 }
1202
1203 #[test]
1204 fn test_typedef_named_render() {
1205 let named = TypeDef::Named {
1206 namespace: vec![],
1207 name: "UserId".into(),
1208 def: Box::new(TypeDef::Primitive(Primitive::String)),
1209 module: None,
1210 };
1211 assert_eq!(named.render(), "UserId");
1213 assert_eq!(named.render_declaration(), "type UserId = string;");
1215 }
1216
1217 #[test]
1218 fn test_typedef_namespaced_render() {
1219 let namespaced = TypeDef::Named {
1221 namespace: vec!["VM".into(), "Git".into()],
1222 name: "State".into(),
1223 def: Box::new(TypeDef::Union(vec![
1224 TypeDef::Literal(Literal::String("clean".into())),
1225 TypeDef::Literal(Literal::String("dirty".into())),
1226 TypeDef::Literal(Literal::String("unknown".into())),
1227 ])),
1228 module: None,
1229 };
1230 assert_eq!(namespaced.render(), "VM.Git.State");
1232 let decl = namespaced.render_declaration();
1234 assert!(decl.contains("namespace VM {"));
1235 assert!(decl.contains("namespace Git {"));
1236 assert!(decl.contains(r#"type State = "clean" | "dirty" | "unknown";"#));
1237 }
1238
1239 #[test]
1240 fn test_typedef_single_namespace_render() {
1241 let namespaced = TypeDef::Named {
1242 namespace: vec!["API".into()],
1243 name: "Response".into(),
1244 def: Box::new(TypeDef::Primitive(Primitive::String)),
1245 module: None,
1246 };
1247 assert_eq!(namespaced.render(), "API.Response");
1248 let decl = namespaced.render_declaration();
1249 assert!(decl.contains("namespace API {"));
1250 assert!(decl.contains("type Response = string;"));
1251 }
1252
1253 #[test]
1254 fn test_typedef_ref_render() {
1255 let ref_type = TypeDef::Ref("User".into());
1256 assert_eq!(ref_type.render(), "User");
1257 }
1258
1259 #[test]
1260 fn test_typedef_literal_render() {
1261 assert_eq!(TypeDef::Literal(Literal::String("foo".into())).render(), "\"foo\"");
1262 assert_eq!(TypeDef::Literal(Literal::Number(42.0)).render(), "42");
1263 assert_eq!(TypeDef::Literal(Literal::Number(3.14)).render(), "3.14");
1264 assert_eq!(TypeDef::Literal(Literal::Boolean(true)).render(), "true");
1265 assert_eq!(TypeDef::Literal(Literal::Boolean(false)).render(), "false");
1266 }
1267
1268 #[test]
1269 fn test_typedef_literal_escaping() {
1270 let lit = Literal::String("say \"hello\"".into());
1271 assert_eq!(lit.render(), "\"say \\\"hello\\\"\"");
1272 }
1273
1274 #[test]
1275 fn test_typedef_function_render() {
1276 let func = TypeDef::Function {
1277 params: vec![
1278 Field::new("name", TypeDef::Primitive(Primitive::String)),
1279 Field::new("age", TypeDef::Primitive(Primitive::Number)),
1280 ],
1281 return_type: Box::new(TypeDef::Primitive(Primitive::Void)),
1282 };
1283 assert_eq!(func.render(), "(name: string, age: number) => void");
1284 }
1285
1286 #[test]
1287 fn test_typedef_generic_render() {
1288 let generic = TypeDef::Generic {
1289 base: "Promise".into(),
1290 args: vec![TypeDef::Primitive(Primitive::String)],
1291 };
1292 assert_eq!(generic.render(), "Promise<string>");
1293
1294 let multi_generic = TypeDef::Generic {
1295 base: "Map".into(),
1296 args: vec![
1297 TypeDef::Primitive(Primitive::String),
1298 TypeDef::Primitive(Primitive::Number),
1299 ],
1300 };
1301 assert_eq!(multi_generic.render(), "Map<string, number>");
1302 }
1303
1304 #[test]
1305 fn test_typedef_template_literal_render() {
1306 let vm_id = TypeDef::TemplateLiteral {
1308 strings: vec!["vm-".into(), "".into()],
1309 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1310 };
1311 assert_eq!(vm_id.render(), "`vm-${string}`");
1312
1313 let semver = TypeDef::TemplateLiteral {
1315 strings: vec!["v".into(), ".".into(), ".".into(), "".into()],
1316 types: vec![
1317 Box::new(TypeDef::Primitive(Primitive::Number)),
1318 Box::new(TypeDef::Primitive(Primitive::Number)),
1319 Box::new(TypeDef::Primitive(Primitive::Number)),
1320 ],
1321 };
1322 assert_eq!(semver.render(), "`v${number}.${number}.${number}`");
1323
1324 let api_route = TypeDef::TemplateLiteral {
1326 strings: vec!["/api/".into(), "".into()],
1327 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1328 };
1329 assert_eq!(api_route.render(), "`/api/${string}`");
1330
1331 let complex_id = TypeDef::TemplateLiteral {
1333 strings: vec!["user-".into(), "-id".into()],
1334 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1335 };
1336 assert_eq!(complex_id.render(), "`user-${string}-id`");
1337
1338 let static_str = TypeDef::TemplateLiteral {
1340 strings: vec!["static-value".into()],
1341 types: vec![],
1342 };
1343 assert_eq!(static_str.render(), "`static-value`");
1344 }
1345
1346 #[test]
1347 fn test_typedef_template_literal_escaping() {
1348 let with_backtick = TypeDef::TemplateLiteral {
1350 strings: vec!["code: `".into(), "`".into()],
1351 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1352 };
1353 assert_eq!(with_backtick.render(), "`code: \\`${string}\\``");
1354
1355 let with_backslash = TypeDef::TemplateLiteral {
1357 strings: vec!["path\\to\\".into(), "".into()],
1358 types: vec![Box::new(TypeDef::Primitive(Primitive::String))],
1359 };
1360 assert_eq!(with_backslash.render(), "`path\\\\to\\\\${string}`");
1361 }
1362
1363 #[test]
1364 fn test_typedef_template_literal_with_refs() {
1365 let typed_id = TypeDef::TemplateLiteral {
1367 strings: vec!["vm-".into(), "".into()],
1368 types: vec![Box::new(TypeDef::Ref("VmIdSuffix".into()))],
1369 };
1370 assert_eq!(typed_id.render(), "`vm-${VmIdSuffix}`");
1371 }
1372
1373 #[test]
1374 fn test_typescript_trait_primitives() {
1375 assert_eq!(<()>::typescript().render(), "void");
1376 assert_eq!(bool::typescript().render(), "boolean");
1377 assert_eq!(String::typescript().render(), "string");
1378 assert_eq!(i32::typescript().render(), "number");
1379 assert_eq!(f64::typescript().render(), "number");
1380 assert_eq!(i128::typescript().render(), "bigint");
1381 assert_eq!(u128::typescript().render(), "bigint");
1382 }
1383
1384 #[test]
1385 fn test_typescript_trait_option() {
1386 let opt = <Option<String>>::typescript();
1387 assert_eq!(opt.render(), "string | null");
1388 }
1389
1390 #[test]
1391 fn test_typescript_trait_vec() {
1392 let vec_type = <Vec<i32>>::typescript();
1393 assert_eq!(vec_type.render(), "number[]");
1394 }
1395
1396 #[test]
1397 fn test_typescript_trait_hashmap() {
1398 let map = <HashMap<String, i32>>::typescript();
1399 assert_eq!(map.render(), "Record<string, number>");
1400 }
1401
1402 #[test]
1403 fn test_typescript_trait_result() {
1404 let result = <Result<String, String>>::typescript();
1405 assert_eq!(
1406 result.render(),
1407 "{ ok: true; value: string } | { ok: false; error: string }"
1408 );
1409 }
1410
1411 #[test]
1412 fn test_typescript_trait_tuples() {
1413 assert_eq!(<(String,)>::typescript().render(), "[string]");
1414 assert_eq!(<(String, i32)>::typescript().render(), "[string, number]");
1415 assert_eq!(
1416 <(String, i32, bool)>::typescript().render(),
1417 "[string, number, boolean]"
1418 );
1419 }
1420
1421 #[test]
1422 fn test_typescript_trait_box() {
1423 assert_eq!(<Box<String>>::typescript().render(), "string");
1425 }
1426
1427 #[test]
1428 fn test_typedef_equality() {
1429 let a = TypeDef::Primitive(Primitive::String);
1430 let b = TypeDef::Primitive(Primitive::String);
1431 let c = TypeDef::Primitive(Primitive::Number);
1432 assert_eq!(a, b);
1433 assert_ne!(a, c);
1434 }
1435
1436 #[test]
1437 fn test_field_builder() {
1438 let field = Field::new("name", TypeDef::Primitive(Primitive::String));
1439 assert!(!field.optional);
1440 assert!(!field.readonly);
1441
1442 let opt_field = Field::optional("name", TypeDef::Primitive(Primitive::String));
1443 assert!(opt_field.optional);
1444
1445 let readonly_field = Field::new("id", TypeDef::Primitive(Primitive::String)).readonly();
1446 assert!(readonly_field.readonly);
1447 }
1448
1449 #[test]
1454 fn test_registry_new() {
1455 let registry = TypeRegistry::new();
1456 assert!(registry.is_empty());
1457 assert_eq!(registry.len(), 0);
1458 }
1459
1460 #[test]
1461 fn test_registry_add_typedef() {
1462 let mut registry = TypeRegistry::new();
1463
1464 let user_type = TypeDef::Named {
1465 namespace: vec![],
1466 name: "User".to_string(),
1467 def: Box::new(TypeDef::Object(vec![
1468 Field::new("id", TypeDef::Primitive(Primitive::String)),
1469 Field::new("name", TypeDef::Primitive(Primitive::String)),
1470 ])),
1471 module: None,
1472 };
1473
1474 registry.add_typedef(user_type);
1475
1476 assert_eq!(registry.len(), 1);
1477 assert!(registry.get("User").is_some());
1478 }
1479
1480 #[test]
1481 fn test_registry_deduplication() {
1482 let mut registry = TypeRegistry::new();
1483
1484 let user_type = TypeDef::Named {
1485 namespace: vec![],
1486 name: "User".to_string(),
1487 def: Box::new(TypeDef::Primitive(Primitive::String)),
1488 module: None,
1489 };
1490
1491 registry.add_typedef(user_type.clone());
1492 registry.add_typedef(user_type);
1493
1494 assert_eq!(registry.len(), 1);
1496 }
1497
1498 #[test]
1499 fn test_registry_extracts_nested_types() {
1500 let mut registry = TypeRegistry::new();
1501
1502 let user_id = TypeDef::Named {
1504 namespace: vec![],
1505 name: "UserId".to_string(),
1506 def: Box::new(TypeDef::Primitive(Primitive::String)),
1507 module: None,
1508 };
1509
1510 let user = TypeDef::Named {
1512 namespace: vec![],
1513 name: "User".to_string(),
1514 def: Box::new(TypeDef::Object(vec![
1515 Field::new("id", TypeDef::Ref("UserId".to_string())),
1516 Field::new("name", TypeDef::Primitive(Primitive::String)),
1517 ])),
1518 module: None,
1519 };
1520
1521 let post_type = TypeDef::Named {
1523 namespace: vec![],
1524 name: "Post".to_string(),
1525 def: Box::new(TypeDef::Object(vec![
1526 Field::new("title", TypeDef::Primitive(Primitive::String)),
1527 Field::new("author", user),
1528 ])),
1529 module: None,
1530 };
1531
1532 registry.add_typedef(post_type);
1533 registry.add_typedef(user_id);
1534
1535 assert_eq!(registry.len(), 3);
1537 assert!(registry.get("Post").is_some());
1538 assert!(registry.get("User").is_some());
1539 assert!(registry.get("UserId").is_some());
1540 }
1541
1542 #[test]
1543 fn test_registry_render() {
1544 let mut registry = TypeRegistry::new();
1545
1546 let user_type = TypeDef::Named {
1547 namespace: vec![],
1548 name: "User".to_string(),
1549 def: Box::new(TypeDef::Object(vec![
1550 Field::new("id", TypeDef::Primitive(Primitive::String)),
1551 Field::new("name", TypeDef::Primitive(Primitive::String)),
1552 ])),
1553 module: None,
1554 };
1555
1556 registry.add_typedef(user_type);
1557
1558 let output = registry.render();
1559 assert!(output.contains("// Generated by ferrotype"));
1560 assert!(output.contains("type User = { id: string; name: string };"));
1561 }
1562
1563 #[test]
1564 fn test_registry_render_exported() {
1565 let mut registry = TypeRegistry::new();
1566
1567 let user_type = TypeDef::Named {
1568 namespace: vec![],
1569 name: "User".to_string(),
1570 def: Box::new(TypeDef::Primitive(Primitive::String)),
1571 module: None,
1572 };
1573
1574 registry.add_typedef(user_type);
1575
1576 let output = registry.render_exported();
1577 assert!(output.contains("export type User = string;"));
1578 }
1579
1580 #[test]
1581 fn test_registry_dependency_order() {
1582 let mut registry = TypeRegistry::new();
1583
1584 let user_id = TypeDef::Named {
1586 namespace: vec![],
1587 name: "UserId".to_string(),
1588 def: Box::new(TypeDef::Primitive(Primitive::String)),
1589 module: None,
1590 };
1591
1592 let user = TypeDef::Named {
1594 namespace: vec![],
1595 name: "User".to_string(),
1596 def: Box::new(TypeDef::Object(vec![
1597 Field::new("id", TypeDef::Ref("UserId".to_string())),
1598 Field::new("name", TypeDef::Primitive(Primitive::String)),
1599 ])),
1600 module: None,
1601 };
1602
1603 registry.add_typedef(user);
1605 registry.add_typedef(user_id);
1606
1607 let sorted = registry.sorted_types();
1608
1609 let user_id_pos = sorted.iter().position(|&n| n == "UserId").unwrap();
1611 let user_pos = sorted.iter().position(|&n| n == "User").unwrap();
1612 assert!(user_id_pos < user_pos, "UserId should come before User");
1613 }
1614
1615 #[test]
1616 fn test_registry_clear() {
1617 let mut registry = TypeRegistry::new();
1618
1619 let user_type = TypeDef::Named {
1620 namespace: vec![],
1621 name: "User".to_string(),
1622 def: Box::new(TypeDef::Primitive(Primitive::String)),
1623 module: None,
1624 };
1625
1626 registry.add_typedef(user_type);
1627 assert_eq!(registry.len(), 1);
1628
1629 registry.clear();
1630 assert!(registry.is_empty());
1631 }
1632
1633 #[test]
1634 fn test_registry_type_names() {
1635 let mut registry = TypeRegistry::new();
1636
1637 registry.add_typedef(TypeDef::Named {
1638 namespace: vec![],
1639 name: "Alpha".to_string(),
1640 def: Box::new(TypeDef::Primitive(Primitive::String)),
1641 module: None,
1642 });
1643 registry.add_typedef(TypeDef::Named {
1644 namespace: vec![],
1645 name: "Beta".to_string(),
1646 def: Box::new(TypeDef::Primitive(Primitive::Number)),
1647 module: None,
1648 });
1649
1650 let names: Vec<_> = registry.type_names().collect();
1651 assert_eq!(names.len(), 2);
1652 assert!(names.contains(&"Alpha"));
1653 assert!(names.contains(&"Beta"));
1654 }
1655
1656 #[test]
1657 fn test_registry_complex_dependencies() {
1658 let mut registry = TypeRegistry::new();
1659
1660 let c = TypeDef::Named {
1662 namespace: vec![],
1663 name: "C".to_string(),
1664 def: Box::new(TypeDef::Primitive(Primitive::String)),
1665 module: None,
1666 };
1667
1668 let b = TypeDef::Named {
1669 namespace: vec![],
1670 name: "B".to_string(),
1671 def: Box::new(TypeDef::Object(vec![
1672 Field::new("c", TypeDef::Ref("C".to_string())),
1673 ])),
1674 module: None,
1675 };
1676
1677 let a = TypeDef::Named {
1678 namespace: vec![],
1679 name: "A".to_string(),
1680 def: Box::new(TypeDef::Object(vec![
1681 Field::new("b", TypeDef::Ref("B".to_string())),
1682 ])),
1683 module: None,
1684 };
1685
1686 registry.add_typedef(a);
1688 registry.add_typedef(b);
1689 registry.add_typedef(c);
1690
1691 let sorted = registry.sorted_types();
1692
1693 let c_pos = sorted.iter().position(|&n| n == "C").unwrap();
1694 let b_pos = sorted.iter().position(|&n| n == "B").unwrap();
1695 let a_pos = sorted.iter().position(|&n| n == "A").unwrap();
1696
1697 assert!(c_pos < b_pos, "C should come before B");
1698 assert!(b_pos < a_pos, "B should come before A");
1699 }
1700
1701 #[derive(Debug)]
1707 struct AutoRegTestUser {
1708 name: String,
1709 age: u32,
1710 }
1711
1712 impl TypeScript for AutoRegTestUser {
1713 fn typescript() -> TypeDef {
1714 TypeDef::Named {
1715 namespace: vec![],
1716 name: "AutoRegTestUser".to_string(),
1717 def: Box::new(TypeDef::Object(vec![
1718 Field::new("name", TypeDef::Primitive(Primitive::String)),
1719 Field::new("age", TypeDef::Primitive(Primitive::Number)),
1720 ])),
1721 module: None,
1722 }
1723 }
1724 }
1725
1726 #[linkme::distributed_slice(TYPESCRIPT_TYPES)]
1728 static __TEST_REGISTER_USER: fn() -> TypeDef = || AutoRegTestUser::typescript();
1729
1730 #[test]
1731 fn test_from_distributed_collects_types() {
1732 let registry = TypeRegistry::from_distributed();
1733
1734 assert!(registry.get("AutoRegTestUser").is_some(),
1736 "Registry should contain AutoRegTestUser");
1737 }
1738
1739 #[test]
1740 fn test_collect_all_adds_to_existing() {
1741 let mut registry = TypeRegistry::new();
1742
1743 let manual_type = TypeDef::Named {
1745 namespace: vec![],
1746 name: "ManualType".to_string(),
1747 def: Box::new(TypeDef::Primitive(Primitive::String)),
1748 module: None,
1749 };
1750 registry.add_typedef(manual_type);
1751
1752 registry.collect_all();
1754
1755 assert!(registry.get("ManualType").is_some(),
1757 "Registry should contain ManualType");
1758 assert!(registry.get("AutoRegTestUser").is_some(),
1759 "Registry should contain AutoRegTestUser from distributed slice");
1760 }
1761
1762 #[test]
1763 fn test_distributed_slice_is_accessible() {
1764 let count = TYPESCRIPT_TYPES.len();
1766 assert!(count >= 1, "TYPESCRIPT_TYPES should have at least 1 entry");
1768 }
1769}