fix(semantic): report module declaration diagnostics (#25285)

## Summary

- report TS1234 for function-local ambient module declarations
- preserve TS1235 for function-local namespace and internal module declarations
- report TS2435 for ambient modules nested in namespaces while allowing module augmentation inside ambient external modules

## Babel Conformance Snapshot Parse Failure

This intentionally introduces one known Babel conformance false positive: `typescript/scope/redeclaration-in-nested-module/input.ts`. The Babel harness supplies `sourceType: "module"`, so Oxc reports TS2435 for its nested ambient module. This affects one unique fixture in both the parser and semantic Babel summaries. This change aligns with TS, and TS expects this error.
This commit is contained in:
camc314 committed 2026-08-04 15:06:10 +00:00
1 parent db407f61cc
commit 0c9b980cb1
6 files changed
+238 -59

No files matched your search

+32 -1
View File
@@ -99,7 +99,11 @@ fn check_duplicate_bound_names<'a, T: BoundNames<'a>>(bound_names: &T, ctx: &Sem
}
pub fn check_ts_module_declaration<'a>(decl: &TSModuleDeclaration<'a>, ctx: &SemanticBuilder<'a>) {
check_ts_module_or_global_declaration(decl.span, ctx);
if matches!(&decl.id, TSModuleDeclarationName::StringLiteral(_)) {
check_ambient_module_declaration(decl.span, ctx);
} else {
check_ts_module_or_global_declaration(decl.span, ctx);
}
check_ts_export_assignment_in_module_decl(decl, ctx);
}
@@ -128,11 +132,38 @@ fn check_ts_module_or_global_declaration(span: Span, ctx: &SemanticBuilder<'_>)
}
_ => {
ctx.error(diagnostics::not_allowed_namespace_declaration(span));
break;
}
}
}
}
fn check_ambient_module_declaration(span: Span, ctx: &SemanticBuilder<'_>) {
let mut ancestors =
ctx.ancestry().ancestor_kinds().filter(|kind| !kind.is_module_declaration());
match ancestors.next() {
Some(AstKind::Program(_)) => {}
Some(AstKind::TSModuleBlock(_)) => match ancestors.next() {
Some(AstKind::TSModuleDeclaration(decl))
if matches!(&decl.id, TSModuleDeclarationName::StringLiteral(_))
&& ctx.source_type.is_script()
&& matches!(ancestors.next(), Some(AstKind::Program(_))) =>
{
// Module augmentations may be nested directly in a top-level ambient module.
}
Some(AstKind::TSModuleDeclaration(_) | AstKind::TSGlobalDeclaration(_)) => {
ctx.error(diagnostics::nested_ambient_module_declaration(span));
}
_ => ctx.error(diagnostics::not_allowed_ambient_module_declaration(span)),
},
Some(AstKind::TSModuleDeclaration(_) | AstKind::TSGlobalDeclaration(_)) => {
ctx.error(diagnostics::nested_ambient_module_declaration(span));
}
_ => ctx.error(diagnostics::not_allowed_ambient_module_declaration(span)),
}
}
pub fn check_ts_enum_declaration<'a>(decl: &TSEnumDeclaration<'a>, ctx: &SemanticBuilder<'a>) {
let mut need_initializer = false;
+12
View File
@@ -361,6 +361,18 @@ pub fn not_allowed_namespace_declaration(span: Span) -> OxcDiagnostic {
.with_label(span)
}
#[cold]
pub fn not_allowed_ambient_module_declaration(span: Span) -> OxcDiagnostic {
ts_error("1234", "An ambient module declaration is only allowed at the top level in a file.")
.with_label(span)
}
#[cold]
pub fn nested_ambient_module_declaration(span: Span) -> OxcDiagnostic {
ts_error("2435", "Ambient modules cannot be nested in other modules or namespaces.")
.with_label(span)
}
#[cold]
pub fn global_scope_augmentation_should_have_declare_modifier(span: Span) -> OxcDiagnostic {
ts_error(
@@ -2,6 +2,22 @@ use oxc_semantic::SymbolFlags;
use crate::util::SemanticTester;
#[test]
fn test_nested_ambient_module_declaration() {
for source in [
r#"declare module "outer" { module "middle" { module "inner" {} } }"#,
r#"export {}; declare module "outer" { module "inner" {} }"#,
r#"export {}; declare global { module "inner" {} }"#,
r#"declare module "outer" { module "inner" {} } import.meta;"#,
] {
let diagnostics = SemanticTester::ts(source).build_with_errors().diagnostics;
let codes =
diagnostics.iter().map(|diagnostic| diagnostic.code.to_string()).collect::<Vec<_>>();
assert_eq!(codes, ["TS(2435)"]);
}
}
#[test]
fn test_import_assignment() {
SemanticTester::ts("import Foo = require('./foo')")
+12 -1
View File
@@ -2,7 +2,7 @@ commit: 06b6eae3
parser_babel Summary:
AST Parsed : 2234/2234 (100.00%)
Positive Passed: 2220/2234 (99.37%)
Positive Passed: 2219/2234 (99.33%)
Negative Passed: 1727/1742 (99.14%)
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/cast/unparenthesized-assert-and-assign/input.ts
@@ -248,6 +248,17 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
· ──
╰────
Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/scope/redeclaration-in-nested-module/input.ts
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[babel/packages/babel-parser/test/fixtures/typescript/scope/redeclaration-in-nested-module/input.ts:3:3]
2 │ import type { JSONSchema7 } from 'json-schema';
3 │ ╭─▶ module 'test/sub2' {
4 │ │ export { JSONSchema7 }
5 │ ╰─▶ }
6 │ }
╰────
Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/types/const-type-parameters/input.ts
× TS(1277): 'const' modifier can only appear on a type parameter of a function, method or class
+162 -56
View File
@@ -3,7 +3,7 @@ commit: b465fdbf
parser_typescript Summary:
AST Parsed : 9779/9779 (100.00%)
Positive Passed: 9779/9779 (100.00%)
Negative Passed: 1644/2640 (62.27%)
Negative Passed: 1645/2640 (62.31%)
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration3.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration4.ts
@@ -948,8 +948,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/widenedTypes
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/ambient/ambientDeclarationsPatterns_merging3.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/ambient/ambientExternalModuleInsideNonAmbient.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/ambient/ambientExternalModuleInsideNonAmbientExternalModule.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/async/es2017/asyncArrowFunction/asyncArrowFunction3_es2017.ts
@@ -9335,7 +9333,7 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
8 │
╰────
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
× TS(1234): An ambient module declaration is only allowed at the top level in a file.
╭─[typescript/tests/cases/compiler/moduleElementsInWrongContext.ts:9:5]
8 │
9 │ ╭─▶ declare module "ambient" {
@@ -9352,23 +9350,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
3 │ export namespace N {
╰────
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
╭─[typescript/tests/cases/compiler/moduleElementsInWrongContext2.ts:2:5]
1 │ function blah () {
2 │ namespace M { }
· ───────────────
3 │ export namespace N {
╰────
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
╭─[typescript/tests/cases/compiler/moduleElementsInWrongContext2.ts:3:12]
2 │ namespace M { }
3 │ ╭─▶ export namespace N {
4 │ │ export interface I { }
5 │ ╰─▶ }
6 │
╰────
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
╭─[typescript/tests/cases/compiler/moduleElementsInWrongContext2.ts:3:12]
2 │ namespace M { }
@@ -9386,24 +9367,7 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
8 │
╰────
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
╭─[typescript/tests/cases/compiler/moduleElementsInWrongContext2.ts:7:5]
6 │
7 │ namespace Q.K { }
· ─────────────────
8 │
╰────
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
╭─[typescript/tests/cases/compiler/moduleElementsInWrongContext2.ts:9:5]
8 │
9 │ ╭─▶ declare module "ambient" {
10 │ │
11 │ ╰─▶ }
12 │
╰────
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
× TS(1234): An ambient module declaration is only allowed at the top level in a file.
╭─[typescript/tests/cases/compiler/moduleElementsInWrongContext2.ts:9:5]
8 │
9 │ ╭─▶ declare module "ambient" {
@@ -9437,7 +9401,7 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
9 │
╰────
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
× TS(1234): An ambient module declaration is only allowed at the top level in a file.
╭─[typescript/tests/cases/compiler/moduleElementsInWrongContext3.ts:10:9]
9 │
10 │ ╭─▶ declare module "ambient" {
@@ -11011,6 +10975,48 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
150 │ }
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyGloImportParseErrors.ts:22:12]
21 │
22 │ ╭─▶ export declare module "m1_M3_public" {
23 │ │ export function f1();
24 │ │ export class c1 {
25 │ │ }
26 │ │ export var v1: { new (): c1; };
27 │ │ export var v2: c1;
28 │ ╰─▶ }
29 │
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyGloImportParseErrors.ts:30:5]
29 │
30 │ ╭─▶ declare module "m1_M4_private" {
31 │ │ export function f1();
32 │ │ export class c1 {
33 │ │ }
34 │ │ export var v1: { new (): c1; };
35 │ │ export var v2: c1;
36 │ ╰─▶ }
37 │
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyGloImportParseErrors.ts:133:9]
132 │ namespace m2 {
133 │ ╭─▶ declare module "abc" {
134 │ ╰─▶ }
135 │ }
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyGloImportParseErrors.ts:138:9]
137 │ namespace m2 {
138 │ ╭─▶ module "abc2" {
139 │ ╰─▶ }
140 │ }
╰────
× TS(1147): Import declarations in a namespace cannot reference a module.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:59:5]
58 │
@@ -11172,6 +11178,106 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
354 │ }
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:22:12]
21 │
22 │ ╭─▶ export declare module "m1_M3_public" {
23 │ │ export function f1();
24 │ │ export class c1 {
25 │ │ }
26 │ │ export var v1: { new (): c1; };
27 │ │ export var v2: c1;
28 │ ╰─▶ }
29 │
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:30:5]
29 │
30 │ ╭─▶ declare module "m1_M4_private" {
31 │ │ export function f1();
32 │ │ export class c1 {
33 │ │ }
34 │ │ export var v1: { new (): c1; };
35 │ │ export var v2: c1;
36 │ ╰─▶ }
37 │
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:106:12]
105 │
106 │ ╭─▶ export declare module "m2_M3_public" {
107 │ │ export function f1();
108 │ │ export class c1 {
109 │ │ }
110 │ │ export var v1: { new (): c1; };
111 │ │ export var v2: c1;
112 │ ╰─▶ }
113 │
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:114:5]
113 │
114 │ ╭─▶ declare module "m2_M4_private" {
115 │ │ export function f1();
116 │ │ export class c1 {
117 │ │ }
118 │ │ export var v1: { new (): c1; };
119 │ │ export var v2: c1;
120 │ ╰─▶ }
121 │
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:314:9]
313 │ namespace m2 {
314 │ ╭─▶ declare module "abc" {
315 │ ╰─▶ }
316 │ }
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:319:9]
318 │ namespace m2 {
319 │ ╭─▶ module "abc2" {
320 │ ╰─▶ }
321 │ }
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:322:5]
321 │ }
322 │ ╭─▶ module "abc3" {
323 │ ╰─▶ }
324 │ }
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:328:9]
327 │ namespace m2 {
328 │ ╭─▶ declare module "abc" {
329 │ ╰─▶ }
330 │ }
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:333:9]
332 │ namespace m2 {
333 │ ╭─▶ module "abc2" {
334 │ ╰─▶ }
335 │ }
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:336:5]
335 │ }
336 │ ╭─▶ module "abc3" {
337 │ ╰─▶ }
338 │ }
╰────
× Unexpected token
╭─[typescript/tests/cases/compiler/privateNameJsx.tsx:4:22]
3 │ render() {
@@ -13825,14 +13931,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
╭─[typescript/tests/cases/compiler/unreachableDeclarations.ts:84:2]
83 │
84 │ namespace Baz { export const value = 1234 }
· ───────────────────────────────────────────
85 │ }
╰────
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
╭─[typescript/tests/cases/compiler/unreachableDeclarations.ts:84:2]
83 │
84 │ namespace Baz { export const value = 1234 }
· ───────────────────────────────────────────
85 │ }
@@ -14074,14 +14172,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
╭─[typescript/tests/cases/compiler/withStatementErrors.ts:15:5]
14 │
15 │ namespace M {} // error
· ──────────────
16 │
╰────
× TS(1235): A namespace declaration is only allowed at the top level of a namespace or module.
╭─[typescript/tests/cases/compiler/withStatementErrors.ts:15:5]
14 │
15 │ namespace M {} // error
· ──────────────
16 │
@@ -14184,6 +14274,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
42 │ }
╰────
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/conformance/ambient/ambientErrors.ts:47:5]
46 │ namespace M2 {
47 │ declare module 'nope' { }
· ─────────────────────────
48 │ }
╰────
× TS(2309): An export assignment cannot be used in a module with other exported elements
╭─[typescript/tests/cases/conformance/ambient/ambientErrors.ts:57:5]
56 │ export var q;
@@ -14193,6 +14291,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
╰────
help: If you want to use `export =`, remove other `export`s and put all of them to the right hand value of `export =`. If you want to use `export`s, remove `export =` statement.
× TS(2435): Ambient modules cannot be nested in other modules or namespaces.
╭─[typescript/tests/cases/conformance/ambient/ambientExternalModuleInsideNonAmbient.ts:2:12]
1 │ namespace M {
2 │ export declare module "M" { }
· ──────────────────────
3 │ }
╰────
× Identifier expected. 'debugger' is a reserved word that cannot be used here.
╭─[typescript/tests/cases/conformance/ambient/ambientModuleDeclarationWithReservedIdentifierInDottedPath.ts:3:26]
2 │
+4 -1
View File
@@ -2,7 +2,7 @@ commit: 06b6eae3
semantic_babel Summary:
AST Parsed : 2234/2234 (100.00%)
Positive Passed: 2145/2234 (96.02%)
Positive Passed: 2144/2234 (95.97%)
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/comments/decorators/decorators-after-export/input.js
Symbol flags mismatch for "_default":
after transform: SymbolId(3): SymbolFlags(Class)
@@ -443,6 +443,9 @@ Symbol reference IDs mismatch for "fooBar":
after transform: SymbolId(1): [ReferenceId(1), ReferenceId(4)]
rebuilt : SymbolId(0): [ReferenceId(0)]
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/scope/redeclaration-in-nested-module/input.ts
Ambient modules cannot be nested in other modules or namespaces.
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/scope/redeclaration-interface-class/input.ts
Bindings mismatch:
after transform: ScopeId(0): []