Skip to main content

tsz_checker/state/
state_checking.rs

1//! Declaration and statement checking, including `StatementCheckCallbacks`.
2
3use crate::state::CheckerState;
4use crate::statements::StatementChecker;
5use tracing::{Level, span};
6use tsz_binder::symbol_flags;
7use tsz_parser::parser::NodeIndex;
8use tsz_parser::parser::node::NodeAccess;
9use tsz_parser::parser::syntax_kind_ext;
10use tsz_scanner::SyntaxKind;
11use web_time::Instant;
12
13/// Check if a name is a strict mode reserved word (ES5 ยง7.6.1.2).
14/// These identifiers cannot be used as variable/function/class names in strict mode.
15pub(crate) fn is_strict_mode_reserved_name(name: &str) -> bool {
16    matches!(
17        name,
18        "implements"
19            | "interface"
20            | "let"
21            | "package"
22            | "private"
23            | "protected"
24            | "public"
25            | "static"
26            | "yield"
27    )
28}
29
30impl<'a> CheckerState<'a> {
31    fn needs_boxed_type_registration(&self) -> bool {
32        for idx in 0..self.ctx.arena.len() {
33            let node_idx = NodeIndex(idx as u32);
34            let Some(node) = self.ctx.arena.get(node_idx) else {
35                continue;
36            };
37            if node.kind == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION
38                || node.kind == syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION
39            {
40                return true;
41            }
42        }
43        false
44    }
45
46    /// Check a declaration name node for strict mode reserved words.
47    /// Emits TS1212 (general strict mode), TS1213 (class context), or TS1214 (module context).
48    pub(crate) fn check_strict_mode_reserved_name_at(
49        &mut self,
50        name_idx: tsz_parser::parser::NodeIndex,
51        context_node: tsz_parser::parser::NodeIndex,
52    ) {
53        if name_idx.is_none() || !self.is_strict_mode_for_node(context_node) {
54            return;
55        }
56        let Some(name_node) = self.ctx.arena.get(name_idx) else {
57            return;
58        };
59        let Some(ident) = self.ctx.arena.get_identifier(name_node) else {
60            return;
61        };
62        if !is_strict_mode_reserved_name(&ident.escaped_text) {
63            return;
64        }
65        use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
66        if self.ctx.enclosing_class.is_some() {
67            let message = format_message(
68                diagnostic_messages::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE_CLASS_DEFINITIONS_ARE_AUTO,
69                &[&ident.escaped_text],
70            );
71            self.error_at_node(
72                name_idx,
73                &message,
74                diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE_CLASS_DEFINITIONS_ARE_AUTO,
75            );
76        } else if self.ctx.binder.is_external_module() {
77            let message = format_message(
78                diagnostic_messages::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE_MODULES_ARE_AUTOMATICALLY,
79                &[&ident.escaped_text],
80            );
81            self.error_at_node(
82                name_idx,
83                &message,
84                diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE_MODULES_ARE_AUTOMATICALLY,
85            );
86        } else {
87            let message = format_message(
88                diagnostic_messages::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE,
89                &[&ident.escaped_text],
90            );
91            self.error_at_node(
92                name_idx,
93                &message,
94                diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE,
95            );
96        }
97    }
98
99    // =========================================================================
100    // Source File Checking (Full Traversal)
101    // =========================================================================
102
103    /// Check a source file and populate diagnostics (main entry point).
104    ///
105    /// This is the primary entry point for type checking after parsing and binding.
106    /// It traverses the entire AST and performs all type checking operations.
107    ///
108    /// ## Checking Process:
109    /// 1. Initializes the type environment
110    /// 2. Traverses all top-level declarations
111    /// 3. Checks all statements and expressions
112    /// 4. Populates diagnostics with errors and warnings
113    ///
114    /// ## What Gets Checked:
115    /// - Type annotations
116    /// - Assignments (variable, property, return)
117    /// - Function calls
118    /// - Property access
119    /// - Type compatibility (extends, implements)
120    /// - Flow analysis (definite assignment, type narrowing)
121    /// - Generic constraints
122    /// - And much more...
123    ///
124    /// ## Diagnostics:
125    /// - Errors are added to `ctx.diagnostics`
126    /// - Includes error codes (`TSxxxx`) and messages
127    /// - Spans point to the problematic code
128    ///
129    /// ## Compilation Flow:
130    /// 1. **Parser**: Source code โ†’ AST
131    /// 2. **Binder**: AST โ†’ Symbols (scopes, declarations)
132    /// 3. **Checker** (this function): AST + Symbols โ†’ Types + Diagnostics
133    ///
134    /// ## TypeScript Example:
135    /// ```typescript
136    /// // File: example.ts
137    /// let x: string = 42;  // Type error: number not assignable to string
138    ///
139    /// function foo(a: number): string {
140    ///   return a;  // Type error: number not assignable to string
141    /// }
142    ///
143    /// interface User {
144    ///   name: string;
145    /// }
146    /// const user: User = { age: 25 };  // Type error: missing 'name' property
147    ///
148    /// // check_source_file() would find all three errors above
149    /// ```
150    pub fn check_source_file(&mut self, root_idx: NodeIndex) {
151        let _span = span!(Level::INFO, "check_source_file", idx = ?root_idx).entered();
152
153        // Reset per-file flags
154        self.ctx.is_in_ambient_declaration_file = false;
155
156        let Some(node) = self.ctx.arena.get(root_idx) else {
157            return;
158        };
159
160        if let Some(sf) = self.ctx.arena.get_source_file(node) {
161            self.resolve_compiler_options_from_source(&sf.text);
162            if self.has_ts_nocheck_pragma(&sf.text) {
163                return;
164            }
165
166            // `type_env` is rebuilt per file, so drop per-file symbol-resolution memoization.
167            self.ctx.application_symbols_resolved.clear();
168            self.ctx.application_symbols_resolution_set.clear();
169
170            // CRITICAL FIX: Build TypeEnvironment with all symbols (including lib symbols)
171            // This ensures Error, Math, JSON, etc. interfaces are registered for property resolution
172            // Without this, TypeData::Ref(Error) returns ERROR, causing TS2339 false positives
173            let env_start = Instant::now();
174            let populated_env = self.build_type_environment();
175            tracing::trace!(target: "wasm::perf", phase = "build_type_environment", ms = env_start.elapsed().as_secs_f64() * 1000.0);
176            *self.ctx.type_env.borrow_mut() = populated_env.clone();
177            // CRITICAL: Also populate type_environment (Rc-wrapped) for FlowAnalyzer
178            // This ensures type alias narrowing works during control flow analysis
179            *self.ctx.type_environment.borrow_mut() = populated_env;
180
181            // Register boxed types (String, Number, Boolean, etc.) from lib.d.ts
182            // This enables primitive property access to use lib definitions instead of hardcoded lists
183            // IMPORTANT: Must run AFTER build_type_environment() because it replaces the
184            // TypeEnvironment, which would erase the boxed/array type registrations.
185            if self.needs_boxed_type_registration() {
186                self.register_boxed_types();
187            }
188
189            // Type check each top-level statement
190            // Mark that we're now in the checking phase. During build_type_environment,
191            // closures may be type-checked without contextual types, which would cause
192            // premature TS7006 errors. The checking phase ensures contextual types are available.
193            self.ctx.is_checking_statements = true;
194            let stmt_start = Instant::now();
195
196            // In .d.ts files, emit TS1036 for non-declaration top-level statements.
197            // The entire file is an ambient context, so statements like break, continue,
198            // return, debugger, if, while, for, etc. are not allowed.
199            let is_dts = self.ctx.file_name.ends_with(".d.ts")
200                || self.ctx.file_name.ends_with(".d.tsx")
201                || self.ctx.file_name.ends_with(".d.mts")
202                || self.ctx.file_name.ends_with(".d.cts");
203            if is_dts {
204                self.ctx.is_in_ambient_declaration_file = true;
205            }
206
207            let prev_unreachable = self.ctx.is_unreachable;
208            let prev_reported = self.ctx.has_reported_unreachable;
209            for &stmt_idx in &sf.statements.nodes {
210                if is_dts {
211                    self.check_dts_statement_in_ambient_context(stmt_idx);
212                }
213                self.check_statement(stmt_idx);
214                if !self.statement_falls_through(stmt_idx) {
215                    self.ctx.is_unreachable = true;
216                }
217            }
218            self.ctx.is_unreachable = prev_unreachable;
219            self.ctx.has_reported_unreachable = prev_reported;
220
221            self.check_reserved_await_identifier_in_module(root_idx);
222
223            tracing::trace!(target: "wasm::perf", phase = "check_statements", ms = stmt_start.elapsed().as_secs_f64() * 1000.0);
224
225            let post_start = Instant::now();
226            // Check for function overload implementations (2389, 2391)
227            self.check_function_implementations(&sf.statements.nodes);
228
229            // Check for export assignment with other exports (2309)
230            self.check_export_assignment(&sf.statements.nodes);
231
232            // Check for circular import aliases (2303)
233            self.check_circular_import_aliases();
234
235            // Check for TS1148: module none errors
236            if matches!(
237                self.ctx.compiler_options.module,
238                tsz_common::common::ModuleKind::None
239            ) && !is_dts
240            {
241                self.check_module_none_statements(&sf.statements.nodes);
242            }
243
244            // Check for duplicate identifiers (2300)
245            self.check_duplicate_identifiers();
246
247            // Check for missing global types (2318)
248            // Emits errors at file start for essential types when libs are not loaded
249            self.check_missing_global_types();
250
251            // Check triple-slash reference directives (TS6053)
252            if !self.ctx.compiler_options.no_resolve {
253                self.check_triple_slash_references(&sf.file_name, &sf.text);
254            }
255
256            // Check for duplicate AMD module name assignments (TS2458)
257            self.check_amd_module_names(&sf.text);
258
259            // Check for unused declarations (TS6133/TS6196)
260            if self.ctx.no_unused_locals() || self.ctx.no_unused_parameters() {
261                self.check_unused_declarations();
262            }
263            // JS grammar checks: emit TS8xxx errors for TypeScript-only syntax in JS files
264            if self.is_js_file() {
265                let js_start = Instant::now();
266                self.check_js_grammar_statements(&sf.statements.nodes);
267                tracing::trace!(target: "wasm::perf", phase = "check_js_grammar", ms = js_start.elapsed().as_secs_f64() * 1000.0);
268            }
269
270            tracing::trace!(target: "wasm::perf", phase = "post_checks", ms = post_start.elapsed().as_secs_f64() * 1000.0);
271        }
272    }
273
274    fn has_ts_nocheck_pragma(&self, source: &str) -> bool {
275        source
276            .lines()
277            .take(20)
278            .any(|line| line.contains("@ts-nocheck"))
279    }
280
281    // =========================================================================
282    // JS Grammar Checking (TS8xxx errors)
283    // =========================================================================
284
285    /// Check all statements in a JS file for TypeScript-only syntax.
286    /// Emits `TS8xxx` errors for constructs that are not valid in JavaScript files.
287    fn check_js_grammar_statements(&mut self, statements: &[NodeIndex]) {
288        for &stmt_idx in statements {
289            self.check_js_grammar_statement(stmt_idx);
290        }
291    }
292
293    /// Check a single statement for TypeScript-only syntax in JS files.
294    fn check_js_grammar_statement(&mut self, stmt_idx: NodeIndex) {
295        use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
296
297        let Some(node) = self.ctx.arena.get(stmt_idx) else {
298            return;
299        };
300
301        match node.kind {
302            // TS8008: Type aliases can only be used in TypeScript files
303            syntax_kind_ext::TYPE_ALIAS_DECLARATION => {
304                self.error_at_node(
305                    stmt_idx,
306                    diagnostic_messages::TYPE_ALIASES_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
307                    diagnostic_codes::TYPE_ALIASES_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
308                );
309            }
310
311            // TS8006: 'interface'/'enum'/'module'/'namespace' declarations
312            // TSC anchors the error at the declaration name, not the whole statement.
313            syntax_kind_ext::INTERFACE_DECLARATION => {
314                let error_node = self
315                    .ctx
316                    .arena
317                    .get_interface(node)
318                    .map_or(stmt_idx, |i| i.name);
319                self.error_ts_only_declaration("interface", error_node);
320            }
321
322            syntax_kind_ext::ENUM_DECLARATION => {
323                let error_node = self.ctx.arena.get_enum(node).map_or(stmt_idx, |e| e.name);
324                self.error_ts_only_declaration("enum", error_node);
325            }
326
327            syntax_kind_ext::MODULE_DECLARATION => {
328                let keyword = self.get_module_keyword(stmt_idx, node);
329                let error_node = self.ctx.arena.get_module(node).map_or(stmt_idx, |m| m.name);
330                self.error_ts_only_declaration(keyword, error_node);
331            }
332
333            // TS8002: 'import ... =' can only be used in TypeScript files
334            syntax_kind_ext::IMPORT_EQUALS_DECLARATION => {
335                self.error_at_node(
336                    stmt_idx,
337                    diagnostic_messages::IMPORT_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
338                    diagnostic_codes::IMPORT_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
339                );
340            }
341
342            // TS8003: 'export =' can only be used in TypeScript files
343            syntax_kind_ext::EXPORT_ASSIGNMENT => {
344                self.error_at_node(
345                    stmt_idx,
346                    diagnostic_messages::EXPORT_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
347                    diagnostic_codes::EXPORT_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
348                );
349            }
350
351            // Function declarations: check for type params, return type, overloads, param types
352            syntax_kind_ext::FUNCTION_DECLARATION => {
353                self.check_js_grammar_function(stmt_idx, node);
354            }
355
356            // Class declarations: check for type params, implements, abstract, members
357            syntax_kind_ext::CLASS_DECLARATION => {
358                self.check_js_grammar_class(stmt_idx, node);
359            }
360
361            // Variable statements: check for declare modifier, type annotations
362            syntax_kind_ext::VARIABLE_STATEMENT => {
363                self.check_js_grammar_variable_statement(stmt_idx, node);
364            }
365
366            // Export declarations may wrap other declarations
367            syntax_kind_ext::EXPORT_DECLARATION => {
368                if let Some(export_decl) = self.ctx.arena.get_export_decl_at(stmt_idx)
369                    && export_decl.export_clause.is_some()
370                {
371                    self.check_js_grammar_statement(export_decl.export_clause);
372                }
373            }
374
375            // Expression statements may contain function expressions and arrow functions.
376            syntax_kind_ext::EXPRESSION_STATEMENT => {
377                if let Some(expr_stmt) = self.ctx.arena.get_expression_statement(node) {
378                    self.check_js_grammar_expression(expr_stmt.expression);
379                }
380            }
381
382            _ => {}
383        }
384    }
385
386    fn check_js_grammar_expression(&mut self, expr_idx: NodeIndex) {
387        let Some(node) = self.ctx.arena.get(expr_idx) else {
388            return;
389        };
390
391        if node.is_function_like() {
392            self.check_js_grammar_function(expr_idx, node);
393        }
394
395        for child_idx in self.ctx.arena.get_children(expr_idx) {
396            if child_idx.is_some() {
397                self.check_js_grammar_expression(child_idx);
398            }
399        }
400    }
401
402    /// Check function declaration for JS grammar errors.
403    pub(crate) fn check_js_grammar_function(
404        &mut self,
405        func_idx: NodeIndex,
406        node: &tsz_parser::parser::node::Node,
407    ) {
408        let Some(func) = self.ctx.arena.get_function(node) else {
409            return;
410        };
411
412        self.error_if_ts_only_modifier(&func.modifiers, SyntaxKind::DeclareKeyword, "declare");
413        self.error_if_ts_only_type_params(&func.type_parameters);
414        self.error_if_ts_only_type_annotation(func.type_annotation);
415
416        // TS8017: Function overload (function without body)
417        let is_overload = func.body.is_none() && node.kind == syntax_kind_ext::FUNCTION_DECLARATION;
418        self.error_if_ts_only_signature_without_body(is_overload, func_idx);
419
420        // Check parameter types and modifiers
421        self.check_js_grammar_parameters(&func.parameters.nodes);
422    }
423
424    /// Check class declaration for JS grammar errors.
425    fn check_js_grammar_class(
426        &mut self,
427        _class_idx: NodeIndex,
428        node: &tsz_parser::parser::node::Node,
429    ) {
430        use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
431
432        let Some(class) = self.ctx.arena.get_class(node) else {
433            return;
434        };
435
436        self.error_if_ts_only_type_params(&class.type_parameters);
437
438        // TS8005: 'implements' clause
439        if let Some(ref heritage_clauses) = class.heritage_clauses {
440            for &clause_idx in &heritage_clauses.nodes {
441                if let Some(clause_node) = self.ctx.arena.get(clause_idx)
442                    && clause_node.kind == syntax_kind_ext::HERITAGE_CLAUSE
443                    && let Some(heritage) = self.ctx.arena.get_heritage_clause(clause_node)
444                    && heritage.token == SyntaxKind::ImplementsKeyword as u16
445                {
446                    self.error_at_node(
447                        clause_idx,
448                        diagnostic_messages::IMPLEMENTS_CLAUSES_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
449                        diagnostic_codes::IMPLEMENTS_CLAUSES_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
450                    );
451                }
452            }
453        }
454
455        self.error_if_ts_only_modifier(&class.modifiers, SyntaxKind::AbstractKeyword, "abstract");
456        self.error_if_ts_only_modifier(&class.modifiers, SyntaxKind::DeclareKeyword, "declare");
457
458        // Check class members for JS grammar errors
459        for &member_idx in &class.members.nodes {
460            self.check_js_grammar_class_member(member_idx);
461        }
462    }
463
464    /// Helper: Report TS8009 error for a TypeScript-only modifier (abstract, override, etc.).
465    fn error_if_ts_only_modifier(
466        &mut self,
467        modifiers: &Option<tsz_parser::parser::NodeList>,
468        kind: SyntaxKind,
469        name: &str,
470    ) {
471        use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
472
473        if self.has_modifier_kind(modifiers, kind) {
474            let message = format_message(
475                diagnostic_messages::THE_MODIFIER_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
476                &[name],
477            );
478            if let Some(mod_idx) = self.get_modifier_index(modifiers, kind as u16) {
479                self.error_at_node(
480                    mod_idx,
481                    &message,
482                    diagnostic_codes::THE_MODIFIER_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
483                );
484            }
485        }
486    }
487
488    /// Helper: Report TS8010 error for a TypeScript-only type annotation.
489    fn error_if_ts_only_type_annotation(&mut self, type_annotation: NodeIndex) {
490        use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
491
492        if type_annotation.is_some() {
493            self.error_at_node(
494                type_annotation,
495                diagnostic_messages::TYPE_ANNOTATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
496                diagnostic_codes::TYPE_ANNOTATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
497            );
498        }
499    }
500
501    /// Helper: Report TS8004 error for TypeScript-only type parameters.
502    fn error_if_ts_only_type_params(
503        &mut self,
504        type_parameters: &Option<tsz_parser::parser::NodeList>,
505    ) {
506        use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
507
508        if let Some(type_params) = type_parameters
509            && !type_params.nodes.is_empty()
510        {
511            self.error_at_node(
512                type_params.nodes[0],
513                diagnostic_messages::TYPE_PARAMETER_DECLARATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
514                diagnostic_codes::TYPE_PARAMETER_DECLARATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
515            );
516        }
517    }
518
519    /// Helper: Report TS8017 error for a signature declaration without a body.
520    fn error_if_ts_only_signature_without_body(&mut self, has_no_body: bool, node_idx: NodeIndex) {
521        use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
522
523        if has_no_body {
524            self.error_at_node(
525                node_idx,
526                diagnostic_messages::SIGNATURE_DECLARATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
527                diagnostic_codes::SIGNATURE_DECLARATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
528            );
529        }
530    }
531
532    /// Helper: Report TS8009 error for optional token (?) in JavaScript.
533    fn error_if_ts_only_optional(&mut self, has_question_token: bool, node_idx: NodeIndex) {
534        use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
535
536        if has_question_token {
537            let message = format_message(
538                diagnostic_messages::THE_MODIFIER_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
539                &["?"],
540            );
541            self.error_at_node(
542                node_idx,
543                &message,
544                diagnostic_codes::THE_MODIFIER_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
545            );
546        }
547    }
548
549    /// Helper: Report TS8006 error for TypeScript-only declarations (interface, enum, module, namespace).
550    fn error_ts_only_declaration(&mut self, keyword: &str, node_idx: NodeIndex) {
551        use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
552
553        let message = format_message(
554            diagnostic_messages::DECLARATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
555            &[keyword],
556        );
557        self.error_at_node(
558            node_idx,
559            &message,
560            diagnostic_codes::DECLARATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
561        );
562    }
563
564    /// Helper: Determine if a module declaration uses 'module' or 'namespace' keyword.
565    fn get_module_keyword(
566        &self,
567        node_idx: NodeIndex,
568        node: &tsz_parser::parser::node::Node,
569    ) -> &'static str {
570        let Some(module) = self.ctx.arena.get_module(node) else {
571            return "module";
572        };
573
574        let Some(name_node) = self.ctx.arena.get(module.name) else {
575            return "module";
576        };
577
578        // If name is a string literal, it's `module "foo"`, otherwise `namespace Foo`
579        if name_node.kind == SyntaxKind::StringLiteral as u16 {
580            return "module";
581        }
582
583        // Check source text for module vs namespace keyword
584        let node_text = self.node_text(node_idx).unwrap_or_default();
585        if node_text.starts_with("namespace") || node_text.contains("namespace ") {
586            "namespace"
587        } else {
588            "module"
589        }
590    }
591
592    /// Check a class member for JS grammar errors.
593    pub(crate) fn check_js_grammar_class_member(&mut self, member_idx: NodeIndex) {
594        let Some(node) = self.ctx.arena.get(member_idx) else {
595            return;
596        };
597
598        match node.kind {
599            syntax_kind_ext::METHOD_DECLARATION => {
600                if let Some(method) = self.ctx.arena.get_method_decl(node) {
601                    self.check_js_grammar_accessibility_modifier(&method.modifiers, member_idx);
602                    self.error_if_ts_only_modifier(
603                        &method.modifiers,
604                        SyntaxKind::AbstractKeyword,
605                        "abstract",
606                    );
607                    self.error_if_ts_only_modifier(
608                        &method.modifiers,
609                        SyntaxKind::OverrideKeyword,
610                        "override",
611                    );
612                    self.error_if_ts_only_type_params(&method.type_parameters);
613                    self.error_if_ts_only_type_annotation(method.type_annotation);
614                    self.error_if_ts_only_signature_without_body(method.body.is_none(), member_idx);
615                    self.error_if_ts_only_optional(method.question_token, member_idx);
616                    self.check_js_grammar_parameters(&method.parameters.nodes);
617                }
618            }
619
620            syntax_kind_ext::CONSTRUCTOR => {
621                if let Some(ctor) = self.ctx.arena.get_constructor(node) {
622                    self.check_js_grammar_accessibility_modifier(&ctor.modifiers, member_idx);
623                    self.error_if_ts_only_signature_without_body(ctor.body.is_none(), member_idx);
624                    self.check_js_grammar_parameters(&ctor.parameters.nodes);
625                }
626            }
627
628            syntax_kind_ext::PROPERTY_DECLARATION => {
629                if let Some(prop) = self.ctx.arena.get_property_decl(node) {
630                    self.error_if_ts_only_optional(prop.question_token, member_idx);
631                    self.error_if_ts_only_modifier(
632                        &prop.modifiers,
633                        SyntaxKind::AbstractKeyword,
634                        "abstract",
635                    );
636                    self.check_js_grammar_accessibility_modifier(&prop.modifiers, member_idx);
637                }
638            }
639
640            syntax_kind_ext::GET_ACCESSOR | syntax_kind_ext::SET_ACCESSOR => {
641                if let Some(accessor) = self.ctx.arena.get_accessor(node) {
642                    self.check_js_grammar_accessibility_modifier(&accessor.modifiers, member_idx);
643                    self.error_if_ts_only_type_annotation(accessor.type_annotation);
644                    self.check_js_grammar_parameters(&accessor.parameters.nodes);
645                }
646            }
647
648            syntax_kind_ext::INDEX_SIGNATURE
649            | syntax_kind_ext::CALL_SIGNATURE
650            | syntax_kind_ext::CONSTRUCT_SIGNATURE => {
651                self.error_if_ts_only_signature_without_body(true, member_idx);
652            }
653
654            _ => {}
655        }
656    }
657
658    /// Check function parameters for JS grammar errors (type annotations, modifiers).
659    fn check_js_grammar_parameters(&mut self, param_nodes: &[NodeIndex]) {
660        use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
661
662        for &param_idx in param_nodes {
663            let Some(param_node) = self.ctx.arena.get(param_idx) else {
664                continue;
665            };
666            let Some(param) = self.ctx.arena.get_parameter(param_node) else {
667                continue;
668            };
669
670            // TS8010: Type annotation on parameter
671            if param.type_annotation.is_some() {
672                self.error_at_node(
673                    param.type_annotation,
674                    diagnostic_messages::TYPE_ANNOTATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
675                    diagnostic_codes::TYPE_ANNOTATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
676                );
677            }
678
679            // TS8012: Parameter modifiers (public/private/protected/readonly on constructor params)
680            if let Some(ref modifiers) = param.modifiers {
681                for &mod_idx in &modifiers.nodes {
682                    if let Some(mod_node) = self.ctx.arena.get(mod_idx) {
683                        match mod_node.kind {
684                            k if k == SyntaxKind::PublicKeyword as u16
685                                || k == SyntaxKind::PrivateKeyword as u16
686                                || k == SyntaxKind::ProtectedKeyword as u16
687                                || k == SyntaxKind::ReadonlyKeyword as u16 =>
688                            {
689                                self.error_at_node(
690                                    mod_idx,
691                                    diagnostic_messages::PARAMETER_MODIFIERS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
692                                    diagnostic_codes::PARAMETER_MODIFIERS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
693                                );
694                            }
695                            _ => {}
696                        }
697                    }
698                }
699            }
700
701            // TS8009: Optional parameter (question token)
702            if param.question_token {
703                let message = crate::diagnostics::format_message(
704                    diagnostic_messages::THE_MODIFIER_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
705                    &["?"],
706                );
707                self.error_at_node(
708                    param_idx,
709                    &message,
710                    diagnostic_codes::THE_MODIFIER_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
711                );
712            }
713        }
714    }
715
716    /// Check for accessibility modifiers (public/private/protected) on a declaration.
717    fn check_js_grammar_accessibility_modifier(
718        &mut self,
719        modifiers: &Option<tsz_parser::parser::NodeList>,
720        _fallback_idx: NodeIndex,
721    ) {
722        use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
723
724        if let Some(mods) = modifiers {
725            for &mod_idx in &mods.nodes {
726                if let Some(mod_node) = self.ctx.arena.get(mod_idx) {
727                    let modifier_name = match mod_node.kind {
728                        k if k == SyntaxKind::PublicKeyword as u16 => Some("public"),
729                        k if k == SyntaxKind::PrivateKeyword as u16 => Some("private"),
730                        k if k == SyntaxKind::ProtectedKeyword as u16 => Some("protected"),
731                        _ => None,
732                    };
733                    if let Some(name) = modifier_name {
734                        let message = format_message(
735                            diagnostic_messages::THE_MODIFIER_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
736                            &[name],
737                        );
738                        self.error_at_node(
739                            mod_idx,
740                            &message,
741                            diagnostic_codes::THE_MODIFIER_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
742                        );
743                    }
744                }
745            }
746        }
747    }
748
749    /// Check a variable statement for JS grammar errors.
750    fn check_js_grammar_variable_statement(
751        &mut self,
752        _stmt_idx: NodeIndex,
753        node: &tsz_parser::parser::node::Node,
754    ) {
755        use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
756
757        // VariableStatement uses VariableData { modifiers, declarations }
758        let Some(var) = self.ctx.arena.get_variable(node) else {
759            return;
760        };
761
762        // TS8009: 'declare' modifier on variable statement
763        if self.has_declare_modifier(&var.modifiers) {
764            let message = format_message(
765                diagnostic_messages::THE_MODIFIER_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
766                &["declare"],
767            );
768            if let Some(mod_idx) =
769                self.get_modifier_index(&var.modifiers, SyntaxKind::DeclareKeyword as u16)
770            {
771                self.error_at_node(
772                    mod_idx,
773                    &message,
774                    diagnostic_codes::THE_MODIFIER_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
775                );
776            }
777        }
778
779        // Check variable declarations for type annotations
780        // VariableStatement.declarations contains VariableDeclarationList nodes
781        for &list_idx in &var.declarations.nodes {
782            if let Some(list_node) = self.ctx.arena.get(list_idx)
783                && let Some(list) = self.ctx.arena.get_variable(list_node)
784            {
785                for &decl_idx in &list.declarations.nodes {
786                    if let Some(decl_node) = self.ctx.arena.get(decl_idx)
787                        && let Some(var_decl) = self.ctx.arena.get_variable_declaration(decl_node)
788                    {
789                        // TS8010: Type annotation on variable
790                        if var_decl.type_annotation.is_some() {
791                            self.error_at_node(
792                                        var_decl.type_annotation,
793                                        diagnostic_messages::TYPE_ANNOTATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
794                                        diagnostic_codes::TYPE_ANNOTATIONS_CAN_ONLY_BE_USED_IN_TYPESCRIPT_FILES,
795                                    );
796                        }
797                    }
798                }
799            }
800        }
801    }
802
803    /// Get the index of a specific modifier kind in a modifier list.
804    fn get_modifier_index(
805        &self,
806        modifiers: &Option<tsz_parser::parser::NodeList>,
807        kind: u16,
808    ) -> Option<NodeIndex> {
809        if let Some(mods) = modifiers {
810            for &mod_idx in &mods.nodes {
811                if let Some(mod_node) = self.ctx.arena.get(mod_idx)
812                    && mod_node.kind == kind
813                {
814                    return Some(mod_idx);
815                }
816            }
817        }
818        None
819    }
820
821    fn has_static_modifier_in_arena(
822        &self,
823        arena: &tsz_parser::parser::NodeArena,
824        modifiers: &Option<tsz_parser::parser::NodeList>,
825    ) -> bool {
826        arena.has_modifier(modifiers, tsz_scanner::SyntaxKind::StaticKeyword)
827    }
828
829    pub(crate) fn declaration_symbol_flags(
830        &self,
831        arena: &tsz_parser::parser::NodeArena,
832        decl_idx: NodeIndex,
833    ) -> Option<u32> {
834        use tsz_parser::parser::node_flags;
835
836        let decl_idx = self.resolve_duplicate_decl_node(arena, decl_idx)?;
837        let node = arena.get(decl_idx)?;
838
839        match node.kind {
840            syntax_kind_ext::VARIABLE_DECLARATION => {
841                let mut decl_flags = node.flags as u32;
842                if (decl_flags & (node_flags::LET | node_flags::CONST)) == 0
843                    && let Some(parent) = arena.get_extended(decl_idx).map(|ext| ext.parent)
844                    && let Some(parent_node) = arena.get(parent)
845                    && parent_node.kind == syntax_kind_ext::VARIABLE_DECLARATION_LIST
846                {
847                    decl_flags |= parent_node.flags as u32;
848                }
849                if (decl_flags & (node_flags::LET | node_flags::CONST)) != 0 {
850                    Some(symbol_flags::BLOCK_SCOPED_VARIABLE)
851                } else {
852                    Some(symbol_flags::FUNCTION_SCOPED_VARIABLE)
853                }
854            }
855            syntax_kind_ext::FUNCTION_DECLARATION => Some(symbol_flags::FUNCTION),
856            syntax_kind_ext::CLASS_DECLARATION => Some(symbol_flags::CLASS),
857            syntax_kind_ext::INTERFACE_DECLARATION => Some(symbol_flags::INTERFACE),
858            syntax_kind_ext::TYPE_ALIAS_DECLARATION => Some(symbol_flags::TYPE_ALIAS),
859            syntax_kind_ext::ENUM_DECLARATION => {
860                // Check if this is a const enum by looking for const modifier
861                let is_const_enum = arena
862                    .get_enum(node)
863                    .and_then(|enum_decl| enum_decl.modifiers.as_ref())
864                    .is_some_and(|modifiers| {
865                        modifiers.nodes.iter().any(|&mod_idx| {
866                            arena.get(mod_idx).is_some_and(|mod_node| {
867                                mod_node.kind == tsz_scanner::SyntaxKind::ConstKeyword as u16
868                            })
869                        })
870                    });
871                if is_const_enum {
872                    Some(symbol_flags::CONST_ENUM)
873                } else {
874                    Some(symbol_flags::REGULAR_ENUM)
875                }
876            }
877            syntax_kind_ext::MODULE_DECLARATION => {
878                // Namespaces (module declarations) can merge with functions, classes, enums
879                Some(symbol_flags::VALUE_MODULE | symbol_flags::NAMESPACE_MODULE)
880            }
881            syntax_kind_ext::GET_ACCESSOR => {
882                let mut flags = symbol_flags::GET_ACCESSOR;
883                if let Some(accessor) = arena.get_accessor(node)
884                    && self.has_static_modifier_in_arena(arena, &accessor.modifiers)
885                {
886                    flags |= symbol_flags::STATIC;
887                }
888                Some(flags)
889            }
890            syntax_kind_ext::SET_ACCESSOR => {
891                let mut flags = symbol_flags::SET_ACCESSOR;
892                if let Some(accessor) = arena.get_accessor(node)
893                    && self.has_static_modifier_in_arena(arena, &accessor.modifiers)
894                {
895                    flags |= symbol_flags::STATIC;
896                }
897                Some(flags)
898            }
899            syntax_kind_ext::METHOD_DECLARATION => {
900                let mut flags = symbol_flags::METHOD;
901                if let Some(method) = arena.get_method_decl(node)
902                    && self.has_static_modifier_in_arena(arena, &method.modifiers)
903                {
904                    flags |= symbol_flags::STATIC;
905                }
906                Some(flags)
907            }
908            syntax_kind_ext::PROPERTY_DECLARATION => {
909                let mut flags = symbol_flags::PROPERTY;
910                if let Some(prop) = arena.get_property_decl(node)
911                    && self.has_static_modifier_in_arena(arena, &prop.modifiers)
912                {
913                    flags |= symbol_flags::STATIC;
914                }
915                Some(flags)
916            }
917            syntax_kind_ext::CONSTRUCTOR => Some(symbol_flags::CONSTRUCTOR),
918            syntax_kind_ext::PARAMETER => Some(symbol_flags::FUNCTION_SCOPED_VARIABLE),
919            syntax_kind_ext::IMPORT_EQUALS_DECLARATION
920            | syntax_kind_ext::IMPORT_CLAUSE
921            | syntax_kind_ext::NAMESPACE_IMPORT
922            | syntax_kind_ext::IMPORT_SPECIFIER => Some(symbol_flags::ALIAS),
923            syntax_kind_ext::NAMESPACE_EXPORT_DECLARATION => {
924                // 'export as namespace' creates a global alias to the module.
925                // It behaves like a global value module alias.
926                Some(symbol_flags::FUNCTION_SCOPED_VARIABLE | symbol_flags::ALIAS)
927            }
928            _ => None,
929        }
930    }
931
932    fn check_reserved_await_identifier_in_module(&mut self, source_file_idx: NodeIndex) {
933        let Some(source_file_node) = self.ctx.arena.get(source_file_idx) else {
934            return;
935        };
936        let Some(source_file) = self.ctx.arena.get_source_file(source_file_node) else {
937            return;
938        };
939
940        let source_file_name = &source_file.file_name;
941        let is_declaration_file = source_file.is_declaration_file
942            || source_file_name.ends_with(".d.ts")
943            || source_file_name.ends_with(".d.tsx")
944            || source_file_name.ends_with(".d.mts")
945            || source_file_name.ends_with(".d.cts")
946            || self.ctx.file_name.ends_with(".d.ts")
947            || self.ctx.file_name.ends_with(".d.tsx")
948            || self.ctx.file_name.ends_with(".d.mts")
949            || self.ctx.file_name.ends_with(".d.cts");
950
951        if is_declaration_file {
952            return;
953        }
954
955        let is_external_module = if let Some(ref map) = self.ctx.is_external_module_by_file {
956            map.get(&self.ctx.file_name).copied().unwrap_or(false)
957        } else {
958            self.ctx.binder.is_external_module()
959        };
960
961        let has_module_indicator = self.source_file_has_module_indicator(source_file);
962        let force_js_module_check = self.is_js_like_file() && has_module_indicator;
963
964        if !is_external_module && !force_js_module_check {
965            return;
966        }
967
968        let Some(await_sym_id) = self.ctx.binder.file_locals.get("await") else {
969            return;
970        };
971
972        let Some(symbol) = self.ctx.binder.get_symbol(await_sym_id) else {
973            return;
974        };
975
976        let mut candidate_decls = symbol.declarations.clone();
977        if symbol.value_declaration.is_some() {
978            candidate_decls.push(symbol.value_declaration);
979        }
980
981        candidate_decls.sort_unstable_by_key(|node| node.0);
982        candidate_decls.dedup();
983
984        for decl_idx in candidate_decls {
985            let Some(node) = self.ctx.arena.get(decl_idx) else {
986                continue;
987            };
988
989            let is_disallowed_top_level_await_decl = matches!(
990                node.kind,
991                syntax_kind_ext::VARIABLE_DECLARATION
992                    | syntax_kind_ext::BINDING_ELEMENT
993                    | syntax_kind_ext::FUNCTION_DECLARATION
994                    | syntax_kind_ext::CLASS_DECLARATION
995                    | syntax_kind_ext::IMPORT_CLAUSE
996                    | syntax_kind_ext::IMPORT_SPECIFIER
997                    | syntax_kind_ext::NAMESPACE_IMPORT
998            );
999            if !is_disallowed_top_level_await_decl {
1000                continue;
1001            }
1002
1003            let is_plain_await_identifier = self
1004                .await_identifier_name_node_for_decl(decl_idx)
1005                .is_some_and(|name_idx| self.is_plain_await_identifier(source_file, name_idx));
1006
1007            if !is_plain_await_identifier {
1008                continue;
1009            }
1010
1011            let mut current = decl_idx;
1012            let mut is_top_level = false;
1013            while let Some(ext) = self.ctx.arena.get_extended(current) {
1014                let parent = ext.parent;
1015                if parent.is_none() {
1016                    break;
1017                }
1018                if parent == source_file_idx {
1019                    is_top_level = true;
1020                    break;
1021                }
1022                current = parent;
1023            }
1024
1025            if !is_top_level {
1026                continue;
1027            }
1028
1029            let report_idx = self
1030                .await_identifier_name_node_for_decl(decl_idx)
1031                .unwrap_or(decl_idx);
1032            self.error_at_node(
1033                report_idx,
1034                "Identifier expected. 'await' is a reserved word at the top-level of a module.",
1035                crate::diagnostics::diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_AT_THE_TOP_LEVEL_OF_A_MODULE,
1036            );
1037            break;
1038        }
1039
1040        self.emit_top_level_await_text_fallback(source_file);
1041    }
1042
1043    fn await_identifier_name_node_for_decl(&self, decl_idx: NodeIndex) -> Option<NodeIndex> {
1044        let node = self.ctx.arena.get(decl_idx)?;
1045        match node.kind {
1046            syntax_kind_ext::VARIABLE_DECLARATION => self
1047                .ctx
1048                .arena
1049                .get_variable_declaration(node)
1050                .map(|decl| decl.name),
1051            syntax_kind_ext::BINDING_ELEMENT => self
1052                .ctx
1053                .arena
1054                .get_binding_element(node)
1055                .map(|decl| decl.name),
1056            syntax_kind_ext::FUNCTION_DECLARATION => {
1057                self.ctx.arena.get_function(node).map(|f| f.name)
1058            }
1059            syntax_kind_ext::CLASS_DECLARATION => self.ctx.arena.get_class(node).map(|c| c.name),
1060            syntax_kind_ext::IMPORT_CLAUSE => self
1061                .ctx
1062                .arena
1063                .get_import_clause(node)
1064                .map(|clause| clause.name),
1065            syntax_kind_ext::IMPORT_SPECIFIER => self
1066                .ctx
1067                .arena
1068                .get_specifier(node)
1069                .map(|specifier| specifier.name),
1070            syntax_kind_ext::NAMESPACE_IMPORT => self
1071                .ctx
1072                .arena
1073                .get_named_imports(node)
1074                .map(|named| named.name),
1075            _ => None,
1076        }
1077    }
1078
1079    fn is_plain_await_identifier(
1080        &self,
1081        source_file: &tsz_parser::parser::node::SourceFileData,
1082        node_idx: NodeIndex,
1083    ) -> bool {
1084        let Some(node) = self.ctx.arena.get(node_idx) else {
1085            return false;
1086        };
1087        if node.kind != SyntaxKind::Identifier as u16 {
1088            return false;
1089        }
1090        let Some((start, end)) = self.get_node_span(node_idx) else {
1091            return false;
1092        };
1093
1094        source_file
1095            .text
1096            .get(start as usize..end as usize)
1097            .is_some_and(|text| text == "await")
1098    }
1099
1100    fn source_file_has_module_indicator(
1101        &self,
1102        source_file: &tsz_parser::parser::node::SourceFileData,
1103    ) -> bool {
1104        source_file.statements.nodes.iter().any(|&stmt_idx| {
1105            let Some(stmt_node) = self.ctx.arena.get(stmt_idx) else {
1106                return false;
1107            };
1108
1109            matches!(
1110                stmt_node.kind,
1111                syntax_kind_ext::EXPORT_DECLARATION
1112                    | syntax_kind_ext::EXPORT_ASSIGNMENT
1113                    | syntax_kind_ext::IMPORT_DECLARATION
1114            )
1115        })
1116    }
1117
1118    fn emit_ts1262_at_first_await(&mut self, statement_start: u32, statement_text: &str) -> bool {
1119        let Some(offset) = statement_text.find("await") else {
1120            return false;
1121        };
1122
1123        self.error_at_position(
1124            statement_start + offset as u32,
1125            5,
1126            "Identifier expected. 'await' is a reserved word at the top-level of a module.",
1127            crate::diagnostics::diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_AT_THE_TOP_LEVEL_OF_A_MODULE,
1128        );
1129        true
1130    }
1131
1132    fn statement_contains_any(text: &str, patterns: &[&str]) -> bool {
1133        patterns.iter().any(|pattern| text.contains(pattern))
1134    }
1135
1136    fn is_js_like_file(&self) -> bool {
1137        self.ctx.file_name.ends_with(".js")
1138            || self.ctx.file_name.ends_with(".jsx")
1139            || self.ctx.file_name.ends_with(".mjs")
1140            || self.ctx.file_name.ends_with(".cjs")
1141    }
1142
1143    fn emit_top_level_await_text_fallback(
1144        &mut self,
1145        source_file: &tsz_parser::parser::node::SourceFileData,
1146    ) {
1147        let ts1262_code =
1148            crate::diagnostics::diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_AT_THE_TOP_LEVEL_OF_A_MODULE;
1149        if self
1150            .ctx
1151            .diagnostics
1152            .iter()
1153            .any(|diag| diag.code == ts1262_code)
1154        {
1155            return;
1156        }
1157
1158        let has_module_indicator = self.source_file_has_module_indicator(source_file);
1159        let is_js_like_file = self.is_js_like_file();
1160
1161        let import_patterns = [
1162            "import await from",
1163            "import * as await from",
1164            "import { await } from",
1165            "import { await as await } from",
1166        ];
1167        let binding_pattern_patterns = ["var {await}", "var [await]"];
1168        let js_variable_patterns = ["const await", "let await", "var await"];
1169
1170        for &stmt_idx in &source_file.statements.nodes {
1171            let Some(stmt_node) = self.ctx.arena.get(stmt_idx) else {
1172                continue;
1173            };
1174
1175            let Some((start, end)) = self.get_node_span(stmt_idx) else {
1176                continue;
1177            };
1178            let Some(stmt_text) = source_file.text.get(start as usize..end as usize) else {
1179                continue;
1180            };
1181
1182            match stmt_node.kind {
1183                syntax_kind_ext::IMPORT_DECLARATION => {
1184                    if Self::statement_contains_any(stmt_text, &import_patterns)
1185                        && self.emit_ts1262_at_first_await(start, stmt_text)
1186                    {
1187                        return;
1188                    }
1189                }
1190                syntax_kind_ext::IMPORT_EQUALS_DECLARATION => {
1191                    let has_await_import_equals = stmt_text.contains("import await =");
1192                    let is_require_form = stmt_text.contains("require(");
1193                    if has_await_import_equals
1194                        && (is_require_form || has_module_indicator)
1195                        && self.emit_ts1262_at_first_await(start, stmt_text)
1196                    {
1197                        return;
1198                    }
1199                }
1200                syntax_kind_ext::VARIABLE_STATEMENT => {
1201                    let has_binding_pattern_await =
1202                        Self::statement_contains_any(stmt_text, &binding_pattern_patterns);
1203                    let has_js_var_await = is_js_like_file
1204                        && Self::statement_contains_any(stmt_text, &js_variable_patterns);
1205                    if (has_binding_pattern_await || has_js_var_await)
1206                        && self.emit_ts1262_at_first_await(start, stmt_text)
1207                    {
1208                        return;
1209                    }
1210                }
1211                _ => {}
1212            }
1213        }
1214
1215        if has_module_indicator && let Some(offset) = source_file.text.find("const await") {
1216            self.error_at_position(
1217                offset as u32 + 6,
1218                5,
1219                "Identifier expected. 'await' is a reserved word at the top-level of a module.",
1220                ts1262_code,
1221            );
1222        }
1223    }
1224
1225    /// Emit TS1036 for non-declaration statements in .d.ts files.
1226    /// In .d.ts files the entire file is implicitly ambient, so non-declaration
1227    /// statements (break, continue, return, if, while, for, debugger, etc.) are not allowed.
1228    fn check_dts_statement_in_ambient_context(&mut self, stmt_idx: NodeIndex) {
1229        use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
1230        use tsz_parser::parser::syntax_kind_ext;
1231
1232        let Some(node) = self.ctx.arena.get(stmt_idx) else {
1233            return;
1234        };
1235
1236        let is_non_declaration = matches!(
1237            node.kind,
1238            k if k == syntax_kind_ext::EXPRESSION_STATEMENT
1239                || k == syntax_kind_ext::IF_STATEMENT
1240                || k == syntax_kind_ext::DO_STATEMENT
1241                || k == syntax_kind_ext::WHILE_STATEMENT
1242                || k == syntax_kind_ext::FOR_STATEMENT
1243                || k == syntax_kind_ext::FOR_IN_STATEMENT
1244                || k == syntax_kind_ext::FOR_OF_STATEMENT
1245                || k == syntax_kind_ext::BREAK_STATEMENT
1246                || k == syntax_kind_ext::CONTINUE_STATEMENT
1247                || k == syntax_kind_ext::RETURN_STATEMENT
1248                || k == syntax_kind_ext::WITH_STATEMENT
1249                || k == syntax_kind_ext::SWITCH_STATEMENT
1250                || k == syntax_kind_ext::THROW_STATEMENT
1251                || k == syntax_kind_ext::TRY_STATEMENT
1252                || k == syntax_kind_ext::DEBUGGER_STATEMENT
1253                || k == syntax_kind_ext::LABELED_STATEMENT
1254        );
1255
1256        if is_non_declaration && let Some((pos, end)) = self.ctx.get_node_span(stmt_idx) {
1257            self.ctx.error(
1258                pos,
1259                end - pos,
1260                diagnostic_messages::STATEMENTS_ARE_NOT_ALLOWED_IN_AMBIENT_CONTEXTS.to_string(),
1261                diagnostic_codes::STATEMENTS_ARE_NOT_ALLOWED_IN_AMBIENT_CONTEXTS,
1262            );
1263        }
1264    }
1265
1266    /// Emit TS1148 if module=none and the file contains imports, exports, or module augmentations.
1267    fn check_module_none_statements(&mut self, stmts: &[NodeIndex]) {
1268        use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
1269        use tsz_parser::parser::syntax_kind_ext;
1270
1271        for &stmt_idx in stmts {
1272            let Some(node) = self.ctx.arena.get(stmt_idx) else {
1273                continue;
1274            };
1275            let mut is_error = false;
1276
1277            match node.kind {
1278                syntax_kind_ext::IMPORT_DECLARATION
1279                | syntax_kind_ext::EXPORT_DECLARATION
1280                | syntax_kind_ext::EXPORT_ASSIGNMENT => {
1281                    is_error = true;
1282                }
1283                syntax_kind_ext::IMPORT_EQUALS_DECLARATION => {
1284                    // `import Alias = Ns.Member` is an internal namespace alias and
1285                    // should not trigger TS1148. Only `import x = require("...")`
1286                    // should be treated as module syntax for module=none checks.
1287                    if let Some(import_decl) = self.ctx.arena.get_import_decl(node)
1288                        && let Some(module_ref_node) =
1289                            self.ctx.arena.get(import_decl.module_specifier)
1290                        && module_ref_node.kind == tsz_scanner::SyntaxKind::StringLiteral as u16
1291                    {
1292                        is_error = true;
1293                    }
1294                }
1295                syntax_kind_ext::MODULE_DECLARATION => {
1296                    if self.is_declaration_exported(self.ctx.arena, stmt_idx) {
1297                        is_error = true;
1298                    } else if let Some(module) = self.ctx.arena.get_module(node)
1299                        && let Some(name_node) = self.ctx.arena.get(module.name)
1300                        && name_node.kind == tsz_scanner::SyntaxKind::StringLiteral as u16
1301                    {
1302                        is_error = true;
1303                    }
1304                }
1305                // Declarations that can have an `export` modifier
1306                k if k == syntax_kind_ext::VARIABLE_STATEMENT
1307                    || k == syntax_kind_ext::FUNCTION_DECLARATION
1308                    || k == syntax_kind_ext::CLASS_DECLARATION
1309                    || k == syntax_kind_ext::INTERFACE_DECLARATION
1310                    || k == syntax_kind_ext::TYPE_ALIAS_DECLARATION
1311                    || k == syntax_kind_ext::ENUM_DECLARATION =>
1312                {
1313                    if self.is_declaration_exported(self.ctx.arena, stmt_idx) {
1314                        is_error = true;
1315                    }
1316                }
1317                _ => {}
1318            }
1319
1320            if is_error {
1321                self.error_at_node(
1322                    stmt_idx,
1323                    diagnostic_messages::CANNOT_USE_IMPORTS_EXPORTS_OR_MODULE_AUGMENTATIONS_WHEN_MODULE_IS_NONE,
1324                    diagnostic_codes::CANNOT_USE_IMPORTS_EXPORTS_OR_MODULE_AUGMENTATIONS_WHEN_MODULE_IS_NONE,
1325                );
1326            }
1327        }
1328    }
1329
1330    /// Check for duplicate parameter names in a parameter list (TS2300).
1331    /// Check a statement and produce type errors.
1332    ///
1333    /// This method delegates to `StatementChecker` for dispatching logic,
1334    /// while providing actual implementations via the `StatementCheckCallbacks` trait.
1335    pub(crate) fn check_statement(&mut self, stmt_idx: NodeIndex) {
1336        StatementChecker::check(stmt_idx, self);
1337    }
1338}