oxc_ast/ast_impl/
ts.rs

1//! TypeScript Definitions
2//!
3//! [AST Spec](https://github.com/typescript-eslint/typescript-eslint/tree/v8.9.0/packages/ast-spec)
4//! [Archived TypeScript spec](https://github.com/microsoft/TypeScript/blob/3c99d50da5a579d9fa92d02664b1b66d4ff55944/doc/spec-ARCHIVED.md)
5#![warn(missing_docs)]
6
7use std::fmt;
8
9use oxc_span::Atom;
10
11use crate::ast::*;
12
13impl<'a> TSEnumMemberName<'a> {
14    /// Get the name of this enum member.
15    /// # Panics
16    /// Panics if `self` is a `TemplateString` with no quasi.
17    pub fn static_name(&self) -> Atom<'a> {
18        match self {
19            Self::Identifier(ident) => ident.name,
20            Self::String(lit) | Self::ComputedString(lit) => lit.value,
21            Self::ComputedTemplateString(template) => template
22                .single_quasi()
23                .expect("`TSEnumMemberName::TemplateString` should have no substitution and at least one quasi"),
24        }
25    }
26}
27
28impl<'a> TSType<'a> {
29    /// Get the first identifier reference in this type.
30    ///
31    /// For qualified (i.e.  namespaced) types, the left-most identifier is
32    /// returned.
33    pub fn get_identifier_reference(&self) -> Option<&IdentifierReference<'a>> {
34        match self {
35            TSType::TSTypeReference(reference) => reference.type_name.get_identifier_reference(),
36            TSType::TSTypeQuery(query) => match &query.expr_name {
37                TSTypeQueryExprName::IdentifierReference(ident) => Some(ident),
38                _ => None,
39            },
40            _ => None,
41        }
42    }
43
44    /// Returns `true` if this type is a type reference to `const`.
45    pub fn is_const_type_reference(&self) -> bool {
46        matches!(self, TSType::TSTypeReference(reference) if reference.type_name.is_const())
47    }
48
49    /// Check if type maybe `undefined`
50    pub fn is_maybe_undefined(&self) -> bool {
51        match self {
52            TSType::TSAnyKeyword(_)
53            | TSType::TSUnknownKeyword(_)
54            | TSType::TSUndefinedKeyword(_) => true,
55            TSType::TSUnionType(un) => un.types.iter().any(Self::is_maybe_undefined),
56            _ => false,
57        }
58    }
59
60    /// Returns `true` if this is a keyword type (e.g. `number`, `any`, `string`).
61    #[rustfmt::skip]
62    pub fn is_keyword(&self) -> bool {
63        matches!(self, TSType::TSAnyKeyword(_) | TSType::TSBigIntKeyword(_) | TSType::TSBooleanKeyword(_)
64                | TSType::TSNeverKeyword(_) | TSType::TSNullKeyword(_) | TSType::TSNumberKeyword(_)
65                | TSType::TSObjectKeyword(_) | TSType::TSStringKeyword(_)| TSType::TSVoidKeyword(_)
66                | TSType::TSIntrinsicKeyword(_) | TSType::TSSymbolKeyword(_) | TSType::TSThisType(_)
67                | TSType::TSUndefinedKeyword(_) | TSType::TSUnknownKeyword(_)
68        )
69    }
70
71    /// Returns `true` if this is a [keyword] or literal type.
72    ///
73    /// [keyword]: Self::is_keyword
74    pub fn is_keyword_or_literal(&self) -> bool {
75        self.is_keyword() || matches!(self, TSType::TSLiteralType(_))
76    }
77}
78
79impl<'a> TSTypeName<'a> {
80    /// Get the "leftmost" identifier in a dot-separated type name.
81    ///
82    /// ## Example
83    /// ```ts
84    /// type Foo = Bar; // -> Bar
85    /// type Foo = Bar.Baz; // -> Bar
86    /// ```
87    pub fn get_identifier_reference(&self) -> Option<&IdentifierReference<'a>> {
88        match self {
89            TSTypeName::IdentifierReference(ident) => Some(ident),
90            TSTypeName::QualifiedName(name) => name.left.get_identifier_reference(),
91            TSTypeName::ThisExpression(_) => None,
92        }
93    }
94
95    /// Returns `true` if this is a reference to `const`.
96    pub fn is_const(&self) -> bool {
97        if let TSTypeName::IdentifierReference(ident) = self
98            && ident.name == "const"
99        {
100            return true;
101        }
102        false
103    }
104
105    /// Returns `true` if this is an [`TSTypeName::IdentifierReference`].
106    pub fn is_identifier(&self) -> bool {
107        matches!(self, Self::IdentifierReference(_))
108    }
109
110    /// Returns `true` if this is a [qualified name](TSTypeName::QualifiedName)
111    /// (i.e. a dot-separated name).
112    pub fn is_qualified_name(&self) -> bool {
113        matches!(self, Self::QualifiedName(_))
114    }
115}
116
117impl fmt::Display for TSTypeName<'_> {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        match self {
120            TSTypeName::IdentifierReference(ident) => ident.fmt(f),
121            TSTypeName::QualifiedName(qualified) => qualified.fmt(f),
122            TSTypeName::ThisExpression(_) => "this".fmt(f),
123        }
124    }
125}
126
127impl fmt::Display for TSQualifiedName<'_> {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        write!(f, "{}.{}", self.left, self.right)
130    }
131}
132
133impl TSType<'_> {
134    /// Remove nested parentheses from this type.
135    pub fn without_parenthesized(&self) -> &Self {
136        match self {
137            Self::TSParenthesizedType(expr) => expr.type_annotation.without_parenthesized(),
138            _ => self,
139        }
140    }
141}
142
143impl TSAccessibility {
144    /// Returns `true` for `private` accessibility modifiers.
145    #[inline]
146    pub fn is_private(self) -> bool {
147        matches!(self, Self::Private)
148    }
149
150    /// Converts this modifier into a string as it would appear in the source code.
151    pub fn as_str(self) -> &'static str {
152        match self {
153            Self::Public => "public",
154            Self::Private => "private",
155            Self::Protected => "protected",
156        }
157    }
158}
159
160impl From<TSAccessibility> for &'static str {
161    fn from(accessibility: TSAccessibility) -> Self {
162        accessibility.as_str()
163    }
164}
165
166impl fmt::Display for TSAccessibility {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        f.write_str(self.as_str())
169    }
170}
171
172impl TSModuleDeclaration<'_> {
173    /// Returns `true` if this module's body exists and has a `"use strict"` directive.
174    ///
175    /// Note that for a nested [`TSModuleDeclaration`], only returns `true` for the innermost `TSModuleDeclaration`.
176    /// e.g. this AST has 3 x `TSModuleDeclaration`s:
177    /// ```ts
178    /// namespace X.Y.Z {
179    ///   "use strict";
180    /// }
181    /// ```
182    /// This method will only return `true` for the innermost one (`Z`).
183    pub fn has_use_strict_directive(&self) -> bool {
184        self.body.as_ref().is_some_and(TSModuleDeclarationBody::has_use_strict_directive)
185    }
186}
187
188impl TSModuleDeclarationKind {
189    /// Declaration keyword as a string, identical to how it would appear in the
190    /// source code.
191    pub fn as_str(self) -> &'static str {
192        match self {
193            Self::Module => "module",
194            Self::Namespace => "namespace",
195        }
196    }
197}
198
199impl<'a> TSModuleDeclarationName<'a> {
200    /// Returns `true` if this name is a string literal.
201    ///
202    /// ## Example
203    /// ```ts
204    /// // true
205    /// module "*.less" {
206    ///     const styles: { [key: string]: string };
207    ///     export default styles;
208    /// }
209    ///
210    /// // false
211    /// module bar {}
212    /// namespace bang {}
213    /// ```
214    pub fn is_string_literal(&self) -> bool {
215        matches!(self, Self::StringLiteral(_))
216    }
217
218    /// Get the static name of this module declaration name.
219    pub fn name(&self) -> Atom<'a> {
220        match self {
221            Self::Identifier(ident) => ident.name,
222            Self::StringLiteral(lit) => lit.value,
223        }
224    }
225}
226
227impl fmt::Display for TSModuleDeclarationName<'_> {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        match self {
230            Self::Identifier(id) => id.fmt(f),
231            Self::StringLiteral(lit) => lit.fmt(f),
232        }
233    }
234}
235
236impl<'a> TSModuleDeclarationBody<'a> {
237    /// Returns `true` if this module has a `"use strict"` directive.
238    ///
239    /// Note that for a nested [`TSModuleDeclaration`], only returns `true` for the innermost [`TSModuleDeclarationBody`].
240    /// e.g. this AST has 3 x `TSModuleDeclarationBody`s:
241    /// ```ts
242    /// namespace X.Y.Z {
243    ///   "use strict";
244    /// }
245    /// ```
246    /// This method will only return `true` for the innermost one (`Z`).
247    pub fn has_use_strict_directive(&self) -> bool {
248        matches!(self, Self::TSModuleBlock(block) if block.has_use_strict_directive())
249    }
250
251    /// Returns `true` if this module contains no statements.
252    pub fn is_empty(&self) -> bool {
253        match self {
254            TSModuleDeclarationBody::TSModuleDeclaration(declaration) => declaration.body.is_none(),
255            TSModuleDeclarationBody::TSModuleBlock(block) => block.body.len() == 0,
256        }
257    }
258
259    /// Get a mutable reference to `self` as a [`TSModuleBlock`]. Returns
260    /// [`None`] if the body is something other than a block.
261    pub fn as_module_block_mut(&mut self) -> Option<&mut TSModuleBlock<'a>> {
262        let mut body = self;
263        loop {
264            match body {
265                TSModuleDeclarationBody::TSModuleBlock(block) => return Some(block.as_mut()),
266                TSModuleDeclarationBody::TSModuleDeclaration(decl) => {
267                    body = decl.body.as_mut()?;
268                }
269            }
270        }
271    }
272}
273
274impl TSModuleBlock<'_> {
275    /// Returns `true` if this module contains a `"use strict"` directive.
276    pub fn has_use_strict_directive(&self) -> bool {
277        self.directives.iter().any(Directive::is_use_strict)
278    }
279}
280
281impl TSModuleReference<'_> {
282    /// Returns `true` if this is an [`TSModuleReference::ExternalModuleReference`].
283    pub fn is_external(&self) -> bool {
284        matches!(self, Self::ExternalModuleReference(_))
285    }
286}
287
288impl<'a> Decorator<'a> {
289    /// Get the name of the decorator
290    /// ```ts
291    /// // The name of the decorator is `decorator`
292    /// @decorator
293    /// @decorator.a.b
294    /// @decorator(xx)
295    /// @decorator.a.b(xx)
296    /// ```
297    pub fn name(&self) -> Option<&'a str> {
298        match &self.expression {
299            Expression::Identifier(ident) => Some(ident.name.as_str()),
300            expr @ match_member_expression!(Expression) => {
301                expr.to_member_expression().static_property_name()
302            }
303            Expression::CallExpression(call) => {
304                call.callee.get_member_expr().and_then(MemberExpression::static_property_name)
305            }
306            _ => None,
307        }
308    }
309}
310
311impl ImportOrExportKind {
312    /// Returns `true` for "regular" imports and exports.
313    pub fn is_value(self) -> bool {
314        matches!(self, Self::Value)
315    }
316
317    /// Returns `true` if this is an `import type` or `export type` statement.
318    pub fn is_type(self) -> bool {
319        matches!(self, Self::Type)
320    }
321}
322
323impl TSTypeOperatorOperator {
324    /// Get the operator string as it would appear in the source code.
325    pub fn to_str(self) -> &'static str {
326        match self {
327            TSTypeOperatorOperator::Keyof => "keyof",
328            TSTypeOperatorOperator::Readonly => "readonly",
329            TSTypeOperatorOperator::Unique => "unique",
330        }
331    }
332}