Skip to main content

tsz_checker/declarations/
declarations_module.rs

1//! Module and namespace declaration validation (TS2580, TS2668, TS2669, TS2433,
2//! TS2434, TS2435, TS1035, TS1235, TS5061, TS2664, TS2666/TS2667).
3
4use crate::declarations::DeclarationChecker;
5use tsz_parser::parser::{NodeIndex, syntax_kind_ext};
6
7impl<'a, 'ctx> DeclarationChecker<'a, 'ctx> {
8    /// Check a module/namespace declaration.
9    pub fn check_module_declaration(&mut self, module_idx: NodeIndex) {
10        use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
11        use tsz_binder::symbol_flags;
12        use tsz_parser::parser::node_flags;
13        use tsz_parser::parser::syntax_kind_ext;
14        use tsz_scanner::SyntaxKind;
15
16        let Some(node) = self.ctx.arena.get(module_idx) else {
17            return;
18        };
19
20        if let Some(module) = self.ctx.arena.get_module(node) {
21            // TS2580: Anonymous module declaration with `module` keyword (not `namespace`)
22            // When `module {` is parsed as a module declaration with a missing name,
23            // TSC also emits TS2580 because `module` could be a Node.js identifier reference.
24            let is_namespace = (node.flags as u32) & node_flags::NAMESPACE != 0;
25
26            if !is_namespace
27                && let Some(name_node) = self.ctx.arena.get(module.name)
28                && let Some(ident) = self.ctx.arena.get_identifier(name_node)
29                && ident.escaped_text.is_empty()
30            {
31                // Detailed node types error (TS2591) is preferred in recent TS versions.
32                let code =
33                    diagnostic_codes::CANNOT_FIND_NAME_DO_YOU_NEED_TO_INSTALL_TYPE_DEFINITIONS_FOR_NODE_TRY_NPM_I_SAVE_2;
34                let message = format_message(
35                    diagnostic_messages::CANNOT_FIND_NAME_DO_YOU_NEED_TO_INSTALL_TYPE_DEFINITIONS_FOR_NODE_TRY_NPM_I_SAVE_2,
36                    &["module"],
37                );
38
39                self.ctx.error(node.pos, 6, message, code);
40            }
41
42            // TS2668: 'export' modifier cannot be applied to ambient modules
43            // This only applies to string-literal-named ambient modules (declare module "foo"),
44            // not to namespace-form modules (declare namespace Foo)
45            // Check this FIRST before early returns so we can emit multiple errors
46            let has_declare = self
47                .ctx
48                .arena
49                .has_modifier(&module.modifiers, SyntaxKind::DeclareKeyword);
50            let has_export = self
51                .ctx
52                .arena
53                .has_modifier(&module.modifiers, SyntaxKind::ExportKeyword);
54
55            // Only check for TS2668 if this is a string-literal-named module
56            let is_string_named = if let Some(name_node) = self.ctx.arena.get(module.name) {
57                name_node.kind == SyntaxKind::StringLiteral as u16
58                    || name_node.kind == SyntaxKind::NoSubstitutionTemplateLiteral as u16
59            } else {
60                false
61            };
62
63            if has_declare && has_export && is_string_named {
64                // Find the export modifier position to report error there
65                if let Some(ref mods) = module.modifiers {
66                    for &mod_idx in &mods.nodes {
67                        if let Some(mod_node) = self.ctx.arena.get(mod_idx)
68                            && mod_node.kind == SyntaxKind::ExportKeyword as u16
69                        {
70                            self.ctx.error(
71                                    mod_node.pos,
72                                    mod_node.end - mod_node.pos,
73                                    "'export' modifier cannot be applied to ambient modules and module augmentations since they are always visible.".to_string(),
74                                    2668, // TS2668
75                                );
76                            break;
77                        }
78                    }
79                }
80            }
81
82            // TS2669/TS2670: Global scope augmentations must be directly nested in
83            // external modules or ambient module declarations, and should have `declare`
84            let is_global_augmentation = (node.flags as u32) & node_flags::GLOBAL_AUGMENTATION != 0
85                || self
86                    .ctx
87                    .arena
88                    .get(module.name)
89                    .and_then(|name_node| self.ctx.arena.get_identifier(name_node))
90                    .is_some_and(|ident| ident.escaped_text == "global");
91            if is_global_augmentation {
92                let mut allowed_context = false;
93                if let Some(ext) = self.ctx.arena.get_extended(module_idx) {
94                    let parent = ext.parent;
95                    if parent.is_some()
96                        && let Some(parent_node) = self.ctx.arena.get(parent)
97                    {
98                        if parent_node.kind == syntax_kind_ext::SOURCE_FILE {
99                            allowed_context = self.is_external_module();
100                        } else if parent_node.kind == syntax_kind_ext::MODULE_BLOCK
101                            && let Some(parent_ext) = self.ctx.arena.get_extended(parent)
102                        {
103                            let gp = parent_ext.parent;
104                            if let Some(gp_node) = self.ctx.arena.get(gp)
105                                && gp_node.kind == syntax_kind_ext::MODULE_DECLARATION
106                                && let Some(gp_module) = self.ctx.arena.get_module(gp_node)
107                                && self
108                                    .ctx
109                                    .arena
110                                    .has_modifier(&gp_module.modifiers, SyntaxKind::DeclareKeyword)
111                            {
112                                let gp_name_node = self.ctx.arena.get(gp_module.name);
113                                let gp_is_string_named = gp_name_node.is_some_and(|name_node| {
114                                    name_node.kind == SyntaxKind::StringLiteral as u16
115                                        || name_node.kind
116                                            == SyntaxKind::NoSubstitutionTemplateLiteral as u16
117                                });
118                                if gp_is_string_named {
119                                    allowed_context = true;
120                                }
121                            }
122                        }
123                    }
124                }
125
126                let error_node = self.ctx.arena.get(module.name).unwrap_or(node);
127                if !allowed_context {
128                    self.ctx.error(
129                        error_node.pos,
130                        error_node.end - error_node.pos,
131                        diagnostic_messages::AUGMENTATIONS_FOR_THE_GLOBAL_SCOPE_CAN_ONLY_BE_DIRECTLY_NESTED_IN_EXTERNAL_MODUL.to_string(),
132                        diagnostic_codes::AUGMENTATIONS_FOR_THE_GLOBAL_SCOPE_CAN_ONLY_BE_DIRECTLY_NESTED_IN_EXTERNAL_MODUL,
133                    );
134                }
135                if !has_declare && !self.is_in_ambient_context(module_idx) {
136                    self.ctx.error(
137                        error_node.pos,
138                        error_node.end - error_node.pos,
139                        diagnostic_messages::AUGMENTATIONS_FOR_THE_GLOBAL_SCOPE_SHOULD_HAVE_DECLARE_MODIFIER_UNLESS_THEY_APPE.to_string(),
140                        diagnostic_codes::AUGMENTATIONS_FOR_THE_GLOBAL_SCOPE_SHOULD_HAVE_DECLARE_MODIFIER_UNLESS_THEY_APPE,
141                    );
142                }
143            }
144
145            // TS2433/TS2434: Check namespace merging with class/function
146            // A namespace declaration cannot be in a different file from a class/function
147            // with which it is merged (TS2433), or located prior to the class/function (TS2434).
148            // Only check for non-ambient, non-string-named, INSTANTIATED modules.
149            // Uninstantiated namespaces (containing only interfaces/type aliases) are allowed
150            // to precede a class/function they merge with.
151            if !has_declare
152                && !is_string_named
153                && module.body.is_some()
154                && !self.is_in_ambient_context(module_idx)
155                && self.is_namespace_declaration_instantiated(module_idx)
156            {
157                self.check_namespace_merges_with_class_or_function(module_idx, module);
158            }
159
160            // TS1035: Only ambient modules can use quoted names.
161            // `module "Foo" {}` without `declare` is invalid.
162            if !has_declare
163                && is_string_named
164                && let Some(name_node) = self.ctx.arena.get(module.name)
165            {
166                self.ctx.error(
167                    name_node.pos,
168                    name_node.end - name_node.pos,
169                    diagnostic_messages::ONLY_AMBIENT_MODULES_CAN_USE_QUOTED_NAMES.to_string(),
170                    diagnostic_codes::ONLY_AMBIENT_MODULES_CAN_USE_QUOTED_NAMES,
171                );
172            }
173
174            // TS2435: Ambient modules cannot be nested in other modules or namespaces
175            // Check if this is an ambient external module (declare module "string")
176            // inside another namespace/module
177            if let Some(name_node) = self.ctx.arena.get(module.name)
178                && name_node.kind == SyntaxKind::StringLiteral as u16
179            {
180                // This is an ambient external module with a string name
181                // Check if it's nested inside a namespace
182                if self.is_inside_namespace(module_idx) {
183                    self.ctx.error(
184                        name_node.pos,
185                        name_node.end - name_node.pos,
186                        "Ambient modules cannot be nested in other modules or namespaces."
187                            .to_string(),
188                        diagnostic_codes::AMBIENT_MODULES_CANNOT_BE_NESTED_IN_OTHER_MODULES_OR_NAMESPACES,
189                    );
190                    return; // Don't emit other errors for nested ambient modules
191                }
192            }
193
194            // TS1235: A namespace declaration is only allowed at the top level of a namespace or module.
195            // This applies to non-string-named module/namespace declarations that are inside labeled statements
196            // or other non-module constructs.
197            if !is_string_named {
198                // Check if the parent is a valid context
199                // Valid parents:
200                // - SourceFile (top-level namespace)
201                // - ModuleBlock (namespace inside namespace body)
202                // - ModuleDeclaration (dotted namespace like namespace A.B { })
203                // - ExportDeclaration (export namespace X { })
204                let is_valid_context = if let Some(ext) = self.ctx.arena.get_extended(module_idx) {
205                    let parent = ext.parent;
206                    if parent.is_none() {
207                        true // Top level is valid
208                    } else if let Some(parent_node) = self.ctx.arena.get(parent) {
209                        // Valid parents: SourceFile, ModuleBlock, ModuleDeclaration
210                        if parent_node.kind == syntax_kind_ext::SOURCE_FILE
211                            || parent_node.kind == syntax_kind_ext::MODULE_BLOCK
212                            || parent_node.kind == syntax_kind_ext::MODULE_DECLARATION
213                        {
214                            true
215                        } else if parent_node.kind == syntax_kind_ext::EXPORT_DECLARATION {
216                            // Check if the export declaration is inside a valid context
217                            if let Some(parent_ext) = self.ctx.arena.get_extended(parent) {
218                                let grandparent = parent_ext.parent;
219                                if let Some(gp_node) = self.ctx.arena.get(grandparent) {
220                                    gp_node.kind == syntax_kind_ext::SOURCE_FILE
221                                        || gp_node.kind == syntax_kind_ext::MODULE_BLOCK
222                                        || gp_node.kind == syntax_kind_ext::MODULE_DECLARATION
223                                } else {
224                                    true
225                                }
226                            } else {
227                                true
228                            }
229                        } else {
230                            false
231                        }
232                    } else {
233                        true
234                    }
235                } else {
236                    true
237                };
238
239                if !is_valid_context && let Some(name_node) = self.ctx.arena.get(module.name) {
240                    self.ctx.error(
241                        name_node.pos,
242                        name_node.end - name_node.pos,
243                        diagnostic_messages::A_NAMESPACE_DECLARATION_IS_ONLY_ALLOWED_AT_THE_TOP_LEVEL_OF_A_NAMESPACE_OR_MODUL.to_string(),
244                        diagnostic_codes::A_NAMESPACE_DECLARATION_IS_ONLY_ALLOWED_AT_THE_TOP_LEVEL_OF_A_NAMESPACE_OR_MODUL,
245                    );
246                }
247            }
248
249            // TS5061: Check for relative module names in ambient declarations
250            // declare module "./foo" { } -> Error (only in script/non-module files)
251            // In module files, `declare module "./foo"` is a module augmentation, not
252            // an ambient module declaration, and relative paths are valid.
253            if self
254                .ctx
255                .arena
256                .has_modifier(&module.modifiers, SyntaxKind::DeclareKeyword)
257                && let Some(name_node) = self.ctx.arena.get(module.name)
258                && name_node.kind == SyntaxKind::StringLiteral as u16
259                && let Some(lit) = self.ctx.arena.get_literal(name_node)
260            {
261                // Check TS5061 first - only for true ambient declarations (non-module files)
262                if self.is_relative_module_name(&lit.text) && !self.is_external_module() {
263                    self.ctx.error(
264                                    name_node.pos,
265                                    name_node.end - name_node.pos,
266                                    diagnostic_messages::AMBIENT_MODULE_DECLARATION_CANNOT_SPECIFY_RELATIVE_MODULE_NAME.to_string(),
267                                    diagnostic_codes::AMBIENT_MODULE_DECLARATION_CANNOT_SPECIFY_RELATIVE_MODULE_NAME,
268                                );
269                }
270                // TS2664: Check if the module being augmented exists
271                // declare module "nonexistent" { } -> Error if module doesn't exist
272                // Only emit TS2664 if:
273                // 1. The file is a module file (has import/export statements)
274                // 2. The file is not a .d.ts file
275                // 3. The module name is not a relative path (relative augmentations
276                //    refer to local files which may not be resolved in all contexts)
277                // In script files (no imports/exports), declare module "xxx" declares
278                // an ambient external module, which is always valid.
279                else if !self.module_exists(&lit.text)
280                    && !self.is_declaration_file()
281                    && self.is_external_module()
282                    && !self.is_relative_module_name(&lit.text)
283                {
284                    let message = format_message(
285                        diagnostic_messages::INVALID_MODULE_NAME_IN_AUGMENTATION_MODULE_CANNOT_BE_FOUND,
286                        &[&lit.text],
287                    );
288                    self.ctx.error(
289                        name_node.pos,
290                        name_node.end - name_node.pos,
291                        message,
292                        diagnostic_codes::INVALID_MODULE_NAME_IN_AUGMENTATION_MODULE_CANNOT_BE_FOUND,
293                    );
294                } else if self.is_external_module()
295                    && self.module_exists(&lit.text)
296                    && self.ctx.module_resolves_to_non_module_entity(&lit.text)
297                {
298                    let has_value_exports = self.module_augmentation_has_value_exports(module.body);
299                    let (code, message) = if has_value_exports {
300                        (
301                            diagnostic_codes::CANNOT_AUGMENT_MODULE_WITH_VALUE_EXPORTS_BECAUSE_IT_RESOLVES_TO_A_NON_MODULE_ENT,
302                            format_message(
303                                diagnostic_messages::CANNOT_AUGMENT_MODULE_WITH_VALUE_EXPORTS_BECAUSE_IT_RESOLVES_TO_A_NON_MODULE_ENT,
304                                &[&lit.text],
305                            ),
306                        )
307                    } else {
308                        (
309                            diagnostic_codes::CANNOT_AUGMENT_MODULE_BECAUSE_IT_RESOLVES_TO_A_NON_MODULE_ENTITY,
310                            format_message(
311                                diagnostic_messages::CANNOT_AUGMENT_MODULE_BECAUSE_IT_RESOLVES_TO_A_NON_MODULE_ENTITY,
312                                &[&lit.text],
313                            ),
314                        )
315                    };
316                    self.ctx
317                        .error(name_node.pos, name_node.end - name_node.pos, message, code);
318                }
319            }
320
321            // TS2666/TS2667: Imports/exports are not permitted in module augmentations
322            if has_declare && is_string_named && self.is_external_module() {
323                let module_specifier = self
324                    .ctx
325                    .arena
326                    .get(module.name)
327                    .and_then(|name_node| self.ctx.arena.get_literal(name_node))
328                    .map(|lit| lit.text.clone());
329                let module_key = module_specifier.as_deref().map_or_else(
330                    || "<unknown>".to_string(),
331                    |spec| self.normalize_module_augmentation_key(spec),
332                );
333
334                let mut value_decl_map = self
335                    .ctx
336                    .module_augmentation_value_decls
337                    .remove(&module_key)
338                    .unwrap_or_default();
339                let mut reported_import = false;
340                let mut reported_export = false;
341                if module.body.is_some()
342                    && let Some(body_node) = self.ctx.arena.get(module.body)
343                    && body_node.kind == syntax_kind_ext::MODULE_BLOCK
344                    && let Some(block) = self.ctx.arena.get_module_block(body_node)
345                    && let Some(ref stmts) = block.statements
346                {
347                    let mut register_value_name = |name: &str, name_node: NodeIndex| -> bool {
348                        if value_decl_map.contains_key(name) {
349                            true
350                        } else {
351                            value_decl_map.insert(name.to_string(), name_node);
352                            false
353                        }
354                    };
355                    for &stmt_idx in &stmts.nodes {
356                        if reported_import && reported_export {
357                            break;
358                        }
359                        let Some(stmt_node) = self.ctx.arena.get(stmt_idx) else {
360                            continue;
361                        };
362                        let kind = stmt_node.kind;
363                        if !reported_import
364                            && (kind == syntax_kind_ext::IMPORT_DECLARATION
365                                || kind == syntax_kind_ext::IMPORT_EQUALS_DECLARATION)
366                        {
367                            self.ctx.error(
368                                            stmt_node.pos,
369                                            stmt_node.end - stmt_node.pos,
370                                            diagnostic_messages::IMPORTS_ARE_NOT_PERMITTED_IN_MODULE_AUGMENTATIONS_CONSIDER_MOVING_THEM_TO_THE_EN.to_string(),
371                                            diagnostic_codes::IMPORTS_ARE_NOT_PERMITTED_IN_MODULE_AUGMENTATIONS_CONSIDER_MOVING_THEM_TO_THE_EN,
372                                        );
373                            reported_import = true;
374                        } else if !reported_export {
375                            let is_forbidden_export = if kind == syntax_kind_ext::EXPORT_ASSIGNMENT
376                                || kind == syntax_kind_ext::NAMESPACE_EXPORT_DECLARATION
377                            {
378                                true
379                            } else if kind == syntax_kind_ext::EXPORT_DECLARATION {
380                                match self.ctx.arena.get_export_decl(stmt_node) {
381                                    Some(export_decl) => {
382                                        if export_decl.is_default_export {
383                                            true
384                                        } else if export_decl.module_specifier.is_some() {
385                                            // Re-exports are not permitted in augmentations
386                                            true
387                                        } else if export_decl.export_clause.is_none() {
388                                            true
389                                        } else if let Some(clause_node) =
390                                            self.ctx.arena.get(export_decl.export_clause)
391                                        {
392                                            !matches!(
393                                                clause_node.kind,
394                                                syntax_kind_ext::FUNCTION_DECLARATION
395                                                    | syntax_kind_ext::CLASS_DECLARATION
396                                                    | syntax_kind_ext::INTERFACE_DECLARATION
397                                                    | syntax_kind_ext::TYPE_ALIAS_DECLARATION
398                                                    | syntax_kind_ext::ENUM_DECLARATION
399                                                    | syntax_kind_ext::MODULE_DECLARATION
400                                                    | syntax_kind_ext::VARIABLE_STATEMENT
401                                            )
402                                        } else {
403                                            true
404                                        }
405                                    }
406                                    None => true,
407                                }
408                            } else {
409                                false
410                            };
411                            if is_forbidden_export {
412                                self.ctx.error(
413                                                stmt_node.pos,
414                                                stmt_node.end - stmt_node.pos,
415                                                diagnostic_messages::EXPORTS_AND_EXPORT_ASSIGNMENTS_ARE_NOT_PERMITTED_IN_MODULE_AUGMENTATIONS.to_string(),
416                                                diagnostic_codes::EXPORTS_AND_EXPORT_ASSIGNMENTS_ARE_NOT_PERMITTED_IN_MODULE_AUGMENTATIONS,
417                                            );
418                                reported_export = true;
419                            }
420                        }
421
422                        if kind == syntax_kind_ext::EXPORT_DECLARATION {
423                            let Some(export_decl) = self.ctx.arena.get_export_decl(stmt_node)
424                            else {
425                                continue;
426                            };
427                            if export_decl.is_default_export
428                                || export_decl.module_specifier.is_some()
429                                || export_decl.export_clause.is_none()
430                            {
431                                continue;
432                            }
433                            let Some(clause_node) = self.ctx.arena.get(export_decl.export_clause)
434                            else {
435                                continue;
436                            };
437                            match clause_node.kind {
438                                syntax_kind_ext::VARIABLE_STATEMENT => {
439                                    if let Some(var_stmt) = self.ctx.arena.get_variable(clause_node)
440                                    {
441                                        for &decl_list_idx in &var_stmt.declarations.nodes {
442                                            let Some(decl_list_node) =
443                                                self.ctx.arena.get(decl_list_idx)
444                                            else {
445                                                continue;
446                                            };
447                                            if decl_list_node.kind
448                                                == syntax_kind_ext::VARIABLE_DECLARATION_LIST
449                                            {
450                                                if let Some(decl_list) =
451                                                    self.ctx.arena.get_variable(decl_list_node)
452                                                {
453                                                    for &decl_idx in &decl_list.declarations.nodes {
454                                                        if let Some(decl_node) =
455                                                            self.ctx.arena.get(decl_idx)
456                                                            && let Some(decl) = self
457                                                                .ctx
458                                                                .arena
459                                                                .get_variable_declaration(decl_node)
460                                                            && let Some(name_node) =
461                                                                self.ctx.arena.get(decl.name)
462                                                            && let Some(ident) = self
463                                                                .ctx
464                                                                .arena
465                                                                .get_identifier(name_node)
466                                                            && register_value_name(
467                                                                &ident.escaped_text,
468                                                                decl.name,
469                                                            )
470                                                            && let Some(node) =
471                                                                self.ctx.arena.get(decl.name)
472                                                        {
473                                                            self.ctx.error(
474                                                                                    node.pos,
475                                                                                    node.end - node.pos,
476                                                                                    diagnostic_messages::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE.to_string(),
477                                                                                    diagnostic_codes::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE,
478                                                                                );
479                                                        }
480                                                    }
481                                                }
482                                            } else if let Some(decl) = self
483                                                .ctx
484                                                .arena
485                                                .get_variable_declaration(decl_list_node)
486                                                && let Some(name_node) =
487                                                    self.ctx.arena.get(decl.name)
488                                                && let Some(ident) =
489                                                    self.ctx.arena.get_identifier(name_node)
490                                                && register_value_name(
491                                                    &ident.escaped_text,
492                                                    decl.name,
493                                                )
494                                                && let Some(node) = self.ctx.arena.get(decl.name)
495                                            {
496                                                self.ctx.error(
497                                                                        node.pos,
498                                                                        node.end - node.pos,
499                                                                        diagnostic_messages::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE.to_string(),
500                                                                        diagnostic_codes::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE,
501                                                                    );
502                                            }
503                                        }
504                                    }
505                                }
506                                syntax_kind_ext::FUNCTION_DECLARATION => {
507                                    if let Some(func) = self.ctx.arena.get_function(clause_node)
508                                        && let Some(name_node) = self.ctx.arena.get(func.name)
509                                        && let Some(ident) =
510                                            self.ctx.arena.get_identifier(name_node)
511                                        && register_value_name(&ident.escaped_text, func.name)
512                                        && let Some(node) = self.ctx.arena.get(func.name)
513                                    {
514                                        self.ctx.error(
515                                                                node.pos,
516                                                                node.end - node.pos,
517                                                                diagnostic_messages::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE.to_string(),
518                                                                diagnostic_codes::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE,
519                                                            );
520                                    }
521                                }
522                                syntax_kind_ext::CLASS_DECLARATION => {
523                                    if let Some(class) = self.ctx.arena.get_class(clause_node)
524                                        && let Some(name_node) = self.ctx.arena.get(class.name)
525                                        && let Some(ident) =
526                                            self.ctx.arena.get_identifier(name_node)
527                                        && register_value_name(&ident.escaped_text, class.name)
528                                        && let Some(node) = self.ctx.arena.get(class.name)
529                                    {
530                                        self.ctx.error(
531                                                                node.pos,
532                                                                node.end - node.pos,
533                                                                diagnostic_messages::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE.to_string(),
534                                                                diagnostic_codes::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE,
535                                                            );
536                                    }
537                                }
538                                syntax_kind_ext::ENUM_DECLARATION => {
539                                    if let Some(enm) = self.ctx.arena.get_enum(clause_node)
540                                        && let Some(name_node) = self.ctx.arena.get(enm.name)
541                                        && let Some(ident) =
542                                            self.ctx.arena.get_identifier(name_node)
543                                    {
544                                        if let Some(specifier) = module_specifier.as_deref()
545                                            && let Some(target_idx) =
546                                                self.ctx.resolve_import_target(specifier)
547                                            && let Some(target_binder) =
548                                                self.ctx.get_binder_for_file(target_idx)
549                                        {
550                                            let target_arena =
551                                                self.ctx.get_arena_for_file(target_idx as u32);
552                                            if let Some(source_file) =
553                                                target_arena.source_files.first()
554                                                && let Some(existing_sym_id) = target_binder
555                                                    .resolve_import_if_needed_public(
556                                                        &source_file.file_name,
557                                                        &ident.escaped_text,
558                                                    )
559                                                && let Some(symbol) =
560                                                    target_binder.get_symbol(existing_sym_id)
561                                            {
562                                                let allowed = (symbol.flags
563                                                    & (symbol_flags::REGULAR_ENUM
564                                                        | symbol_flags::CONST_ENUM
565                                                        | symbol_flags::MODULE))
566                                                    != 0;
567                                                if !allowed {
568                                                    self.ctx.error(
569                                                                        name_node.pos,
570                                                                        name_node.end
571                                                                            - name_node.pos,
572                                                                        diagnostic_messages::ENUM_DECLARATIONS_CAN_ONLY_MERGE_WITH_NAMESPACE_OR_OTHER_ENUM_DECLARATIONS.to_string(),
573                                                                        diagnostic_codes::ENUM_DECLARATIONS_CAN_ONLY_MERGE_WITH_NAMESPACE_OR_OTHER_ENUM_DECLARATIONS,
574                                                                    );
575                                                }
576                                            }
577                                        }
578                                        if register_value_name(&ident.escaped_text, enm.name)
579                                            && let Some(node) = self.ctx.arena.get(enm.name)
580                                        {
581                                            self.ctx.error(
582                                                                node.pos,
583                                                                node.end - node.pos,
584                                                                diagnostic_messages::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE.to_string(),
585                                                                diagnostic_codes::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE,
586                                                            );
587                                        }
588                                    }
589                                }
590                                _ => {}
591                            }
592                        } else if kind == syntax_kind_ext::VARIABLE_STATEMENT {
593                            if let Some(var_stmt) = self.ctx.arena.get_variable(stmt_node)
594                                && self
595                                    .ctx
596                                    .arena
597                                    .has_modifier(&var_stmt.modifiers, SyntaxKind::ExportKeyword)
598                            {
599                                for &decl_list_idx in &var_stmt.declarations.nodes {
600                                    let Some(decl_list_node) = self.ctx.arena.get(decl_list_idx)
601                                    else {
602                                        continue;
603                                    };
604                                    if decl_list_node.kind
605                                        == syntax_kind_ext::VARIABLE_DECLARATION_LIST
606                                    {
607                                        if let Some(decl_list) =
608                                            self.ctx.arena.get_variable(decl_list_node)
609                                        {
610                                            for &decl_idx in &decl_list.declarations.nodes {
611                                                if let Some(decl_node) =
612                                                    self.ctx.arena.get(decl_idx)
613                                                    && let Some(decl) = self
614                                                        .ctx
615                                                        .arena
616                                                        .get_variable_declaration(decl_node)
617                                                    && let Some(name_node) =
618                                                        self.ctx.arena.get(decl.name)
619                                                    && let Some(ident) =
620                                                        self.ctx.arena.get_identifier(name_node)
621                                                    && register_value_name(
622                                                        &ident.escaped_text,
623                                                        decl.name,
624                                                    )
625                                                    && let Some(node) =
626                                                        self.ctx.arena.get(decl.name)
627                                                {
628                                                    self.ctx.error(
629                                                                            node.pos,
630                                                                            node.end - node.pos,
631                                                                            diagnostic_messages::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE.to_string(),
632                                                                            diagnostic_codes::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE,
633                                                                        );
634                                                }
635                                            }
636                                        }
637                                    } else if let Some(decl) =
638                                        self.ctx.arena.get_variable_declaration(decl_list_node)
639                                        && let Some(name_node) = self.ctx.arena.get(decl.name)
640                                        && let Some(ident) =
641                                            self.ctx.arena.get_identifier(name_node)
642                                        && register_value_name(&ident.escaped_text, decl.name)
643                                        && let Some(node) = self.ctx.arena.get(decl.name)
644                                    {
645                                        self.ctx.error(
646                                                                node.pos,
647                                                                node.end - node.pos,
648                                                                diagnostic_messages::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE.to_string(),
649                                                                diagnostic_codes::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE,
650                                                            );
651                                    }
652                                }
653                            }
654                        } else if kind == syntax_kind_ext::FUNCTION_DECLARATION {
655                            if let Some(func) = self.ctx.arena.get_function(stmt_node)
656                                && self
657                                    .ctx
658                                    .arena
659                                    .has_modifier(&func.modifiers, SyntaxKind::ExportKeyword)
660                                && let Some(name_node) = self.ctx.arena.get(func.name)
661                                && let Some(ident) = self.ctx.arena.get_identifier(name_node)
662                                && register_value_name(&ident.escaped_text, func.name)
663                                && let Some(node) = self.ctx.arena.get(func.name)
664                            {
665                                self.ctx.error(
666                                    node.pos,
667                                    node.end - node.pos,
668                                    diagnostic_messages::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE
669                                        .to_string(),
670                                    diagnostic_codes::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE,
671                                );
672                            }
673                        } else if kind == syntax_kind_ext::CLASS_DECLARATION {
674                            if let Some(class) = self.ctx.arena.get_class(stmt_node)
675                                && self
676                                    .ctx
677                                    .arena
678                                    .has_modifier(&class.modifiers, SyntaxKind::ExportKeyword)
679                                && let Some(name_node) = self.ctx.arena.get(class.name)
680                                && let Some(ident) = self.ctx.arena.get_identifier(name_node)
681                                && register_value_name(&ident.escaped_text, class.name)
682                                && let Some(node) = self.ctx.arena.get(class.name)
683                            {
684                                self.ctx.error(
685                                    node.pos,
686                                    node.end - node.pos,
687                                    diagnostic_messages::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE
688                                        .to_string(),
689                                    diagnostic_codes::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE,
690                                );
691                            }
692                        } else if kind == syntax_kind_ext::ENUM_DECLARATION
693                            && let Some(enm) = self.ctx.arena.get_enum(stmt_node)
694                            && self
695                                .ctx
696                                .arena
697                                .has_modifier(&enm.modifiers, SyntaxKind::ExportKeyword)
698                            && let Some(name_node) = self.ctx.arena.get(enm.name)
699                            && let Some(ident) = self.ctx.arena.get_identifier(name_node)
700                            && register_value_name(&ident.escaped_text, enm.name)
701                            && let Some(node) = self.ctx.arena.get(enm.name)
702                        {
703                            self.ctx.error(
704                                node.pos,
705                                node.end - node.pos,
706                                diagnostic_messages::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE
707                                    .to_string(),
708                                diagnostic_codes::CANNOT_REDECLARE_BLOCK_SCOPED_VARIABLE,
709                            );
710                        }
711                    }
712                }
713                self.ctx
714                    .module_augmentation_value_decls
715                    .insert(module_key, value_decl_map);
716            }
717
718            if module.body.is_some() {
719                // Check module body (which can be a block or nested module)
720                self.check_module_body(module.body);
721            }
722        }
723    }
724
725    // Module resolution helpers (is_declaration_file, is_external_module,
726    // module_exists, etc.) are in `declarations_module_helpers.rs`.
727    /// Check a module body (block or nested module).
728    fn check_module_body(&mut self, body_idx: NodeIndex) {
729        let Some(node) = self.ctx.arena.get(body_idx) else {
730            return;
731        };
732
733        if node.kind == syntax_kind_ext::MODULE_BLOCK {
734            if let Some(block) = self.ctx.arena.get_module_block(node)
735                && let Some(ref stmts) = block.statements
736            {
737                let is_ambient = self.is_in_ambient_context(body_idx);
738                for &stmt_idx in &stmts.nodes {
739                    if is_ambient {
740                        self.check_statement_in_ambient_context(stmt_idx);
741                    }
742                    // Also check for nested module declarations in non-ambient context
743                    if let Some(stmt_node) = self.ctx.arena.get(stmt_idx) {
744                        if stmt_node.kind == syntax_kind_ext::MODULE_DECLARATION {
745                            self.check_module_declaration(stmt_idx);
746                        }
747                        // Check for export declarations that contain nested modules
748                        if stmt_node.kind == syntax_kind_ext::EXPORT_DECLARATION
749                            && let Some(export_decl) = self.ctx.arena.get_export_decl(stmt_node)
750                            && let Some(clause_node) = self.ctx.arena.get(export_decl.export_clause)
751                            && clause_node.kind == syntax_kind_ext::MODULE_DECLARATION
752                        {
753                            self.check_module_declaration(export_decl.export_clause);
754                        }
755                    }
756                }
757            }
758        } else if node.kind == syntax_kind_ext::MODULE_DECLARATION {
759            // Nested module (for dotted namespace syntax like `namespace A.B { }`)
760            self.check_module_declaration(body_idx);
761        }
762    }
763
764    /// Check TS2433/TS2434: Namespace merging with class/function across files or out of order.
765    ///
766    /// TS2433: A namespace declaration cannot be in a different file from a class or function
767    ///         with which it is merged.
768    /// TS2434: A namespace declaration cannot be located prior to a class or function with
769    ///         which it is merged.
770    ///
771    /// This check applies to non-ambient instantiated namespace declarations that have
772    /// multiple declarations (merged with a class or function).
773    fn check_namespace_merges_with_class_or_function(
774        &mut self,
775        module_idx: NodeIndex,
776        module: &tsz_parser::parser::node::ModuleData,
777    ) {
778        use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
779
780        // Get the symbol for this module declaration
781        let Some(&sym_id) = self.ctx.binder.node_symbols.get(&module_idx.0) else {
782            return;
783        };
784        let Some(symbol) = self.ctx.binder.get_symbol(sym_id) else {
785            return;
786        };
787
788        // Only check if the symbol has multiple declarations (merged)
789        if symbol.declarations.len() <= 1 {
790            return;
791        }
792
793        // First check if any non-ambient function with a body merges with this namespace.
794        // When a function merge exists, the global duplicate-check path in
795        // type_checking_global.rs handles TS2434 for the function case, and we
796        // should suppress TS2434 for any class that also merges (tsc emits
797        // TS2813/TS2814 for the class conflict, not TS2434).
798        let has_merged_function = symbol.declarations.iter().any(|&decl_idx| {
799            if decl_idx == module_idx {
800                return false;
801            }
802            let Some(decl_node) = self.ctx.arena.get(decl_idx) else {
803                return false;
804            };
805            if decl_node.kind != syntax_kind_ext::FUNCTION_DECLARATION {
806                return false;
807            }
808            if self.is_ambient_declaration(decl_idx) {
809                return false;
810            }
811            self.ctx
812                .arena
813                .get_function(decl_node)
814                .is_some_and(|f| f.body.is_some())
815        });
816
817        // Look for a non-ambient class or function declaration among the merged declarations
818        for &decl_idx in &symbol.declarations {
819            if decl_idx == module_idx {
820                continue; // Skip the current namespace declaration
821            }
822
823            let Some(decl_node) = self.ctx.arena.get(decl_idx) else {
824                continue;
825            };
826
827            let is_class = decl_node.kind == syntax_kind_ext::CLASS_DECLARATION;
828            let is_function = decl_node.kind == syntax_kind_ext::FUNCTION_DECLARATION;
829
830            if !is_class && !is_function {
831                continue;
832            }
833
834            // Check if the declaration is ambient: `declare class`, or inside
835            // an ambient context (e.g. `declare module 'M' { class C {} }`)
836            if self.is_ambient_declaration(decl_idx) {
837                continue;
838            }
839
840            // For functions, they must have a body to be considered a value declaration
841            if is_function
842                && let Some(func) = self.ctx.arena.get_function(decl_node)
843                && func.body.is_none()
844            {
845                continue; // Function overload signature, not an implementation
846            }
847
848            // Found a non-ambient class or function declaration
849            // Now check if they're in different files (TS2433) or namespace is prior (TS2434)
850
851            // Get the source file of the current namespace declaration
852            let current_file = self.get_source_file_of_node(module_idx);
853            let other_file = self.get_source_file_of_node(decl_idx);
854
855            if current_file != other_file {
856                // TS2433: Different files
857                if let Some(name_node) = self.ctx.arena.get(module.name) {
858                    self.ctx.error(
859                        name_node.pos,
860                        name_node.end - name_node.pos,
861                        diagnostic_messages::A_NAMESPACE_DECLARATION_CANNOT_BE_IN_A_DIFFERENT_FILE_FROM_A_CLASS_OR_FUNCTION_W.to_string(),
862                        diagnostic_codes::A_NAMESPACE_DECLARATION_CANNOT_BE_IN_A_DIFFERENT_FILE_FROM_A_CLASS_OR_FUNCTION_W,
863                    );
864                }
865            } else {
866                // TS2434: Namespace comes before class/function in the same file.
867                // Function-order TS2434 is already handled by the global duplicate-check path;
868                // keep this path for class-order checks to avoid duplicate TS2434 diagnostics.
869                if is_function {
870                    continue;
871                }
872
873                // Skip class-order TS2434 when the namespace also merges with a function;
874                // tsc emits TS2813/TS2814 for the class conflict, not TS2434.
875                if is_class && has_merged_function {
876                    continue;
877                }
878
879                // Compare positions - only emit error if namespace is before class/function
880                let namespace_pos = self.ctx.arena.get(module_idx).map_or(0, |n| n.pos);
881                let class_or_func_pos = self.ctx.arena.get(decl_idx).map_or(0, |n| n.pos);
882
883                if namespace_pos < class_or_func_pos
884                    && let Some(name_node) = self.ctx.arena.get(module.name)
885                {
886                    self.ctx.error(
887                        name_node.pos,
888                        name_node.end - name_node.pos,
889                        diagnostic_messages::A_NAMESPACE_DECLARATION_CANNOT_BE_LOCATED_PRIOR_TO_A_CLASS_OR_FUNCTION_WITH_WHIC.to_string(),
890                        diagnostic_codes::A_NAMESPACE_DECLARATION_CANNOT_BE_LOCATED_PRIOR_TO_A_CLASS_OR_FUNCTION_WITH_WHIC,
891                    );
892                }
893            }
894
895            // Only report error once (for the first matching class/function)
896            break;
897        }
898    }
899
900    /// Check if a namespace declaration is instantiated (contains runtime code).
901    /// Uninstantiated namespaces only contain interfaces, type aliases, etc.
902    fn is_namespace_declaration_instantiated(&self, namespace_idx: NodeIndex) -> bool {
903        self.ctx.arena.is_namespace_instantiated(namespace_idx)
904    }
905
906    /// Get the source file path of a node's declaration.
907    /// Returns the file name if we can determine it, or empty string if unknown.
908    fn get_source_file_of_node(&self, node_idx: NodeIndex) -> String {
909        // Walk up to find the source file
910        let mut current = node_idx;
911        let mut fuel = 10000;
912        while let Some(ext) = self.ctx.arena.get_extended(current) {
913            if fuel == 0 {
914                break;
915            }
916            fuel -= 1;
917
918            let parent = ext.parent;
919            if parent.is_none() {
920                break;
921            }
922            if let Some(parent_node) = self.ctx.arena.get(parent)
923                && parent_node.kind == syntax_kind_ext::SOURCE_FILE
924                && let Some(source_file) = self.ctx.arena.get_source_file(parent_node)
925            {
926                return source_file.file_name.clone();
927            }
928            current = parent;
929        }
930        // Fallback to current file name
931        self.ctx.file_name.clone()
932    }
933}