Skip to main content

tsz_parser/parser/
state.rs

1//! Parser - Cache-optimized parser using `NodeArena`
2//!
3//! This parser uses the Node architecture (16 bytes per node vs 208 bytes)
4//! for 13x better cache locality. It produces the same AST semantically
5//! but stored in a more efficient format.
6//!
7//! # Architecture
8//!
9//! - Uses `NodeArena` instead of `NodeArena`
10//! - Each node is 16 bytes (vs 208 bytes for fat Node enum)
11//! - Node data is stored in separate typed pools
12//! - 4 nodes fit per 64-byte cache line (vs 0.31 for fat nodes)
13
14use tsz_common::diagnostics::diagnostic_codes;
15use tsz_common::limits::MAX_PARSER_RECURSION_DEPTH;
16
17use crate::parser::{
18    NodeIndex, NodeList,
19    node::{IdentifierData, NodeArena},
20    syntax_kind_ext,
21};
22use rustc_hash::FxHashMap;
23use tracing::trace;
24use tsz_common::interner::Atom;
25use tsz_scanner::scanner_impl::{ScannerState, TokenFlags};
26use tsz_scanner::{SyntaxKind, token_is_keyword};
27// =============================================================================
28// Parser Context Flags
29// =============================================================================
30
31/// Context flag: inside an async function/method/arrow
32pub const CONTEXT_FLAG_ASYNC: u32 = 1;
33/// Context flag: inside a generator function/method
34pub const CONTEXT_FLAG_GENERATOR: u32 = 2;
35/// Context flag: inside a static block (where 'await' is reserved)
36pub const CONTEXT_FLAG_STATIC_BLOCK: u32 = 4;
37/// Context flag: parsing a parameter default (where 'await' is not allowed)
38pub const CONTEXT_FLAG_PARAMETER_DEFAULT: u32 = 8;
39/// Context flag: disallow 'in' as a binary operator (for for-statement initializers)
40pub const CONTEXT_FLAG_DISALLOW_IN: u32 = 16;
41/// Context flag: parsing the `true` branch of a conditional expression.
42/// Suppresses type-annotated single-parameter arrow lookahead while
43/// that colon belongs to the surrounding conditional operator.
44pub const CONTEXT_FLAG_IN_CONDITIONAL_TRUE: u32 = 64;
45/// Context flag: parsing a class member name.
46pub const CONTEXT_FLAG_CLASS_MEMBER_NAME: u32 = 2048;
47/// Context flag: inside an ambient context (declare namespace/module)
48pub const CONTEXT_FLAG_AMBIENT: u32 = 32;
49/// Context flag: parsing a class body
50pub const CONTEXT_FLAG_IN_CLASS: u32 = 4096;
51/// Context flag: inside a decorator expression (@expr)
52/// When set, `[` should not be treated as element access (it starts a computed property name)
53pub const CONTEXT_FLAG_IN_DECORATOR: u32 = 128;
54/// Context flag: parsing parameters of a class constructor.
55pub const CONTEXT_FLAG_CONSTRUCTOR_PARAMETERS: u32 = 256;
56/// Context flag: parsing arrow function parameters.
57pub const CONTEXT_FLAG_ARROW_PARAMETERS: u32 = 512;
58/// Context flag: disallow conditional types (used inside `infer T extends X` constraint parsing).
59/// When set, `T extends U ? X : Y` is not parsed as a conditional type.
60pub const CONTEXT_FLAG_DISALLOW_CONDITIONAL_TYPES: u32 = 1024;
61/// Context flag: inside a block statement (function body, bare block, if/while/for body).
62/// When set, modifiers like `export` and `declare` are not allowed and emit TS1184.
63pub const CONTEXT_FLAG_IN_BLOCK: u32 = 8192;
64/// Context flag: parsing inside a parenthesized expression.
65/// Used to keep arrow-function/parenthesized recovery behavior consistent.
66pub const CONTEXT_FLAG_IN_PARENTHESIZED_EXPRESSION: u32 = 16384;
67
68// =============================================================================
69// Parse Diagnostic
70// =============================================================================
71
72/// A parse-time diagnostic (error or warning).
73#[derive(Clone, Debug)]
74pub struct ParseDiagnostic {
75    pub start: u32,
76    pub length: u32,
77    pub message: String,
78    pub code: u32,
79}
80
81pub struct IncrementalParseResult {
82    pub statements: NodeList,
83    pub end_pos: u32,
84    pub end_of_file_token: NodeIndex,
85    pub reparse_start: u32,
86}
87
88// =============================================================================
89// ParserState
90// =============================================================================
91
92/// A high-performance parser using Node architecture.
93///
94/// Error suppression distance in tokens
95///
96/// If we emitted an error within this distance, suppress subsequent errors
97/// to prevent cascading TS1005 and other noise errors.
98///
99/// This value was chosen empirically to match TypeScript's behavior:
100/// - Too small: Cascading errors aren't suppressed effectively
101/// - Too large: Genuine secondary errors are suppressed
102const ERROR_SUPPRESSION_DISTANCE: u32 = 3;
103
104/// This parser produces the same AST semantically as `ParserState`,
105/// but uses the cache-optimized `NodeArena` for storage.
106pub struct ParserState {
107    /// The scanner for tokenizing
108    pub(crate) scanner: ScannerState,
109    /// Arena for allocating Nodes
110    pub arena: NodeArena,
111    /// Source file name
112    pub(crate) file_name: String,
113    /// Parser context flags
114    pub context_flags: u32,
115    /// Current token
116    pub(crate) current_token: SyntaxKind,
117    /// List of parse diagnostics
118    pub(crate) parse_diagnostics: Vec<ParseDiagnostic>,
119    /// Node count for assigning IDs
120    pub(crate) node_count: u32,
121    /// Recursion depth for stack overflow protection
122    pub(crate) recursion_depth: u32,
123    /// Position of last error (to prevent cascading errors at same position)
124    pub(crate) last_error_pos: u32,
125    /// Stack of label scopes for duplicate label detection (TS1114)
126    /// Each scope is a map from label name to the position where it was first defined
127    pub(crate) label_scopes: Vec<FxHashMap<String, u32>>,
128}
129
130impl ParserState {
131    #[inline]
132    #[must_use]
133    pub(crate) fn u32_from_usize(&self, value: usize) -> u32 {
134        let _ = self;
135        u32::try_from(value).expect("parser offsets must fit in u32")
136    }
137
138    #[inline]
139    #[must_use]
140    pub(crate) fn u16_from_node_flags(&self, value: u32) -> u16 {
141        let _ = self;
142        u16::try_from(value).expect("parser node flags must fit in u16")
143    }
144
145    /// Create a new Parser for the given source text.
146    #[must_use]
147    pub fn new(file_name: String, source_text: String) -> Self {
148        let estimated_nodes = source_text.len() / 20; // Rough estimate
149        // Zero-copy: Pass source_text directly to scanner without cloning
150        // This eliminates the 2x memory overhead from duplicating the source
151        let scanner = ScannerState::new(source_text, true);
152        Self {
153            scanner,
154            arena: NodeArena::with_capacity(estimated_nodes),
155            file_name,
156            context_flags: 0,
157            current_token: SyntaxKind::Unknown,
158            parse_diagnostics: Vec::new(),
159            node_count: 0,
160            recursion_depth: 0,
161            last_error_pos: 0,
162            label_scopes: vec![FxHashMap::default()],
163        }
164    }
165
166    pub fn reset(&mut self, file_name: String, source_text: String) {
167        self.file_name = file_name;
168        self.scanner.set_text(source_text, None, None);
169        self.arena.clear();
170        self.context_flags = 0;
171        self.current_token = SyntaxKind::Unknown;
172        self.parse_diagnostics.clear();
173        self.node_count = 0;
174        self.recursion_depth = 0;
175        self.last_error_pos = 0;
176        self.label_scopes.clear();
177        self.label_scopes.push(FxHashMap::default());
178    }
179
180    /// Check recursion limit - returns true if we can continue, false if limit exceeded
181    pub(crate) fn enter_recursion(&mut self) -> bool {
182        if self.recursion_depth >= MAX_PARSER_RECURSION_DEPTH {
183            self.parse_error_at_current_token(
184                "Maximum recursion depth exceeded",
185                diagnostic_codes::UNEXPECTED_TOKEN,
186            );
187            false
188        } else {
189            self.recursion_depth += 1;
190            true
191        }
192    }
193
194    /// Centralized error suppression heuristic
195    ///
196    /// Prevents cascading errors by suppressing error reports if we've already
197    /// emitted an error recently (within `ERROR_SUPPRESSION_DISTANCE` tokens).
198    ///
199    /// This standardizes the inconsistency where:
200    /// - `parse_expected()` uses strict equality `!=`
201    /// - `parse_semicolon()` uses `abs_diff > 3`
202    ///
203    /// Returns true if we should report an error, false if we should suppress it
204    fn should_report_error(&self) -> bool {
205        // Always report first error
206        if self.last_error_pos == 0 {
207            return true;
208        }
209        let current = self.token_pos();
210        // Report if we've advanced past the suppression distance
211        // This prevents multiple errors for the same position while still
212        // catching genuine secondary errors
213        current.abs_diff(self.last_error_pos) > ERROR_SUPPRESSION_DISTANCE
214    }
215
216    /// Exit recursion scope
217    pub(crate) const fn exit_recursion(&mut self) {
218        self.recursion_depth = self.recursion_depth.saturating_sub(1);
219    }
220
221    // =========================================================================
222    // Token Utilities (shared with regular parser)
223    // =========================================================================
224
225    /// Check if we're in a JSX file.
226    /// In tsc, .js/.cjs/.mjs/.jsx/.tsx all use LanguageVariant.JSX,
227    /// only .ts/.cts/.mts use LanguageVariant.Standard.
228    pub(crate) fn is_jsx_file(&self) -> bool {
229        std::path::Path::new(&self.file_name)
230            .extension()
231            .and_then(|ext| ext.to_str())
232            .is_some_and(|ext| {
233                ext.eq_ignore_ascii_case("tsx")
234                    || ext.eq_ignore_ascii_case("jsx")
235                    || ext.eq_ignore_ascii_case("js")
236                    || ext.eq_ignore_ascii_case("cjs")
237                    || ext.eq_ignore_ascii_case("mjs")
238            })
239    }
240
241    /// Get current token
242    #[inline]
243    pub(crate) const fn token(&self) -> SyntaxKind {
244        self.current_token
245    }
246
247    /// Get current token position
248    #[inline]
249    pub(crate) fn token_pos(&self) -> u32 {
250        self.u32_from_usize(self.scanner.get_token_start())
251    }
252
253    /// Get current token end position
254    #[inline]
255    pub(crate) fn token_end(&self) -> u32 {
256        self.u32_from_usize(self.scanner.get_token_end())
257    }
258
259    /// Advance to next token
260    pub(crate) fn next_token(&mut self) -> SyntaxKind {
261        self.current_token = self.scanner.scan();
262        self.current_token
263    }
264
265    /// Consume a keyword token, checking for TS1260 (keywords cannot contain escape characters).
266    /// Call this instead of `next_token()` when consuming a keyword in a keyword position.
267    pub(crate) fn consume_keyword(&mut self) {
268        self.check_keyword_with_escape();
269        self.next_token();
270    }
271
272    /// Check if current token is a keyword with unicode escape and emit TS1260 if so.
273    /// Only call this when consuming a token that is expected to be a keyword.
274    fn check_keyword_with_escape(&mut self) {
275        // Skip if not a keyword
276        if !token_is_keyword(self.current_token) {
277            return;
278        }
279        // Check for UnicodeEscape flag
280        let flags = self.scanner.get_token_flags();
281        if (flags & TokenFlags::UnicodeEscape as u32) != 0 {
282            use tsz_common::diagnostics::diagnostic_codes;
283            self.parse_error_at(
284                self.u32_from_usize(self.scanner.get_token_start()),
285                self.u32_from_usize(self.scanner.get_token_end() - self.scanner.get_token_start()),
286                "Keywords cannot contain escape characters.",
287                diagnostic_codes::KEYWORDS_CANNOT_CONTAIN_ESCAPE_CHARACTERS,
288            );
289        }
290    }
291
292    /// Check if current token matches kind
293    #[inline]
294    pub(crate) fn is_token(&self, kind: SyntaxKind) -> bool {
295        self.current_token == kind
296    }
297
298    /// Check if current token is an identifier or any keyword
299    /// Keywords can be used as identifiers in many contexts (e.g., class names, property names)
300    #[inline]
301    pub(crate) const fn is_identifier_or_keyword(&self) -> bool {
302        self.current_token as u16 >= SyntaxKind::Identifier as u16
303    }
304
305    /// Check if current token can start a type member declaration
306    #[inline]
307    pub(crate) const fn is_type_member_start(&self) -> bool {
308        match self.current_token {
309            SyntaxKind::OpenParenToken | SyntaxKind::LessThanToken | SyntaxKind::NewKeyword => true,
310            _ => self.is_property_name(),
311        }
312    }
313
314    /// Check if current token can be a property name
315    /// Includes identifiers, keywords (as property names), string/numeric literals, computed properties
316    #[inline]
317    pub(crate) const fn is_property_name(&self) -> bool {
318        match self.current_token {
319            SyntaxKind::Identifier
320            | SyntaxKind::StringLiteral
321            | SyntaxKind::NumericLiteral
322            | SyntaxKind::PrivateIdentifier
323            | SyntaxKind::OpenBracketToken // computed property name
324            | SyntaxKind::GetKeyword
325            | SyntaxKind::SetKeyword => true,
326            // Any keyword can be used as a property name
327            _ => self.is_identifier_or_keyword()
328        }
329    }
330
331    /// Used to emit TS1110 (Type expected) instead of TS1005 (identifier expected)
332    /// when a type is expected but we encounter a token that can't start a type
333    #[inline]
334    pub(crate) const fn can_token_start_type(&self) -> bool {
335        match self.current_token {
336            // Tokens that definitely cannot start a type
337            SyntaxKind::CloseParenToken       // )
338            | SyntaxKind::CloseBraceToken     // }
339            | SyntaxKind::CloseBracketToken   // ]
340            | SyntaxKind::CommaToken          // ,
341            | SyntaxKind::SemicolonToken      // ;
342            | SyntaxKind::ColonToken          // :
343            | SyntaxKind::EqualsToken         // =
344            | SyntaxKind::EqualsGreaterThanToken  // =>
345            | SyntaxKind::GreaterThanToken    // > (e.g., missing type in generic default: T = >)
346            | SyntaxKind::BarToken            // | (when at start, not a union)
347            | SyntaxKind::AmpersandToken      // & (when at start, not an intersection)
348            | SyntaxKind::QuestionToken       // ?
349            | SyntaxKind::EndOfFileToken => false,
350            // Everything else could potentially start a type
351            // (identifiers, keywords, literals, type operators, etc.)
352            _ => true
353        }
354    }
355
356    /// Check if the current token is a delimiter/terminator where a missing type
357    /// should be silently recovered (no TS1110). TSC doesn't emit "Type expected" when
358    /// a type is simply omitted before a structural delimiter like `)`, `,`, `=>`, etc.
359    pub(crate) const fn is_type_terminator_token(&self) -> bool {
360        matches!(
361            self.current_token,
362            SyntaxKind::CloseParenToken          // ) - end of parameter list, parenthesized type
363            | SyntaxKind::CloseBracketToken      // ] - end of tuple/array type
364            | SyntaxKind::CloseBraceToken        // } - end of object type / block
365            | SyntaxKind::CommaToken             // , - next element in list
366            | SyntaxKind::SemicolonToken         // ; - end of statement
367            | SyntaxKind::EqualsGreaterThanToken // => - arrow (return type missing)
368            | SyntaxKind::EndOfFileToken // EOF
369        )
370    }
371
372    /// Parse type member separators with ASI-aware recovery.
373    ///
374    /// Type members in interface/type literal bodies allow:
375    /// - Explicit `;` or `,`
376    /// - ASI-separated members when a line break exists
377    ///
378    /// When members are missing a separator on the same line, emit
379    /// `';' expected.` (TS1005) and continue parsing.
380    pub(crate) fn parse_type_member_separator_with_asi(&mut self) {
381        if self.parse_optional(SyntaxKind::SemicolonToken)
382            || self.parse_optional(SyntaxKind::CommaToken)
383        {
384            return;
385        }
386
387        // No explicit separator and not at a boundary that permits implicit recovery.
388        if self.scanner.has_preceding_line_break() || self.is_token(SyntaxKind::CloseBraceToken) {
389            return;
390        }
391
392        if self.is_type_member_start() {
393            self.error_token_expected(";");
394        }
395    }
396
397    /// Check if we're inside an async function/method/arrow
398    #[inline]
399    pub(crate) const fn in_async_context(&self) -> bool {
400        (self.context_flags & CONTEXT_FLAG_ASYNC) != 0
401    }
402
403    /// Check if we're inside a generator function/method
404    #[inline]
405    pub(crate) const fn in_generator_context(&self) -> bool {
406        (self.context_flags & CONTEXT_FLAG_GENERATOR) != 0
407    }
408
409    /// Check if we're parsing a class member name.
410    #[inline]
411    pub(crate) const fn in_class_member_name(&self) -> bool {
412        (self.context_flags & CONTEXT_FLAG_CLASS_MEMBER_NAME) != 0
413    }
414
415    /// Check if we're parsing inside a class body.
416    #[inline]
417    pub(crate) const fn in_class_body(&self) -> bool {
418        (self.context_flags & CONTEXT_FLAG_IN_CLASS) != 0
419    }
420
421    /// Check if we're inside a static block
422    #[inline]
423    pub(crate) const fn in_static_block_context(&self) -> bool {
424        (self.context_flags & CONTEXT_FLAG_STATIC_BLOCK) != 0
425    }
426
427    /// Check if we're parsing a parameter default (where 'await' is not allowed)
428    #[inline]
429    pub(crate) const fn in_parameter_default_context(&self) -> bool {
430        (self.context_flags & CONTEXT_FLAG_PARAMETER_DEFAULT) != 0
431    }
432
433    /// Check if 'in' is disallowed as a binary operator (e.g., in for-statement initializers)
434    #[inline]
435    pub(crate) const fn in_disallow_in_context(&self) -> bool {
436        (self.context_flags & CONTEXT_FLAG_DISALLOW_IN) != 0
437    }
438
439    /// Check if we're inside a block statement (function body, bare block, etc.)
440    /// where modifiers like `export`/`declare` are not allowed.
441    #[inline]
442    pub(crate) const fn in_block_context(&self) -> bool {
443        (self.context_flags & CONTEXT_FLAG_IN_BLOCK) != 0
444    }
445
446    /// Check if we're currently parsing inside a parenthesized expression.
447    #[inline]
448    pub(crate) const fn in_parenthesized_expression_context(&self) -> bool {
449        (self.context_flags & CONTEXT_FLAG_IN_PARENTHESIZED_EXPRESSION) != 0
450    }
451
452    /// Check if the current token is an illegal binding identifier in the current context
453    /// Returns true if illegal and emits appropriate diagnostic
454    pub(crate) fn check_illegal_binding_identifier(&mut self) -> bool {
455        use tsz_common::diagnostics::diagnostic_codes;
456
457        // Check if current token is 'await' (either as keyword or identifier)
458        let is_await = self.is_token(SyntaxKind::AwaitKeyword)
459            || (self.is_token(SyntaxKind::Identifier)
460                && self.scanner.get_token_value_ref() == "await");
461
462        // Class members reject modifier-like keywords as computed property names.
463        // This emits TS1213 in class member context while leaving object/literal contexts unchanged.
464        if self.in_class_member_name()
465            && matches!(
466                self.token(),
467                SyntaxKind::PublicKeyword
468                    | SyntaxKind::PrivateKeyword
469                    | SyntaxKind::ProtectedKeyword
470                    | SyntaxKind::ReadonlyKeyword
471                    | SyntaxKind::StaticKeyword
472                    | SyntaxKind::AbstractKeyword
473                    | SyntaxKind::OverrideKeyword
474                    | SyntaxKind::AsyncKeyword
475                    | SyntaxKind::AwaitKeyword
476                    | SyntaxKind::YieldKeyword
477            )
478        {
479            let token_text = self.scanner.get_token_value_ref();
480            self.parse_error_at_current_token(
481                &format!(
482                    "Identifier expected. '{token_text}' is a reserved word in strict mode. Class definitions are automatically in strict mode."
483                ),
484                diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE_CLASS_DEFINITIONS_ARE_AUTO,
485            );
486            return true;
487        }
488
489        if is_await {
490            // In static blocks, 'await' cannot be used as a binding identifier
491            if self.in_static_block_context() {
492                self.parse_error_at_current_token(
493                    "Identifier expected. 'await' is a reserved word that cannot be used here.",
494                    diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_THAT_CANNOT_BE_USED_HERE,
495                );
496                return true;
497            }
498
499            // In async contexts, 'await' cannot be used as a binding identifier
500            if self.in_async_context() {
501                self.parse_error_at_current_token(
502                    "Identifier expected. 'await' is a reserved word that cannot be used here.",
503                    diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_THAT_CANNOT_BE_USED_HERE,
504                );
505                return true;
506            }
507        }
508
509        // Check if current token is 'yield' (either as keyword or identifier)
510        // TS1359: 'yield' is a reserved word in generator functions
511        let is_yield = self.is_token(SyntaxKind::YieldKeyword)
512            || (self.is_token(SyntaxKind::Identifier)
513                && self.scanner.get_token_value_ref() == "yield");
514
515        if is_yield && self.in_generator_context() {
516            let is_class_context = self.in_class_body() || self.in_class_member_name();
517            if is_class_context {
518                self.parse_error_at_current_token(
519                    "Identifier expected. 'yield' is a reserved word in strict mode. Class definitions are automatically in strict mode.",
520                    diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE_CLASS_DEFINITIONS_ARE_AUTO,
521                );
522            } else {
523                self.parse_error_at_current_token(
524                    "Identifier expected. 'yield' is a reserved word in strict mode.",
525                    diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE,
526                );
527            }
528            return true;
529        }
530
531        false
532    }
533
534    /// Recover from invalid method/member syntax when `(` is missing after the member name.
535    /// This is used for async/generator forms like `async * get x() {}` where a single TS1005
536    /// should be emitted and the parser should skip the rest of the member to avoid cascades.
537    pub(crate) fn recover_from_missing_method_open_paren(&mut self) {
538        while !(self.is_token(SyntaxKind::OpenBraceToken)
539            || self.is_token(SyntaxKind::SemicolonToken)
540            || self.is_token(SyntaxKind::CommaToken)
541            || self.is_token(SyntaxKind::CloseBraceToken))
542        {
543            self.next_token();
544        }
545
546        if self.is_token(SyntaxKind::OpenBraceToken) {
547            let body = self.parse_block();
548            let _ = body;
549            return;
550        }
551
552        if self.is_token(SyntaxKind::SemicolonToken) || self.is_token(SyntaxKind::CommaToken) {
553            self.next_token();
554        }
555    }
556
557    /// Parse optional token, returns true if found
558    pub fn parse_optional(&mut self, kind: SyntaxKind) -> bool {
559        if self.is_token(kind) {
560            // Check for TS1260 if consuming a keyword
561            if token_is_keyword(kind) {
562                self.check_keyword_with_escape();
563            }
564            self.next_token();
565            true
566        } else {
567            false
568        }
569    }
570
571    /// Parse expected token, report error if not found
572    /// Suppresses error if we already emitted an error at the current position
573    /// (to prevent cascading errors from sequential `parse_expected` calls)
574    pub fn parse_expected(&mut self, kind: SyntaxKind) -> bool {
575        if self.is_token(kind) {
576            // Check for TS1260 if consuming a keyword
577            if token_is_keyword(kind) {
578                self.check_keyword_with_escape();
579            }
580            self.next_token();
581            true
582        } else if self.is_token(SyntaxKind::Unknown) {
583            // Unknown token = invalid character. In tsc, the scanner emits TS1127 via
584            // scanError callback and advances past the invalid char during scanning.
585            // The parser then sees the next real token. We replicate this by emitting
586            // TS1127, advancing, and re-checking for the expected token.
587            {
588                use tsz_common::diagnostics::diagnostic_codes;
589                self.parse_error_at_current_token(
590                    "Invalid character.",
591                    diagnostic_codes::INVALID_CHARACTER,
592                );
593            }
594            self.next_token();
595            // After skipping the invalid character, check if expected token is now present
596            if self.is_token(kind) {
597                self.next_token();
598                return true;
599            }
600            // Expected token still not found — emit TS1005 at the new position.
601            // In tsc, parseErrorAtPosition dedup is same-position only (not distance-based),
602            // so TS1005 at the post-Unknown position always emits. Use direct error emit
603            // to bypass our distance-based should_report_error() suppression.
604            {
605                use tsz_common::diagnostics::diagnostic_codes;
606                self.parse_error_at_current_token(
607                    &format!("'{}' expected.", Self::token_to_string(kind)),
608                    diagnostic_codes::EXPECTED,
609                );
610            }
611            false
612        } else {
613            // Force error emission for missing ) in common patterns.
614            // This bypasses the should_report_error() distance check.
615            let force_emit = kind == SyntaxKind::CloseParenToken
616                && (self.is_token(SyntaxKind::OpenBraceToken)
617                    || self.is_token(SyntaxKind::CloseBraceToken)
618                    || self.is_token(SyntaxKind::EndOfFileToken));
619
620            // Only emit error if we haven't already emitted one at this position
621            // This prevents cascading errors like "';' expected" followed by "')' expected"
622            // when the real issue is a single missing token
623            // Use centralized error suppression heuristic
624            if force_emit || self.should_report_error() {
625                // Additional check: suppress error for missing closing tokens when we're
626                // at a clear statement boundary or EOF (reduces false-positive TS1005 errors)
627                let should_suppress = if force_emit {
628                    false // Never suppress forced errors
629                } else {
630                    match kind {
631                        SyntaxKind::CloseBraceToken | SyntaxKind::CloseBracketToken => {
632                            // At EOF, the file ended before this closing token. TypeScript reports
633                            // these missing closing delimiters, so do not suppress at EOF.
634                            if self.is_token(SyntaxKind::EndOfFileToken) {
635                                false
636                            }
637                            // If next token starts a statement, the user has clearly moved on
638                            // Don't complain about missing closing token
639                            else if self.is_statement_start() {
640                                true
641                            }
642                            // If there's a line break, give the user benefit of doubt
643                            else {
644                                self.scanner.has_preceding_line_break()
645                            }
646                        }
647                        SyntaxKind::CloseParenToken => {
648                            // Missing ) is almost always a genuine error — don't suppress
649                            // at EOF, statement boundaries, or block delimiters.
650                            // Only suppress if on same line with no clear boundary.
651                            if self.is_token(SyntaxKind::EndOfFileToken) {
652                                false
653                            } else if self.scanner.has_preceding_line_break() {
654                                // At a line break, suppress unless it's a clear boundary
655                                !self.is_statement_start()
656                                    && !self.is_token(SyntaxKind::CloseBraceToken)
657                            } else {
658                                false
659                            }
660                        }
661                        _ => false,
662                    }
663                };
664
665                if !should_suppress {
666                    // For forced errors, bypass the normal error budget logic
667                    if force_emit {
668                        use tsz_common::diagnostics::diagnostic_codes;
669                        self.parse_error_at_current_token(
670                            &format!("'{}' expected.", Self::token_to_string(kind)),
671                            diagnostic_codes::EXPECTED,
672                        );
673                    } else {
674                        self.error_token_expected(Self::token_to_string(kind));
675                    }
676                }
677            }
678            false
679        }
680    }
681
682    /// Convert `SyntaxKind` to human-readable token string
683    pub(crate) const fn token_to_string(kind: SyntaxKind) -> &'static str {
684        match kind {
685            SyntaxKind::OpenBraceToken => "{",
686            SyntaxKind::CloseBraceToken => "}",
687            SyntaxKind::OpenParenToken => "(",
688            SyntaxKind::CloseParenToken => ")",
689            SyntaxKind::OpenBracketToken => "[",
690            SyntaxKind::CloseBracketToken => "]",
691            SyntaxKind::SemicolonToken => ";",
692            SyntaxKind::CommaToken => ",",
693            SyntaxKind::ColonToken => ":",
694            SyntaxKind::DotToken => ".",
695            SyntaxKind::EqualsToken => "=",
696            SyntaxKind::GreaterThanToken => ">",
697            SyntaxKind::LessThanToken => "<",
698            SyntaxKind::QuestionToken => "?",
699            SyntaxKind::ExclamationToken => "!",
700            SyntaxKind::AtToken => "@",
701            SyntaxKind::AmpersandToken => "&",
702            SyntaxKind::BarToken => "|",
703            SyntaxKind::PlusToken => "+",
704            SyntaxKind::MinusToken => "-",
705            SyntaxKind::AsteriskToken => "*",
706            SyntaxKind::SlashToken => "/",
707            SyntaxKind::EqualsGreaterThanToken => "=>",
708            SyntaxKind::DotDotDotToken => "...",
709            SyntaxKind::Identifier => "identifier",
710            SyntaxKind::TryKeyword => "try",
711            _ => "token",
712        }
713    }
714
715    pub(crate) fn parse_error_at(&mut self, start: u32, length: u32, message: &str, code: u32) {
716        // Don't report another error if it would just be at the same position as the last error.
717        // This matches tsc's parseErrorAtPosition deduplication behavior where parser errors
718        // at the same position are suppressed (only the first one survives).
719        if let Some(last) = self.parse_diagnostics.last()
720            && last.start == start
721        {
722            return;
723        }
724        // Track the position of this error to prevent cascading errors at same position
725        self.last_error_pos = start;
726        self.parse_diagnostics.push(ParseDiagnostic {
727            start,
728            length,
729            message: message.to_string(),
730            code,
731        });
732    }
733
734    /// Report parse error at current token with specific error code
735    pub fn parse_error_at_current_token(&mut self, message: &str, code: u32) {
736        let start = self.u32_from_usize(self.scanner.get_token_start());
737        let end = self.u32_from_usize(self.scanner.get_token_end());
738        self.parse_error_at(start, end - start, message, code);
739    }
740
741    /// Report escaped sequence diagnostics for string and template tokens.
742    pub(crate) fn report_invalid_string_or_template_escape_errors(&mut self) {
743        let token_text = self.scanner.get_token_text_ref().to_string();
744        if token_text.is_empty()
745            || (self.scanner.get_token_flags() & TokenFlags::ContainsInvalidEscape as u32) == 0
746        {
747            return;
748        }
749
750        let bytes = token_text.as_bytes();
751        let token_len = bytes.len();
752        let token_start = self.token_pos() as usize;
753
754        let Some((content_start_offset, content_end_offset)) =
755            self.string_template_escape_content_span(token_len, bytes)
756        else {
757            return;
758        };
759
760        if content_end_offset <= content_start_offset || content_end_offset > token_len {
761            return;
762        }
763
764        let raw = &bytes[content_start_offset..content_end_offset];
765        let content_start = token_start + content_start_offset;
766
767        let mut i = 0usize;
768
769        while i < raw.len() {
770            if raw[i] != b'\\' {
771                i += 1;
772                continue;
773            }
774            if i + 1 >= raw.len() {
775                break;
776            }
777            i = match raw[i + 1] {
778                b'x' => self.report_invalid_string_or_template_hex_escape(raw, content_start, i),
779                b'u' => {
780                    self.report_invalid_string_or_template_unicode_escape(raw, content_start, i)
781                }
782                _ => i + 1,
783            };
784        }
785    }
786
787    fn string_template_escape_content_span(
788        &self,
789        token_len: usize,
790        bytes: &[u8],
791    ) -> Option<(usize, usize)> {
792        match self.current_token {
793            SyntaxKind::StringLiteral => {
794                if token_len < 2
795                    || (bytes[0] != b'"' && bytes[0] != b'\'')
796                    || bytes[token_len - 1] != bytes[0]
797                {
798                    return None;
799                }
800                Some((1, token_len - 1))
801            }
802            SyntaxKind::NoSubstitutionTemplateLiteral => {
803                if bytes[0] != b'`' || bytes[token_len - 1] != b'`' {
804                    return None;
805                }
806                Some((1, token_len - 1))
807            }
808            SyntaxKind::TemplateHead => {
809                if bytes[0] != b'`' || !bytes.ends_with(b"${") {
810                    return None;
811                }
812                Some((1, token_len - 2))
813            }
814            SyntaxKind::TemplateMiddle | SyntaxKind::TemplateTail => {
815                if bytes[0] != b'}' {
816                    return None;
817                }
818                if bytes.ends_with(b"${") {
819                    Some((1, token_len - 2))
820                } else if bytes.ends_with(b"`") {
821                    Some((1, token_len - 1))
822                } else {
823                    Some((1, token_len))
824                }
825            }
826            _ => None,
827        }
828    }
829
830    fn report_invalid_string_or_template_hex_escape(
831        &mut self,
832        raw: &[u8],
833        content_start: usize,
834        i: usize,
835    ) -> usize {
836        use tsz_common::diagnostics::{diagnostic_codes, diagnostic_messages};
837
838        let first = i + 2;
839        let second = i + 3;
840        let err_len = |offset: usize| u32::from(offset < raw.len());
841
842        if first >= raw.len() || !Self::is_hex_digit(raw[first]) {
843            self.parse_error_at(
844                self.u32_from_usize(content_start + first),
845                err_len(first),
846                diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
847                diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
848            );
849        } else if second >= raw.len() || !Self::is_hex_digit(raw[second]) {
850            self.parse_error_at(
851                self.u32_from_usize(content_start + second),
852                err_len(second),
853                diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
854                diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
855            );
856        }
857        i + 2
858    }
859
860    fn report_invalid_string_or_template_unicode_escape(
861        &mut self,
862        raw: &[u8],
863        content_start: usize,
864        i: usize,
865    ) -> usize {
866        use tsz_common::diagnostics::{diagnostic_codes, diagnostic_messages};
867
868        if i + 2 >= raw.len() {
869            self.parse_error_at(
870                self.u32_from_usize(content_start + i + 2),
871                u32::from(i + 2 < raw.len()),
872                diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
873                diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
874            );
875            return i + 2;
876        }
877
878        if raw[i + 2] == b'{' {
879            let mut close = i + 3;
880            let mut has_digit = false;
881            while close < raw.len() && Self::is_hex_digit(raw[close]) {
882                has_digit = true;
883                close += 1;
884            }
885            if close >= raw.len() {
886                if !has_digit {
887                    // No hex digits at all: \u{ followed by end → TS1125
888                    self.parse_error_at(
889                        self.u32_from_usize(content_start + i + 3),
890                        0,
891                        diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
892                        diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
893                    );
894                } else {
895                    // Had hex digits but no closing brace → TS1508
896                    self.parse_error_at(
897                        self.u32_from_usize(content_start + close),
898                        0,
899                        diagnostic_messages::UNTERMINATED_UNICODE_ESCAPE_SEQUENCE,
900                        diagnostic_codes::UNTERMINATED_UNICODE_ESCAPE_SEQUENCE,
901                    );
902                }
903            } else if raw[close] == b'}' {
904                if !has_digit {
905                    self.parse_error_at(
906                        self.u32_from_usize(content_start + close),
907                        1,
908                        diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
909                        diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
910                    );
911                } else {
912                    // Check if the value exceeds 0x10FFFF (TS1198)
913                    let hex_str = std::str::from_utf8(&raw[i + 3..close]).unwrap_or("");
914                    if let Ok(value) = u32::from_str_radix(hex_str, 16)
915                        && value > 0x10FFFF
916                    {
917                        self.parse_error_at(
918                            self.u32_from_usize(content_start + i),
919                            (close + 1 - i) as u32,
920                            diagnostic_messages::AN_EXTENDED_UNICODE_ESCAPE_VALUE_MUST_BE_BETWEEN_0X0_AND_0X10FFFF_INCLUSIVE,
921                            diagnostic_codes::AN_EXTENDED_UNICODE_ESCAPE_VALUE_MUST_BE_BETWEEN_0X0_AND_0X10FFFF_INCLUSIVE,
922                        );
923                    }
924                }
925            } else if !has_digit {
926                self.parse_error_at(
927                    self.u32_from_usize(content_start + i + 3),
928                    1,
929                    diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
930                    diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
931                );
932            } else {
933                self.parse_error_at(
934                    self.u32_from_usize(content_start + close),
935                    1,
936                    diagnostic_messages::UNTERMINATED_UNICODE_ESCAPE_SEQUENCE,
937                    diagnostic_codes::UNTERMINATED_UNICODE_ESCAPE_SEQUENCE,
938                );
939            }
940            close + 1
941        } else {
942            let first = i + 2;
943            let second = i + 3;
944            let third = i + 4;
945            let fourth = i + 5;
946            let err_len = |offset: usize| u32::from(offset < raw.len());
947
948            if first >= raw.len() || !Self::is_hex_digit(raw[first]) {
949                self.parse_error_at(
950                    self.u32_from_usize(content_start + first),
951                    err_len(first),
952                    diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
953                    diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
954                );
955            } else if second >= raw.len() || !Self::is_hex_digit(raw[second]) {
956                self.parse_error_at(
957                    self.u32_from_usize(content_start + second),
958                    err_len(second),
959                    diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
960                    diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
961                );
962            } else if third >= raw.len() || !Self::is_hex_digit(raw[third]) {
963                self.parse_error_at(
964                    self.u32_from_usize(content_start + third),
965                    err_len(third),
966                    diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
967                    diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
968                );
969            } else if fourth >= raw.len() || !Self::is_hex_digit(raw[fourth]) {
970                self.parse_error_at(
971                    self.u32_from_usize(content_start + fourth),
972                    err_len(fourth),
973                    diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
974                    diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
975                );
976            }
977            i + 2
978        }
979    }
980
981    #[inline]
982    const fn is_hex_digit(byte: u8) -> bool {
983        byte.is_ascii_hexdigit()
984    }
985
986    /// Parse regex unicode escape diagnostics for regex literals in /u or /v mode.
987    pub(crate) fn report_invalid_regular_expression_escape_errors(&mut self) {
988        let token_text = self.scanner.get_token_text_ref().to_string();
989        if !token_text.starts_with('/') || token_text.len() < 2 {
990            return;
991        }
992
993        let bytes = token_text.as_bytes();
994        let mut i = 1usize;
995        let mut in_escape = false;
996        let mut in_character_class = false;
997        while i < bytes.len() {
998            let ch = bytes[i];
999            if in_escape {
1000                in_escape = false;
1001                i += 1;
1002                continue;
1003            }
1004            if ch == b'\\' {
1005                in_escape = true;
1006                i += 1;
1007                continue;
1008            }
1009            if ch == b'[' {
1010                in_character_class = true;
1011                i += 1;
1012                continue;
1013            }
1014            if ch == b']' {
1015                in_character_class = false;
1016                i += 1;
1017                continue;
1018            }
1019            if ch == b'/' && !in_character_class {
1020                break;
1021            }
1022            i += 1;
1023        }
1024        if i >= bytes.len() {
1025            return;
1026        }
1027
1028        let body = &token_text[1..i];
1029        let flags = if i + 1 < token_text.len() {
1030            &token_text[i + 1..]
1031        } else {
1032            ""
1033        };
1034        let has_unicode_flag = flags.as_bytes().iter().any(|&b| b == b'u' || b == b'v');
1035        if !has_unicode_flag {
1036            return;
1037        }
1038
1039        let body_start = self.token_pos() as usize + 1;
1040        let raw = body.as_bytes();
1041        let mut j = 0usize;
1042
1043        while j < raw.len() {
1044            if raw[j] != b'\\' {
1045                j += 1;
1046                continue;
1047            }
1048            if j + 1 >= raw.len() {
1049                break;
1050            }
1051            match raw[j + 1] {
1052                b'x' => {
1053                    j = self.report_invalid_regular_expression_hex_escape(raw, body_start, j);
1054                }
1055                b'u' => {
1056                    if let Some(next) =
1057                        self.report_invalid_regular_expression_unicode_escape(raw, body_start, j)
1058                    {
1059                        j = next;
1060                    } else {
1061                        break;
1062                    }
1063                }
1064                _ => {
1065                    j += 1;
1066                }
1067            }
1068        }
1069    }
1070
1071    fn report_invalid_regular_expression_hex_escape(
1072        &mut self,
1073        raw: &[u8],
1074        body_start: usize,
1075        j: usize,
1076    ) -> usize {
1077        use tsz_common::diagnostics::{diagnostic_codes, diagnostic_messages};
1078
1079        let first = j + 2;
1080        let second = j + 3;
1081        if first >= raw.len() || !Self::is_hex_digit(raw[first]) {
1082            self.parse_error_at(
1083                self.u32_from_usize(body_start + first),
1084                u32::from(first < raw.len()),
1085                diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
1086                diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
1087            );
1088        } else if second >= raw.len() || !Self::is_hex_digit(raw[second]) {
1089            self.parse_error_at(
1090                self.u32_from_usize(body_start + second),
1091                u32::from(second < raw.len()),
1092                diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
1093                diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
1094            );
1095        }
1096        j + 2
1097    }
1098
1099    fn report_invalid_regular_expression_unicode_escape(
1100        &mut self,
1101        raw: &[u8],
1102        body_start: usize,
1103        j: usize,
1104    ) -> Option<usize> {
1105        use tsz_common::diagnostics::{diagnostic_codes, diagnostic_messages};
1106
1107        if j + 2 < raw.len() && raw[j + 2] == b'{' {
1108            let mut close = j + 3;
1109            let mut has_digit = false;
1110            while close < raw.len() && Self::is_hex_digit(raw[close]) {
1111                has_digit = true;
1112                close += 1;
1113            }
1114            if close >= raw.len() {
1115                self.parse_error_at(
1116                    self.u32_from_usize(body_start + close),
1117                    0,
1118                    diagnostic_messages::UNTERMINATED_UNICODE_ESCAPE_SEQUENCE,
1119                    diagnostic_codes::UNTERMINATED_UNICODE_ESCAPE_SEQUENCE,
1120                );
1121                return None;
1122            }
1123            if raw[close] == b'}' {
1124                if !has_digit {
1125                    self.parse_error_at(
1126                        self.u32_from_usize(body_start + close),
1127                        1,
1128                        diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
1129                        diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
1130                    );
1131                }
1132            } else if !has_digit {
1133                self.parse_error_at(
1134                    self.u32_from_usize(body_start + j + 3),
1135                    1,
1136                    diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
1137                    diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
1138                );
1139            } else {
1140                self.parse_error_at(
1141                    self.u32_from_usize(body_start + close),
1142                    1,
1143                    diagnostic_messages::UNTERMINATED_UNICODE_ESCAPE_SEQUENCE,
1144                    diagnostic_codes::UNTERMINATED_UNICODE_ESCAPE_SEQUENCE,
1145                );
1146                self.parse_error_at(
1147                    self.u32_from_usize(body_start + close),
1148                    1,
1149                    diagnostic_messages::UNEXPECTED_DID_YOU_MEAN_TO_ESCAPE_IT_WITH_BACKSLASH,
1150                    diagnostic_codes::UNEXPECTED_DID_YOU_MEAN_TO_ESCAPE_IT_WITH_BACKSLASH,
1151                );
1152            }
1153            Some(close + 1)
1154        } else {
1155            let first = j + 2;
1156            let second = j + 3;
1157            let third = j + 4;
1158            let fourth = j + 5;
1159            if first >= raw.len() || !Self::is_hex_digit(raw[first]) {
1160                self.parse_error_at(
1161                    self.u32_from_usize(body_start + first),
1162                    u32::from(first < raw.len()),
1163                    diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
1164                    diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
1165                );
1166            } else if second >= raw.len() || !Self::is_hex_digit(raw[second]) {
1167                self.parse_error_at(
1168                    self.u32_from_usize(body_start + second),
1169                    u32::from(second < raw.len()),
1170                    diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
1171                    diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
1172                );
1173            } else if third >= raw.len() || !Self::is_hex_digit(raw[third]) {
1174                self.parse_error_at(
1175                    self.u32_from_usize(body_start + third),
1176                    u32::from(third < raw.len()),
1177                    diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
1178                    diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
1179                );
1180            } else if fourth >= raw.len() || !Self::is_hex_digit(raw[fourth]) {
1181                self.parse_error_at(
1182                    self.u32_from_usize(body_start + fourth),
1183                    u32::from(fourth < raw.len()),
1184                    diagnostic_messages::HEXADECIMAL_DIGIT_EXPECTED,
1185                    diagnostic_codes::HEXADECIMAL_DIGIT_EXPECTED,
1186                );
1187            }
1188            Some(j + 2)
1189        }
1190    }
1191
1192    // =========================================================================
1193    // Typed error helper methods (use these instead of parse_error_at_current_token)
1194    // =========================================================================
1195
1196    /// Error: Expression expected (TS1109)
1197    pub(crate) fn error_expression_expected(&mut self) {
1198        // Only emit error if we haven't already emitted one at this position
1199        // This prevents cascading TS1109 errors when TS1005 or other errors already reported
1200        // Use centralized error suppression heuristic
1201        if self.should_report_error() {
1202            use tsz_common::diagnostics::diagnostic_codes;
1203            self.parse_error_at_current_token(
1204                "Expression expected.",
1205                diagnostic_codes::EXPRESSION_EXPECTED,
1206            );
1207        }
1208    }
1209
1210    /// Error: Expression or comma expected (TS1137)
1211    /// Used in array literal element parsing where tsc uses TS1137 instead of TS1109.
1212    pub(crate) fn error_expression_or_comma_expected(&mut self) {
1213        if self.should_report_error() {
1214            use tsz_common::diagnostics::{diagnostic_codes, diagnostic_messages};
1215            self.parse_error_at_current_token(
1216                diagnostic_messages::EXPRESSION_OR_COMMA_EXPECTED,
1217                diagnostic_codes::EXPRESSION_OR_COMMA_EXPECTED,
1218            );
1219        }
1220    }
1221
1222    /// Error: Argument expression expected (TS1135)
1223    /// Used in function call argument list parsing instead of generic TS1109.
1224    pub(crate) fn error_argument_expression_expected(&mut self) {
1225        if self.should_report_error() {
1226            use tsz_common::diagnostics::diagnostic_codes;
1227            self.parse_error_at_current_token(
1228                "Argument expression expected.",
1229                diagnostic_codes::ARGUMENT_EXPRESSION_EXPECTED,
1230            );
1231        }
1232    }
1233
1234    /// Error: Type expected (TS1110)
1235    pub(crate) fn error_type_expected(&mut self) {
1236        use tsz_common::diagnostics::diagnostic_codes;
1237        self.parse_error_at_current_token("Type expected", diagnostic_codes::TYPE_EXPECTED);
1238    }
1239
1240    /// Error: Identifier expected (TS1003), or Invalid character (TS1127)
1241    pub(crate) fn error_identifier_expected(&mut self) {
1242        // When the current token is Unknown (invalid character), emit TS1127
1243        // instead of TS1003, matching tsc's behavior where the scanner's
1244        // TS1127 shadows the parser's TS1003 via position-based dedup.
1245        if self.is_token(SyntaxKind::Unknown) {
1246            if self.should_report_error() {
1247                use tsz_common::diagnostics::diagnostic_codes;
1248                self.parse_error_at_current_token(
1249                    "Invalid character.",
1250                    diagnostic_codes::INVALID_CHARACTER,
1251                );
1252            }
1253            return;
1254        }
1255        // Only emit error if we haven't already emitted one at this position
1256        // This prevents cascading errors when a missing token causes identifier to be expected
1257        // Use centralized error suppression heuristic
1258        if self.should_report_error() {
1259            use tsz_common::diagnostics::diagnostic_codes;
1260            self.parse_error_at_current_token(
1261                "Identifier expected",
1262                diagnostic_codes::IDENTIFIER_EXPECTED,
1263            );
1264        }
1265    }
1266
1267    /// Check if current token is a reserved word that cannot be used as an identifier
1268    /// Reserved words are keywords from `BreakKeyword` through `WithKeyword`
1269    #[inline]
1270    pub(crate) const fn is_reserved_word(&self) -> bool {
1271        // Match TypeScript's isReservedWord logic:
1272        // token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord
1273        self.current_token as u16 >= SyntaxKind::FIRST_RESERVED_WORD as u16
1274            && self.current_token as u16 <= SyntaxKind::LAST_RESERVED_WORD as u16
1275    }
1276
1277    /// Get the text representation of the current keyword token
1278    const fn current_keyword_text(&self) -> &'static str {
1279        match self.current_token {
1280            SyntaxKind::BreakKeyword => "break",
1281            SyntaxKind::CaseKeyword => "case",
1282            SyntaxKind::CatchKeyword => "catch",
1283            SyntaxKind::ClassKeyword => "class",
1284            SyntaxKind::ConstKeyword => "const",
1285            SyntaxKind::ContinueKeyword => "continue",
1286            SyntaxKind::DebuggerKeyword => "debugger",
1287            SyntaxKind::DefaultKeyword => "default",
1288            SyntaxKind::DeleteKeyword => "delete",
1289            SyntaxKind::DoKeyword => "do",
1290            SyntaxKind::ElseKeyword => "else",
1291            SyntaxKind::EnumKeyword => "enum",
1292            SyntaxKind::ExportKeyword => "export",
1293            SyntaxKind::ExtendsKeyword => "extends",
1294            SyntaxKind::FalseKeyword => "false",
1295            SyntaxKind::FinallyKeyword => "finally",
1296            SyntaxKind::ForKeyword => "for",
1297            SyntaxKind::FunctionKeyword => "function",
1298            SyntaxKind::IfKeyword => "if",
1299            SyntaxKind::ImportKeyword => "import",
1300            SyntaxKind::InKeyword => "in",
1301            SyntaxKind::InstanceOfKeyword => "instanceof",
1302            SyntaxKind::NewKeyword => "new",
1303            SyntaxKind::NullKeyword => "null",
1304            SyntaxKind::ReturnKeyword => "return",
1305            SyntaxKind::SuperKeyword => "super",
1306            SyntaxKind::SwitchKeyword => "switch",
1307            SyntaxKind::ThisKeyword => "this",
1308            SyntaxKind::ThrowKeyword => "throw",
1309            SyntaxKind::TrueKeyword => "true",
1310            SyntaxKind::TryKeyword => "try",
1311            SyntaxKind::TypeOfKeyword => "typeof",
1312            SyntaxKind::VarKeyword => "var",
1313            SyntaxKind::VoidKeyword => "void",
1314            SyntaxKind::WhileKeyword => "while",
1315            SyntaxKind::WithKeyword => "with",
1316            _ => "reserved word",
1317        }
1318    }
1319
1320    /// Error: TS1359 - Identifier expected. '{0}' is a reserved word that cannot be used here.
1321    pub(crate) fn error_reserved_word_identifier(&mut self) {
1322        // Use centralized error suppression heuristic
1323        if self.should_report_error() {
1324            use tsz_common::diagnostics::diagnostic_codes;
1325            let word = self.current_keyword_text();
1326            if self.is_token(SyntaxKind::YieldKeyword) && self.in_generator_context() {
1327                self.parse_error_at_current_token(
1328                    "Identifier expected. 'yield' is a reserved word in strict mode.",
1329                    diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE,
1330                );
1331                // Consume the reserved word token to prevent cascading errors
1332                self.next_token();
1333                return;
1334            }
1335            self.parse_error_at_current_token(
1336                &format!(
1337                    "Identifier expected. '{word}' is a reserved word that cannot be used here."
1338                ),
1339                diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_THAT_CANNOT_BE_USED_HERE,
1340            );
1341            // Consume the reserved word token to prevent cascading errors
1342            self.next_token();
1343        }
1344    }
1345
1346    /// Error: '{token}' expected (TS1005)
1347    pub(crate) fn error_token_expected(&mut self, token: &str) {
1348        // When the current token is Unknown (invalid character), emit only TS1127.
1349        // In tsc, the scanner emits TS1127 into parseDiagnostics via scanError callback
1350        // *before* the parser's parseExpected runs. Since tsc's parseErrorAtPosition dedup
1351        // suppresses errors at the same position as the last error, the parser's TS1005 is
1352        // always shadowed by the scanner's TS1127. We replicate this by emitting only TS1127.
1353        if self.is_token(SyntaxKind::Unknown) {
1354            if self.should_report_error() {
1355                use tsz_common::diagnostics::diagnostic_codes;
1356                self.parse_error_at_current_token(
1357                    "Invalid character.",
1358                    diagnostic_codes::INVALID_CHARACTER,
1359                );
1360            }
1361            return;
1362        }
1363        // Only emit error if we haven't already emitted one at this position
1364        // This prevents cascading errors when parse_semicolon() and similar functions call this
1365        // Use centralized error suppression heuristic
1366        if self.should_report_error() {
1367            use tsz_common::diagnostics::diagnostic_codes;
1368            self.parse_error_at_current_token(
1369                &format!("'{token}' expected."),
1370                diagnostic_codes::EXPECTED,
1371            );
1372        }
1373    }
1374
1375    /// Error: Comma expected (TS1005) - specifically for missing commas between parameters/arguments
1376    pub(crate) fn error_comma_expected(&mut self) {
1377        self.error_token_expected(",");
1378    }
1379
1380    /// Check if current token could start a parameter
1381    pub(crate) fn is_parameter_start(&mut self) -> bool {
1382        // Parameters can start with modifiers, identifiers, or binding patterns
1383        self.is_parameter_modifier()
1384            || self.is_token(SyntaxKind::AtToken) // decorators on parameters
1385            || self.is_token(SyntaxKind::DotDotDotToken) // rest parameter
1386            || self.is_identifier_or_keyword()
1387            || self.is_token(SyntaxKind::OpenBraceToken) // object binding pattern
1388            || self.is_token(SyntaxKind::OpenBracketToken) // array binding pattern
1389    }
1390
1391    /// Error: Unterminated template literal (TS1160)
1392    pub(crate) fn error_unterminated_template_literal_at(&mut self, start: u32, end: u32) {
1393        use tsz_common::diagnostics::diagnostic_codes;
1394        let length = end.saturating_sub(start).max(1);
1395        self.parse_error_at(
1396            start,
1397            length,
1398            "Unterminated template literal.",
1399            diagnostic_codes::UNTERMINATED_TEMPLATE_LITERAL,
1400        );
1401    }
1402
1403    /// Error: Declaration expected (TS1146)
1404    pub(crate) fn error_declaration_expected(&mut self) {
1405        use tsz_common::diagnostics::diagnostic_codes;
1406        self.parse_error_at_current_token(
1407            "Declaration expected",
1408            diagnostic_codes::DECLARATION_EXPECTED,
1409        );
1410    }
1411
1412    /// Error: Statement expected (TS1129)
1413    pub(crate) fn error_statement_expected(&mut self) {
1414        use tsz_common::diagnostics::diagnostic_codes;
1415        self.parse_error_at_current_token(
1416            "Statement expected",
1417            diagnostic_codes::STATEMENT_EXPECTED,
1418        );
1419    }
1420
1421    /// Check if a statement is a using/await using declaration not inside a block (TS1156)
1422    pub(crate) fn check_using_outside_block(&mut self, statement: NodeIndex) {
1423        use crate::parser::node_flags;
1424        use tsz_common::diagnostics::{diagnostic_codes, diagnostic_messages};
1425
1426        if statement.is_none() {
1427            return;
1428        }
1429
1430        // Get the node and check if it's a variable statement with using flags
1431        if let Some(node) = self.arena.get(statement) {
1432            // Check if it's a variable statement (not a block)
1433            if node.kind == syntax_kind_ext::VARIABLE_STATEMENT {
1434                // Check if it has using or await using flags
1435                let is_using = (node.flags
1436                    & self.u16_from_node_flags(node_flags::USING | node_flags::AWAIT_USING))
1437                    != 0;
1438                if is_using {
1439                    // Emit TS1156 error at the statement position
1440                    self.parse_error_at(
1441                        node.pos,
1442                        node.end.saturating_sub(node.pos).max(1),
1443                        diagnostic_messages::DECLARATIONS_CAN_ONLY_BE_DECLARED_INSIDE_A_BLOCK,
1444                        diagnostic_codes::DECLARATIONS_CAN_ONLY_BE_DECLARED_INSIDE_A_BLOCK,
1445                    );
1446                }
1447            }
1448        }
1449    }
1450
1451    /// Error: Unexpected token (TS1012)
1452    pub(crate) fn error_unexpected_token(&mut self) {
1453        use tsz_common::diagnostics::diagnostic_codes;
1454        self.parse_error_at_current_token("Unexpected token", diagnostic_codes::UNEXPECTED_TOKEN);
1455    }
1456
1457    /// Parse semicolon (or recover from missing)
1458    pub(crate) fn parse_semicolon(&mut self) {
1459        if self.is_token(SyntaxKind::SemicolonToken) {
1460            self.next_token();
1461        } else if self.is_token(SyntaxKind::Unknown) {
1462            // Scanner/lexer already reported an error for this token.
1463            // Avoid cascading TS1005 (';' expected) at the same position.
1464        } else if !self.can_parse_semicolon() {
1465            // Suppress cascading TS1005 "';' expected" when a recent error was already
1466            // emitted. This happens when a prior parse failure (e.g., missing identifier,
1467            // unsupported syntax) causes the parser to not consume tokens, then
1468            // parse_semicolon is called and fails too.
1469            // Use centralized error suppression heuristic
1470            if self.should_report_error() {
1471                self.error_token_expected(";");
1472            }
1473        }
1474    }
1475
1476    // =========================================================================
1477    // Keyword suggestion for misspelled keywords (TS1434/TS1435/TS1438)
1478    // =========================================================================
1479
1480    /// Provides a better error message than the generic "';' expected" for
1481    /// known common variants of a missing semicolon, such as misspelled keywords.
1482    ///
1483    /// Matches TypeScript's `parseErrorForMissingSemicolonAfter`.
1484    ///
1485    /// `expression` is the node index of the expression that was parsed before
1486    /// the missing semicolon.
1487    pub(crate) fn parse_error_for_missing_semicolon_after(&mut self, expression: NodeIndex) {
1488        use crate::parser::spelling;
1489        use tsz_common::diagnostics::diagnostic_codes;
1490
1491        let Some((pos, len, expression_text)) =
1492            self.missing_semicolon_after_expression_text(expression)
1493        else {
1494            if self.should_report_error() {
1495                self.error_token_expected(";");
1496            }
1497            return;
1498        };
1499
1500        if self.parse_missing_semicolon_keyword_error(pos, len, &expression_text) {
1501            return;
1502        }
1503
1504        if self.should_suppress_type_or_keyword_suggestion_for_missing_semicolon(
1505            expression_text.as_str(),
1506            pos,
1507        ) {
1508            return;
1509        }
1510
1511        if let Some(suggestion) = spelling::suggest_keyword(&expression_text) {
1512            if !self.should_suppress_type_or_keyword_suggestion_for_missing_semicolon(
1513                suggestion.as_str(),
1514                pos,
1515            ) {
1516                self.parse_error_at(
1517                    pos,
1518                    len,
1519                    &format!("Unknown keyword or identifier. Did you mean '{suggestion}'?"),
1520                    diagnostic_codes::UNKNOWN_KEYWORD_OR_IDENTIFIER_DID_YOU_MEAN,
1521                );
1522            }
1523            return;
1524        }
1525
1526        if self.is_token(SyntaxKind::Unknown) {
1527            return;
1528        }
1529
1530        if self.should_report_error() {
1531            self.parse_error_at(
1532                pos,
1533                len,
1534                "Unexpected keyword or identifier.",
1535                diagnostic_codes::UNEXPECTED_KEYWORD_OR_IDENTIFIER,
1536            );
1537        }
1538    }
1539
1540    fn missing_semicolon_after_expression_text(
1541        &self,
1542        expression: NodeIndex,
1543    ) -> Option<(u32, u32, String)> {
1544        let node = self.arena.get(expression)?;
1545
1546        if node.kind != SyntaxKind::Identifier as u16 {
1547            return None;
1548        }
1549
1550        // Use source text directly — arena identifier data may be empty for
1551        // identifiers created during parsing before data is fully populated.
1552        let source = self.scanner.source_text();
1553        let text = &source[node.pos as usize..node.end as usize];
1554        if text.is_empty() {
1555            return None;
1556        }
1557
1558        Some((node.pos, node.end - node.pos, text.to_string()))
1559    }
1560
1561    fn parse_missing_semicolon_keyword_error(
1562        &mut self,
1563        pos: u32,
1564        len: u32,
1565        expression_text: &str,
1566    ) -> bool {
1567        use tsz_common::diagnostics::diagnostic_codes;
1568
1569        match expression_text {
1570            "const" | "let" | "var" => {
1571                self.parse_error_at(
1572                    pos,
1573                    len,
1574                    "Variable declaration not allowed at this location.",
1575                    diagnostic_codes::VARIABLE_DECLARATION_NOT_ALLOWED_AT_THIS_LOCATION,
1576                );
1577                true
1578            }
1579            "declare" => true,
1580            "interface" => {
1581                if self.is_token(SyntaxKind::OpenBraceToken) {
1582                    self.parse_error_at_current_token(
1583                        "Interface must be given a name.",
1584                        diagnostic_codes::INTERFACE_MUST_BE_GIVEN_A_NAME,
1585                    );
1586                } else {
1587                    let name = self.scanner.get_token_value_ref().to_string();
1588                    self.parse_error_at_current_token(
1589                        &format!("Interface name cannot be '{name}'."),
1590                        diagnostic_codes::EXPECTED,
1591                    );
1592                }
1593                true
1594            }
1595            "type" => {
1596                self.parse_error_at_current_token("'=' expected.", diagnostic_codes::EXPECTED);
1597                true
1598            }
1599            _ => false,
1600        }
1601    }
1602
1603    fn should_suppress_type_or_keyword_suggestion_for_missing_semicolon(
1604        &self,
1605        text: &str,
1606        token_pos: u32,
1607    ) -> bool {
1608        if !matches!(
1609            text,
1610            "string"
1611                | "number"
1612                | "boolean"
1613                | "symbol"
1614                | "bigint"
1615                | "object"
1616                | "void"
1617                | "undefined"
1618                | "null"
1619                | "never"
1620                | "unknown"
1621                | "any"
1622        ) {
1623            return false;
1624        }
1625
1626        let source = self.scanner.source_text().as_bytes();
1627        let mut i = token_pos as usize;
1628        while i > 0 && source[i - 1].is_ascii_whitespace() {
1629            i -= 1;
1630        }
1631        i > 0 && source[i - 1] == b':'
1632    }
1633
1634    /// Check if we can parse a semicolon (ASI rules)
1635    /// Returns true if current token is semicolon or ASI applies
1636    ///
1637    /// ASI (Automatic Semicolon Insertion) rules (matching TypeScript):
1638    /// 1. Explicit semicolon
1639    /// 2. Before closing brace
1640    /// 3. At EOF
1641    /// 4. After line break (no additional checks!)
1642    ///
1643    /// Note: This matches TypeScript's `canParseSemicolon()` implementation exactly.
1644    /// The previous "enhanced" ASI with statement start checks was causing
1645    /// false-positive TS1005 errors because it was more restrictive than TypeScript.
1646    pub(crate) fn can_parse_semicolon(&self) -> bool {
1647        // Explicit semicolon
1648        if self.is_token(SyntaxKind::SemicolonToken) {
1649            return true;
1650        }
1651
1652        // ASI applies before closing brace
1653        if self.is_token(SyntaxKind::CloseBraceToken) {
1654            return true;
1655        }
1656
1657        // ASI applies at EOF
1658        if self.is_token(SyntaxKind::EndOfFileToken) {
1659            return true;
1660        }
1661
1662        // ASI applies after line break (matching TypeScript - no extra checks!)
1663        self.scanner.has_preceding_line_break()
1664    }
1665
1666    /// Check if ASI applies for restricted productions (return, throw, yield, break, continue)
1667    ///
1668    /// Restricted productions have special ASI rules:
1669    /// ASI applies immediately after a line break, WITHOUT checking if the next token starts a statement.
1670    ///
1671    /// Examples:
1672    /// - `return\nx` parses as `return; x;` (ASI applies due to line break)
1673    /// - `return x` parses as `return x;` (no ASI, x is the return value)
1674    /// - `throw\nx` parses as `throw; x;` (ASI applies due to line break)
1675    /// - `throw x` parses as `throw x;` (no ASI, x is the thrown value)
1676    pub(crate) fn can_parse_semicolon_for_restricted_production(&self) -> bool {
1677        // Explicit semicolon
1678        if self.is_token(SyntaxKind::SemicolonToken) {
1679            return true;
1680        }
1681
1682        // ASI applies before closing brace
1683        if self.is_token(SyntaxKind::CloseBraceToken) {
1684            return true;
1685        }
1686
1687        // ASI applies at EOF
1688        if self.is_token(SyntaxKind::EndOfFileToken) {
1689            return true;
1690        }
1691
1692        // ASI applies after line break (without checking statement start)
1693        // This is the key difference from can_parse_semicolon()
1694        if self.scanner.has_preceding_line_break() {
1695            return true;
1696        }
1697
1698        false
1699    }
1700
1701    // =========================================================================
1702    // Error Resynchronization
1703    // =========================================================================
1704
1705    /// Check if we're at a position where an expression can reasonably end
1706    /// This is used to suppress spurious TS1109 "expression expected" errors when
1707    /// the user has clearly moved on to the next statement/context.
1708    ///
1709    /// For TS1109 (expression expected), we should only suppress if we've reached a closing
1710    /// delimiter or EOF. We should NOT suppress on statement start keywords because if we're
1711    /// expecting an expression and see `var`, `let`, `function`, etc., that's likely an error.
1712    pub(crate) const fn is_at_expression_end(&self) -> bool {
1713        match self.token() {
1714            // Only tokens that naturally end expressions and indicate we've moved on
1715            SyntaxKind::SemicolonToken
1716            | SyntaxKind::CloseBraceToken
1717            | SyntaxKind::CloseParenToken
1718            | SyntaxKind::CloseBracketToken
1719            | SyntaxKind::EndOfFileToken => true,
1720            // NOTE: We do NOT suppress on statement start keywords
1721            // If we're expecting an expression and see `var`, `let`, `function`, etc.,
1722            // that's likely a genuine error where the user forgot the expression.
1723            // This fixes the "missing TS1109" issue where errors were being suppressed too aggressively.
1724            _ => false,
1725        }
1726    }
1727
1728    /// Check if current token can start a statement (synchronization point)
1729    pub(crate) const fn is_statement_start(&self) -> bool {
1730        matches!(
1731            self.token(),
1732            SyntaxKind::VarKeyword
1733                | SyntaxKind::LetKeyword
1734                | SyntaxKind::ConstKeyword
1735                | SyntaxKind::FunctionKeyword
1736                | SyntaxKind::ClassKeyword
1737                | SyntaxKind::IfKeyword
1738                | SyntaxKind::ForKeyword
1739                | SyntaxKind::WhileKeyword
1740                | SyntaxKind::DoKeyword
1741                | SyntaxKind::SwitchKeyword
1742                | SyntaxKind::TryKeyword
1743                | SyntaxKind::CatchKeyword
1744                | SyntaxKind::FinallyKeyword
1745                | SyntaxKind::WithKeyword
1746                | SyntaxKind::DebuggerKeyword
1747                | SyntaxKind::ReturnKeyword
1748                | SyntaxKind::BreakKeyword
1749                | SyntaxKind::ContinueKeyword
1750                | SyntaxKind::ThrowKeyword
1751                | SyntaxKind::YieldKeyword
1752                | SyntaxKind::AsyncKeyword
1753                | SyntaxKind::UsingKeyword
1754                | SyntaxKind::AwaitKeyword
1755                | SyntaxKind::InterfaceKeyword
1756                | SyntaxKind::TypeKeyword
1757                | SyntaxKind::EnumKeyword
1758                | SyntaxKind::NamespaceKeyword
1759                | SyntaxKind::ModuleKeyword
1760                | SyntaxKind::ImportKeyword
1761                | SyntaxKind::ExportKeyword
1762                | SyntaxKind::DeclareKeyword
1763                | SyntaxKind::Identifier
1764                | SyntaxKind::StringLiteral
1765                | SyntaxKind::AtToken
1766                | SyntaxKind::NumericLiteral
1767                | SyntaxKind::BigIntLiteral
1768                | SyntaxKind::TrueKeyword
1769                | SyntaxKind::FalseKeyword
1770                | SyntaxKind::NullKeyword
1771                | SyntaxKind::ThisKeyword
1772                | SyntaxKind::SuperKeyword
1773                | SyntaxKind::ExclamationToken
1774                | SyntaxKind::TildeToken
1775                | SyntaxKind::PlusToken
1776                | SyntaxKind::MinusToken
1777                | SyntaxKind::PlusPlusToken
1778                | SyntaxKind::MinusMinusToken
1779                | SyntaxKind::TypeOfKeyword
1780                | SyntaxKind::VoidKeyword
1781                | SyntaxKind::DeleteKeyword
1782                | SyntaxKind::OpenBraceToken
1783                | SyntaxKind::SemicolonToken
1784                | SyntaxKind::OpenParenToken
1785                | SyntaxKind::OpenBracketToken
1786                | SyntaxKind::LessThanToken
1787        )
1788    }
1789
1790    /// Check if current token is a synchronization point for error recovery
1791    /// This includes statement starts plus additional keywords that indicate
1792    /// boundaries in control structures (else, case, default, catch, finally, etc.)
1793    pub(crate) const fn is_resync_sync_point(&self) -> bool {
1794        self.is_statement_start()
1795            || matches!(
1796                self.token(),
1797                SyntaxKind::ElseKeyword
1798                    | SyntaxKind::CaseKeyword
1799                    | SyntaxKind::DefaultKeyword
1800                    | SyntaxKind::CatchKeyword
1801                    | SyntaxKind::FinallyKeyword
1802                    | SyntaxKind::CommaToken
1803            )
1804    }
1805
1806    /// Resynchronize after a parse error by skipping to the next statement boundary
1807    /// This prevents cascading errors by finding a known good synchronization point.
1808    /// `allow_statement_starts` controls whether token kinds that begin statements
1809    /// (especially identifiers) are valid sync points.
1810    pub(crate) fn resync_after_error_with_statement_starts(
1811        &mut self,
1812        allow_statement_starts: bool,
1813    ) {
1814        // If we're already at a sync point or EOF, no need to resync
1815        if self.is_resync_sync_point_with_statement_starts(allow_statement_starts) {
1816            return;
1817        }
1818
1819        // Skip tokens until we find a synchronization point
1820        let mut brace_depth = 0u32;
1821        let mut paren_depth = 0u32;
1822        let mut bracket_depth = 0u32;
1823        let max_iterations = 1000; // Prevent infinite loops
1824
1825        for _ in 0..max_iterations {
1826            // Check for EOF
1827            if self.is_token(SyntaxKind::EndOfFileToken) {
1828                break;
1829            }
1830
1831            // Track nesting depth to handle nested structures
1832            match self.token() {
1833                SyntaxKind::OpenBraceToken => {
1834                    brace_depth += 1;
1835                    self.next_token();
1836                    continue;
1837                }
1838                SyntaxKind::CloseBraceToken => {
1839                    if brace_depth > 0 {
1840                        brace_depth -= 1;
1841                        self.next_token();
1842                        continue;
1843                    }
1844                    // Found closing brace at same level - this is a sync point
1845                    self.next_token();
1846                    break;
1847                }
1848                SyntaxKind::OpenParenToken => {
1849                    paren_depth += 1;
1850                    self.next_token();
1851                    continue;
1852                }
1853                SyntaxKind::CloseParenToken => {
1854                    if paren_depth > 0 {
1855                        paren_depth -= 1;
1856                        self.next_token();
1857                        continue;
1858                    }
1859                    // Found closing paren at same level - could be end of expression
1860                    // Skip it and check if next token is a sync point
1861                    self.next_token();
1862                    if self.is_resync_sync_point_with_statement_starts(allow_statement_starts) {
1863                        break;
1864                    }
1865                    continue;
1866                }
1867                SyntaxKind::OpenBracketToken => {
1868                    bracket_depth += 1;
1869                    self.next_token();
1870                    continue;
1871                }
1872                SyntaxKind::CloseBracketToken => {
1873                    if bracket_depth > 0 {
1874                        bracket_depth -= 1;
1875                        self.next_token();
1876                        continue;
1877                    }
1878                    // Found closing bracket at same level - skip it
1879                    self.next_token();
1880                    continue;
1881                }
1882                SyntaxKind::SemicolonToken => {
1883                    // Semicolon is always a sync point (even in nested contexts)
1884                    self.next_token();
1885                    break;
1886                }
1887                _ => {}
1888            }
1889
1890            // If we're at depth 0 and found a sync point, we've resync'd
1891            if brace_depth == 0
1892                && paren_depth == 0
1893                && bracket_depth == 0
1894                && self.is_resync_sync_point_with_statement_starts(allow_statement_starts)
1895            {
1896                break;
1897            }
1898
1899            // Keep skipping tokens
1900            self.next_token();
1901        }
1902    }
1903
1904    fn is_resync_sync_point_with_statement_starts(&self, allow_statement_starts: bool) -> bool {
1905        self.is_resync_sync_point()
1906            && (allow_statement_starts || self.token() != SyntaxKind::Identifier)
1907    }
1908
1909    /// Default resync behavior: allow statement starts as sync points.
1910    pub(crate) fn resync_after_error(&mut self) {
1911        self.resync_after_error_with_statement_starts(true);
1912    }
1913
1914    // =========================================================================
1915    // Expression-Level Error Recovery
1916    // =========================================================================
1917
1918    /// Check if current token can start an expression
1919    pub(crate) const fn is_expression_start(&self) -> bool {
1920        matches!(
1921            self.token(),
1922            SyntaxKind::NumericLiteral
1923                | SyntaxKind::BigIntLiteral
1924                | SyntaxKind::StringLiteral
1925                | SyntaxKind::NoSubstitutionTemplateLiteral
1926                | SyntaxKind::TemplateHead
1927                | SyntaxKind::TemplateMiddle
1928                | SyntaxKind::TemplateTail
1929                | SyntaxKind::TrueKeyword
1930                | SyntaxKind::FalseKeyword
1931                | SyntaxKind::NullKeyword
1932                | SyntaxKind::Identifier
1933                | SyntaxKind::ThisKeyword
1934                | SyntaxKind::SuperKeyword
1935                | SyntaxKind::ImportKeyword
1936                | SyntaxKind::TypeKeyword
1937                | SyntaxKind::AnyKeyword
1938                | SyntaxKind::StringKeyword
1939                | SyntaxKind::NumberKeyword
1940                | SyntaxKind::BooleanKeyword
1941                | SyntaxKind::SymbolKeyword
1942                | SyntaxKind::BigIntKeyword
1943                | SyntaxKind::ObjectKeyword
1944                | SyntaxKind::NeverKeyword
1945                | SyntaxKind::UnknownKeyword
1946                | SyntaxKind::UndefinedKeyword
1947                | SyntaxKind::RequireKeyword
1948                | SyntaxKind::ModuleKeyword
1949                | SyntaxKind::NamespaceKeyword
1950                | SyntaxKind::AsyncKeyword
1951                | SyntaxKind::AwaitKeyword
1952                | SyntaxKind::YieldKeyword
1953                | SyntaxKind::LetKeyword
1954                | SyntaxKind::NewKeyword
1955                | SyntaxKind::ClassKeyword
1956                | SyntaxKind::FunctionKeyword
1957                | SyntaxKind::DeleteKeyword
1958                | SyntaxKind::VoidKeyword
1959                | SyntaxKind::TypeOfKeyword
1960                | SyntaxKind::InstanceOfKeyword
1961                | SyntaxKind::StaticKeyword
1962                | SyntaxKind::AbstractKeyword
1963                | SyntaxKind::OverrideKeyword
1964                | SyntaxKind::ReadonlyKeyword
1965                | SyntaxKind::AccessorKeyword
1966                | SyntaxKind::GetKeyword
1967                | SyntaxKind::SetKeyword
1968                | SyntaxKind::DeclareKeyword
1969                | SyntaxKind::PublicKeyword
1970                | SyntaxKind::ProtectedKeyword
1971                | SyntaxKind::PrivateKeyword
1972                | SyntaxKind::OfKeyword
1973                | SyntaxKind::SatisfiesKeyword
1974                | SyntaxKind::FromKeyword
1975                | SyntaxKind::AsKeyword
1976                | SyntaxKind::IsKeyword
1977                | SyntaxKind::AssertKeyword
1978                | SyntaxKind::AssertsKeyword
1979                | SyntaxKind::IntrinsicKeyword
1980                | SyntaxKind::OutKeyword
1981                | SyntaxKind::InferKeyword
1982                | SyntaxKind::ConstructorKeyword
1983                | SyntaxKind::UsingKeyword
1984                | SyntaxKind::KeyOfKeyword
1985                | SyntaxKind::UniqueKeyword
1986                | SyntaxKind::GlobalKeyword
1987                | SyntaxKind::InterfaceKeyword
1988                | SyntaxKind::EnumKeyword
1989                | SyntaxKind::DeferKeyword
1990                | SyntaxKind::PrivateIdentifier
1991                | SyntaxKind::PlusToken
1992                | SyntaxKind::MinusToken
1993                | SyntaxKind::AsteriskToken
1994                | SyntaxKind::TildeToken
1995                | SyntaxKind::ExclamationToken
1996                | SyntaxKind::PlusPlusToken
1997                | SyntaxKind::MinusMinusToken
1998                | SyntaxKind::OpenParenToken
1999                | SyntaxKind::OpenBracketToken
2000                | SyntaxKind::OpenBraceToken
2001                | SyntaxKind::LessThanToken
2002                | SyntaxKind::SlashToken
2003                | SyntaxKind::SlashEqualsToken
2004                | SyntaxKind::AtToken
2005        )
2006    }
2007
2008    /// Check if current token is a binary operator
2009    pub(crate) const fn is_binary_operator(&self) -> bool {
2010        let precedence = self.get_operator_precedence(self.token());
2011        precedence > 0
2012    }
2013
2014    /// Resynchronize to next expression boundary after parse error
2015    pub(crate) fn resync_to_next_expression_boundary(&mut self) {
2016        let max_iterations = 100;
2017        for _ in 0..max_iterations {
2018            if self.is_token(SyntaxKind::EndOfFileToken) {
2019                break;
2020            }
2021            if self.is_expression_boundary() {
2022                break;
2023            }
2024            if self.is_binary_operator() {
2025                break;
2026            }
2027            if self.is_expression_start() {
2028                break;
2029            }
2030            self.next_token();
2031        }
2032    }
2033
2034    /// Check if current token is at an expression boundary (a natural stopping point)
2035    pub(crate) const fn is_expression_boundary(&self) -> bool {
2036        matches!(
2037            self.token(),
2038            SyntaxKind::SemicolonToken
2039                | SyntaxKind::CloseBraceToken
2040                | SyntaxKind::CloseParenToken
2041                | SyntaxKind::CloseBracketToken
2042                | SyntaxKind::CommaToken
2043                | SyntaxKind::ColonToken
2044                | SyntaxKind::CaseKeyword
2045                | SyntaxKind::DefaultKeyword
2046                | SyntaxKind::ElseKeyword
2047                | SyntaxKind::WhileKeyword // for do-while
2048                | SyntaxKind::AsKeyword
2049                | SyntaxKind::SatisfiesKeyword
2050        )
2051    }
2052
2053    /// Create a missing expression placeholder for error recovery.
2054    /// This allows the AST to remain structurally valid even when an expression is missing.
2055    pub(crate) fn create_missing_expression(&mut self) -> NodeIndex {
2056        let pos = self.token_pos();
2057        // Create an identifier with empty text to represent missing expression
2058        self.arena.add_identifier(
2059            SyntaxKind::Identifier as u16,
2060            pos,
2061            pos,
2062            IdentifierData {
2063                atom: Atom::NONE,
2064                escaped_text: String::new(),
2065                original_text: None,
2066                type_arguments: None,
2067            },
2068        )
2069    }
2070
2071    /// Try to recover from a missing right-hand operand in a binary expression.
2072    /// Returns a placeholder expression if recovery is possible.
2073    pub(crate) fn try_recover_binary_rhs(&mut self) -> NodeIndex {
2074        // If we're at an expression boundary after an operator, create a placeholder
2075        if self.is_expression_boundary() || self.is_statement_start() {
2076            self.create_missing_expression()
2077        } else {
2078            NodeIndex::NONE
2079        }
2080    }
2081
2082    /// Try to rescan `>` as a compound token (`>>`, `>>>`, `>=`, `>>=`, `>>>=`)
2083    /// Returns the rescanned token (which may be unchanged if no compound token found)
2084    pub(crate) fn try_rescan_greater_token(&mut self) -> SyntaxKind {
2085        if self.current_token == SyntaxKind::GreaterThanToken {
2086            self.current_token = self.scanner.re_scan_greater_token();
2087        }
2088        self.current_token
2089    }
2090
2091    /// Parse expected `>` token, handling compound tokens like `>>` and `>>>`
2092    /// When we have `>>`, we need to consume just one `>` and leave `>` for the next parse
2093    pub(crate) fn parse_expected_greater_than(&mut self) {
2094        match self.current_token {
2095            SyntaxKind::GreaterThanToken => {
2096                // Simple case - just consume the single `>`
2097                self.next_token();
2098            }
2099            SyntaxKind::GreaterThanGreaterThanToken => {
2100                // `>>` - back up scanner and treat as single `>`
2101                // After consuming, the remaining `>` becomes the current token
2102                self.scanner.set_pos(self.scanner.get_pos() - 1);
2103                self.current_token = SyntaxKind::GreaterThanToken;
2104            }
2105            SyntaxKind::GreaterThanGreaterThanGreaterThanToken => {
2106                // `>>>` - back up scanner and treat as single `>`
2107                // After consuming, the remaining `>>` becomes the current token
2108                self.scanner.set_pos(self.scanner.get_pos() - 2);
2109                self.current_token = SyntaxKind::GreaterThanGreaterThanToken;
2110            }
2111            SyntaxKind::GreaterThanEqualsToken => {
2112                // `>=` - back up scanner and treat as single `>`
2113                self.scanner.set_pos(self.scanner.get_pos() - 1);
2114                self.current_token = SyntaxKind::EqualsToken;
2115            }
2116            SyntaxKind::GreaterThanGreaterThanEqualsToken => {
2117                // `>>=` - back up scanner and treat as single `>`
2118                self.scanner.set_pos(self.scanner.get_pos() - 2);
2119                self.current_token = SyntaxKind::GreaterThanEqualsToken;
2120            }
2121            SyntaxKind::GreaterThanGreaterThanGreaterThanEqualsToken => {
2122                // `>>>=` - back up scanner and treat as single `>`
2123                self.scanner.set_pos(self.scanner.get_pos() - 3);
2124                self.current_token = SyntaxKind::GreaterThanGreaterThanEqualsToken;
2125            }
2126            _ => {
2127                // error_token_expected already has error suppression check
2128                self.error_token_expected(">");
2129            }
2130        }
2131    }
2132
2133    /// Check if the current token starts with `<` (includes `<<` and `<<=`).
2134    /// Mirrors `is_greater_than_or_compound` for the opening side.
2135    pub(crate) const fn is_less_than_or_compound(&self) -> bool {
2136        matches!(
2137            self.current_token,
2138            SyntaxKind::LessThanToken
2139                | SyntaxKind::LessThanLessThanToken
2140                | SyntaxKind::LessThanLessThanEqualsToken
2141        )
2142    }
2143
2144    /// Consume a single `<` from the current token.
2145    /// Handles compound tokens like `<<` and `<<=` by leaving the scanner
2146    /// position unchanged (past the compound token) and setting `current_token`
2147    /// to the remainder. Unlike `>`, `<` is eagerly combined by the scanner,
2148    /// so we cannot back up—the scanner would re-combine. Instead, we leave
2149    /// pos past the compound and set `current_token` to the remainder.
2150    /// When the remainder is later consumed via `parse_expected(<)` →
2151    /// `next_token()`, the scanner scans from past the compound, correctly
2152    /// yielding the token that follows.
2153    pub(crate) fn parse_expected_less_than(&mut self) {
2154        match self.current_token {
2155            SyntaxKind::LessThanToken => {
2156                self.next_token();
2157            }
2158            SyntaxKind::LessThanLessThanToken => {
2159                // `<<` → consume first `<`, remainder is `<`
2160                // Scanner pos stays past `<<`; when the second `<` is consumed,
2161                // next_token() will scan from past both, yielding the following token.
2162                self.current_token = SyntaxKind::LessThanToken;
2163            }
2164            SyntaxKind::LessThanLessThanEqualsToken => {
2165                // `<<=` → consume first `<`, remainder is `<=`
2166                // Scanner pos stays past `<<=`; same logic as above.
2167                self.current_token = SyntaxKind::LessThanEqualsToken;
2168            }
2169            _ => {
2170                self.error_token_expected("<");
2171            }
2172        }
2173    }
2174
2175    /// Create a `NodeList` from a Vec of `NodeIndex`
2176    pub(crate) const fn make_node_list(&self, nodes: Vec<NodeIndex>) -> NodeList {
2177        let _ = self;
2178        NodeList {
2179            nodes,
2180            pos: 0,
2181            end: 0,
2182            has_trailing_comma: false,
2183        }
2184    }
2185
2186    /// Get operator precedence
2187    pub(crate) const fn get_operator_precedence(&self, token: SyntaxKind) -> u8 {
2188        match token {
2189            SyntaxKind::CommaToken => 1,
2190            SyntaxKind::EqualsToken
2191            | SyntaxKind::PlusEqualsToken
2192            | SyntaxKind::MinusEqualsToken
2193            | SyntaxKind::AsteriskEqualsToken
2194            | SyntaxKind::AsteriskAsteriskEqualsToken
2195            | SyntaxKind::SlashEqualsToken
2196            | SyntaxKind::PercentEqualsToken
2197            | SyntaxKind::LessThanLessThanEqualsToken
2198            | SyntaxKind::GreaterThanGreaterThanEqualsToken
2199            | SyntaxKind::GreaterThanGreaterThanGreaterThanEqualsToken
2200            | SyntaxKind::AmpersandEqualsToken
2201            | SyntaxKind::BarEqualsToken
2202            | SyntaxKind::BarBarEqualsToken
2203            | SyntaxKind::AmpersandAmpersandEqualsToken
2204            | SyntaxKind::QuestionQuestionEqualsToken
2205            | SyntaxKind::CaretEqualsToken => 2,
2206            SyntaxKind::QuestionToken => 3,
2207            SyntaxKind::BarBarToken | SyntaxKind::QuestionQuestionToken => 4,
2208            SyntaxKind::AmpersandAmpersandToken => 5,
2209            SyntaxKind::BarToken => 6,
2210            SyntaxKind::CaretToken => 7,
2211            SyntaxKind::AmpersandToken => 8,
2212            SyntaxKind::EqualsEqualsToken
2213            | SyntaxKind::ExclamationEqualsToken
2214            | SyntaxKind::EqualsEqualsEqualsToken
2215            | SyntaxKind::ExclamationEqualsEqualsToken => 9,
2216            // 'in' is not a binary operator in for-statement initializers
2217            SyntaxKind::InKeyword => {
2218                if self.in_disallow_in_context() {
2219                    0
2220                } else {
2221                    10
2222                }
2223            }
2224            SyntaxKind::LessThanToken
2225            | SyntaxKind::GreaterThanToken
2226            | SyntaxKind::LessThanEqualsToken
2227            | SyntaxKind::GreaterThanEqualsToken
2228            | SyntaxKind::InstanceOfKeyword
2229            | SyntaxKind::AsKeyword
2230            | SyntaxKind::SatisfiesKeyword => 10,
2231            SyntaxKind::LessThanLessThanToken
2232            | SyntaxKind::GreaterThanGreaterThanToken
2233            | SyntaxKind::GreaterThanGreaterThanGreaterThanToken => 11,
2234            SyntaxKind::PlusToken | SyntaxKind::MinusToken => 12,
2235            SyntaxKind::AsteriskToken | SyntaxKind::SlashToken | SyntaxKind::PercentToken => 13,
2236            SyntaxKind::AsteriskAsteriskToken => 14,
2237            _ => 0,
2238        }
2239    }
2240
2241    /// Push a new label scope (called when entering a function or module)
2242    pub(crate) fn push_label_scope(&mut self) {
2243        let new_depth = self.label_scopes.len() + 1;
2244        trace!(pos = self.token_pos(), new_depth, "push_label_scope");
2245        self.label_scopes.push(FxHashMap::default());
2246    }
2247
2248    /// Pop the current label scope (called when exiting a function or module)
2249    pub(crate) fn pop_label_scope(&mut self) {
2250        let old_depth = self.label_scopes.len();
2251        trace!(pos = self.token_pos(), old_depth, "pop_label_scope");
2252        self.label_scopes.pop();
2253    }
2254
2255    /// Check if a label already exists in the current scope, and if so, emit TS1114.
2256    /// Returns true if the label is a duplicate.
2257    pub(crate) fn check_duplicate_label(&mut self, label_name: &str, label_pos: u32) -> bool {
2258        let scope_depth = self.label_scopes.len();
2259        trace!(label_name, label_pos, scope_depth, "check_duplicate_label");
2260        if let Some(current_scope) = self.label_scopes.last_mut() {
2261            if current_scope.contains_key(label_name) {
2262                // Duplicate label - emit TS1114
2263                use tsz_common::diagnostics::diagnostic_codes;
2264                let message = format!("Duplicate label '{label_name}'.");
2265                trace!(label_name, "duplicate label found");
2266                self.parse_error_at(
2267                    label_pos,
2268                    self.u32_from_usize(label_name.len()),
2269                    &message,
2270                    diagnostic_codes::DUPLICATE_LABEL,
2271                );
2272                return true;
2273            }
2274            // Not a duplicate - record this label
2275            trace!(label_name, "adding label to scope");
2276            current_scope.insert(label_name.to_string(), label_pos);
2277        }
2278        false
2279    }
2280}
2281
2282// Integration tests for parse_error_for_missing_semicolon_after live in
2283// parser/tests/spelling_integration_tests.rs.  Pure spelling-logic tests
2284// live in parser/spelling.rs.