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}