fix(parser): reject declare in an already-ambient context (TS1038) (#22850)

A `declare` modifier is redundant and not allowed when its declaration is nested
directly inside an ambient `declare namespace` / `declare module` body (TS1038).
A top-level `declare` in a `.d.ts` file stays valid:

    declare namespace N { declare function f(): void; }  // now TS1038
    declare module "m" { declare function f(): void; }   // now TS1038

    // foo.d.ts
    declare function f(): void;                            // valid (top level)
    namespace N { declare function f(): void; }           // valid (non-ambient namespace)

Checked in `parse_ts_declaration_statement` against the enclosing context captured
before the declaration unions in its own ambient flag: `declare` modifier present,
`reserved_ctx.has_ambient()`, and `!reserved_ctx.has_top_level()`. The
`has_top_level()` guard is what exempts a `.d.ts` source file (whose parent is the
SourceFile, not a module block) while still covering internal namespaces and
external `declare module "x"` bodies. No valid construct has
`declare + ambient + !top_level`, so there is no positive regression.

Two linter fixtures used a redundant nested `declare` and are updated to valid TS
(`no_namespace`, `prefer_namespace_keyword`).

parser_typescript +9 negative-pass; positive/AST stay 100%.
This commit is contained in:
Boshen committed 2026-05-30 09:58:14 +00:00
1 parent 51528545ea
commit 3d13e293e4
6 files changed
+175 -29

No files matched your search

@@ -348,7 +348,7 @@ fn test() {
(
"namespace A {
export declare namespace B {
declare namespace C {}
namespace C {}
}
}",
Some(serde_json::json!([{ "allowDeclarations": true }])),
@@ -358,7 +358,7 @@ fn test() {
(
"namespace A {
export declare namespace B {
export declare namespace C {}
export namespace C {}
}
}",
Some(serde_json::json!([{ "allowDeclarations": true }])),
@@ -428,7 +428,7 @@ fn test() {
(
"export namespace A {
export declare namespace B {
declare namespace C {}
namespace C {}
}
}",
Some(serde_json::json!([{ "allowDeclarations": true }])),
@@ -438,7 +438,7 @@ fn test() {
(
"export namespace A {
export declare namespace B {
export declare namespace C {}
export namespace C {}
}
}",
Some(serde_json::json!([{ "allowDeclarations": true }])),
@@ -114,7 +114,7 @@ fn test() {
"declare module foo {}",
"
declare module foo {
declare module bar {}
module bar {}
}
",
"
@@ -144,12 +144,12 @@ fn test() {
(
"
declare module foo {
declare module bar {}
module bar {}
}
",
"
declare namespace foo {
declare namespace bar {}
namespace bar {}
}
",
None,
@@ -30,7 +30,7 @@ source: crates/oxc_linter/src/tester.rs
╭─[prefer_namespace_keyword.tsx:2:9]
1 │
2 │ ╭─▶ declare module foo {
3 │ │ declare module bar {}
3 │ │ module bar {}
4 │ ╰─▶ }
5 │
╰────
@@ -40,8 +40,8 @@ source: crates/oxc_linter/src/tester.rs
⚠ typescript(prefer-namespace-keyword): Use `namespace` instead of `module` to declare custom TypeScript modules.
╭─[prefer_namespace_keyword.tsx:3:11]
2 │ declare module foo {
3 │ declare module bar {}
· ─────────────────────
3 │ module bar {}
· ─────────────
4 │ }
╰────
help: Replace `module` with `namespace` for internal declarations.
+6
View File
@@ -313,6 +313,12 @@ pub fn statement_in_ambient_context(span: Span) -> OxcDiagnostic {
ts_error("1036", "Statements are not allowed in ambient contexts.").with_label(span)
}
#[cold]
pub fn declare_in_ambient_context(span: Span) -> OxcDiagnostic {
ts_error("1038", "A 'declare' modifier cannot be used in an already ambient context.")
.with_label(span)
}
#[cold]
pub fn async_function_declaration(span: Span) -> OxcDiagnostic {
OxcDiagnostic::error("Async functions can only be declared at the top level or inside a block")
+6
View File
@@ -458,6 +458,12 @@ impl<'a, C: Config> ParserImpl<'a, C> {
pub(crate) fn parse_ts_declaration_statement(&mut self, start_span: u32) -> Statement<'a> {
let reserved_ctx = self.ctx;
let modifiers = self.eat_modifiers_before_declaration();
if let Some(modifier) = modifiers.get(ModifierKind::Declare)
&& reserved_ctx.has_ambient()
&& !reserved_ctx.has_top_level()
{
self.error(diagnostics::declare_in_ambient_context(modifier.span()));
}
self.ctx = self
.ctx
.union_ambient_if(modifiers.contains_declare())
+153 -19
View File
@@ -3,7 +3,7 @@ commit: e5509e21
parser_typescript Summary:
AST Parsed : 9779/9779 (100.00%)
Positive Passed: 9779/9779 (100.00%)
Negative Passed: 1611/2640 (61.02%)
Negative Passed: 1620/2640 (61.36%)
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration3.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration4.ts
@@ -182,10 +182,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/crashDeclare
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/crashRegressionTest.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFile.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFileWithOut.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declarationEmitCommonJsModuleReferencedType.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declarationEmitComputedPropertyNameEnum3.ts
@@ -1758,14 +1754,10 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ec
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration12.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration7.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName4.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ConstructorDeclarations/parserConstructorDeclaration2.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/EnumDeclarations/parserEnumDeclaration2.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/EnumDeclarations/parserEnumDeclaration3.d.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserCommaInTypeMemberList2.ts
@@ -1776,8 +1768,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ec
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock4.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/FunctionDeclarations/parserFunctionDeclaration1.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/FunctionDeclarations/parserFunctionDeclaration3.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/FunctionDeclarations/parserFunctionDeclaration4.ts
@@ -1806,12 +1796,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ec
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration2.d.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration3.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration4.d.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration5.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ParameterLists/parserParameterList14.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ParameterLists/parserParameterList15.ts
@@ -1850,8 +1834,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ec
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/Symbols/parserES5SymbolProperty5.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/VariableDeclarations/parserVariableDeclaration4.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/parserNoASIOnCallAfterFunctionExpression1.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/parserRealSource13.ts
@@ -5633,6 +5615,70 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
2 │
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFile.ts:2:5]
1 │ declare namespace M {
2 │ declare var x;
· ───────
3 │ declare function f();
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFile.ts:3:5]
2 │ declare var x;
3 │ declare function f();
· ───────
4 │
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFile.ts:5:5]
4 │
5 │ declare namespace N { }
· ───────
6 │
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFile.ts:7:5]
6 │
7 │ declare class C { }
· ───────
8 │ }
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFileWithOut.ts:2:5]
1 │ declare namespace M {
2 │ declare var x;
· ───────
3 │ declare function f();
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFileWithOut.ts:3:5]
2 │ declare var x;
3 │ declare function f();
· ───────
4 │
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFileWithOut.ts:5:5]
4 │
5 │ declare namespace N { }
· ───────
6 │
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFileWithOut.ts:7:5]
6 │
7 │ declare class C { }
· ───────
8 │ }
╰────
× Identifier `a` has already been declared
╭─[typescript/tests/cases/compiler/declarationEmitDestructuring2.ts:3:13]
2 │ function g([a, b, c, d] = [1, 2, 3, 4]) { }
@@ -7757,6 +7803,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
7 │ var b: a;
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/importDeclWithDeclareModifierInAmbientContext.ts:6:5]
5 │ }
6 │ declare export import a = x.c;
· ───────
7 │ var b: a;
╰────
× TS(2309): An export assignment cannot be used in a module with other exported elements
╭─[typescript/tests/cases/compiler/importDeclWithExportModifierAndExportAssignment.ts:6:1]
5 │ export import a = x.c;
@@ -10869,6 +10923,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
126 │ import m5_nonerrorImport = glo_M1_public;
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/privacyGloImportParseErrors.ts:133:9]
132 │ namespace m2 {
133 │ declare module "abc" {
· ───────
134 │ }
╰────
× TS(1147): Import declarations in a namespace cannot reference a module.
╭─[typescript/tests/cases/compiler/privacyGloImportParseErrors.ts:146:5]
145 │ namespace m2 {
@@ -10949,6 +11011,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
307 │ import m5_nonerrorImport = glo_M3_private;
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:314:9]
313 │ namespace m2 {
314 │ declare module "abc" {
· ───────
315 │ }
╰────
× TS(1029): 'export' modifier must precede 'declare' modifier.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:326:9]
325 │
@@ -10957,6 +11027,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
327 │ namespace m2 {
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:328:9]
327 │ namespace m2 {
328 │ declare module "abc" {
· ───────
329 │ }
╰────
× 'export' modifier cannot be used here.
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:326:9]
325 │
@@ -24395,6 +24473,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
╰────
help: Remove the extra base class or use interfaces for multiple inheritance
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration7.ts:2:3]
1 │ declare namespace M {
2 │ declare class C {
· ───────
3 │ }
╰────
× TS(2390): Constructor implementation is missing.
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration8.ts:2:3]
1 │ class C {
@@ -24522,6 +24608,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
3 │ }
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/conformance/parser/ecmascript5/EnumDeclarations/parserEnumDeclaration2.ts:2:3]
1 │ declare namespace M {
2 │ declare enum E {
· ───────
3 │ }
╰────
× Identifier expected. 'void' is a reserved word that cannot be used here.
╭─[typescript/tests/cases/conformance/parser/ecmascript5/EnumDeclarations/parserEnumDeclaration4.ts:1:6]
1 │ enum void {
@@ -25270,6 +25364,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
· ─────────
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/conformance/parser/ecmascript5/FunctionDeclarations/parserFunctionDeclaration1.ts:2:3]
1 │ declare namespace M {
2 │ declare function F();
· ───────
3 │ }
╰────
× TS(1183): An implementation cannot be declared in ambient contexts.
╭─[typescript/tests/cases/conformance/parser/ecmascript5/FunctionDeclarations/parserFunctionDeclaration2.d.ts:1:14]
1 │ function F() {
@@ -25836,6 +25938,30 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
· ────
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration3.ts:2:3]
1 │ declare namespace M {
2 │ declare namespace M2 {
· ───────
3 │ }
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration4.d.ts:2:3]
1 │ namespace M {
2 │ declare namespace M1 {
· ───────
3 │ }
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration5.ts:3:5]
2 │ declare namespace M2 {
3 │ declare namespace M3 {
· ───────
4 │ }
╰────
× Expected `(` but found `;`
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ObjectTypes/parserObjectType5.ts:3:7]
2 │ A: B
@@ -26970,6 +27096,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
· ─
╰────
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
╭─[typescript/tests/cases/conformance/parser/ecmascript5/VariableDeclarations/parserVariableDeclaration4.ts:2:4]
1 │ declare namespace M {
2 │ declare var v;
· ───────
3 │ }
╰────
× Unexpected token
╭─[typescript/tests/cases/conformance/parser/ecmascript5/VariableDeclarations/parserVariableDeclaration5.ts:1:7]
1 │ var a,