Skip to main content

oxc_parser/lexer/
kind.rs

1//! ECMAScript Token Kinds
2
3use std::fmt::{self, Display};
4
5use oxc_data_structures::fieldless_enum;
6
7// `fieldless_enum!` macro provides `Kind::VARIANTS` constant listing all variants
8fieldless_enum! {
9    /// Lexer token kind
10    ///
11    /// Exported for other oxc crates to use. You generally don't need to use this directly.
12    #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
13    #[repr(u8)]
14    #[non_exhaustive]
15    pub enum Kind {
16        #[default]
17        Eof = 0,
18        Undetermined,
19        Skip, // Whitespace, line breaks, comments
20        // 12.5 Hashbang Comments
21        HashbangComment,
22        // 12.7.1 identifier
23        Ident,
24        // 12.7.2 keyword
25        Await,
26        Break,
27        Case,
28        Catch,
29        Class,
30        Const,
31        Continue,
32        Debugger,
33        Default,
34        Delete,
35        Do,
36        Else,
37        Enum,
38        Export,
39        Extends,
40        Finally,
41        For,
42        Function,
43        If,
44        Import,
45        In,
46        Instanceof,
47        New,
48        Return,
49        Super,
50        Switch,
51        This,
52        Throw,
53        Try,
54        Typeof,
55        Var,
56        Void,
57        While,
58        With,
59        // Contextual Keywords
60        Async,
61        From,
62        Get,
63        Meta, // import.meta
64        Of,
65        Set,
66        Target,   // new.target
67        Accessor, // keyword from https://github.com/tc39/proposal-decorators
68        Source,   // import.source https://github.com/tc39/proposal-source-phase-imports
69        Defer,    // import.defer https://github.com/tc39/proposal-defer-import-eval
70        // TypeScript Contextual Keywords
71        Abstract,
72        As,
73        Asserts,
74        Assert,
75        Any,
76        Boolean,
77        Constructor,
78        Declare,
79        Infer,
80        Intrinsic,
81        Is,
82        KeyOf,
83        Module,
84        Namespace,
85        Never,
86        Out,
87        Readonly,
88        Require,
89        Number, // the "number" keyword for TypeScript
90        Object,
91        Satisfies,
92        String, // the "string" keyword for TypeScript
93        Symbol,
94        Type,
95        Undefined,
96        Unique,
97        Using,
98        Unknown,
99        Global,
100        BigInt, // the "bigint" keyword for TypeScript
101        Override,
102        // Future keywords (strict mode reserved words)
103        Implements,
104        Interface,
105        Let,
106        Package,
107        Private,
108        Protected,
109        Public,
110        Static,
111        Yield,
112        // 12.9.1 Null Literals
113        // 12.9.2 Boolean Literals
114        // Moved here to make all keywords contiguous for range check optimization
115        True,
116        False,
117        Null,
118        // 12.8 punctuators
119        Amp, // &
120        Amp2,
121        Amp2Eq,
122        AmpEq,
123        Bang, // !
124        Caret,
125        CaretEq,
126        Colon,
127        Comma,
128        Dot,
129        Dot3, // ...
130        Eq,
131        Eq2,
132        Eq3,
133        GtEq, // >=
134        LAngle,
135        LBrack,
136        LCurly,
137        LParen,
138        LtEq, // <=
139        Minus,
140        Minus2,
141        MinusEq,
142        Neq,
143        Neq2,
144        Percent,
145        PercentEq,
146        Pipe,
147        Pipe2,
148        Pipe2Eq,
149        PipeEq,
150        Plus,
151        Plus2,
152        PlusEq,
153        Question,
154        Question2,
155        Question2Eq,
156        QuestionDot,
157        RAngle,
158        RBrack,
159        RCurly,
160        RParen,
161        Semicolon,
162        ShiftLeft,     // <<
163        ShiftLeftEq,   // <<=
164        ShiftRight,    // >>
165        ShiftRight3,   // >>>
166        ShiftRight3Eq, // >>>=
167        ShiftRightEq,  // >>=
168        Slash,
169        SlashEq,
170        Star,
171        Star2,
172        Star2Eq,
173        StarEq,
174        Tilde,
175        // arrow function
176        Arrow,
177        // 12.9.3 Numeric Literals
178        Decimal,
179        Float,
180        Binary,
181        Octal,
182        Hex,
183        // for `1e10`, `1e+10`
184        PositiveExponential,
185        // for `1e-10`
186        NegativeExponential,
187        // BigInt Literals (numeric literals with 'n' suffix)
188        DecimalBigInt,
189        BinaryBigInt,
190        OctalBigInt,
191        HexBigInt,
192        // 12.9.4 String Literals
193        /// String Type
194        Str,
195        // 12.9.5 Regular Expression Literals
196        RegExp,
197        // 12.9.6 Template Literal
198        NoSubstitutionTemplate,
199        TemplateHead,
200        TemplateMiddle,
201        TemplateTail,
202        // es2022 Private Identifier
203        PrivateIdentifier,
204        // JSX
205        JSXText,
206        // Decorator
207        At,
208    }
209}
210
211#[allow(clippy::enum_glob_use, clippy::allow_attributes)]
212use Kind::*;
213
214impl Kind {
215    #[inline]
216    pub const fn is_eof(self) -> bool {
217        matches!(self, Eof)
218    }
219
220    #[rustfmt::skip]
221    #[inline]
222    pub const fn is_number(self) -> bool {
223        matches!(
224            self,
225            Decimal | Float | Binary | Octal | Hex | PositiveExponential | NegativeExponential
226            | DecimalBigInt | BinaryBigInt | OctalBigInt | HexBigInt
227        )
228    }
229
230    #[inline] // Inline into `read_non_decimal` - see comment there as to why
231    pub fn matches_number_byte(self, b: u8) -> bool {
232        match self {
233            Decimal => b.is_ascii_digit(),
234            Binary => matches!(b, b'0'..=b'1'),
235            Octal => matches!(b, b'0'..=b'7'),
236            Hex => b.is_ascii_hexdigit(),
237            _ => unreachable!(),
238        }
239    }
240
241    /// [Identifiers](https://tc39.es/ecma262/#sec-identifiers)
242    /// `IdentifierReference`
243    #[inline]
244    pub const fn is_identifier_reference(
245        self,
246        is_yield_context: bool,
247        is_await_context: bool,
248    ) -> bool {
249        self.is_identifier()
250            || (!is_yield_context && matches!(self, Yield))
251            || (!is_await_context && matches!(self, Await))
252    }
253
254    /// `BindingIdentifier`
255    #[inline]
256    pub const fn is_binding_identifier(self) -> bool {
257        self.is_identifier() || matches!(self, Yield | Await)
258    }
259
260    /// `LabelIdentifier`
261    #[inline]
262    pub const fn is_label_identifier(self, is_yield_context: bool, is_await_context: bool) -> bool {
263        self.is_identifier()
264            || (!is_yield_context && matches!(self, Yield))
265            || (!is_await_context && matches!(self, Await))
266    }
267
268    /// Identifier
269    /// `IdentifierName` but not `ReservedWord`
270    #[inline]
271    pub const fn is_identifier(self) -> bool {
272        self.is_identifier_name() && !self.is_reserved_keyword()
273    }
274
275    /// TypeScript Identifier
276    ///
277    /// <https://github.com/microsoft/TypeScript/blob/15392346d05045742e653eab5c87538ff2a3c863/src/compiler/parser.ts#L2316-L2335>
278    #[inline]
279    pub const fn is_ts_identifier(self, is_yield_context: bool, is_await_context: bool) -> bool {
280        self.is_identifier_reference(is_yield_context, is_await_context)
281            && !self.is_strict_mode_contextual_keyword()
282            && !self.is_contextual_keyword()
283    }
284
285    /// `IdentifierName`
286    /// All identifier names are either `Ident` or keywords (Await..=Null in the enum).
287    #[inline]
288    pub const fn is_identifier_name(self) -> bool {
289        matches!(self, Ident) || matches!(self as u8, x if x >= Await as u8 && x <= Null as u8)
290    }
291
292    /// Check the succeeding token of a `let` keyword.
293    ///
294    /// ```javascript
295    /// let { a, b } = c, let [a, b] = c, let ident
296    /// ```
297    #[inline]
298    pub const fn is_after_let(self) -> bool {
299        !matches!(self, In | Instanceof)
300            && (matches!(self, LCurly | LBrack | Ident) || self.is_any_keyword())
301    }
302
303    /// Section 13.2.4 Literals
304    /// Literal :
305    ///     `NullLiteral`
306    ///     `BooleanLiteral`
307    ///     `NumericLiteral`
308    ///     `StringLiteral`
309    #[inline]
310    pub const fn is_literal(self) -> bool {
311        matches!(self, Null | True | False | Str | RegExp) || self.is_number()
312    }
313
314    #[inline]
315    pub const fn is_after_await_or_yield(self) -> bool {
316        !self.is_binary_operator() && (self.is_literal() || self.is_identifier_name())
317    }
318
319    /// Section 13.2.6 Object Initializer
320    /// `LiteralPropertyName` :
321    ///     `IdentifierName`
322    ///     `StringLiteral`
323    ///     `NumericLiteral`
324    #[inline]
325    pub const fn is_literal_property_name(self) -> bool {
326        self.is_identifier_name() || matches!(self, Str) || self.is_number()
327    }
328
329    #[inline]
330    pub const fn is_identifier_or_keyword(self) -> bool {
331        self.is_literal_property_name() || matches!(self, PrivateIdentifier)
332    }
333
334    #[rustfmt::skip]
335    #[inline]
336    pub const fn is_assignment_operator(self) -> bool {
337        matches!(
338            self,
339            Eq | PlusEq | MinusEq | StarEq | SlashEq | PercentEq | ShiftLeftEq | ShiftRightEq
340            | ShiftRight3Eq | Pipe2Eq | Amp2Eq | PipeEq | CaretEq | AmpEq | Question2Eq | Star2Eq
341        )
342    }
343
344    #[rustfmt::skip]
345    #[inline]
346    pub const fn is_binary_operator(self) -> bool {
347        matches!(
348            self,
349            Eq2 | Neq | Eq3 | Neq2 | LAngle | LtEq | RAngle | GtEq | ShiftLeft | ShiftRight | ShiftRight3
350            | Plus | Minus | Star | Slash | Percent | Pipe | Caret | Amp | In | Instanceof | Star2
351        )
352    }
353
354    #[inline]
355    pub const fn is_logical_operator(self) -> bool {
356        matches!(self, Pipe2 | Amp2 | Question2)
357    }
358
359    #[inline]
360    pub const fn is_unary_operator(self) -> bool {
361        matches!(self, Minus | Plus | Bang | Tilde | Typeof | Void | Delete)
362    }
363
364    #[inline]
365    pub const fn is_update_operator(self) -> bool {
366        matches!(self, Plus2 | Minus2)
367    }
368
369    /// [Keywords and Reserved Words](https://tc39.es/ecma262/#sec-keywords-and-reserved-words)
370    #[inline]
371    pub const fn is_any_keyword(self) -> bool {
372        // Note: `is_future_reserved_keyword` is a subset of `is_strict_mode_contextual_keyword`,
373        // so the last arm is redundant. We include it anyway so each spec category is represented:
374        // - Reserved words: https://tc39.es/ecma262/#prod-ReservedWord
375        // - Contextual keywords: https://tc39.es/ecma262/#sec-keywords-and-reserved-words
376        // - Strict mode reserved words: https://tc39.es/ecma262/#sec-strict-mode-of-ecmascript
377        // - Future reserved words: https://tc39.es/ecma262/#sec-future-reserved-words
378        // The compiler optimizes all four calls into a single range check (2 instructions total),
379        // so keeping `is_future_reserved_keyword` has no performance impact.
380        self.is_reserved_keyword()
381            || self.is_contextual_keyword()
382            || self.is_strict_mode_contextual_keyword()
383            || self.is_future_reserved_keyword()
384    }
385
386    #[rustfmt::skip]
387    #[inline]
388    pub const fn is_reserved_keyword(self) -> bool {
389        matches!(
390            self,
391            Await | Break | Case | Catch | Class | Const | Continue | Debugger | Default
392            | Delete | Do | Else | Enum | Export | Extends | False | Finally | For | Function | If
393            | Import | In | Instanceof | New | Null | Return | Super | Switch | This | Throw
394            | True | Try | Typeof | Var | Void | While | With | Yield
395        )
396    }
397
398    #[rustfmt::skip]
399    #[inline]
400    pub const fn is_strict_mode_contextual_keyword(self) -> bool {
401        matches!(self, Let | Static | Implements | Interface | Package | Private | Protected | Public)
402    }
403
404    #[rustfmt::skip]
405    #[inline]
406    pub const fn is_contextual_keyword(self) -> bool {
407        matches!(
408            self,
409            Async | From | Get | Meta | Of | Set | Target | Accessor | Abstract | As | Asserts | Assert
410            | Any | Boolean | Constructor | Declare | Infer | Intrinsic | Is | KeyOf | Module | Namespace
411            | Never | Out | Readonly | Require | Number | Object | Satisfies | String | Symbol | Type
412            | Undefined | Unique | Unknown | Using | Global | BigInt | Override | Source | Defer
413        )
414    }
415
416    #[rustfmt::skip]
417    #[inline]
418    pub const fn is_future_reserved_keyword(self) -> bool {
419        matches!(self, Implements | Interface | Package | Private | Protected | Public | Static)
420    }
421
422    #[inline]
423    pub const fn is_template_start_of_tagged_template(self) -> bool {
424        matches!(self, NoSubstitutionTemplate | TemplateHead)
425    }
426
427    #[rustfmt::skip]
428    #[inline]
429    pub const fn is_modifier_kind(self) -> bool {
430        matches!(
431            self,
432            Abstract | Accessor | Async | Const | Declare
433            | In | Out | Public | Private | Protected | Readonly | Static | Override
434            | Default | Export
435        )
436    }
437
438    #[inline]
439    pub const fn is_binding_identifier_or_private_identifier_or_pattern(self) -> bool {
440        matches!(self, LCurly | LBrack | PrivateIdentifier) || self.is_binding_identifier()
441    }
442
443    #[cold]
444    pub fn match_keyword(s: &str) -> Self {
445        let len = s.len();
446        // SAFETY: Already checked `len <= 1`.
447        if len <= 1 || len >= 12 || !unsafe { s.as_bytes().get_unchecked(0) }.is_ascii_lowercase() {
448            return Ident;
449        }
450        Self::match_keyword_impl(s)
451    }
452
453    fn match_keyword_impl(s: &str) -> Self {
454        match s {
455            "as" => As,
456            "do" => Do,
457            "if" => If,
458            "in" => In,
459            "is" => Is,
460            "of" => Of,
461
462            "any" => Any,
463            "for" => For,
464            "get" => Get,
465            "let" => Let,
466            "new" => New,
467            "out" => Out,
468            "set" => Set,
469            "try" => Try,
470            "var" => Var,
471
472            "case" => Case,
473            "else" => Else,
474            "enum" => Enum,
475            "from" => From,
476            "meta" => Meta,
477            "null" => Null,
478            "this" => This,
479            "true" => True,
480            "type" => Type,
481            "void" => Void,
482            "with" => With,
483
484            "async" => Async,
485            "await" => Await,
486            "break" => Break,
487            "catch" => Catch,
488            "class" => Class,
489            "const" => Const,
490            "false" => False,
491            "infer" => Infer,
492            "keyof" => KeyOf,
493            "never" => Never,
494            "super" => Super,
495            "throw" => Throw,
496            "using" => Using,
497            "while" => While,
498            "yield" => Yield,
499            "defer" => Defer,
500
501            "assert" => Assert,
502            "bigint" => BigInt,
503            "delete" => Delete,
504            "export" => Export,
505            "global" => Global,
506            "import" => Import,
507            "module" => Module,
508            "number" => Number,
509            "object" => Object,
510            "public" => Public,
511            "return" => Return,
512            "static" => Static,
513            "string" => String,
514            "switch" => Switch,
515            "symbol" => Symbol,
516            "target" => Target,
517            "typeof" => Typeof,
518            "unique" => Unique,
519            "source" => Source,
520
521            "asserts" => Asserts,
522            "boolean" => Boolean,
523            "declare" => Declare,
524            "default" => Default,
525            "extends" => Extends,
526            "finally" => Finally,
527            "package" => Package,
528            "private" => Private,
529            "require" => Require,
530            "unknown" => Unknown,
531
532            "abstract" => Abstract,
533            "accessor" => Accessor,
534            "continue" => Continue,
535            "debugger" => Debugger,
536            "function" => Function,
537            "override" => Override,
538            "readonly" => Readonly,
539
540            "interface" => Interface,
541            "intrinsic" => Intrinsic,
542            "namespace" => Namespace,
543            "protected" => Protected,
544            "satisfies" => Satisfies,
545            "undefined" => Undefined,
546
547            "implements" => Implements,
548            "instanceof" => Instanceof,
549
550            "constructor" => Constructor,
551            _ => Ident,
552        }
553    }
554
555    pub fn to_str(self) -> &'static str {
556        #[expect(clippy::match_same_arms)]
557        match self {
558            Undetermined => "Unknown",
559            Eof => "EOF",
560            Skip => "Skipped",
561            HashbangComment => "#!",
562            Ident => "Identifier",
563            Await => "await",
564            Break => "break",
565            Case => "case",
566            Catch => "catch",
567            Class => "class",
568            Const => "const",
569            Continue => "continue",
570            Debugger => "debugger",
571            Default => "default",
572            Delete => "delete",
573            Do => "do",
574            Else => "else",
575            Enum => "enum",
576            Export => "export",
577            Extends => "extends",
578            Finally => "finally",
579            For => "for",
580            Function => "function",
581            Using => "using",
582            If => "if",
583            Import => "import",
584            In => "in",
585            Instanceof => "instanceof",
586            New => "new",
587            Return => "return",
588            Super => "super",
589            Switch => "switch",
590            This => "this",
591            Throw => "throw",
592            Try => "try",
593            Typeof => "typeof",
594            Var => "var",
595            Void => "void",
596            While => "while",
597            With => "with",
598            As => "as",
599            Async => "async",
600            From => "from",
601            Get => "get",
602            Meta => "meta",
603            Of => "of",
604            Set => "set",
605            Asserts => "asserts",
606            Accessor => "accessor",
607            Abstract => "abstract",
608            Readonly => "readonly",
609            Declare => "declare",
610            Override => "override",
611            Type => "type",
612            Target => "target",
613            Source => "source",
614            Defer => "defer",
615            Implements => "implements",
616            Interface => "interface",
617            Package => "package",
618            Private => "private",
619            Protected => "protected",
620            Public => "public",
621            Static => "static",
622            Let => "let",
623            Yield => "yield",
624            Amp => "&",
625            Amp2 => "&&",
626            Amp2Eq => "&&=",
627            AmpEq => "&=",
628            Bang => "!",
629            Caret => "^",
630            CaretEq => "^=",
631            Colon => ":",
632            Comma => ",",
633            Dot => ".",
634            Dot3 => "...",
635            Eq => "=",
636            Eq2 => "==",
637            Eq3 => "===",
638            GtEq => ">=",
639            LAngle => "<",
640            LBrack => "[",
641            LCurly => "{",
642            LParen => "(",
643            LtEq => "<=",
644            Minus => "-",
645            Minus2 => "--",
646            MinusEq => "-=",
647            Neq => "!=",
648            Neq2 => "!==",
649            Percent => "%",
650            PercentEq => "%=",
651            Pipe => "|",
652            Pipe2 => "||",
653            Pipe2Eq => "||=",
654            PipeEq => "|=",
655            Plus => "+",
656            Plus2 => "++",
657            PlusEq => "+=",
658            Question => "?",
659            Question2 => "??",
660            Question2Eq => "??=",
661            QuestionDot => "?.",
662            RAngle => ">",
663            RBrack => "]",
664            RCurly => "}",
665            RParen => ")",
666            Semicolon => ";",
667            ShiftLeft => "<<",
668            ShiftLeftEq => "<<=",
669            ShiftRight => ">>",
670            ShiftRight3 => ">>>",
671            ShiftRight3Eq => ">>>=",
672            ShiftRightEq => ">>=",
673            Slash => "/",
674            SlashEq => "/=",
675            Star => "*",
676            Star2 => "**",
677            Star2Eq => "**=",
678            StarEq => "*=",
679            Tilde => "~",
680            Arrow => "=>",
681            Null => "null",
682            True => "true",
683            False => "false",
684            Decimal => "decimal",
685            Float | PositiveExponential | NegativeExponential => "float",
686            Binary => "binary",
687            Octal => "octal",
688            Hex => "hex",
689            DecimalBigInt => "decimal bigint",
690            BinaryBigInt => "binary bigint",
691            OctalBigInt => "octal bigint",
692            HexBigInt => "hex bigint",
693            Str | String => "string",
694            RegExp => "/regexp/",
695            NoSubstitutionTemplate => "${}",
696            TemplateHead => "${",
697            TemplateMiddle => "${expr}",
698            TemplateTail => "}",
699            PrivateIdentifier => "#identifier",
700            JSXText => "jsx",
701            At => "@",
702            Assert => "assert",
703            Any => "any",
704            Boolean => "boolean",
705            Constructor => "constructor",
706            Infer => "infer",
707            Intrinsic => "intrinsic",
708            Is => "is",
709            KeyOf => "keyof",
710            Module => "module",
711            Namespace => "namaespace",
712            Never => "never",
713            Out => "out",
714            Require => "require",
715            Number => "number",
716            Object => "object",
717            Satisfies => "satisfies",
718            Symbol => "symbol",
719            Undefined => "undefined",
720            Unique => "unique",
721            Unknown => "unknown",
722            Global => "global",
723            BigInt => "bigint",
724        }
725    }
726}
727
728impl Display for Kind {
729    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
730        self.to_str().fmt(f)
731    }
732}