Skip to content

Commit 3d13e29

Browse files
committed
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%.
1 parent 5152854 commit 3d13e29

6 files changed

Lines changed: 175 additions & 29 deletions

File tree

crates/oxc_linter/src/rules/typescript/no_namespace.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ fn test() {
348348
(
349349
"namespace A {
350350
export declare namespace B {
351-
declare namespace C {}
351+
namespace C {}
352352
}
353353
}",
354354
Some(serde_json::json!([{ "allowDeclarations": true }])),
@@ -358,7 +358,7 @@ fn test() {
358358
(
359359
"namespace A {
360360
export declare namespace B {
361-
export declare namespace C {}
361+
export namespace C {}
362362
}
363363
}",
364364
Some(serde_json::json!([{ "allowDeclarations": true }])),
@@ -428,7 +428,7 @@ fn test() {
428428
(
429429
"export namespace A {
430430
export declare namespace B {
431-
declare namespace C {}
431+
namespace C {}
432432
}
433433
}",
434434
Some(serde_json::json!([{ "allowDeclarations": true }])),
@@ -438,7 +438,7 @@ fn test() {
438438
(
439439
"export namespace A {
440440
export declare namespace B {
441-
export declare namespace C {}
441+
export namespace C {}
442442
}
443443
}",
444444
Some(serde_json::json!([{ "allowDeclarations": true }])),

crates/oxc_linter/src/rules/typescript/prefer_namespace_keyword.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ fn test() {
114114
"declare module foo {}",
115115
"
116116
declare module foo {
117-
declare module bar {}
117+
module bar {}
118118
}
119119
",
120120
"
@@ -144,12 +144,12 @@ fn test() {
144144
(
145145
"
146146
declare module foo {
147-
declare module bar {}
147+
module bar {}
148148
}
149149
",
150150
"
151151
declare namespace foo {
152-
declare namespace bar {}
152+
namespace bar {}
153153
}
154154
",
155155
None,

crates/oxc_linter/src/snapshots/typescript_prefer_namespace_keyword.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ source: crates/oxc_linter/src/tester.rs
3030
╭─[prefer_namespace_keyword.tsx:2:9]
3131
1
3232
2 │ ╭─▶ declare module foo {
33-
3 │ │ declare module bar {}
33+
3 │ │ module bar {}
3434
4 │ ╰─▶ }
3535
5
3636
╰────
@@ -40,8 +40,8 @@ source: crates/oxc_linter/src/tester.rs
4040
typescript(prefer-namespace-keyword): Use `namespace` instead of `module` to declare custom TypeScript modules.
4141
╭─[prefer_namespace_keyword.tsx:3:11]
4242
2declare module foo {
43-
3declare module bar {}
44-
· ─────────────────────
43+
3module bar {}
44+
· ─────────────
4545
4 │ }
4646
╰────
4747
help: Replace `module` with `namespace` for internal declarations.

crates/oxc_parser/src/diagnostics.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,12 @@ pub fn statement_in_ambient_context(span: Span) -> OxcDiagnostic {
313313
ts_error("1036", "Statements are not allowed in ambient contexts.").with_label(span)
314314
}
315315

316+
#[cold]
317+
pub fn declare_in_ambient_context(span: Span) -> OxcDiagnostic {
318+
ts_error("1038", "A 'declare' modifier cannot be used in an already ambient context.")
319+
.with_label(span)
320+
}
321+
316322
#[cold]
317323
pub fn async_function_declaration(span: Span) -> OxcDiagnostic {
318324
OxcDiagnostic::error("Async functions can only be declared at the top level or inside a block")

crates/oxc_parser/src/ts/statement.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,12 @@ impl<'a, C: Config> ParserImpl<'a, C> {
458458
pub(crate) fn parse_ts_declaration_statement(&mut self, start_span: u32) -> Statement<'a> {
459459
let reserved_ctx = self.ctx;
460460
let modifiers = self.eat_modifiers_before_declaration();
461+
if let Some(modifier) = modifiers.get(ModifierKind::Declare)
462+
&& reserved_ctx.has_ambient()
463+
&& !reserved_ctx.has_top_level()
464+
{
465+
self.error(diagnostics::declare_in_ambient_context(modifier.span()));
466+
}
461467
self.ctx = self
462468
.ctx
463469
.union_ambient_if(modifiers.contains_declare())

tasks/coverage/snapshots/parser_typescript.snap

Lines changed: 153 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ commit: e5509e21
33
parser_typescript Summary:
44
AST Parsed : 9779/9779 (100.00%)
55
Positive Passed: 9779/9779 (100.00%)
6-
Negative Passed: 1611/2640 (61.02%)
6+
Negative Passed: 1620/2640 (61.36%)
77
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration3.ts
88

99
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
182182

183183
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/crashRegressionTest.ts
184184

185-
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFile.ts
186-
187-
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFileWithOut.ts
188-
189185
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declarationEmitCommonJsModuleReferencedType.ts
190186

191187
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
17581754

17591755
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration12.ts
17601756

1761-
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration7.ts
1762-
17631757
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName4.ts
17641758

17651759
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ConstructorDeclarations/parserConstructorDeclaration2.ts
17661760

1767-
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/EnumDeclarations/parserEnumDeclaration2.ts
1768-
17691761
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/EnumDeclarations/parserEnumDeclaration3.d.ts
17701762

17711763
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
17761768

17771769
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock4.ts
17781770

1779-
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/FunctionDeclarations/parserFunctionDeclaration1.ts
1780-
17811771
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/FunctionDeclarations/parserFunctionDeclaration3.ts
17821772

17831773
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
18061796

18071797
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration2.d.ts
18081798

1809-
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration3.ts
1810-
1811-
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration4.d.ts
1812-
1813-
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration5.ts
1814-
18151799
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ParameterLists/parserParameterList14.ts
18161800

18171801
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
18501834

18511835
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/Symbols/parserES5SymbolProperty5.ts
18521836

1853-
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/VariableDeclarations/parserVariableDeclaration4.ts
1854-
18551837
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/parserNoASIOnCallAfterFunctionExpression1.ts
18561838

18571839
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
56335615
2 │
56345616
╰────
56355617

5618+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
5619+
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFile.ts:2:5]
5620+
1 │ declare namespace M {
5621+
2 │ declare var x;
5622+
· ───────
5623+
3 │ declare function f();
5624+
╰────
5625+
5626+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
5627+
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFile.ts:3:5]
5628+
2 │ declare var x;
5629+
3 │ declare function f();
5630+
· ───────
5631+
4 │
5632+
╰────
5633+
5634+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
5635+
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFile.ts:5:5]
5636+
4 │
5637+
5 │ declare namespace N { }
5638+
· ───────
5639+
6 │
5640+
╰────
5641+
5642+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
5643+
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFile.ts:7:5]
5644+
6 │
5645+
7 │ declare class C { }
5646+
· ───────
5647+
8 │ }
5648+
╰────
5649+
5650+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
5651+
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFileWithOut.ts:2:5]
5652+
1 │ declare namespace M {
5653+
2 │ declare var x;
5654+
· ───────
5655+
3 │ declare function f();
5656+
╰────
5657+
5658+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
5659+
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFileWithOut.ts:3:5]
5660+
2 │ declare var x;
5661+
3 │ declare function f();
5662+
· ───────
5663+
4 │
5664+
╰────
5665+
5666+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
5667+
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFileWithOut.ts:5:5]
5668+
4 │
5669+
5 │ declare namespace N { }
5670+
· ───────
5671+
6 │
5672+
╰────
5673+
5674+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
5675+
╭─[typescript/tests/cases/compiler/declFileWithErrorsInInputDeclarationFileWithOut.ts:7:5]
5676+
6 │
5677+
7 │ declare class C { }
5678+
· ───────
5679+
8 │ }
5680+
╰────
5681+
56365682
× Identifier `a` has already been declared
56375683
╭─[typescript/tests/cases/compiler/declarationEmitDestructuring2.ts:3:13]
56385684
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
77577803
7 │ var b: a;
77587804
╰────
77597805

7806+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
7807+
╭─[typescript/tests/cases/compiler/importDeclWithDeclareModifierInAmbientContext.ts:6:5]
7808+
5 │ }
7809+
6 │ declare export import a = x.c;
7810+
· ───────
7811+
7 │ var b: a;
7812+
╰────
7813+
77607814
× TS(2309): An export assignment cannot be used in a module with other exported elements
77617815
╭─[typescript/tests/cases/compiler/importDeclWithExportModifierAndExportAssignment.ts:6:1]
77627816
5 │ export import a = x.c;
@@ -10869,6 +10923,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
1086910923
126 │ import m5_nonerrorImport = glo_M1_public;
1087010924
╰────
1087110925

10926+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
10927+
╭─[typescript/tests/cases/compiler/privacyGloImportParseErrors.ts:133:9]
10928+
132 │ namespace m2 {
10929+
133 │ declare module "abc" {
10930+
· ───────
10931+
134 │ }
10932+
╰────
10933+
1087210934
× TS(1147): Import declarations in a namespace cannot reference a module.
1087310935
╭─[typescript/tests/cases/compiler/privacyGloImportParseErrors.ts:146:5]
1087410936
145 │ namespace m2 {
@@ -10949,6 +11011,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
1094911011
307 │ import m5_nonerrorImport = glo_M3_private;
1095011012
╰────
1095111013

11014+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
11015+
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:314:9]
11016+
313 │ namespace m2 {
11017+
314 │ declare module "abc" {
11018+
· ───────
11019+
315 │ }
11020+
╰────
11021+
1095211022
× TS(1029): 'export' modifier must precede 'declare' modifier.
1095311023
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:326:9]
1095411024
325 │
@@ -10957,6 +11027,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
1095711027
327 │ namespace m2 {
1095811028
╰────
1095911029

11030+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
11031+
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:328:9]
11032+
327 │ namespace m2 {
11033+
328 │ declare module "abc" {
11034+
· ───────
11035+
329 │ }
11036+
╰────
11037+
1096011038
× 'export' modifier cannot be used here.
1096111039
╭─[typescript/tests/cases/compiler/privacyImportParseErrors.ts:326:9]
1096211040
325 │
@@ -24395,6 +24473,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
2439524473
╰────
2439624474
help: Remove the extra base class or use interfaces for multiple inheritance
2439724475

24476+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
24477+
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration7.ts:2:3]
24478+
1 │ declare namespace M {
24479+
2 │ declare class C {
24480+
· ───────
24481+
3 │ }
24482+
╰────
24483+
2439824484
× TS(2390): Constructor implementation is missing.
2439924485
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration8.ts:2:3]
2440024486
1 │ class C {
@@ -24522,6 +24608,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
2452224608
3 │ }
2452324609
╰────
2452424610

24611+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
24612+
╭─[typescript/tests/cases/conformance/parser/ecmascript5/EnumDeclarations/parserEnumDeclaration2.ts:2:3]
24613+
1 │ declare namespace M {
24614+
2 │ declare enum E {
24615+
· ───────
24616+
3 │ }
24617+
╰────
24618+
2452524619
× Identifier expected. 'void' is a reserved word that cannot be used here.
2452624620
╭─[typescript/tests/cases/conformance/parser/ecmascript5/EnumDeclarations/parserEnumDeclaration4.ts:1:6]
2452724621
1 │ enum void {
@@ -25270,6 +25364,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
2527025364
· ─────────
2527125365
╰────
2527225366

25367+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
25368+
╭─[typescript/tests/cases/conformance/parser/ecmascript5/FunctionDeclarations/parserFunctionDeclaration1.ts:2:3]
25369+
1 │ declare namespace M {
25370+
2 │ declare function F();
25371+
· ───────
25372+
3 │ }
25373+
╰────
25374+
2527325375
× TS(1183): An implementation cannot be declared in ambient contexts.
2527425376
╭─[typescript/tests/cases/conformance/parser/ecmascript5/FunctionDeclarations/parserFunctionDeclaration2.d.ts:1:14]
2527525377
1 │ function F() {
@@ -25836,6 +25938,30 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
2583625938
· ────
2583725939
╰────
2583825940

25941+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
25942+
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration3.ts:2:3]
25943+
1 │ declare namespace M {
25944+
2 │ declare namespace M2 {
25945+
· ───────
25946+
3 │ }
25947+
╰────
25948+
25949+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
25950+
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration4.d.ts:2:3]
25951+
1 │ namespace M {
25952+
2 │ declare namespace M1 {
25953+
· ───────
25954+
3 │ }
25955+
╰────
25956+
25957+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
25958+
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ModuleDeclarations/parserModuleDeclaration5.ts:3:5]
25959+
2 │ declare namespace M2 {
25960+
3 │ declare namespace M3 {
25961+
· ───────
25962+
4 │ }
25963+
╰────
25964+
2583925965
× Expected `(` but found `;`
2584025966
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ObjectTypes/parserObjectType5.ts:3:7]
2584125967
2 │ A: B
@@ -26970,6 +27096,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
2697027096
· ─
2697127097
╰────
2697227098

27099+
× TS(1038): A 'declare' modifier cannot be used in an already ambient context.
27100+
╭─[typescript/tests/cases/conformance/parser/ecmascript5/VariableDeclarations/parserVariableDeclaration4.ts:2:4]
27101+
1 │ declare namespace M {
27102+
2 │ declare var v;
27103+
· ───────
27104+
3 │ }
27105+
╰────
27106+
2697327107
× Unexpected token
2697427108
╭─[typescript/tests/cases/conformance/parser/ecmascript5/VariableDeclarations/parserVariableDeclaration5.ts:1:7]
2697527109
1 │ var a,

0 commit comments

Comments
 (0)