Skip to main content

tsz_checker/state_checking_members/
statement_callback_bridge.rs

1use crate::state::CheckerState;
2use crate::statements::StatementCheckCallbacks;
3use tsz_parser::parser::{NodeIndex, syntax_kind_ext};
4use tsz_solver::TypeId;
5
6/// Implementation of `StatementCheckCallbacks` for `CheckerState`.
7///
8/// This provides the actual implementation of statement checking operations
9/// that `StatementChecker` delegates to. Each callback method calls the
10/// corresponding method on `CheckerState`.
11impl<'a> StatementCheckCallbacks for CheckerState<'a> {
12    fn arena(&self) -> &tsz_parser::parser::node::NodeArena {
13        self.ctx.arena
14    }
15
16    fn get_type_of_node(&mut self, idx: NodeIndex) -> TypeId {
17        CheckerState::get_type_of_node(self, idx)
18    }
19
20    fn get_type_of_node_no_narrowing(&mut self, idx: NodeIndex) -> TypeId {
21        let prev = self.ctx.skip_flow_narrowing;
22        self.ctx.skip_flow_narrowing = true;
23        let ty = CheckerState::get_type_of_node(self, idx);
24        self.ctx.skip_flow_narrowing = prev;
25        ty
26    }
27
28    fn check_variable_statement(&mut self, stmt_idx: NodeIndex) {
29        CheckerState::check_variable_statement(self, stmt_idx);
30    }
31
32    fn check_variable_declaration_list(&mut self, list_idx: NodeIndex) {
33        CheckerState::check_variable_declaration_list(self, list_idx);
34    }
35
36    fn check_variable_declaration(&mut self, decl_idx: NodeIndex) {
37        CheckerState::check_variable_declaration(self, decl_idx);
38    }
39
40    fn check_return_statement(&mut self, stmt_idx: NodeIndex) {
41        CheckerState::check_return_statement(self, stmt_idx);
42    }
43
44    fn check_function_implementations(&mut self, stmts: &[NodeIndex]) {
45        CheckerState::check_function_implementations(self, stmts);
46    }
47
48    fn check_function_declaration(&mut self, func_idx: NodeIndex) {
49        let Some(node) = self.ctx.arena.get(func_idx) else {
50            return;
51        };
52
53        // Delegate to DeclarationChecker for function declaration-specific checks
54        // (only for actual function declarations, not expressions/arrows)
55        if node.kind == syntax_kind_ext::FUNCTION_DECLARATION {
56            let mut checker = crate::declarations::DeclarationChecker::new(&mut self.ctx);
57            checker.check_function_declaration(func_idx);
58        }
59
60        // Re-get node after DeclarationChecker borrows ctx
61        let Some(node) = self.ctx.arena.get(func_idx) else {
62            return;
63        };
64
65        let Some(func) = self.ctx.arena.get_function(node) else {
66            return;
67        };
68
69        // TS1100: 'arguments' and 'eval' are invalid in function names in strict contexts.
70        if self.is_strict_mode_for_node(func_idx)
71            && func.name.is_some()
72            && let Some(func_name_node) = self.ctx.arena.get(func.name)
73            && let Some(ident) = self.ctx.arena.get_identifier(func_name_node)
74        {
75            if ident.escaped_text == "arguments" || ident.escaped_text == "eval" {
76                self.error_at_node_msg(
77                    func.name,
78                    crate::diagnostics::diagnostic_codes::INVALID_USE_OF_IN_STRICT_MODE,
79                    &[&ident.escaped_text],
80                );
81            }
82
83            // TS1212/TS1213/TS1214: Reserved word used as function name in strict mode
84            if crate::state_checking::is_strict_mode_reserved_name(&ident.escaped_text) {
85                use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
86                if self.ctx.enclosing_class.is_some() {
87                    let message = format_message(
88                        diagnostic_messages::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE_CLASS_DEFINITIONS_ARE_AUTO,
89                        &[&ident.escaped_text],
90                    );
91                    self.error_at_node(
92                        func.name,
93                        &message,
94                        diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE_CLASS_DEFINITIONS_ARE_AUTO,
95                    );
96                } else if self.ctx.binder.is_external_module() {
97                    let message = format_message(
98                        diagnostic_messages::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE_MODULES_ARE_AUTOMATICALLY,
99                        &[&ident.escaped_text],
100                    );
101                    self.error_at_node(
102                        func.name,
103                        &message,
104                        diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE_MODULES_ARE_AUTOMATICALLY,
105                    );
106                } else {
107                    let message = format_message(
108                        diagnostic_messages::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE,
109                        &[&ident.escaped_text],
110                    );
111                    self.error_at_node(
112                        func.name,
113                        &message,
114                        diagnostic_codes::IDENTIFIER_EXPECTED_IS_A_RESERVED_WORD_IN_STRICT_MODE,
115                    );
116                }
117            }
118        }
119
120        // Error 1183: An implementation cannot be declared in ambient contexts
121        // Check if function has 'declare' modifier but also has a body
122        // Point error at the body (opening brace) to match tsc
123        if func.body.is_some() && self.has_declare_modifier(&func.modifiers) {
124            use crate::diagnostics::diagnostic_codes;
125            self.error_at_node(
126                func.body,
127                "An implementation cannot be declared in ambient contexts.",
128                diagnostic_codes::AN_IMPLEMENTATION_CANNOT_BE_DECLARED_IN_AMBIENT_CONTEXTS,
129            );
130        }
131
132        // Check for missing Promise global type when function is async (TS2318)
133        // TSC emits this at the start of the file when Promise is not available
134        // Only check for non-generator async functions (async generators use AsyncGenerator, not Promise)
135        if func.is_async && !func.asterisk_token {
136            self.check_global_promise_available();
137        }
138
139        // TS1221 / TS1222
140        if func.asterisk_token {
141            use crate::diagnostics::diagnostic_codes;
142            let is_ambient = self.has_declare_modifier(&func.modifiers)
143                || self.ctx.file_name.ends_with(".d.ts")
144                || self.is_ambient_declaration(func_idx);
145
146            if is_ambient {
147                self.error_at_node(
148                    func_idx,
149                    "Generators are not allowed in an ambient context.",
150                    diagnostic_codes::GENERATORS_ARE_NOT_ALLOWED_IN_AN_AMBIENT_CONTEXT,
151                );
152            } else if func.body.is_none() {
153                self.error_at_node(
154                    func_idx,
155                    "An overload signature cannot be declared as a generator.",
156                    diagnostic_codes::AN_OVERLOAD_SIGNATURE_CANNOT_BE_DECLARED_AS_A_GENERATOR,
157                );
158            }
159        }
160
161        let (_type_params, type_param_updates) = self.push_type_parameters(&func.type_parameters);
162
163        // Check for unused type parameters (TS6133)
164        self.check_unused_type_params(&func.type_parameters, func_idx);
165
166        // Check for parameter properties (error 2369)
167        // Parameter properties are only allowed in constructors
168        self.check_parameter_properties(&func.parameters.nodes);
169
170        // Check for duplicate parameter names (TS2300)
171        self.check_duplicate_parameters(&func.parameters, func.body.is_some());
172        if !self.has_declare_modifier(&func.modifiers) && !self.ctx.file_name.ends_with(".d.ts") {
173            self.check_strict_mode_reserved_parameter_names(
174                &func.parameters.nodes,
175                func_idx,
176                false,
177            );
178        }
179
180        // Check for required parameters following optional parameters (TS1016)
181        self.check_parameter_ordering(&func.parameters);
182        self.check_binding_pattern_optionality(&func.parameters.nodes, func.body.is_some());
183
184        // Check that rest parameters have array types (TS2370)
185        self.check_rest_parameter_types(&func.parameters.nodes);
186
187        // Check return type annotation for parameter properties in function types
188        if func.type_annotation.is_some() {
189            self.check_type_for_parameter_properties(func.type_annotation);
190            // Check for undefined type names in return type
191            self.check_type_for_missing_names(func.type_annotation);
192        }
193
194        // Check parameter type annotations for parameter properties
195        for &param_idx in &func.parameters.nodes {
196            if let Some(param_node) = self.ctx.arena.get(param_idx)
197                && let Some(param) = self.ctx.arena.get_parameter(param_node)
198                && param.type_annotation.is_some()
199            {
200                self.check_type_for_parameter_properties(param.type_annotation);
201                // Check for undefined type names in parameter type
202                self.check_type_for_missing_names(param.type_annotation);
203            }
204        }
205
206        // Extract JSDoc for function declarations to suppress TS7006/TS7010 in JS files
207        let func_decl_jsdoc = self.get_jsdoc_for_function(func_idx);
208
209        // TS7006: Check parameters for implicit any.
210        // For closures (function expressions and arrow functions), TS7006 is already
211        // handled by get_type_of_function which has contextual type information.
212        // Only check here for actual function declarations.
213        let is_closure = matches!(
214            node.kind,
215            syntax_kind_ext::FUNCTION_EXPRESSION | syntax_kind_ext::ARROW_FUNCTION
216        );
217        if !is_closure {
218            for (pi, &param_idx) in func.parameters.nodes.iter().enumerate() {
219                let Some(param_node) = self.ctx.arena.get(param_idx) else {
220                    continue;
221                };
222                let Some(param) = self.ctx.arena.get_parameter(param_node) else {
223                    continue;
224                };
225                // Check if JSDoc provides a @param type for this parameter,
226                // or if the parameter has an inline /** @type {T} */ annotation,
227                // or if the function has a @type tag declaring its full type.
228                let has_jsdoc_param = if param.type_annotation.is_none() {
229                    let from_func_jsdoc = if let Some(ref jsdoc) = func_decl_jsdoc {
230                        let pname = self.parameter_name_for_error(param.name);
231                        Self::jsdoc_has_param_type(jsdoc, &pname)
232                            || Self::jsdoc_has_type_tag(jsdoc)
233                            || self.ctx.arena.get(param.name).is_some_and(|n| {
234                                n.kind == syntax_kind_ext::OBJECT_BINDING_PATTERN
235                                    || n.kind == syntax_kind_ext::ARRAY_BINDING_PATTERN
236                            }) && Self::jsdoc_has_type_annotations(jsdoc)
237                    } else {
238                        false
239                    };
240                    from_func_jsdoc || self.param_has_inline_jsdoc_type(param_idx)
241                } else {
242                    false
243                };
244                self.maybe_report_implicit_any_parameter(param, has_jsdoc_param, pi);
245            }
246        }
247
248        // Check parameter initializer placement for implementation vs signature (TS2371)
249        self.check_non_impl_parameter_initializers(
250            &func.parameters.nodes,
251            self.has_declare_modifier(&func.modifiers),
252            func.body.is_some(),
253        );
254
255        // Check function body if present
256        let has_type_annotation = func.type_annotation.is_some();
257        if func.body.is_some() {
258            let mut return_type = if has_type_annotation {
259                self.get_type_from_type_node(func.type_annotation)
260            } else {
261                // Use UNKNOWN to enforce strict checking
262                TypeId::UNKNOWN
263            };
264
265            // Extract this type from explicit `this` parameter EARLY
266            // so that infer_return_type_from_body has the correct `this` context
267            // (prevents false TS2683 during return type inference)
268            let mut pushed_this_type = false;
269            if let Some(&first_param) = func.parameters.nodes.first()
270                && let Some(param_node) = self.ctx.arena.get(first_param)
271                && let Some(param) = self.ctx.arena.get_parameter(param_node)
272            {
273                // Check if parameter name is "this"
274                // Must check both ThisKeyword and Identifier("this") to match parser behavior
275                let is_this = if let Some(name_node) = self.ctx.arena.get(param.name) {
276                    if name_node.kind == tsz_scanner::SyntaxKind::ThisKeyword as u16 {
277                        true
278                    } else if let Some(ident) = self.ctx.arena.get_identifier(name_node) {
279                        ident.escaped_text == "this"
280                    } else {
281                        false
282                    }
283                } else {
284                    false
285                };
286                if is_this && param.type_annotation.is_some() {
287                    let this_type = self.get_type_from_type_node(param.type_annotation);
288                    self.ctx.this_type_stack.push(this_type);
289                    pushed_this_type = true;
290                }
291            }
292
293            // Cache parameter types from annotations (so for-of binding uses correct types)
294            // and then infer for any remaining unknown parameters using contextual information.
295            // For closures (function expressions / arrow functions), parameter types are
296            // already properly cached by get_type_of_function with contextual typing.
297            // Calling cache_parameter_types(None) here would overwrite contextually-typed
298            // parameters (e.g., `data` in `() => data => data.map(s => ...)`) with ANY,
299            // causing downstream callback contextual typing to break (false TS7006).
300            if !is_closure {
301                self.cache_parameter_types(&func.parameters.nodes, None);
302            }
303            self.infer_parameter_types_from_context(&func.parameters.nodes);
304
305            // Check that parameter default values are assignable to declared types (TS2322)
306            self.check_parameter_initializers(&func.parameters.nodes);
307
308            if !has_type_annotation {
309                // Suppress definite assignment errors during return type inference.
310                // The function body will be checked again below, and that's when
311                // we want to emit TS2454 errors to avoid duplicates.
312                let prev_suppress = self.ctx.suppress_definite_assignment_errors;
313                self.ctx.suppress_definite_assignment_errors = true;
314                return_type = self.infer_return_type_from_body(func_idx, func.body, None);
315                self.ctx.suppress_definite_assignment_errors = prev_suppress;
316            }
317
318            // TS7010/TS7011 (implicit any return) for function declarations.
319            // For closures (function expressions and arrow functions), TS7010/TS7011
320            // is already handled by get_type_of_function which has contextual return
321            // type information. Only check here for actual function declarations.
322            if !is_closure {
323                let has_jsdoc_return = func_decl_jsdoc
324                    .as_ref()
325                    .is_some_and(|j| Self::jsdoc_has_type_annotations(j));
326                if !func.is_async && !has_jsdoc_return {
327                    let func_name = self.get_function_name_from_node(func_idx);
328                    let name_node = (func.name.is_some()).then_some(func.name);
329                    self.maybe_report_implicit_any_return(
330                        func_name,
331                        name_node,
332                        return_type,
333                        has_type_annotation,
334                        false,
335                        func_idx,
336                    );
337                }
338            }
339
340            // TS2705: Async function must return Promise
341            // Only check if there's an explicit return type annotation that is NOT Promise
342            // Skip this check if the return type is ERROR or the annotation looks like Promise
343            // Note: Async generators (async function*) return AsyncGenerator, not Promise
344            if func.is_async && !func.asterisk_token && has_type_annotation {
345                let should_emit_ts2705 = !self.is_promise_type(return_type)
346                    && return_type != TypeId::ERROR
347                    && !self.return_type_annotation_looks_like_promise(func.type_annotation);
348
349                if should_emit_ts2705 {
350                    use crate::context::ScriptTarget;
351                    use crate::diagnostics::diagnostic_codes;
352
353                    // For ES5/ES3 targets, emit TS1055 instead of TS2705
354                    let is_es5_or_lower = matches!(
355                        self.ctx.compiler_options.target,
356                        ScriptTarget::ES3 | ScriptTarget::ES5
357                    );
358
359                    let type_name = self.format_type(return_type);
360                    if is_es5_or_lower {
361                        self.error_at_node_msg(
362                            func.type_annotation,
363                            diagnostic_codes::TYPE_IS_NOT_A_VALID_ASYNC_FUNCTION_RETURN_TYPE_IN_ES5_BECAUSE_IT_DOES_NOT_REFER,
364                            &[&type_name],
365                        );
366                    } else {
367                        // TS1064: For ES6+ targets, the return type must be Promise<T>
368                        self.error_at_node_msg(
369                            func.type_annotation,
370                            diagnostic_codes::THE_RETURN_TYPE_OF_AN_ASYNC_FUNCTION_OR_METHOD_MUST_BE_THE_GLOBAL_PROMISE_T_TYPE,
371                            &[&type_name],
372                        );
373                    }
374                }
375            }
376
377            // Enter async context for await expression checking
378            if func.is_async {
379                self.ctx.enter_async_context();
380            }
381
382            // For generator functions with explicit return type (Generator<Y, R, N> or AsyncGenerator<Y, R, N>),
383            // return statements should be checked against TReturn (R), not the full Generator type.
384            // This matches TypeScript's behavior where `return x` in a generator checks `x` against TReturn.
385            let is_generator = func.asterisk_token;
386            let body_return_type = if is_generator && has_type_annotation {
387                // Ensure the annotated return type is actually compatible with the Generator protocol.
388                let generator_base = if func.is_async {
389                    self.resolve_lib_type_by_name("AsyncGenerator")
390                        .unwrap_or(TypeId::ERROR)
391                } else {
392                    self.resolve_lib_type_by_name("Generator")
393                        .unwrap_or(TypeId::ERROR)
394                };
395                if generator_base != TypeId::ERROR {
396                    let any_gen = self
397                        .ctx
398                        .types
399                        .factory()
400                        .application(generator_base, vec![TypeId::ANY, TypeId::ANY, TypeId::ANY]);
401
402                    // Fast path: if the return type is already recognized as a valid generator type,
403                    // we don't need to do the complex structural subtyping check that fails due to overloads.
404                    // If it is not (e.g. `number`), we run the check to emit the TS2322 assignability error.
405                    if self
406                        .get_generator_return_type_argument(return_type)
407                        .is_none()
408                    {
409                        self.check_assignable_or_report(any_gen, return_type, func.type_annotation);
410                    }
411                }
412
413                self.get_generator_return_type_argument(return_type)
414                    .unwrap_or(return_type)
415            } else if func.is_async && has_type_annotation {
416                // Unwrap Promise<T> to T for async function return type checking.
417                // The function body returns T, which gets auto-wrapped in a Promise.
418                self.unwrap_promise_type(return_type).unwrap_or(return_type)
419            } else {
420                return_type
421            };
422
423            self.push_return_type(body_return_type);
424
425            // For generator functions, push the contextual yield type so that
426            // yield expressions can contextually type their operand.
427            let contextual_yield_type = if is_generator && has_type_annotation {
428                self.get_generator_yield_type_argument(return_type)
429            } else {
430                None
431            };
432            self.ctx.push_yield_type(contextual_yield_type);
433
434            // Save and reset control flow context (function body creates new context)
435            let saved_cf_context = (
436                self.ctx.iteration_depth,
437                self.ctx.switch_depth,
438                self.ctx.label_stack.len(),
439                self.ctx.had_outer_loop,
440            );
441            // If we were in a loop/switch, or already had an outer loop, mark it
442            if self.ctx.iteration_depth > 0 || self.ctx.switch_depth > 0 || self.ctx.had_outer_loop
443            {
444                self.ctx.had_outer_loop = true;
445            }
446            self.ctx.iteration_depth = 0;
447            self.ctx.switch_depth = 0;
448            self.ctx.function_depth += 1;
449            // Note: we don't truncate label_stack here - labels remain visible
450            // but function_depth is used to detect crosses over function boundary
451            self.check_statement(func.body);
452            // Restore control flow context
453            self.ctx.iteration_depth = saved_cf_context.0;
454            self.ctx.switch_depth = saved_cf_context.1;
455            self.ctx.function_depth -= 1;
456            self.ctx.label_stack.truncate(saved_cf_context.2);
457            self.ctx.had_outer_loop = saved_cf_context.3;
458
459            // Check for error 2355: function with return type must return a value
460            // Only check if there's an explicit return type annotation
461            let is_async = func.is_async;
462            let is_generator = func.asterisk_token;
463            let mut check_return_type =
464                self.return_type_for_implicit_return_check(return_type, is_async, is_generator);
465            // For async functions, if we couldn't unwrap Promise<T> (e.g. lib files not loaded),
466            // fall back to the annotation syntax. If it looks like Promise<...>, suppress TS2355
467            // since we can't verify the inner type anyway.
468            if is_async
469                && check_return_type == return_type
470                && has_type_annotation
471                && self.return_type_annotation_looks_like_promise(func.type_annotation)
472            {
473                check_return_type = TypeId::VOID;
474            }
475            let check_explicit_return_paths = has_type_annotation;
476            let requires_return = if check_explicit_return_paths {
477                self.requires_return_value(check_return_type)
478            } else {
479                false
480            };
481            let check_no_implicit_returns = self.ctx.no_implicit_returns();
482            let need_return_flow_scan =
483                (check_explicit_return_paths && requires_return) || check_no_implicit_returns;
484            let (has_return, falls_through) = if need_return_flow_scan {
485                (
486                    self.body_has_return_with_value(func.body),
487                    self.function_body_falls_through(func.body),
488                )
489            } else {
490                (false, false)
491            };
492
493            if check_explicit_return_paths && requires_return && falls_through {
494                if !has_return {
495                    use crate::diagnostics::diagnostic_codes;
496                    self.error_at_node(
497                        func.type_annotation,
498                        "A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.",
499                        diagnostic_codes::A_FUNCTION_WHOSE_DECLARED_TYPE_IS_NEITHER_UNDEFINED_VOID_NOR_ANY_MUST_RETURN_A_V,
500                    );
501                } else if self.ctx.strict_null_checks() {
502                    // TS2366: Only emit with strictNullChecks
503                    use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
504                    self.error_at_node(
505                        func.type_annotation,
506                        diagnostic_messages::FUNCTION_LACKS_ENDING_RETURN_STATEMENT_AND_RETURN_TYPE_DOES_NOT_INCLUDE_UNDEFINE,
507                        diagnostic_codes::FUNCTION_LACKS_ENDING_RETURN_STATEMENT_AND_RETURN_TYPE_DOES_NOT_INCLUDE_UNDEFINE,
508                    );
509                }
510            } else if check_no_implicit_returns
511                && has_return
512                && falls_through
513                && !self
514                    .should_skip_no_implicit_return_check(check_return_type, has_type_annotation)
515            {
516                // TS7030: noImplicitReturns - not all code paths return a value
517                use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
518                let error_node = if func.name.is_some() {
519                    func.name
520                } else {
521                    func.body
522                };
523                self.error_at_node(
524                    error_node,
525                    diagnostic_messages::NOT_ALL_CODE_PATHS_RETURN_A_VALUE,
526                    diagnostic_codes::NOT_ALL_CODE_PATHS_RETURN_A_VALUE,
527                );
528            }
529
530            self.pop_return_type();
531            self.ctx.pop_yield_type();
532
533            // Exit async context
534            if func.is_async {
535                self.ctx.exit_async_context();
536            }
537
538            if pushed_this_type {
539                self.ctx.this_type_stack.pop();
540            }
541        } else if self.ctx.no_implicit_any() && !has_type_annotation {
542            let is_ambient =
543                self.has_declare_modifier(&func.modifiers) || self.ctx.file_name.ends_with(".d.ts");
544            if let Some(func_name) = self.get_function_name_from_node(func_idx) {
545                let name_node = (func.name.is_some()).then_some(func.name);
546                if is_ambient {
547                    use crate::diagnostics::diagnostic_codes;
548                    self.error_at_node_msg(
549                        name_node.unwrap_or(func_idx),
550                        diagnostic_codes::WHICH_LACKS_RETURN_TYPE_ANNOTATION_IMPLICITLY_HAS_AN_RETURN_TYPE,
551                        &[&func_name, "any"],
552                    );
553                } else {
554                    // TS7010 for bodyless declaration signatures (TS2391 sibling error)
555                    // in non-ambient contexts.
556                    self.maybe_report_implicit_any_return(
557                        Some(func_name),
558                        name_node,
559                        TypeId::ANY,
560                        false,
561                        false,
562                        func_idx,
563                    );
564                }
565            }
566        }
567
568        // Check overload compatibility: implementation must be assignable to all overloads
569        // This is the function implementation validation (TS2394)
570        if func.body.is_some() {
571            // Only check for implementations (functions with bodies)
572            self.check_overload_compatibility(func_idx);
573        }
574
575        self.pop_type_parameters(type_param_updates);
576    }
577
578    fn check_class_declaration(&mut self, class_idx: NodeIndex) {
579        // Note: DeclarationChecker::check_class_declaration handles TS2564 (property
580        // initialization) but CheckerState::check_class_declaration also handles it
581        // more comprehensively (with parameter properties, derived classes, etc.).
582        // We skip the DeclarationChecker delegation for classes to avoid duplicate
583        // TS2564 emissions. DeclarationChecker::check_class_declaration is tested
584        // independently via its own test suite.
585        CheckerState::check_class_declaration(self, class_idx);
586    }
587
588    fn check_interface_declaration(&mut self, iface_idx: NodeIndex) {
589        // Delegate to DeclarationChecker first
590        let mut checker = crate::declarations::DeclarationChecker::new(&mut self.ctx);
591        checker.check_interface_declaration(iface_idx);
592
593        // Continue with comprehensive interface checking in CheckerState
594        CheckerState::check_interface_declaration(self, iface_idx);
595    }
596
597    fn check_import_declaration(&mut self, import_idx: NodeIndex) {
598        CheckerState::check_import_declaration(self, import_idx);
599    }
600
601    fn check_import_equals_declaration(&mut self, import_idx: NodeIndex) {
602        CheckerState::check_import_equals_declaration(self, import_idx);
603    }
604
605    fn check_export_declaration(&mut self, export_idx: NodeIndex) {
606        if let Some(export_decl) = self.ctx.arena.get_export_decl_at(export_idx) {
607            if export_decl.is_default_export && self.is_inside_namespace_declaration(export_idx) {
608                self.error_at_node(
609                    export_idx,
610                    crate::diagnostics::diagnostic_messages::A_DEFAULT_EXPORT_CAN_ONLY_BE_USED_IN_AN_ECMASCRIPT_STYLE_MODULE,
611                    crate::diagnostics::diagnostic_codes::A_DEFAULT_EXPORT_CAN_ONLY_BE_USED_IN_AN_ECMASCRIPT_STYLE_MODULE,
612                );
613                // tsc does not further resolve the exported expression when
614                // the export default is invalid in a namespace context.
615                return;
616            }
617
618            // TS1194: `export { ... }` / `export ... from` forms are not valid inside namespaces.
619            let is_reexport_syntax = export_decl.module_specifier.is_some()
620                || self
621                    .ctx
622                    .arena
623                    .get(export_decl.export_clause)
624                    .is_some_and(|n| n.kind == syntax_kind_ext::NAMED_EXPORTS);
625            if is_reexport_syntax && self.is_inside_namespace_declaration(export_idx) {
626                let report_idx = if export_decl.module_specifier.is_some() {
627                    export_decl.module_specifier
628                } else {
629                    export_idx
630                };
631                self.error_at_node(
632                    report_idx,
633                    crate::diagnostics::diagnostic_messages::EXPORT_DECLARATIONS_ARE_NOT_PERMITTED_IN_A_NAMESPACE,
634                    crate::diagnostics::diagnostic_codes::EXPORT_DECLARATIONS_ARE_NOT_PERMITTED_IN_A_NAMESPACE,
635                );
636            }
637
638            // TS2823: Import attributes require specific module options
639            self.check_import_attributes_module_option(export_decl.attributes);
640
641            // Check module specifier for unresolved modules (TS2792)
642            if export_decl.module_specifier.is_some() {
643                self.check_export_module_specifier(export_idx);
644            }
645
646            // Check the wrapped declaration
647            if export_decl.export_clause.is_some() {
648                let clause_idx = export_decl.export_clause;
649                let mut expected_type = None;
650                let mut prev_context = None;
651                if export_decl.is_default_export {
652                    expected_type = self.jsdoc_type_annotation_for_node(export_idx);
653                    if let Some(et) = expected_type {
654                        prev_context = self.ctx.contextual_type;
655                        self.ctx.contextual_type = Some(et);
656                    }
657                }
658
659                self.check_statement(clause_idx);
660
661                if let Some(et) = expected_type {
662                    let actual_type = self.get_type_of_node(clause_idx);
663                    self.ctx.contextual_type = prev_context;
664                    self.check_assignable_or_report(actual_type, et, clause_idx);
665                    if let Some(expr_node) = self.ctx.arena.get(clause_idx)
666                        && expr_node.kind == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
667                    {
668                        self.check_object_literal_excess_properties(actual_type, et, clause_idx);
669                    }
670                }
671
672                if export_decl.module_specifier.is_none()
673                    && !self.is_inside_namespace_declaration(export_idx)
674                    && self
675                        .ctx
676                        .arena
677                        .get(clause_idx)
678                        .is_some_and(|n| n.kind == syntax_kind_ext::NAMED_EXPORTS)
679                {
680                    self.check_local_named_exports(clause_idx);
681                }
682            }
683        }
684    }
685
686    fn check_type_alias_declaration(&mut self, type_alias_idx: NodeIndex) {
687        // Keep type-node validation and indexed-access diagnostics wired via CheckerState.
688        CheckerState::check_type_alias_declaration(self, type_alias_idx);
689
690        if let Some(node) = self.ctx.arena.get(type_alias_idx) {
691            // Continue with comprehensive type alias checking
692            if let Some(type_alias) = self.ctx.arena.get_type_alias(node) {
693                // TS1212: Check type alias name for strict mode reserved words
694                self.check_strict_mode_reserved_name_at(type_alias.name, type_alias_idx);
695
696                // TS2457: Type alias name cannot be 'undefined'
697                if let Some(name_node) = self.ctx.arena.get(type_alias.name)
698                    && let Some(ident) = self.ctx.arena.get_identifier(name_node)
699                    && ident.escaped_text == "undefined"
700                {
701                    use crate::diagnostics::diagnostic_codes;
702                    self.error_at_node(
703                        type_alias.name,
704                        "Type alias name cannot be 'undefined'.",
705                        diagnostic_codes::TYPE_ALIAS_NAME_CANNOT_BE,
706                    );
707                }
708                let (_params, updates) = self.push_type_parameters(&type_alias.type_parameters);
709                // Check for unused type parameters (TS6133)
710                self.check_unused_type_params(&type_alias.type_parameters, type_alias_idx);
711                self.check_type_for_missing_names(type_alias.type_node);
712                self.check_type_for_parameter_properties(type_alias.type_node);
713                self.pop_type_parameters(updates);
714            }
715        }
716    }
717    fn check_enum_duplicate_members(&mut self, enum_idx: NodeIndex) {
718        // TS1042: async modifier cannot be used on enum declarations
719        if let Some(node) = self.ctx.arena.get(enum_idx)
720            && let Some(enum_data) = self.ctx.arena.get_enum(node)
721        {
722            self.check_async_modifier_on_declaration(&enum_data.modifiers);
723            // TS1212: Check enum name for strict mode reserved words
724            self.check_strict_mode_reserved_name_at(enum_data.name, enum_idx);
725        }
726
727        // Delegate to DeclarationChecker first
728        let mut checker = crate::declarations::DeclarationChecker::new(&mut self.ctx);
729        checker.check_enum_declaration(enum_idx);
730
731        // Continue with enum duplicate members checking
732        CheckerState::check_enum_duplicate_members(self, enum_idx);
733    }
734
735    fn check_module_declaration(&mut self, module_idx: NodeIndex) {
736        if let Some(node) = self.ctx.arena.get(module_idx) {
737            // Delegate to DeclarationChecker first
738            let mut checker = crate::declarations::DeclarationChecker::new(&mut self.ctx);
739            checker.check_module_declaration(module_idx);
740
741            // Check module body and modifiers
742            if let Some(module) = self.ctx.arena.get_module(node) {
743                // TS1212: Check module/namespace name for strict mode reserved words
744                self.check_strict_mode_reserved_name_at(module.name, module_idx);
745
746                // TS1042: async modifier cannot be used on module/namespace declarations
747                self.check_async_modifier_on_declaration(&module.modifiers);
748
749                let is_ambient = self.has_declare_modifier(&module.modifiers);
750                if module.body.is_some() {
751                    self.check_module_body(module.body);
752                }
753
754                // TS1038: Check for 'declare' modifiers inside ambient module/namespace
755                // TS1039: Check for initializers in ambient contexts
756                // Even if we don't fully check the body, we still need to emit these errors
757                if is_ambient && module.body.is_some() {
758                    self.check_declare_modifiers_in_ambient_body(module.body);
759                    self.check_initializers_in_ambient_body(module.body);
760
761                    // TS2300/TS2309: Check for duplicate export assignments even in ambient modules
762                    // TS2300: Check for duplicate import aliases even in ambient modules
763                    // TS2303: Check for circular import aliases in ambient modules
764                    // Need to extract statements from module body
765                    if let Some(body_node) = self.ctx.arena.get(module.body)
766                        && body_node.kind == tsz_parser::parser::syntax_kind_ext::MODULE_BLOCK
767                        && let Some(block) = self.ctx.arena.get_module_block(body_node)
768                        && let Some(ref statements) = block.statements
769                    {
770                        self.check_export_assignment(&statements.nodes);
771                        self.check_import_alias_duplicates(&statements.nodes);
772                        // Check import equals declarations for circular imports (TS2303)
773                        for &stmt_idx in &statements.nodes {
774                            if let Some(stmt_node) = self.ctx.arena.get(stmt_idx)
775                                && stmt_node.kind == tsz_parser::parser::syntax_kind_ext::IMPORT_EQUALS_DECLARATION {
776                                    self.check_import_equals_declaration(stmt_idx);
777                                }
778                        }
779                    }
780                }
781
782                // TS2300: Check for duplicate import aliases in non-ambient modules too
783                // This handles namespace { import X = ...; import X = ...; }
784                if !is_ambient
785                    && module.body.is_some()
786                    && let Some(body_node) = self.ctx.arena.get(module.body)
787                    && body_node.kind == tsz_parser::parser::syntax_kind_ext::MODULE_BLOCK
788                    && let Some(block) = self.ctx.arena.get_module_block(body_node)
789                    && let Some(ref statements) = block.statements
790                {
791                    self.check_import_alias_duplicates(&statements.nodes);
792                }
793            }
794        }
795    }
796
797    fn check_await_expression(&mut self, expr_idx: NodeIndex) {
798        CheckerState::check_await_expression(self, expr_idx);
799    }
800
801    fn check_for_await_statement(&mut self, stmt_idx: NodeIndex) {
802        CheckerState::check_for_await_statement(self, stmt_idx);
803    }
804
805    fn check_truthy_or_falsy(&mut self, node_idx: NodeIndex) {
806        CheckerState::check_truthy_or_falsy(self, node_idx);
807    }
808
809    fn check_callable_truthiness(&mut self, cond_expr: NodeIndex, body: Option<NodeIndex>) {
810        CheckerState::check_callable_truthiness(self, cond_expr, body);
811    }
812
813    fn is_true_condition(&self, condition_idx: NodeIndex) -> bool {
814        CheckerState::is_true_condition(self, condition_idx)
815    }
816
817    fn is_false_condition(&self, condition_idx: NodeIndex) -> bool {
818        CheckerState::is_false_condition(self, condition_idx)
819    }
820
821    fn report_unreachable_statement(&mut self, stmt_idx: NodeIndex) {
822        if !self.ctx.is_unreachable {
823            return;
824        }
825
826        // Delegate to a helper that checks should_skip
827        let should_skip = if let Some(node) = self.ctx.arena.get(stmt_idx) {
828            node.kind == syntax_kind_ext::EMPTY_STATEMENT
829                || node.kind == syntax_kind_ext::FUNCTION_DECLARATION
830                || node.kind == syntax_kind_ext::INTERFACE_DECLARATION
831                || node.kind == syntax_kind_ext::TYPE_ALIAS_DECLARATION
832                || node.kind == syntax_kind_ext::MODULE_DECLARATION
833                || node.kind == syntax_kind_ext::BLOCK
834                || CheckerState::is_var_without_initializer(self, stmt_idx, node)
835        } else {
836            false
837        };
838
839        if !should_skip && !self.ctx.has_reported_unreachable {
840            if self.ctx.compiler_options.allow_unreachable_code != Some(false) {
841                return;
842            }
843            self.error_at_node(
844                stmt_idx,
845                crate::diagnostics::diagnostic_messages::UNREACHABLE_CODE_DETECTED,
846                crate::diagnostics::diagnostic_codes::UNREACHABLE_CODE_DETECTED,
847            );
848            self.ctx.has_reported_unreachable = true;
849        }
850    }
851
852    fn check_for_in_expression_type(&mut self, expr_type: TypeId, expression: NodeIndex) {
853        CheckerState::check_for_in_expression_type(self, expr_type, expression);
854    }
855
856    fn assign_for_in_of_initializer_types(
857        &mut self,
858        decl_list_idx: NodeIndex,
859        loop_var_type: TypeId,
860        is_for_in: bool,
861    ) {
862        CheckerState::assign_for_in_of_initializer_types(
863            self,
864            decl_list_idx,
865            loop_var_type,
866            is_for_in,
867        );
868    }
869
870    fn for_of_element_type(&mut self, expr_type: TypeId) -> TypeId {
871        CheckerState::for_of_element_type(self, expr_type)
872    }
873
874    fn check_for_of_iterability(
875        &mut self,
876        expr_type: TypeId,
877        expr_idx: NodeIndex,
878        await_modifier: bool,
879    ) {
880        CheckerState::check_for_of_iterability(self, expr_type, expr_idx, await_modifier);
881    }
882
883    fn check_for_in_of_expression_initializer(
884        &mut self,
885        initializer: NodeIndex,
886        element_type: TypeId,
887        is_for_of: bool,
888        has_await_modifier: bool,
889    ) {
890        CheckerState::check_for_in_of_expression_initializer(
891            self,
892            initializer,
893            element_type,
894            is_for_of,
895            has_await_modifier,
896        );
897    }
898
899    fn check_for_in_destructuring_pattern(&mut self, initializer: NodeIndex) {
900        CheckerState::check_for_in_destructuring_pattern(self, initializer);
901    }
902
903    fn check_for_in_expression_destructuring(&mut self, initializer: NodeIndex) {
904        CheckerState::check_for_in_expression_destructuring(self, initializer);
905    }
906
907    fn check_statement(&mut self, stmt_idx: NodeIndex) {
908        // This calls back to the main check_statement which will delegate to StatementChecker
909        CheckerState::check_statement(self, stmt_idx);
910    }
911
912    fn check_switch_exhaustiveness(
913        &mut self,
914        _stmt_idx: NodeIndex,
915        expression: NodeIndex,
916        _case_block: NodeIndex,
917        has_default: bool,
918    ) {
919        // If there's a default clause, the switch is syntactically exhaustive
920        if has_default {
921            return;
922        }
923
924        // Evaluate discriminant type (populates type caches needed by flow analysis)
925        let _ = self.get_type_of_node(expression);
926
927        // Note: exhaustiveness narrowing for switch is handled at the function level
928        // in control flow analysis (TS2366), not at the switch statement level.
929        //
930        // This is because:
931        // 1. Code after the switch might handle missing cases
932        // 2. The return type might accept undefined (e.g., number | undefined)
933        // 3. Exhaustiveness must be checked in the context of the entire function
934        //
935        // The FlowAnalyzer uses no_match_type to correctly narrow types within
936        // subsequent code blocks, but the error emission happens elsewhere.
937    }
938
939    fn check_switch_case_comparable(
940        &mut self,
941        switch_type: TypeId,
942        case_type: TypeId,
943        switch_expr: NodeIndex,
944        case_expr: NodeIndex,
945    ) {
946        // Skip if either type is error/any/unknown to avoid cascade errors
947        if switch_type == TypeId::ERROR
948            || case_type == TypeId::ERROR
949            || switch_type == TypeId::ANY
950            || case_type == TypeId::ANY
951            || switch_type == TypeId::UNKNOWN
952            || case_type == TypeId::UNKNOWN
953        {
954            return;
955        }
956
957        // Use literal type for the switch expression if available, since
958        // get_type_of_node widens literals (e.g., 12 -> number).
959        // tsc's checkExpression preserves literal types for comparability checks.
960        let effective_switch_type = self
961            .literal_type_from_initializer(switch_expr)
962            .unwrap_or(switch_type);
963
964        // Use literal type for the case expression if available, since
965        // get_type_of_node widens literals (e.g., "c" -> string).
966        let effective_case_type = self
967            .literal_type_from_initializer(case_expr)
968            .unwrap_or(case_type);
969
970        // Check if the types are comparable (assignable in either direction).
971        // Types are comparable if they overlap — i.e., at least one direction works.
972        // For example, "a" is comparable to "a" | "b" | "c" because "a" <: union.
973        // TypeScript unconditionally allows 'null' and 'undefined' as the case type.
974        let is_comparable = effective_case_type == tsz_solver::TypeId::NULL
975            || effective_case_type == tsz_solver::TypeId::UNDEFINED
976            || self.is_type_comparable_to(effective_case_type, effective_switch_type);
977
978        if !is_comparable {
979            // TS2678: Type 'X' is not comparable to type 'Y'
980            if let Some(loc) = self.get_source_location(case_expr) {
981                let case_str = self.format_type(effective_case_type);
982                let switch_str = self.format_type(effective_switch_type);
983                use crate::diagnostics::{
984                    Diagnostic, diagnostic_codes, diagnostic_messages, format_message,
985                };
986                let message = format_message(
987                    diagnostic_messages::TYPE_IS_NOT_COMPARABLE_TO_TYPE,
988                    &[&case_str, &switch_str],
989                );
990                self.ctx.diagnostics.push(Diagnostic::error(
991                    self.ctx.file_name.clone(),
992                    loc.start,
993                    loc.length(),
994                    message,
995                    diagnostic_codes::TYPE_IS_NOT_COMPARABLE_TO_TYPE,
996                ));
997            }
998        }
999    }
1000
1001    fn check_with_statement(&mut self, stmt_idx: NodeIndex) {
1002        CheckerState::check_with_statement(self, stmt_idx);
1003    }
1004
1005    fn check_break_statement(&mut self, stmt_idx: NodeIndex) {
1006        CheckerState::check_break_statement(self, stmt_idx);
1007    }
1008
1009    fn check_continue_statement(&mut self, stmt_idx: NodeIndex) {
1010        CheckerState::check_continue_statement(self, stmt_idx);
1011    }
1012
1013    fn is_unreachable(&self) -> bool {
1014        self.ctx.is_unreachable
1015    }
1016
1017    fn set_unreachable(&mut self, value: bool) {
1018        self.ctx.is_unreachable = value;
1019    }
1020
1021    fn has_reported_unreachable(&self) -> bool {
1022        self.ctx.has_reported_unreachable
1023    }
1024
1025    fn set_reported_unreachable(&mut self, value: bool) {
1026        self.ctx.has_reported_unreachable = value;
1027    }
1028
1029    fn statement_falls_through(&mut self, stmt_idx: NodeIndex) -> bool {
1030        CheckerState::statement_falls_through(self, stmt_idx)
1031    }
1032
1033    fn enter_iteration_statement(&mut self) {
1034        self.ctx.iteration_depth += 1;
1035    }
1036
1037    fn leave_iteration_statement(&mut self) {
1038        self.ctx.iteration_depth = self.ctx.iteration_depth.saturating_sub(1);
1039    }
1040
1041    fn enter_switch_statement(&mut self) {
1042        self.ctx.switch_depth += 1;
1043    }
1044
1045    fn leave_switch_statement(&mut self) {
1046        self.ctx.switch_depth = self.ctx.switch_depth.saturating_sub(1);
1047    }
1048
1049    fn save_and_reset_control_flow_context(&mut self) -> (u32, u32, bool) {
1050        let saved = (
1051            self.ctx.iteration_depth,
1052            self.ctx.switch_depth,
1053            self.ctx.had_outer_loop,
1054        );
1055        // If we were in a loop/switch, or already had an outer loop, mark it
1056        if self.ctx.iteration_depth > 0 || self.ctx.switch_depth > 0 || self.ctx.had_outer_loop {
1057            self.ctx.had_outer_loop = true;
1058        }
1059        self.ctx.iteration_depth = 0;
1060        self.ctx.switch_depth = 0;
1061        saved
1062    }
1063
1064    fn restore_control_flow_context(&mut self, saved: (u32, u32, bool)) {
1065        self.ctx.iteration_depth = saved.0;
1066        self.ctx.switch_depth = saved.1;
1067        self.ctx.had_outer_loop = saved.2;
1068    }
1069
1070    fn enter_labeled_statement(&mut self, label: String, is_iteration: bool) {
1071        self.ctx.label_stack.push(crate::context::LabelInfo {
1072            name: label,
1073            is_iteration,
1074            function_depth: self.ctx.function_depth,
1075        });
1076    }
1077
1078    fn leave_labeled_statement(&mut self) {
1079        self.ctx.label_stack.pop();
1080    }
1081
1082    fn get_node_text(&self, idx: NodeIndex) -> Option<String> {
1083        // For identifiers (like label names), get the identifier data and resolve the text
1084        let ident = self.ctx.arena.get_identifier_at(idx)?;
1085        // Use the resolved text from the identifier data
1086        Some(self.ctx.arena.resolve_identifier_text(ident).to_string())
1087    }
1088
1089    fn check_declaration_in_statement_position(&mut self, stmt_idx: NodeIndex) {
1090        use tsz_parser::parser::node_flags;
1091
1092        let Some(node) = self.ctx.arena.get(stmt_idx) else {
1093            return;
1094        };
1095
1096        // TS1156: '{0}' declarations can only be declared inside a block.
1097        // This fires when a const/let/interface/type declaration appears as
1098        // the body of a control flow statement (if/while/for) without braces.
1099        let decl_kind = match node.kind {
1100            syntax_kind_ext::INTERFACE_DECLARATION => Some("interface"),
1101            syntax_kind_ext::VARIABLE_STATEMENT => {
1102                // Check the VariableDeclarationList for const/let flags
1103                if let Some(var_data) = self.ctx.arena.get_variable(node) {
1104                    let list_idx = var_data
1105                        .declarations
1106                        .nodes
1107                        .first()
1108                        .copied()
1109                        .unwrap_or(NodeIndex::NONE);
1110                    if let Some(list_node) = self.ctx.arena.get(list_idx) {
1111                        let flags = list_node.flags as u32;
1112                        // Check USING first — AWAIT_USING (6) includes CONST bit
1113                        if (flags & node_flags::AWAIT_USING) == node_flags::AWAIT_USING {
1114                            Some("await using")
1115                        } else if flags & node_flags::USING != 0 {
1116                            Some("using")
1117                        } else if flags & node_flags::CONST != 0 {
1118                            Some("const")
1119                        } else if flags & node_flags::LET != 0 {
1120                            Some("let")
1121                        } else {
1122                            None
1123                        }
1124                    } else {
1125                        None
1126                    }
1127                } else {
1128                    None
1129                }
1130            }
1131            _ => None,
1132        };
1133
1134        if let Some(kind_name) = decl_kind {
1135            let msg = format!("'{kind_name}' declarations can only be declared inside a block.");
1136            self.error_at_node(
1137                stmt_idx,
1138                &msg,
1139                crate::diagnostics::diagnostic_codes::DECLARATIONS_CAN_ONLY_BE_DECLARED_INSIDE_A_BLOCK,
1140            );
1141        }
1142    }
1143
1144    fn check_label_on_declaration(&mut self, label_idx: NodeIndex, statement_idx: NodeIndex) {
1145        // TS1344: In strict mode with target >= ES2015, a label is not allowed
1146        // before declaration statements or variable statements.
1147        // This matches TSC's checkStrictModeLabeledStatement in binder.ts.
1148        if !self.ctx.compiler_options.target.supports_es2015() {
1149            return;
1150        }
1151        if !self.is_strict_mode_for_node(label_idx) {
1152            return;
1153        }
1154
1155        let Some(stmt_node) = self.ctx.arena.get(statement_idx) else {
1156            return;
1157        };
1158
1159        // isDeclarationStatement || isVariableStatement
1160        let is_declaration_or_variable = matches!(
1161            stmt_node.kind,
1162            syntax_kind_ext::FUNCTION_DECLARATION
1163                | syntax_kind_ext::CLASS_DECLARATION
1164                | syntax_kind_ext::INTERFACE_DECLARATION
1165                | syntax_kind_ext::TYPE_ALIAS_DECLARATION
1166                | syntax_kind_ext::ENUM_DECLARATION
1167                | syntax_kind_ext::MODULE_DECLARATION
1168                | syntax_kind_ext::IMPORT_DECLARATION
1169                | syntax_kind_ext::EXPORT_DECLARATION
1170                | syntax_kind_ext::VARIABLE_STATEMENT
1171        );
1172
1173        if is_declaration_or_variable {
1174            self.error_at_node(
1175                label_idx,
1176                "'A label is not allowed here.",
1177                crate::diagnostics::diagnostic_codes::A_LABEL_IS_NOT_ALLOWED_HERE,
1178            );
1179        }
1180    }
1181}