1extern crate alloc;
24
25use alloc::collections::BTreeSet;
26use alloc::string::String;
27use alloc::vec::Vec;
28use core::fmt::Write;
29
30use facet_core::{Def, Facet, Field, Shape, StructKind, Type, UserType};
31
32pub fn to_typescript<T: Facet<'static>>() -> String {
36 let mut generator = TypeScriptGenerator::new();
37 generator.add_shape(T::SHAPE);
38 generator.finish()
39}
40
41pub struct TypeScriptGenerator {
45 output: String,
46 generated: BTreeSet<&'static str>,
48 queue: Vec<&'static Shape>,
50 indent: usize,
52}
53
54impl Default for TypeScriptGenerator {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60impl TypeScriptGenerator {
61 pub fn new() -> Self {
63 Self {
64 output: String::new(),
65 generated: BTreeSet::new(),
66 queue: Vec::new(),
67 indent: 0,
68 }
69 }
70
71 pub fn add_type<T: Facet<'static>>(&mut self) {
73 self.add_shape(T::SHAPE);
74 }
75
76 pub fn add_shape(&mut self, shape: &'static Shape) {
78 if !self.generated.contains(shape.type_identifier) {
79 self.queue.push(shape);
80 }
81 }
82
83 pub fn finish(mut self) -> String {
85 while let Some(shape) = self.queue.pop() {
87 if self.generated.contains(shape.type_identifier) {
88 continue;
89 }
90 self.generated.insert(shape.type_identifier);
91 self.generate_shape(shape);
92 }
93 self.output
94 }
95
96 fn write_indent(&mut self) {
97 for _ in 0..self.indent {
98 self.output.push_str(" ");
99 }
100 }
101
102 fn generate_shape(&mut self, shape: &'static Shape) {
103 if let Some(inner) = shape.inner {
105 self.add_shape(inner);
106 let inner_type = self.type_for_shape(inner);
108 writeln!(
109 self.output,
110 "export type {} = {};",
111 shape.type_identifier, inner_type
112 )
113 .unwrap();
114 self.output.push('\n');
115 return;
116 }
117
118 if !shape.doc.is_empty() {
120 self.output.push_str("/**\n");
121 for line in shape.doc {
122 self.output.push_str(" *");
123 self.output.push_str(line);
124 self.output.push('\n');
125 }
126 self.output.push_str(" */\n");
127 }
128
129 match &shape.ty {
130 Type::User(UserType::Struct(st)) => {
131 self.generate_struct(shape, st.fields, st.kind);
132 }
133 Type::User(UserType::Enum(en)) => {
134 self.generate_enum(shape, en);
135 }
136 _ => {
137 let type_str = self.type_for_shape(shape);
139 writeln!(
140 self.output,
141 "export type {} = {};",
142 shape.type_identifier, type_str
143 )
144 .unwrap();
145 self.output.push('\n');
146 }
147 }
148 }
149
150 fn generate_struct(
151 &mut self,
152 shape: &'static Shape,
153 fields: &'static [Field],
154 kind: StructKind,
155 ) {
156 match kind {
157 StructKind::Unit => {
158 writeln!(self.output, "export type {} = null;", shape.type_identifier).unwrap();
160 }
161 StructKind::TupleStruct if fields.len() == 1 => {
162 let inner_type = self.type_for_shape(fields[0].shape.get());
164 writeln!(
165 self.output,
166 "export type {} = {};",
167 shape.type_identifier, inner_type
168 )
169 .unwrap();
170 }
171 StructKind::TupleStruct | StructKind::Tuple => {
172 let types: Vec<String> = fields
174 .iter()
175 .map(|f| self.type_for_shape(f.shape.get()))
176 .collect();
177 writeln!(
178 self.output,
179 "export type {} = [{}];",
180 shape.type_identifier,
181 types.join(", ")
182 )
183 .unwrap();
184 }
185 StructKind::Struct => {
186 writeln!(self.output, "export interface {} {{", shape.type_identifier).unwrap();
187 self.indent += 1;
188
189 for field in fields {
190 if field.flags.contains(facet_core::FieldFlags::SKIP) {
192 continue;
193 }
194
195 if !field.doc.is_empty() {
197 self.write_indent();
198 self.output.push_str("/**\n");
199 for line in field.doc {
200 self.write_indent();
201 self.output.push_str(" *");
202 self.output.push_str(line);
203 self.output.push('\n');
204 }
205 self.write_indent();
206 self.output.push_str(" */\n");
207 }
208
209 let field_name = field.rename.unwrap_or(field.name);
210 let is_option = matches!(field.shape.get().def, Def::Option(_));
211
212 self.write_indent();
213
214 if is_option {
216 if let Def::Option(opt) = &field.shape.get().def {
218 let inner_type = self.type_for_shape(opt.t);
219 writeln!(self.output, "{}?: {};", field_name, inner_type).unwrap();
220 }
221 } else {
222 let field_type = self.type_for_shape(field.shape.get());
223 writeln!(self.output, "{}: {};", field_name, field_type).unwrap();
224 }
225 }
226
227 self.indent -= 1;
228 self.output.push_str("}\n");
229 }
230 }
231 self.output.push('\n');
232 }
233
234 fn generate_enum(&mut self, shape: &'static Shape, enum_type: &facet_core::EnumType) {
235 let all_unit = enum_type
237 .variants
238 .iter()
239 .all(|v| matches!(v.data.kind, StructKind::Unit));
240
241 if all_unit {
242 let variants: Vec<String> = enum_type
244 .variants
245 .iter()
246 .map(|v| format!("\"{}\"", v.name))
247 .collect();
248 writeln!(
249 self.output,
250 "export type {} = {};",
251 shape.type_identifier,
252 variants.join(" | ")
253 )
254 .unwrap();
255 } else {
256 let mut variant_types = Vec::new();
259
260 for variant in enum_type.variants {
261 match variant.data.kind {
262 StructKind::Unit => {
263 variant_types.push(format!("{{ {}: \"{}\" }}", variant.name, variant.name));
265 }
266 StructKind::TupleStruct if variant.data.fields.len() == 1 => {
267 let inner = self.type_for_shape(variant.data.fields[0].shape.get());
269 variant_types.push(format!("{{ {}: {} }}", variant.name, inner));
270 }
271 _ => {
272 let mut field_types = Vec::new();
274 for field in variant.data.fields {
275 let field_name = field.rename.unwrap_or(field.name);
276 let field_type = self.type_for_shape(field.shape.get());
277 field_types.push(format!("{}: {}", field_name, field_type));
278 }
279 variant_types.push(format!(
280 "{{ {}: {{ {} }} }}",
281 variant.name,
282 field_types.join("; ")
283 ));
284 }
285 }
286 }
287
288 writeln!(
289 self.output,
290 "export type {} =\n | {};",
291 shape.type_identifier,
292 variant_types.join("\n | ")
293 )
294 .unwrap();
295 }
296 self.output.push('\n');
297 }
298
299 fn type_for_shape(&mut self, shape: &'static Shape) -> String {
300 match &shape.def {
302 Def::Scalar => self.scalar_type(shape),
303 Def::Option(opt) => {
304 format!("{} | null", self.type_for_shape(opt.t))
305 }
306 Def::List(list) => {
307 format!("{}[]", self.type_for_shape(list.t))
308 }
309 Def::Array(arr) => {
310 format!("{}[]", self.type_for_shape(arr.t))
311 }
312 Def::Set(set) => {
313 format!("{}[]", self.type_for_shape(set.t))
314 }
315 Def::Map(map) => {
316 format!("Record<string, {}>", self.type_for_shape(map.v))
317 }
318 Def::Pointer(ptr) => {
319 if let Some(pointee) = ptr.pointee {
321 self.type_for_shape(pointee)
322 } else {
323 "unknown".to_string()
324 }
325 }
326 Def::Undefined => {
327 match &shape.ty {
329 Type::User(UserType::Struct(_) | UserType::Enum(_)) => {
330 self.add_shape(shape);
331 shape.type_identifier.to_string()
332 }
333 _ => {
334 if let Some(inner) = shape.inner {
336 self.type_for_shape(inner)
337 } else {
338 "unknown".to_string()
339 }
340 }
341 }
342 }
343 _ => {
344 if let Some(inner) = shape.inner {
346 self.type_for_shape(inner)
347 } else {
348 "unknown".to_string()
349 }
350 }
351 }
352 }
353
354 fn scalar_type(&self, shape: &'static Shape) -> String {
355 match shape.type_identifier {
356 "String" | "str" | "&str" | "Cow" => "string".to_string(),
358
359 "bool" => "boolean".to_string(),
361
362 "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64"
364 | "i128" | "isize" | "f32" | "f64" => "number".to_string(),
365
366 "char" => "string".to_string(),
368
369 _ => "unknown".to_string(),
371 }
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use facet::Facet;
379
380 #[test]
381 fn test_simple_struct() {
382 #[derive(Facet)]
383 struct User {
384 name: String,
385 age: u32,
386 }
387
388 let ts = to_typescript::<User>();
389 insta::assert_snapshot!(ts);
390 }
391
392 #[test]
393 fn test_optional_field() {
394 #[derive(Facet)]
395 struct Config {
396 required: String,
397 optional: Option<String>,
398 }
399
400 let ts = to_typescript::<Config>();
401 insta::assert_snapshot!(ts);
402 }
403
404 #[test]
405 fn test_simple_enum() {
406 #[derive(Facet)]
407 #[repr(u8)]
408 enum Status {
409 Active,
410 Inactive,
411 Pending,
412 }
413
414 let ts = to_typescript::<Status>();
415 insta::assert_snapshot!(ts);
416 }
417
418 #[test]
419 fn test_vec() {
420 #[derive(Facet)]
421 struct Data {
422 items: Vec<String>,
423 }
424
425 let ts = to_typescript::<Data>();
426 insta::assert_snapshot!(ts);
427 }
428
429 #[test]
430 fn test_nested_types() {
431 #[derive(Facet)]
432 struct Inner {
433 value: i32,
434 }
435
436 #[derive(Facet)]
437 struct Outer {
438 inner: Inner,
439 name: String,
440 }
441
442 let ts = to_typescript::<Outer>();
443 insta::assert_snapshot!(ts);
444 }
445}