camo_typescript/
ast.rs

1use camo_core as camo;
2use std::{convert::TryFrom, fmt};
3
4/// A top-level type definition.
5#[derive(Clone, Debug, PartialEq)]
6pub enum Definition {
7    /// An interface definition.
8    Interface(Interface),
9    /// A type definition.
10    Alias(TypeAlias),
11}
12
13impl Definition {
14    /// The name of the type definition.
15    pub fn name(&self) -> &str {
16        match self {
17            Definition::Interface(i) => &i.name,
18            Definition::Alias(a) => &a.name,
19        }
20    }
21
22    /// `true` if the type definition is exported.
23    pub fn export(&self) -> bool {
24        match self {
25            Definition::Interface(i) => i.export,
26            Definition::Alias(a) => a.export,
27        }
28    }
29}
30
31impl From<Interface> for Definition {
32    fn from(value: Interface) -> Self {
33        Definition::Interface(value)
34    }
35}
36
37impl From<TypeAlias> for Definition {
38    fn from(value: TypeAlias) -> Self {
39        Definition::Alias(value)
40    }
41}
42
43#[derive(Clone, Copy)]
44struct Renamer(Option<camo::RenameRule>);
45
46impl Renamer {
47    fn rename_field(&self, name: &str) -> String {
48        let Self(rule) = self;
49        match rule {
50            Some(camo::RenameRule::LowerCase) => name.to_lowercase(),
51            Some(camo::RenameRule::UpperCase) => name.to_uppercase(),
52            Some(camo::RenameRule::PascalCase) => snake_to_non_snake_case(true, name),
53            Some(camo::RenameRule::CamelCase) => snake_to_non_snake_case(false, name),
54            Some(camo::RenameRule::SnakeCase) => name.to_string(),
55            Some(camo::RenameRule::ScreamingSnakeCase) => name.to_uppercase(),
56            Some(camo::RenameRule::KebabCase) => name.replace('_', "-"),
57            Some(camo::RenameRule::ScreamingKebabCase) => name.to_uppercase().replace('_', "-"),
58            None => name.to_string(),
59        }
60    }
61
62    fn rename_type(&self, name: &str) -> String {
63        let Self(rule) = self;
64        match rule {
65            Some(camo::RenameRule::LowerCase) => name.to_lowercase(),
66            Some(camo::RenameRule::UpperCase) => name.to_uppercase(),
67            Some(camo::RenameRule::PascalCase) => name.to_string(),
68            Some(camo::RenameRule::CamelCase) => name[..1].to_ascii_lowercase() + &name[1..],
69            Some(camo::RenameRule::SnakeCase) => pascal_to_separated_case('_', name),
70            Some(camo::RenameRule::ScreamingSnakeCase) => {
71                pascal_to_separated_case('_', name).to_uppercase()
72            }
73            Some(camo::RenameRule::KebabCase) => pascal_to_separated_case('-', name),
74            Some(camo::RenameRule::ScreamingKebabCase) => {
75                pascal_to_separated_case('-', name).to_uppercase()
76            }
77            None => name.to_string(),
78        }
79    }
80}
81
82fn snake_to_non_snake_case(capitalize_first: bool, field: &str) -> String {
83    let mut result = String::new();
84    let mut capitalize = capitalize_first;
85    for ch in field.chars() {
86        if ch == '_' {
87            capitalize = true;
88        } else if capitalize {
89            result.push(ch.to_ascii_uppercase());
90            capitalize = false;
91        } else {
92            result.push(ch);
93        }
94    }
95    result
96}
97
98fn pascal_to_separated_case(separator: char, name: &str) -> String {
99    let mut result = String::new();
100    for (i, ch) in name.char_indices() {
101        if i > 0 && ch.is_uppercase() {
102            result.push(separator);
103        }
104        result.push(ch.to_ascii_lowercase());
105    }
106    result
107}
108
109impl From<camo::Container> for Definition {
110    fn from(container: camo::Container) -> Self {
111        let rename = Renamer(container.attributes.rename);
112        let rename_all = Renamer(container.attributes.rename_all);
113        let tag_rule = container.attributes.tag;
114        let content_rule = container.attributes.content;
115
116        match container.item {
117            camo::Item::Struct(s) => match s.content {
118                camo::StructContent::NamedFields(fields) => Definition::Interface(Interface {
119                    export: s.visibility.is_pub(),
120                    name: rename.rename_type(s.name),
121                    parameters: s
122                        .parameters
123                        .into_iter()
124                        .filter_map(|parameter| match parameter {
125                            // Lifetimes are ignored
126                            camo::GenericParameter::Lifetime(_) => None,
127                            camo::GenericParameter::Type(ty) => Some(ty),
128                        })
129                        .collect(),
130                    fields: fields
131                        .into_iter()
132                        .map(|field| Field {
133                            name: rename_all.rename_field(field.name),
134                            ty: Type::from(field.ty),
135                            optional: false,
136                        })
137                        .collect(),
138                }),
139                camo::StructContent::UnnamedField(field) => {
140                    Definition::Alias(TypeAlias {
141                        export: s.visibility.is_pub(),
142                        name: rename.rename_type(s.name),
143                        parameters: s
144                            .parameters
145                            .into_iter()
146                            .filter_map(|parameter| match parameter {
147                                // Lifetimes are ignored
148                                camo::GenericParameter::Lifetime(_) => None,
149                                camo::GenericParameter::Type(ty) => Some(ty),
150                            })
151                            .collect(),
152                        ty: Type::from(field.ty),
153                    })
154                }
155            },
156            camo::Item::Enum(ty) => Definition::Alias(if let Some(tag) = tag_rule {
157                if let Some(content) = content_rule {
158                    TypeAlias::adjacently_tagged(rename, rename_all, tag, content, ty)
159                } else {
160                    TypeAlias::internally_tagged(rename, rename_all, tag, ty)
161                }
162            } else {
163                TypeAlias::externally_tagged(rename, rename_all, ty)
164            }),
165        }
166    }
167}
168
169impl fmt::Display for Definition {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        match self {
172            Definition::Interface(ty) => write!(f, "{}", ty),
173            Definition::Alias(ty) => write!(f, "{}", ty),
174        }
175    }
176}
177
178/// A top-level `interface` definition.
179///
180/// Example:
181///
182/// ```ts
183/// interface Foo {
184///     value: number;
185/// }
186/// ```
187///
188/// See: <https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#interfaces>
189#[derive(Clone, Debug, PartialEq)]
190pub struct Interface {
191    /// Whether the interface is marked with `export`.
192    pub export: bool,
193    /// The name of the interface.
194    pub name: String,
195    /// The generic parameters of the interface.
196    pub parameters: Vec<&'static str>,
197    /// The fields of the interface.
198    pub fields: Vec<Field>,
199}
200
201impl fmt::Display for Interface {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        if self.export {
204            write!(f, "export ")?;
205        }
206        write!(f, "interface {}", self.name)?;
207        if !self.parameters.is_empty() {
208            write!(f, "<")?;
209            for parameter in &self.parameters {
210                write!(f, "{}", parameter)?;
211            }
212            write!(f, ">")?;
213        }
214        writeln!(f, " {{")?;
215        for field in &self.fields {
216            writeln!(f, "\t{}", field)?;
217        }
218        writeln!(f, "}}")
219    }
220}
221
222/// A field in e.g. an interface or an object type.
223#[derive(Clone, Debug, PartialEq)]
224pub struct Field {
225    /// The name of the field.
226    pub name: String,
227    /// The type of the field.
228    pub ty: Type,
229    /// Whether the field is optional, e.g. `optional?: boolean`.
230    pub optional: bool,
231}
232
233impl fmt::Display for Field {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        let name = &self.name;
236        let ty = &self.ty;
237
238        if is_valid_identifier(self.name.as_str()) {
239            if self.optional {
240                write!(f, "{name}?: {ty};")
241            } else {
242                write!(f, "{name}: {ty};")
243            }
244        } else if self.optional {
245            write!(f, r#"'{name}'?: {ty};"#)
246        } else {
247            write!(f, r#"'{name}': {ty};"#)
248        }
249    }
250}
251
252fn is_valid_identifier(string: &str) -> bool {
253    let mut chars = string.chars();
254    if let Some(c) = chars.next() {
255        if !c.is_alphabetic() && c != '_' {
256            return false;
257        }
258    }
259    chars.all(|c| c.is_alphanumeric() || c == '_')
260}
261
262/// A top-level `type` definition.
263/// Example:
264///
265/// ```ts
266/// type Foo = { value: number };
267/// ```
268///
269/// See: <https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases>
270#[derive(Clone, Debug, PartialEq)]
271pub struct TypeAlias {
272    /// Whether the type is marked with `export`.
273    pub export: bool,
274    /// The name of the type definition.
275    pub name: String,
276    /// The generic parameters of the type definition.
277    pub parameters: Vec<&'static str>,
278    /// The content of the type definition.
279    pub ty: Type,
280}
281
282impl TypeAlias {
283    /// Create a new type alias for the type `T`.
284    pub fn alias<T: Into<Type>>(name: &str, ty: T) -> Self {
285        Self {
286            export: false,
287            name: String::from(name),
288            parameters: Vec::new(),
289            ty: ty.into(),
290        }
291    }
292
293    /// Mark the type with `export`.
294    pub fn exported(self) -> Self {
295        Self {
296            export: true,
297            ..self
298        }
299    }
300
301    fn externally_tagged(rename: Renamer, rename_all: Renamer, ty: camo::Enum) -> Self {
302        Self {
303            export: ty.visibility.is_pub(),
304            name: rename.rename_type(ty.name),
305            parameters: ty
306                .parameters
307                .into_iter()
308                .filter_map(|parameter| match parameter {
309                    // Lifetimes are ignored
310                    camo::GenericParameter::Lifetime(_) => None,
311                    camo::GenericParameter::Type(ty) => Some(ty),
312                })
313                .collect(),
314            ty: Type::Union(UnionType::externally_tagged(rename_all, ty.variants)),
315        }
316    }
317
318    fn adjacently_tagged(
319        rename: Renamer,
320        rename_all: Renamer,
321        tag: &'static str,
322        content: &'static str,
323        ty: camo::Enum,
324    ) -> Self {
325        Self {
326            export: ty.visibility.is_pub(),
327            name: rename.rename_type(ty.name),
328            parameters: ty
329                .parameters
330                .into_iter()
331                .filter_map(|parameter| match parameter {
332                    // Lifetimes are ignored
333                    camo::GenericParameter::Lifetime(_) => None,
334                    camo::GenericParameter::Type(ty) => Some(ty),
335                })
336                .collect(),
337            ty: Type::Union(UnionType::adjacently_tagged(
338                rename_all,
339                tag,
340                content,
341                ty.variants,
342            )),
343        }
344    }
345
346    fn internally_tagged(
347        rename: Renamer,
348        rename_all: Renamer,
349        tag: &'static str,
350        ty: camo::Enum,
351    ) -> Self {
352        Self {
353            export: ty.visibility.is_pub(),
354            name: rename.rename_field(ty.name),
355            parameters: ty
356                .parameters
357                .into_iter()
358                .filter_map(|parameter| match parameter {
359                    // Lifetimes are ignored
360                    camo::GenericParameter::Lifetime(_) => None,
361                    camo::GenericParameter::Type(ty) => Some(ty),
362                })
363                .collect(),
364            ty: Type::Union(UnionType::internally_tagged(rename_all, tag, ty.variants)),
365        }
366    }
367}
368
369impl fmt::Display for TypeAlias {
370    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371        if self.export {
372            write!(f, "export ")?;
373        }
374        write!(f, "type {}", self.name)?;
375        if !self.parameters.is_empty() {
376            write!(f, "<")?;
377            for parameter in &self.parameters {
378                write!(f, "{}", parameter)?;
379            }
380            write!(f, ">")?;
381        }
382        if self.ty.is_union() {
383            writeln!(f, " ={};", self.ty)
384        } else {
385            writeln!(f, " = {};", self.ty)
386        }
387    }
388}
389
390/// A type with multiple cases.
391///
392/// Example:
393/// ```ts
394/// type Primitive =
395///     | number
396///     | boolean
397///     | symbol;
398/// ```
399///
400/// See: <https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types>
401#[derive(Clone, Debug, PartialEq)]
402pub struct UnionType {
403    /// The variants of the union type.
404    pub variants: Vec<Variant>,
405}
406
407impl UnionType {
408    fn externally_tagged(rename_all: Renamer, variants: Vec<camo::Variant>) -> Self {
409        Self {
410            variants: variants
411                .into_iter()
412                .map(|variant| Variant::externally_tagged(rename_all, variant))
413                .collect(),
414        }
415    }
416
417    fn adjacently_tagged(
418        rename_all: Renamer,
419        tag: &'static str,
420        content: &'static str,
421        variants: Vec<camo::Variant>,
422    ) -> Self {
423        Self {
424            variants: variants
425                .into_iter()
426                .map(|variant| Variant::adjacently_tagged(rename_all, tag, content, variant))
427                .collect(),
428        }
429    }
430
431    fn internally_tagged(
432        rename_all: Renamer,
433        tag: &'static str,
434        variants: Vec<camo::Variant>,
435    ) -> Self {
436        Self {
437            variants: variants
438                .into_iter()
439                .map(|variant| Variant::internally_tagged(rename_all, tag, variant))
440                .collect(),
441        }
442    }
443}
444
445impl fmt::Display for UnionType {
446    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447        for variant in &self.variants {
448            write!(f, "\n\t| {}", variant)?;
449        }
450        Ok(())
451    }
452}
453
454/// A variant of a union type.
455#[derive(Clone, Debug, PartialEq)]
456pub struct Variant(pub Type);
457
458impl Variant {
459    fn externally_tagged(rename_all: Renamer, variant: camo::Variant) -> Self {
460        let variant_renamer = match variant.attributes.rename {
461            Some(rename) => Renamer(Some(rename)),
462            None => rename_all,
463        };
464        let field_renamer = Renamer(variant.attributes.rename_all);
465        match variant.content {
466            camo::VariantContent::Unit => Self(Type::Literal(LiteralType::String(
467                variant_renamer.rename_type(variant.name),
468            ))),
469            camo::VariantContent::Unnamed(ty) => Self(Type::Object(ObjectType {
470                fields: Vec::from([Field {
471                    name: variant_renamer.rename_type(variant.name),
472                    ty: Type::from(ty),
473                    optional: false,
474                }]),
475            })),
476            camo::VariantContent::Named(fields) => Self(Type::Object(ObjectType {
477                fields: Vec::from([Field {
478                    name: variant_renamer.rename_type(variant.name),
479                    ty: Type::Object(ObjectType {
480                        fields: fields
481                            .into_iter()
482                            .map(|field| Field {
483                                name: field_renamer.rename_field(field.name),
484                                ty: Type::from(field.ty),
485                                optional: false,
486                            })
487                            .collect(),
488                    }),
489                    optional: false,
490                }]),
491            })),
492        }
493    }
494
495    fn adjacently_tagged(
496        rename_all: Renamer,
497        tag: &'static str,
498        content: &'static str,
499        variant: camo::Variant,
500    ) -> Self {
501        let variant_renamer = match variant.attributes.rename {
502            Some(rename) => Renamer(Some(rename)),
503            None => rename_all,
504        };
505        let field_renamer = Renamer(variant.attributes.rename_all);
506        match variant.content {
507            camo::VariantContent::Unit => Self(Type::Object(ObjectType {
508                fields: Vec::from([Field {
509                    name: String::from(tag),
510                    ty: Type::Literal(LiteralType::String(
511                        variant_renamer.rename_type(variant.name),
512                    )),
513                    optional: false,
514                }]),
515            })),
516            camo::VariantContent::Unnamed(ty) => Self(Type::Object(ObjectType {
517                fields: Vec::from([
518                    Field {
519                        name: String::from(tag),
520                        ty: Type::Literal(LiteralType::String(
521                            variant_renamer.rename_type(variant.name),
522                        )),
523                        optional: false,
524                    },
525                    Field {
526                        name: String::from(content),
527                        ty: Type::from(ty),
528                        optional: false,
529                    },
530                ]),
531            })),
532            camo::VariantContent::Named(fields) => Self(Type::Object(ObjectType {
533                fields: Vec::from([
534                    Field {
535                        name: String::from(tag),
536                        ty: Type::Literal(LiteralType::String(
537                            variant_renamer.rename_type(variant.name),
538                        )),
539                        optional: false,
540                    },
541                    Field {
542                        name: String::from(content),
543                        ty: Type::Object(ObjectType {
544                            fields: fields
545                                .into_iter()
546                                .map(|field| Field {
547                                    name: field_renamer.rename_field(field.name),
548                                    ty: Type::from(field.ty),
549                                    optional: false,
550                                })
551                                .collect(),
552                        }),
553                        optional: false,
554                    },
555                ]),
556            })),
557        }
558    }
559
560    fn internally_tagged(rename_all: Renamer, tag: &'static str, variant: camo::Variant) -> Self {
561        let variant_renamer = match variant.attributes.rename {
562            Some(rename) => Renamer(Some(rename)),
563            None => rename_all,
564        };
565        let field_renamer = Renamer(variant.attributes.rename_all);
566        match variant.content {
567            camo::VariantContent::Unit => Self(Type::Object(ObjectType {
568                fields: Vec::from([Field {
569                    name: String::from(tag),
570                    ty: Type::Literal(LiteralType::String(
571                        variant_renamer.rename_type(variant.name),
572                    )),
573                    optional: false,
574                }]),
575            })),
576            camo::VariantContent::Unnamed(ty) => Self(Type::Intersection(IntersectionType {
577                left: Box::new(Type::Object(ObjectType {
578                    fields: Vec::from([Field {
579                        name: String::from(tag),
580                        ty: Type::Literal(LiteralType::String(
581                            variant_renamer.rename_type(variant.name),
582                        )),
583                        optional: false,
584                    }]),
585                })),
586                right: Box::new(Type::from(ty)),
587            })),
588            camo::VariantContent::Named(fields) => Self(Type::Intersection(IntersectionType {
589                left: Box::new(Type::Object(ObjectType {
590                    fields: Vec::from([Field {
591                        name: String::from(tag),
592                        ty: Type::Literal(LiteralType::String(
593                            variant_renamer.rename_type(variant.name),
594                        )),
595                        optional: false,
596                    }]),
597                })),
598                right: Box::new(Type::Object(ObjectType {
599                    fields: fields
600                        .into_iter()
601                        .map(|field| Field {
602                            name: field_renamer.rename_field(field.name),
603                            ty: Type::from(field.ty),
604                            optional: false,
605                        })
606                        .collect(),
607                })),
608            })),
609        }
610    }
611}
612
613impl fmt::Display for Variant {
614    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
615        write!(f, "{}", self.0)
616    }
617}
618
619/// Represents a type use, e. g. in an interface definition,
620/// function type definition, or type alias.
621#[derive(Clone, Debug, PartialEq)]
622pub enum Type {
623    /// A built-in type like `number` or `string`.
624    Builtin(BuiltinType),
625    /// A path to some type.
626    /// Includes simple names like `MyType`.
627    Path(TypePath),
628    /// An object type.
629    Object(ObjectType),
630    /// A literal type.
631    Literal(LiteralType),
632    /// An array type.
633    Array(ArrayType),
634    /// A union type, combining multiple cases.
635    Union(UnionType),
636    /// An intersection type, combining two types.
637    Intersection(IntersectionType),
638}
639
640impl Type {
641    fn is_union(&self) -> bool {
642        matches!(self, Self::Union(..))
643    }
644}
645
646impl From<&str> for Type {
647    fn from(value: &str) -> Self {
648        Self::Path(TypePath::from(value))
649    }
650}
651
652impl From<String> for Type {
653    fn from(value: String) -> Self {
654        Self::Path(TypePath::from(value))
655    }
656}
657
658impl From<BuiltinType> for Type {
659    fn from(value: BuiltinType) -> Self {
660        Self::Builtin(value)
661    }
662}
663
664impl From<TypePath> for Type {
665    fn from(value: TypePath) -> Self {
666        Self::Path(value)
667    }
668}
669
670impl From<ObjectType> for Type {
671    fn from(value: ObjectType) -> Self {
672        Self::Object(value)
673    }
674}
675
676impl From<LiteralType> for Type {
677    fn from(value: LiteralType) -> Self {
678        Self::Literal(value)
679    }
680}
681
682impl From<ArrayType> for Type {
683    fn from(value: ArrayType) -> Self {
684        Self::Array(value)
685    }
686}
687
688impl From<IntersectionType> for Type {
689    fn from(value: IntersectionType) -> Self {
690        Self::Intersection(value)
691    }
692}
693
694impl From<camo::Type> for Type {
695    fn from(ty: camo::Type) -> Self {
696        match ty {
697            camo::Type::Path(ty) => match camo::BuiltinType::try_from(ty) {
698                Ok(ty) => Type::Builtin(BuiltinType::from(ty)),
699                Err(ty) => {
700                    if let Some(segment) = ty.segments.first() {
701                        match segment.name {
702                            "String" => {
703                                return Type::Builtin(BuiltinType::String);
704                            }
705                            "Vec" => {
706                                let component_ty = match segment.arguments.first().unwrap().clone()
707                                {
708                                    camo::GenericArgument::Type(ty) => ty,
709                                    camo::GenericArgument::Lifetime(_) => {
710                                        panic!("unexpected lifetime argument provided to Vec")
711                                    }
712                                };
713                                return Type::Array(ArrayType::from(Type::from(component_ty)));
714                            }
715                            "Option" => {
716                                let component_ty = match segment.arguments.first().unwrap().clone()
717                                {
718                                    camo::GenericArgument::Type(ty) => ty,
719                                    camo::GenericArgument::Lifetime(_) => {
720                                        panic!("unexpected lifetime argument provided to Option")
721                                    }
722                                };
723                                return Type::Union(UnionType {
724                                    variants: Vec::from([
725                                        Variant(Type::from(component_ty)),
726                                        Variant(Type::Builtin(BuiltinType::Null)),
727                                    ]),
728                                });
729                            }
730                            _ => return Type::Path(TypePath::from(ty)),
731                        }
732                    }
733                    Type::Path(TypePath::from(ty))
734                }
735            },
736            camo::Type::Reference(ty) => {
737                if let camo::Type::Path(path) = &*ty.ty {
738                    if let Some(segment) = path.segments.first() {
739                        if segment.name == "str" {
740                            return Type::Builtin(BuiltinType::String);
741                        }
742                    }
743                }
744                Type::from(*ty.ty)
745            }
746            camo::Type::Slice(ty) => Type::Array(ArrayType::from(ty)),
747            camo::Type::Array(ty) => Type::Array(ArrayType::from(ty)),
748        }
749    }
750}
751
752impl fmt::Display for Type {
753    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
754        match self {
755            Type::Builtin(ty) => write!(f, "{}", ty),
756            Type::Path(ty) => write!(f, "{}", ty),
757            Type::Object(ty) => write!(f, "{}", ty),
758            Type::Literal(ty) => write!(f, "{}", ty),
759            Type::Array(ty) => write!(f, "{}", ty),
760            Type::Union(ty) => write!(f, "{}", ty),
761            Type::Intersection(ty) => write!(f, "{}", ty),
762        }
763    }
764}
765
766/// The built-in types.
767#[derive(Clone, Debug, PartialEq)]
768pub enum BuiltinType {
769    /// The `number` type.
770    Number,
771    /// The `boolean` type.
772    Boolean,
773    /// The `string` type.
774    String,
775    /// The `object` type.
776    Object,
777    /// The `null` type.
778    Null,
779    /// The `undefined` type.
780    Undefined,
781    /// The `never` type.
782    Never,
783    /// The `any` type.
784    Any,
785    /// The `unknown` type.
786    Unknown,
787    /// The `bigint` type.
788    BigInt,
789    /// The `symbol` type.
790    Symbol,
791}
792
793impl BuiltinType {
794    /// The name of the built-in type.
795    pub fn as_str(&self) -> &'static str {
796        match self {
797            BuiltinType::Number => "number",
798            BuiltinType::Boolean => "boolean",
799            BuiltinType::String => "string",
800            BuiltinType::Object => "object",
801            BuiltinType::Null => "null",
802            BuiltinType::Undefined => "undefined",
803            BuiltinType::Never => "never",
804            BuiltinType::Any => "any",
805            BuiltinType::Unknown => "unknown",
806            BuiltinType::BigInt => "bigint",
807            BuiltinType::Symbol => "symbol",
808        }
809    }
810}
811
812impl From<camo::BuiltinType> for BuiltinType {
813    fn from(builtin: camo::BuiltinType) -> Self {
814        match builtin {
815            camo::BuiltinType::Bool => BuiltinType::Boolean,
816            camo::BuiltinType::U8
817            | camo::BuiltinType::U16
818            | camo::BuiltinType::U32
819            | camo::BuiltinType::U64
820            | camo::BuiltinType::U128
821            | camo::BuiltinType::Usize
822            | camo::BuiltinType::I8
823            | camo::BuiltinType::I16
824            | camo::BuiltinType::I32
825            | camo::BuiltinType::I64
826            | camo::BuiltinType::I128
827            | camo::BuiltinType::Isize
828            | camo::BuiltinType::F32
829            | camo::BuiltinType::F64 => BuiltinType::Number,
830            camo::BuiltinType::Char => BuiltinType::String,
831        }
832    }
833}
834
835impl fmt::Display for BuiltinType {
836    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
837        write!(f, "{}", self.as_str())
838    }
839}
840
841/// The name of a type.
842///
843/// Example:
844///
845/// ```ts
846/// const x: types.X = { /* ... */}
847/// //       ^^^^^^^
848/// ```
849#[derive(Clone, Debug, PartialEq)]
850pub struct TypePath {
851    /// The segments of the type name.
852    pub segments: Vec<PathSegment>,
853}
854
855impl From<camo::TypePath> for TypePath {
856    fn from(value: camo::TypePath) -> Self {
857        Self {
858            segments: value.segments.into_iter().map(Into::into).collect(),
859        }
860    }
861}
862
863impl<const N: usize> From<[&str; N]> for TypePath {
864    fn from(value: [&str; N]) -> Self {
865        Self {
866            segments: value
867                .map(|name| PathSegment {
868                    name: name.to_string(),
869                    arguments: Vec::new(),
870                })
871                .to_vec(),
872        }
873    }
874}
875
876impl From<&str> for TypePath {
877    fn from(value: &str) -> Self {
878        Self {
879            segments: Vec::from([PathSegment {
880                name: value.to_string(),
881                arguments: Vec::new(),
882            }]),
883        }
884    }
885}
886
887impl From<String> for TypePath {
888    fn from(value: String) -> Self {
889        Self {
890            segments: Vec::from([PathSegment {
891                name: value,
892                arguments: Vec::new(),
893            }]),
894        }
895    }
896}
897
898impl fmt::Display for TypePath {
899    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
900        let mut iter = self.segments.iter();
901        if let Some(segment) = iter.next() {
902            write!(f, "{}", segment)?;
903        }
904        for segment in iter {
905            write!(f, ".{}", segment)?;
906        }
907        Ok(())
908    }
909}
910
911/// A segment of a type path.
912#[derive(Clone, Debug, PartialEq)]
913pub struct PathSegment {
914    /// The name of the segment.
915    pub name: String,
916    /// The arguments provided to the segment.
917    pub arguments: Vec<Type>,
918}
919
920impl From<camo::PathSegment> for PathSegment {
921    fn from(value: camo::PathSegment) -> Self {
922        Self {
923            name: value.name.to_string(),
924            arguments: value
925                .arguments
926                .into_iter()
927                .filter_map(|argument| match argument {
928                    camo::GenericArgument::Type(ty) => Some(Type::from(ty)),
929                    camo::GenericArgument::Lifetime(_) => None,
930                })
931                .collect(),
932        }
933    }
934}
935
936impl fmt::Display for PathSegment {
937    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
938        write!(f, "{}", self.name)?;
939        if !self.arguments.is_empty() {
940            write!(f, "<")?;
941            let mut iter = self.arguments.iter();
942            if let Some(argument) = iter.next() {
943                write!(f, "{}", argument)?;
944            }
945            for argument in iter {
946                write!(f, ", {}", argument)?;
947            }
948            write!(f, ">")?;
949        }
950        Ok(())
951    }
952}
953
954/// An object type.
955///
956/// See: <https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#object-types>
957#[derive(Clone, Debug, PartialEq)]
958pub struct ObjectType {
959    /// The fields of the object type.
960    pub fields: Vec<Field>,
961}
962
963impl fmt::Display for ObjectType {
964    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
965        write!(f, "{{")?;
966        for field in &self.fields {
967            write!(f, " {}", field)?;
968        }
969        write!(f, " }}")
970    }
971}
972
973/// A literal type.
974///
975/// Example:
976/// ```ts
977/// type Tag = "Tag";
978/// ```
979///
980/// See: <https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types>
981#[derive(Clone, Debug, PartialEq)]
982pub enum LiteralType {
983    /// A string literal type.
984    String(String),
985}
986
987impl fmt::Display for LiteralType {
988    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
989        match self {
990            LiteralType::String(s) => write!(f, "\"{}\"", s),
991        }
992    }
993}
994
995/// An array type expression.
996///
997/// Example:
998/// ```ts
999/// type Numbers = number[];
1000/// ```
1001#[derive(Clone, Debug, PartialEq)]
1002pub struct ArrayType(pub Box<Type>);
1003
1004impl From<Type> for ArrayType {
1005    fn from(value: Type) -> Self {
1006        Self(Box::new(value))
1007    }
1008}
1009
1010impl From<camo::SliceType> for ArrayType {
1011    fn from(value: camo::SliceType) -> Self {
1012        Self(Box::new(Type::from(*value.0)))
1013    }
1014}
1015
1016impl From<camo::ArrayType> for ArrayType {
1017    fn from(value: camo::ArrayType) -> Self {
1018        Self(Box::new(Type::from(*value.0)))
1019    }
1020}
1021
1022impl fmt::Display for ArrayType {
1023    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1024        write!(f, "{}[]", self.0)
1025    }
1026}
1027
1028/// An intersection type.
1029///
1030/// Example:
1031/// ```ts
1032/// type T = { name: string } & { value: number };
1033/// //        -------------   ^  ----------------
1034/// ```
1035///
1036/// See: <https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types>
1037#[derive(Clone, Debug, PartialEq)]
1038pub struct IntersectionType {
1039    /// The left-hand side of the intersection.
1040    pub left: Box<Type>,
1041    /// The right-hand side of the intersection.
1042    pub right: Box<Type>,
1043}
1044
1045impl fmt::Display for IntersectionType {
1046    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1047        write!(f, "{} & {}", self.left, self.right)
1048    }
1049}