ts_type/
lib.rs

1use std::{fmt, panic::Location, str::FromStr, vec};
2
3mod macros;
4
5use lazy_static::lazy_static;
6use regex::Regex;
7use syn::{GenericArgument, PathArguments, Type};
8
9lazy_static! {
10    /// A regex to match an `Option` type and capture the inner type.
11    static ref OPTION_REGEX: Regex = Regex::new(r"^Option<(.+)>$").unwrap();
12    /// A regex to match a `Vec` type and capture the inner type.
13    static ref VEC_REGEX: Regex = Regex::new(r"^Vec<(.+)>$").unwrap();
14}
15
16/// A TypeScript type.
17///
18/// This type is used to represent TypeScript types in a Rust-friendly way.
19///
20/// Note: This is not an exhaustive representation of TypeScript types.
21#[derive(Clone, Eq, PartialEq, Hash)]
22pub enum TsType {
23    /// A single type, e.g., `string`.
24    Base(String),
25    Paren(Box<TsType>),
26    Array(Box<TsType>),
27    Tuple(Vec<TsType>),
28    Union(Vec<TsType>),
29    Intersection(Vec<TsType>),
30    /// A generic type with arguments, e.g., `Set<string, number>`.
31    Generic(Box<TsType>, Vec<TsType>),
32    /// A type with an indexing key type, e.g., `Car["make"]`.
33    IndexedAccess(Box<TsType>, Box<TsType>),
34}
35
36impl Default for TsType {
37    fn default() -> Self {
38        TsType::Base("any".to_string())
39    }
40}
41
42// Error //
43
44pub struct TsTypeError {
45    pub message: String,
46    pub location: String,
47}
48
49impl fmt::Display for TsTypeError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        write!(
52            f,
53            "TypeError: {}\n    Location: {}",
54            self.message, self.location
55        )
56    }
57}
58
59impl fmt::Debug for TsTypeError {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(
62            f,
63            "TypeError: {}\n    Location: {}",
64            self.message, self.location
65        )
66    }
67}
68
69// Methods //
70
71impl TsType {
72    /// Find out if this type is a union with another type
73    pub fn is_union_with(&self, other: &Self) -> bool {
74        match self {
75            Self::Union(types) => types.iter().any(|ty| ty == other),
76            _ => false,
77        }
78    }
79
80    /// Find out if this type contains another type e.g. `(string | number)[]`
81    /// contains `string`
82    pub fn contains(&self, other: &Self) -> bool {
83        if self == other {
84            return true;
85        }
86        match self {
87            Self::Base(name) => match other {
88                // compare the names of 2 base types
89                Self::Base(other_name) => name == other_name,
90                // a base type can't contain anything other than itself
91                _ => false,
92            },
93            Self::Paren(inner) => inner.contains(other),
94            Self::Array(inner) => inner.contains(other),
95            Self::Tuple(types) => types.iter().any(|ty| ty.contains(other)),
96            Self::Union(types) => types.iter().any(|ty| ty.contains(other)),
97            Self::Intersection(types) => types.iter().any(|ty| ty.contains(other)),
98            Self::Generic(base, args) => {
99                base.contains(other) || args.iter().any(|arg| arg.contains(other))
100            }
101            Self::IndexedAccess(base, key) => base.contains(other) || key.contains(other),
102        }
103    }
104
105    /// convert this type to a generic with arguments
106    pub fn as_generic(self, args: Vec<Self>) -> Self {
107        match self {
108            Self::Base(_) => Self::Generic(Box::new(self), args),
109            Self::IndexedAccess(_, _) => Self::Generic(Box::new(self), args),
110            _ => panic!("Type can't be generic."),
111        }
112    }
113
114    /// convert this type to an indexed access type with a key
115    pub fn property(self, key: Self) -> Self {
116        Self::IndexedAccess(Box::new(self), Box::new(key))
117    }
118
119    /// place this type in an array
120    pub fn in_array(self) -> Self {
121        Self::Array(Box::new(self))
122    }
123
124    /// place this type in parentheses
125    pub fn in_parens(self) -> Self {
126        match self {
127            Self::Intersection(_) => Self::Paren(Box::new(self)),
128            Self::Union(_) => Self::Paren(Box::new(self)),
129            // parens have no effect on base types
130            _ => self,
131        }
132    }
133
134    /// union this type with another
135    pub fn or(self, other: Self) -> Self {
136        match self {
137            Self::Union(mut types) => match other {
138                // a | b or c | d -> a | b | c | d
139                Self::Union(mut other_types) => {
140                    types.append(&mut other_types);
141                    Self::Union(types)
142                }
143                // a | b or _ => a | b | _
144                _ => {
145                    types.push(other);
146                    Self::Union(types)
147                }
148            },
149            _ => match other {
150                // _ or a | b -> _ | a | b
151                Self::Union(mut other_types) => {
152                    other_types.insert(0, self);
153                    Self::Union(other_types)
154                }
155                // _ or _ -> _ | _
156                _ => Self::Union(vec![self, other]),
157            },
158        }
159    }
160
161    /// intersect this type with another
162    pub fn and(self, other: Self) -> Self {
163        match self {
164            Self::Intersection(mut types) => match other {
165                // a & b and c & d -> a & b & c & d
166                Self::Intersection(mut other_types) => {
167                    types.append(&mut other_types);
168                    Self::Intersection(types)
169                }
170                // a & b and _ -> a & b & _
171                _ => {
172                    types.push(other);
173                    Self::Intersection(types)
174                }
175            },
176            _ => match other {
177                // _ and a & b -> _ & a & b
178                Self::Intersection(mut types) => {
179                    types.insert(0, self);
180                    Self::Intersection(types)
181                }
182                // _ and _ -> _ & _
183                _ => Self::Intersection(vec![self, other]),
184            },
185        }
186    }
187
188    /// Attempts to join this type with a right hand type.
189    /// This is used during parsing and may not be intuitive for general use.
190    pub fn join(self, other: Self) -> Result<Self, &'static str> {
191        match self {
192            Self::Base(mut str) => match other {
193                // a join b -> ab
194                Self::Base(other_str) => {
195                    str.push_str(&other_str);
196                    Ok(Self::Base(str))
197                }
198                // a join _ => _ join a
199                _ => other.join(Self::Base(str)),
200            },
201            // a<b> join c -> a<b, c>
202            Self::Generic(ty, mut args) => {
203                args.push(other);
204                Ok(Self::Generic(ty, args))
205            }
206            // a[b | c] join d -> a[b | c | d]
207            Self::IndexedAccess(object, key) => {
208                let key_inner = *key;
209                Ok(Self::IndexedAccess(
210                    object,
211                    Box::new(key_inner.join(other)?),
212                ))
213            }
214            Self::Union(mut types) => {
215                match other {
216                    // a | b join c | d -> a | b | c | d
217                    Self::Union(mut other_types) => {
218                        types.append(&mut other_types);
219                        Ok(Self::Union(types))
220                    }
221                    // a | b join _ => a | b | _
222                    _ => {
223                        types.push(other);
224                        Ok(Self::Union(types))
225                    }
226                }
227            }
228            Self::Intersection(mut types) => {
229                match other {
230                    // a & b join c & d -> a & b & c & d
231                    Self::Intersection(mut other_types) => {
232                        types.append(&mut other_types);
233                        Ok(Self::Intersection(types))
234                    }
235                    // a & b join c | d -> (a & b & c) | d
236                    Self::Union(mut union_types) => {
237                        let first_member = union_types.remove(0);
238                        let intersection = Self::Intersection(types);
239                        let intersected_member = intersection.and(first_member);
240                        union_types.insert(0, intersected_member);
241                        Ok(Self::Union(union_types))
242                    }
243                    // a & b join _ -> a & b & _
244                    _ => {
245                        types.push(other);
246                        Ok(Self::Intersection(types))
247                    }
248                }
249            }
250            // [a, b] join c -> [a, b, c]
251            Self::Tuple(mut types) => {
252                types.push(other);
253                Ok(Self::Tuple(types))
254            }
255            // (a | b) join c -> (a | b | c)
256            Self::Paren(inner) => inner.join(other),
257            _ => Err("Type does not support joining."),
258        }
259    }
260
261    /// Attempts to parse a TypeScript type from a typescript string.
262    ///
263    /// Returns a [`TsTypeError`] if the string is empty or contains an unexpected character.
264    ///
265    /// # Example
266    ///
267    /// ```
268    /// use ts_type::TsType;
269    ///
270    /// let type1 = TsType::from_ts_str("string | number").unwrap();
271    /// let type2 = TsType::Union(vec![
272    ///     TsType::Base("string".to_string()),
273    ///     TsType::Base("number".to_string()),
274    /// ])
275    /// assert_eq!(type1, type2);
276    /// ```
277    #[track_caller]
278    pub fn from_ts_str(str: &str) -> Result<Self, TsTypeError> {
279        let location = Location::caller();
280
281        if str.is_empty() {
282            return Err(type_error_at!(location, "Empty string."));
283        }
284
285        let mut stacks: Vec<Vec<Self>> = vec![];
286        let mut pending_stack: Vec<Self> = vec![];
287        let mut pending_type: Option<Self> = None;
288        let mut ambiguous_bracket = false;
289        let chars = str.trim().chars();
290
291        for char in chars {
292            if ambiguous_bracket && char != ']' {
293                pending_stack.push(pending_type.unwrap().property(TsType::Base("".to_string())));
294                pending_type = None;
295                ambiguous_bracket = false;
296            }
297            match char {
298                ' ' => continue,
299                '|' => {
300                    // push pending as union or an empty union since types can
301                    // start with `|`
302                    let member = match pending_type {
303                        Some(ty) => vec![ty],
304                        None => vec![],
305                    };
306                    let mut _union = Self::Union(member);
307                    pending_stack.push(_union);
308                    pending_type = None;
309                }
310                '&' => {
311                    // push pending as union or an empty union since types can
312                    // start with `&`
313                    let member = match pending_type {
314                        Some(ty) => vec![ty],
315                        None => vec![],
316                    };
317                    let intersection = Self::Intersection(member);
318                    pending_stack.push(intersection);
319                    pending_type = None;
320                }
321                '<' => {
322                    // push pending as generic
323                    if pending_type.is_none() {
324                        return Err(type_error_at!(location, "Unexpected `<` found."));
325                    }
326                    let inner = pending_type.unwrap();
327                    let generic = inner.as_generic(vec![]);
328                    pending_stack.push(generic);
329                    pending_type = None;
330                }
331                ',' => {
332                    // collapse the stack to the nearest comma delimited type,
333                    // joining the pending type with the top of the stack
334                    if pending_type.is_none() {
335                        return Err(type_error_at!(location, "Unexpected `,` found."));
336                    }
337                    let mut inner = pending_type.unwrap();
338
339                    loop {
340                        let top = pending_stack.pop().unwrap();
341                        inner = top.join(inner).unwrap();
342
343                        match inner {
344                            Self::Generic(_, _) => break,
345                            Self::IndexedAccess(_, _) => break,
346                            Self::Tuple(_) => break,
347                            _ => {}
348                        }
349
350                        if pending_stack.is_empty() {
351                            return Err(type_error_at!(location, "Unexpected `,` found."));
352                        }
353                    }
354                    pending_stack.push(inner);
355                    pending_type = None;
356                }
357                '>' => {
358                    // collapse the stack to the nearest generic, joining the
359                    // pending type with the top of the stack
360                    if pending_type.is_none() {
361                        return Err(type_error_at!(location, "Unexpected `>` found."));
362                    };
363                    let mut ty = pending_type.unwrap();
364                    loop {
365                        let top = pending_stack.pop().unwrap();
366                        ty = top.join(ty).unwrap();
367
368                        if let Self::Generic(_, _) = ty {
369                            break;
370                        }
371
372                        if pending_stack.is_empty() {
373                            return Err(type_error_at!(location, "Unexpected `,` found."));
374                        }
375                    }
376                    pending_type = Some(ty);
377                }
378                '[' => {
379                    if pending_type.is_none() {
380                        // If there's no pending type, open a tuple
381                        let tuple = Self::Tuple(vec![]);
382                        pending_stack.push(tuple);
383                    } else {
384                        // Otherwise, we'll have to wait to see if it's the
385                        // start of an array or an indexed access type
386                        ambiguous_bracket = true;
387                    }
388                }
389                ']' => {
390                    if pending_type.is_none() {
391                        return Err(type_error_at!(location, "Unexpected `]` found."));
392                    };
393                    let mut ty = pending_type.unwrap();
394
395                    // If there's an ambiguous bracket, it's an array, otherwise
396                    // it's the end of a bracketed type on the stack.
397                    if ambiguous_bracket {
398                        pending_type = Some(ty.in_array());
399                        ambiguous_bracket = false;
400                    } else {
401                        loop {
402                            let top = pending_stack.pop().unwrap();
403                            ty = top.join(ty).unwrap();
404
405                            match ty {
406                                Self::IndexedAccess(_, _) => break,
407                                Self::Tuple(_) => break,
408                                _ => {}
409                            }
410
411                            if pending_stack.is_empty() {
412                                return Err(type_error_at!(location, "Unexpected `]` found."));
413                            }
414                        }
415                        pending_type = Some(ty);
416                    }
417                }
418                '(' => {
419                    if pending_type.is_some() {
420                        return Err(type_error_at!(location, "Unexpected `(` found."));
421                    };
422
423                    // push the pending stack and start a new one for the nested type
424                    stacks.push(pending_stack);
425                    pending_stack = vec![];
426                }
427                ')' => {
428                    // collapse all pending types and pop the stack to exit the
429                    // nested type
430                    if pending_type.is_none() {
431                        return Err(type_error_at!(location, "Unexpected `)` found."));
432                    };
433                    let mut inner = pending_type.unwrap();
434
435                    for _ in 0..pending_stack.len() {
436                        let top = pending_stack.pop().unwrap();
437                        inner = top.join(inner).unwrap();
438                    }
439
440                    pending_type = Some(inner.in_parens());
441                    pending_stack = stacks.pop().unwrap();
442                }
443                part => {
444                    match pending_type {
445                        Some(pending) => {
446                            // If there's a pending type, join it with the next part
447                            let next = Self::Base(part.to_string());
448                            pending_type = Some(pending.join(next).unwrap());
449                        }
450                        None => {
451                            // Otherwise, start a new pending type
452                            pending_type = Some(Self::Base(part.to_string()));
453                        }
454                    }
455                }
456            }
457        }
458
459        let mut final_ty = pending_type.unwrap_or_else(|| pending_stack.pop().unwrap());
460
461        while let Some(top) = pending_stack.pop() {
462            final_ty = top.join(final_ty).unwrap();
463        }
464
465        Ok(final_ty)
466    }
467}
468
469// Formatting //
470
471impl fmt::Display for TsType {
472    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473        match self {
474            TsType::Base(name) => write!(f, "{}", name.trim()),
475            TsType::Array(ty) => match ty.as_ref() {
476                // Wrap the inner type in parentheses if it's a union or intersection
477                // a | b[] -> (a | b)[]
478                TsType::Union(_) => write!(f, "({})[]", ty),
479                TsType::Intersection(_) => write!(f, "({})[]", ty),
480                _ => write!(f, "{}[]", ty.to_string()),
481            },
482            TsType::Paren(ty) => write!(f, "({})", ty.to_string()),
483            TsType::IndexedAccess(ty, key_ty) => {
484                write!(f, "{}[{}]", ty.to_string(), key_ty.to_string())
485            }
486            TsType::Generic(name, args) => {
487                let args = args
488                    .iter()
489                    .map(|ty| ty.to_string())
490                    .collect::<Vec<_>>()
491                    .join(", ");
492                write!(f, "{}<{}>", name, args)
493            }
494            TsType::Union(types) => {
495                let types = types
496                    .iter()
497                    .map(|ty| match ty {
498                        // Wrap the inner type in parentheses if it's a intersection.
499                        // a & b | c -> (a & b) | c
500                        TsType::Intersection(_) => format!("({})", ty),
501                        _ => ty.to_string(),
502                    })
503                    .collect::<Vec<_>>()
504                    .join(" | ");
505                write!(f, "{}", types)
506            }
507            TsType::Intersection(types) => {
508                let types = types
509                    .iter()
510                    .map(|ty| ty.to_string())
511                    .collect::<Vec<_>>()
512                    .join(" & ");
513                write!(f, "{}", types)
514            }
515            TsType::Tuple(types) => {
516                let types = types
517                    .iter()
518                    .map(|ty| ty.to_string())
519                    .collect::<Vec<_>>()
520                    .join(", ");
521                write!(f, "[{}]", types)
522            }
523        }
524    }
525}
526
527impl fmt::Debug for TsType {
528    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529        match self {
530            TsType::Base(name) => write!(f, "Base({})", name.trim()),
531            TsType::Array(ty) => write!(f, "Array({:?})", ty),
532            TsType::Paren(ty) => write!(f, "Paren({:?})", ty),
533            TsType::IndexedAccess(ty, key_ty) => {
534                write!(f, "IndexedAccess({:?}[{:?}])", ty, key_ty)
535            }
536            TsType::Generic(name, args) => {
537                write!(f, "Generic(")?;
538                write!(f, "{:?}<", name)?;
539
540                for (i, arg) in args.iter().enumerate() {
541                    write!(f, "{:?}", arg)?;
542                    if i < args.len() - 1 {
543                        write!(f, ", ")?;
544                    }
545                }
546
547                write!(f, ">)")
548            }
549            TsType::Union(types) => {
550                write!(f, "Union(")?;
551
552                for (i, ty) in types.iter().enumerate() {
553                    write!(f, "{:?}", ty)?;
554                    if i < types.len() - 1 {
555                        write!(f, " | ")?;
556                    }
557                }
558
559                write!(f, ")")
560            }
561            TsType::Intersection(types) => {
562                write!(f, "Intersection(")?;
563
564                for (i, ty) in types.iter().enumerate() {
565                    write!(f, "{:?}", ty)?;
566                    if i < types.len() - 1 {
567                        write!(f, " & ")?;
568                    }
569                }
570
571                write!(f, ")")
572            }
573            TsType::Tuple(types) => {
574                write!(f, "Tuple([")?;
575
576                for (i, ty) in types.iter().enumerate() {
577                    write!(f, "{:?}", ty)?;
578                    if i < types.len() - 1 {
579                        write!(f, ", ")?;
580                    }
581                }
582
583                write!(f, "])")
584            }
585        }
586    }
587}
588
589// Conversions //
590
591impl TryFrom<&Type> for TsType {
592    type Error = TsTypeError;
593
594    #[track_caller]
595    fn try_from(ty: &Type) -> Result<Self, Self::Error> {
596        let rust_type_str = strip_type(ty)?;
597
598        // If it can be matched to a simple type, return the match
599        if let Some(ts_type) = match_simple_type(&rust_type_str) {
600            return Ok(ts_type);
601        }
602
603        // If the type is an Option, convert it to a union with `undefined`
604        if let Some(captures) = OPTION_REGEX.captures(&rust_type_str) {
605            let inner_rust_type_str = &captures[1];
606            let ts_type = match_simple_type(inner_rust_type_str)
607                .unwrap_or(TsType::from_str(inner_rust_type_str)?);
608            return Ok(ts_type.or(ts_type!(undefined)));
609        }
610
611        // If the type is a Vec, convert it to an array
612        if let Some(captures) = VEC_REGEX.captures(&rust_type_str) {
613            let inner_rust_type_str = &captures[1];
614            let ts_type = match_simple_type(inner_rust_type_str)
615                .unwrap_or(TsType::from_str(inner_rust_type_str)?);
616            return Ok(ts_type.in_array());
617        }
618
619        // If no supported match is found, attempt to parse the type as a
620        // TypeScript type
621        TsType::from_ts_str(&rust_type_str)
622    }
623}
624
625impl FromStr for TsType {
626    type Err = TsTypeError;
627
628    #[track_caller]
629    fn from_str(s: &str) -> Result<Self, Self::Err> {
630        TsType::from_ts_str(s)
631    }
632}
633
634pub trait ToTsType {
635    fn to_ts_type(&self) -> Result<TsType, TsTypeError>;
636}
637
638impl ToTsType for Type {
639    #[track_caller]
640    fn to_ts_type(&self) -> Result<TsType, TsTypeError> {
641        TsType::try_from(self)
642    }
643}
644
645impl ToTsType for &Type {
646    #[track_caller]
647    fn to_ts_type(&self) -> Result<TsType, TsTypeError> {
648        TsType::try_from(*self)
649    }
650}
651
652impl ToTsType for &str {
653    #[track_caller]
654    fn to_ts_type(&self) -> Result<TsType, TsTypeError> {
655        TsType::from_str(self)
656    }
657}
658
659impl ToTsType for String {
660    #[track_caller]
661    fn to_ts_type(&self) -> Result<TsType, TsTypeError> {
662        TsType::from_str(self.as_str())
663    }
664}
665
666// Internal helpers //
667
668/// Strips references, pointers, and paths to get the base type string of a Rust type.
669#[track_caller]
670fn strip_type(ty: &Type) -> Result<String, TsTypeError> {
671    let location = Location::caller();
672    match ty {
673        Type::Group(group) => strip_type(&group.elem),
674        Type::Paren(paren) => strip_type(&paren.elem),
675        Type::Ptr(ptr) => strip_type(&ptr.elem),
676        Type::Reference(reference) => strip_type(&reference.elem),
677        Type::Slice(type_slice) => Ok(format!("[{}]", strip_type(&type_slice.elem)?)),
678        Type::Array(type_array) => Ok(format!("[{}; _]", strip_type(&type_array.elem)?)),
679        Type::Tuple(tuple) => {
680            if tuple.elems.is_empty() {
681                Ok("()".to_string())
682            } else {
683                let types = tuple
684                    .elems
685                    .iter()
686                    .map(|elem| strip_type(elem))
687                    .collect::<Result<Vec<_>, _>>()?
688                    .join(", ");
689                Ok(format!("({})", types))
690            }
691        }
692        Type::Path(path) => {
693            let last_segment = path
694                .path
695                .segments
696                .last()
697                .ok_or_else(|| type_error_at!(location, "No segments found"))?;
698            let outer_type = last_segment.ident.to_string();
699
700            if last_segment.arguments.is_empty() {
701                Ok(outer_type)
702            } else {
703                let arguments = match &last_segment.arguments {
704                    PathArguments::AngleBracketed(angle) => {
705                        let args = angle
706                            .args
707                            .iter()
708                            .map(|arg| match arg {
709                                GenericArgument::Type(ty) => strip_type(ty),
710                                _ => Err(type_error_at!(location, "Unsupported type argument.",)),
711                            })
712                            .collect::<Result<Vec<_>, _>>()?
713                            .join(", ");
714                        format!("<{}>", args)
715                    }
716                    PathArguments::Parenthesized(paren) => {
717                        let inputs = paren
718                            .inputs
719                            .iter()
720                            .map(strip_type)
721                            .collect::<Result<Vec<_>, _>>()?
722                            .join(", ");
723                        format!("({})", inputs)
724                    }
725                    _ => String::new(),
726                };
727
728                Ok(format!("{}{}", last_segment.ident, arguments))
729            }
730        }
731
732        _ => Err(type_error_at!(location, "Unsupported type.")),
733    }
734}
735
736/// Matches a "simple" Rust type to a TypeScript type.
737///
738/// Simple types are those that have a direct equivalent in TypeScript:
739/// - Native types, e.g., `bool` -> `boolean`
740/// - Ethers types, e.g., `U256` -> `bigint`
741/// - `js-sys` types, e.g., `BigInt` -> `bigint`
742/// - Other known types, e.g., `FixedPoint` -> `bigint`
743fn match_simple_type(rust_type: &str) -> Option<TsType> {
744    let simple_match = match rust_type {
745        // native types
746        "bool" => ts_type!(boolean),
747        "String" | "str" | "char" => ts_type!(string),
748        // Max safe JS integer: -(2^53 - 1) to 2^53 - 1 (double-precision float)
749        "u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "f32" | "f64" => ts_type!(number),
750        "u64" | "i64" | "u128" | "i128" => ts_type!(bigint),
751
752        // ethers types
753        "U256" | "I256" => ts_type!(bigint),
754        "Address" => TsType::from_ts_str("`0x${string}`").unwrap(),
755
756        // js_sys types
757        "BigInt" => ts_type!(bigint),
758        "Boolean" => ts_type!(boolean),
759        "JsString" => ts_type!(string),
760        "Number" => ts_type!(number),
761        "Object" => ts_type!(object),
762
763        // other known types
764        "FixedPoint" => ts_type!(bigint),
765
766        // No simple match found
767        _ => return None,
768    };
769    Some(simple_match)
770}
771
772#[cfg(test)]
773mod tests {
774    use super::*;
775
776    #[test]
777    fn test_contains() {
778        let base = ts_type!(string);
779        assert!(base.contains(&ts_type!(string)));
780        assert!(!base.contains(&ts_type!(number)));
781
782        let paren = ts_type!((string));
783        assert!(paren.contains(&ts_type!((string))));
784        assert!(paren.contains(&ts_type!(string)));
785        assert!(!paren.contains(&ts_type!(number)));
786
787        let array = ts_type!(string[]);
788        assert!(array.contains(&ts_type!(string[])));
789        assert!(array.contains(&ts_type!(string)));
790        assert!(!array.contains(&ts_type!(number)));
791
792        let tuple = ts_type!([string, number]);
793        assert!(tuple.contains(&ts_type!([string, number])));
794        assert!(tuple.contains(&ts_type!(string)));
795        assert!(tuple.contains(&ts_type!(number)));
796        assert!(!tuple.contains(&ts_type!(boolean)));
797
798        let union = ts_type!(string | number);
799        assert!(union.contains(&ts_type!(string | number)));
800        assert!(union.contains(&ts_type!(string)));
801        assert!(union.contains(&ts_type!(number)));
802        assert!(!union.contains(&ts_type!(boolean)));
803
804        let intersection = ts_type!(string & number);
805        assert!(intersection.contains(&ts_type!(string & number)));
806        assert!(intersection.contains(&ts_type!(string)));
807        assert!(intersection.contains(&ts_type!(number)));
808        assert!(!intersection.contains(&ts_type!(boolean)));
809
810        let generic = ts_type!(Set<string, number>);
811        assert!(generic.contains(&ts_type!(Set<string, number>)));
812        assert!(generic.contains(&ts_type!(Set)));
813        assert!(generic.contains(&ts_type!(string)));
814        assert!(generic.contains(&ts_type!(number)));
815        assert!(!generic.contains(&ts_type!(boolean)));
816
817        let indexed_access = ts_type!(Car[string]);
818        assert!(indexed_access.contains(&ts_type!(Car[string])));
819        assert!(indexed_access.contains(&ts_type!(Car)));
820        assert!(indexed_access.contains(&ts_type!(string)));
821        assert!(!indexed_access.contains(&ts_type!(number)));
822    }
823
824    #[test]
825    fn test_formatting() {
826        let base = ts_type!(string);
827        assert_eq!(base.to_string(), "string");
828
829        #[rustfmt::skip]
830        let paren = ts_type!((  string |     number ));
831        assert_eq!(paren.to_string(), "(string | number)");
832
833        #[rustfmt::skip]
834        let array = ts_type!(string  [  ]);
835        assert_eq!(array.to_string(), "string[]");
836
837        #[rustfmt::skip]
838        let generic = ts_type!(Set< string,   number >);
839        assert_eq!(generic.to_string(), "Set<string, number>");
840
841        #[rustfmt::skip]
842        let _union = ts_type!(
843            | string
844            | number
845            | boolean
846        );
847        assert_eq!(_union.to_string(), "string | number | boolean");
848
849        #[rustfmt::skip]
850        let intersection = ts_type!(  Foo   &  Bar &     Baz);
851        assert_eq!(intersection.to_string(), "Foo & Bar & Baz");
852
853        #[rustfmt::skip]
854        let wrapped_intersection = ts_type!(Foo |  Bar   & Baz);
855        assert_eq!(wrapped_intersection.to_string(), "Foo | (Bar & Baz)");
856
857        let template_string = TsType::from_ts_str("`0x${string}`");
858        assert_eq!(template_string.unwrap().to_string(), "`0x${string}`");
859    }
860
861    #[test]
862    fn test_variable_parsing() {
863        let base = ts_type!(string);
864        let generic = ts_type!(Set<string>);
865        let group = ts_type!((string | number));
866        let intersection = ts_type!(string & number);
867        let _union = ts_type!(string | number);
868
869        //  Single variable //
870
871        let single = ts_type!((#base));
872        assert_eq!(single.to_string(), "string",);
873
874        let single_generic = ts_type!((#generic));
875        assert_eq!(single_generic.to_string(), "Set<string>");
876
877        let single_group = ts_type!((#group));
878        assert_eq!(single_group.to_string(), "(string | number)");
879
880        let single_intersection = ts_type!((#intersection));
881        assert_eq!(single_intersection.to_string(), "string & number");
882
883        let single_union = ts_type!((#_union));
884        assert_eq!(single_union.to_string(), "string | number");
885
886        // Generics //
887
888        let generic = ts_type!(Set<(#base)>);
889        assert_eq!(generic.to_string(), "Set<string>");
890
891        let generic_two = ts_type!(Set<(#base), (#_union)>);
892        assert_eq!(generic_two.to_string(), "Set<string, string | number>");
893
894        // Unions //
895
896        let start_union = ts_type!((#base) | true | false);
897        assert_eq!(start_union.to_string(), "string | true | false");
898
899        let mid_union = ts_type!(true | (#base) | false);
900        assert_eq!(mid_union.to_string(), "true | string | false");
901
902        let end_union = ts_type!(true | false | (#base));
903        assert_eq!(end_union.to_string(), "true | false | string");
904
905        let start_union_pair = ts_type!((#base) | true);
906        assert_eq!(start_union_pair.to_string(), "string | true");
907
908        let end_union_pair = ts_type!(true | (#base));
909        assert_eq!(end_union_pair.to_string(), "true | string");
910
911        let var_union = ts_type!((#base) | (#generic) | (#group));
912        assert_eq!(
913            var_union.to_string(),
914            "string | Set<string> | (string | number)"
915        );
916
917        let var_union_pair = ts_type!((#base) | (#generic));
918        assert_eq!(var_union_pair.to_string(), "string | Set<string>");
919
920        // Intersections //
921
922        let start_intersection = ts_type!((#base) & true & false);
923        assert_eq!(start_intersection.to_string(), "string & true & false");
924
925        let mid_intersection = ts_type!(true & (#base) & false);
926        assert_eq!(mid_intersection.to_string(), "true & string & false");
927
928        let end_intersection = ts_type!(true & false & (#base));
929        assert_eq!(end_intersection.to_string(), "true & false & string");
930
931        let start_intersection_pair = ts_type!((#base) & true);
932        assert_eq!(start_intersection_pair.to_string(), "string & true");
933
934        let end_intersection_pair = ts_type!(true & (#base));
935        assert_eq!(end_intersection_pair.to_string(), "true & string");
936
937        let var_intersection = ts_type!((#base) & (#generic) & (#group));
938        assert_eq!(
939            var_intersection.to_string(),
940            "string & Set<string> & (string | number)"
941        );
942
943        let var_intersection_pair = ts_type!((#base) & (#generic));
944        assert_eq!(var_intersection_pair.to_string(), "string & Set<string>");
945    }
946}