Skip to content

Commit f23f1c6

Browse files
committed
Add support for import attributes in ambient module declarations
Enables syntax: declare module "*.css" with { type: "css" } { ... } Reuses existing ImportAttributes infrastructure from import statements.
1 parent d9d9eea commit f23f1c6

File tree

12 files changed

+286
-10
lines changed

12 files changed

+286
-10
lines changed

src/compiler/emitter.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3635,6 +3635,11 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
36353635
}
36363636
emit(node.name);
36373637

3638+
if (node.withClause) {
3639+
writeSpace();
3640+
emit(node.withClause);
3641+
}
3642+
36383643
let body = node.body;
36393644
if (!body) return writeTrailingSemicolon();
36403645
while (body && isModuleDeclaration(body)) {

src/compiler/factory/nodeFactory.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4546,12 +4546,14 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
45464546
name: ModuleName,
45474547
body: ModuleBody | undefined,
45484548
flags = NodeFlags.None,
4549+
withClause?: ImportAttributes,
45494550
) {
45504551
const node = createBaseDeclaration<ModuleDeclaration>(SyntaxKind.ModuleDeclaration);
45514552
node.modifiers = asNodeArray(modifiers);
45524553
node.flags |= flags & (NodeFlags.Namespace | NodeFlags.NestedNamespace | NodeFlags.GlobalAugmentation);
45534554
node.name = name;
45544555
node.body = body;
4556+
node.withClause = withClause;
45554557
if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) {
45564558
node.transformFlags = TransformFlags.ContainsTypeScript;
45574559
}
@@ -4575,11 +4577,13 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
45754577
modifiers: readonly ModifierLike[] | undefined,
45764578
name: ModuleName,
45774579
body: ModuleBody | undefined,
4580+
withClause?: ImportAttributes,
45784581
) {
45794582
return node.modifiers !== modifiers
45804583
|| node.name !== name
45814584
|| node.body !== body
4582-
? update(createModuleDeclaration(modifiers, name, body, node.flags), node)
4585+
|| node.withClause !== withClause
4586+
? update(createModuleDeclaration(modifiers, name, body, node.flags, withClause), node)
45834587
: node;
45844588
}
45854589

@@ -7092,7 +7096,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
70927096
isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) :
70937097
isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, modifierArray, node.name, node.typeParameters, node.type) :
70947098
isEnumDeclaration(node) ? updateEnumDeclaration(node, modifierArray, node.name, node.members) :
7095-
isModuleDeclaration(node) ? updateModuleDeclaration(node, modifierArray, node.name, node.body) :
7099+
isModuleDeclaration(node) ? updateModuleDeclaration(node, modifierArray, node.name, node.body, node.withClause) :
70967100
isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, modifierArray, node.isTypeOnly, node.name, node.moduleReference) :
70977101
isImportDeclaration(node) ? updateImportDeclaration(node, modifierArray, node.importClause, node.moduleSpecifier, node.attributes) :
70987102
isExportAssignment(node) ? updateExportAssignment(node, modifierArray, node.expression) :

src/compiler/parser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8324,14 +8324,15 @@ namespace Parser {
83248324
name = parseLiteralNode() as StringLiteral;
83258325
name.text = internIdentifier(name.text);
83268326
}
8327+
const withClause = tryParseImportAttributes();
83278328
let body: ModuleBlock | undefined;
83288329
if (token() === SyntaxKind.OpenBraceToken) {
83298330
body = parseModuleBlock();
83308331
}
83318332
else {
83328333
parseSemicolon();
83338334
}
8334-
const node = factory.createModuleDeclaration(modifiersIn, name, body, flags);
8335+
const node = factory.createModuleDeclaration(modifiersIn, name, body, flags, withClause);
83358336
return withJSDoc(finishNode(node, pos), hasJSDoc);
83368337
}
83378338

src/compiler/transformers/declarations.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1360,7 +1360,7 @@ export function transformDeclarations(context: TransformationContext): Transform
13601360
name: ModuleName,
13611361
body: ModuleBody | undefined,
13621362
) {
1363-
const updated = factory.updateModuleDeclaration(node, modifiers, name, body);
1363+
const updated = factory.updateModuleDeclaration(node, modifiers, name, body, node.withClause);
13641364

13651365
if (isAmbientModule(updated) || updated.flags & NodeFlags.Namespace) {
13661366
return updated;
@@ -1371,6 +1371,7 @@ export function transformDeclarations(context: TransformationContext): Transform
13711371
updated.name,
13721372
updated.body,
13731373
updated.flags | NodeFlags.Namespace,
1374+
updated.withClause,
13741375
);
13751376

13761377
setOriginalNode(fixed, updated);
@@ -1512,6 +1513,7 @@ export function transformDeclarations(context: TransformationContext): Transform
15121513
modifiers,
15131514
namespaceDecl.name,
15141515
namespaceDecl.body,
1516+
namespaceDecl.withClause,
15151517
);
15161518

15171519
const exportDefaultDeclaration = factory.createExportAssignment(

src/compiler/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3634,6 +3634,7 @@ export interface ModuleDeclaration extends DeclarationStatement, JSDocContainer,
36343634
readonly modifiers?: NodeArray<ModifierLike>;
36353635
readonly name: ModuleName;
36363636
readonly body?: ModuleBody | JSDocNamespaceDeclaration;
3637+
readonly withClause?: ImportAttributes;
36373638
}
36383639

36393640
export type NamespaceBody =
@@ -3749,7 +3750,7 @@ export interface ImportAttribute extends Node {
37493750
export interface ImportAttributes extends Node {
37503751
readonly token: SyntaxKind.WithKeyword | SyntaxKind.AssertKeyword;
37513752
readonly kind: SyntaxKind.ImportAttributes;
3752-
readonly parent: ImportDeclaration | ExportDeclaration;
3753+
readonly parent: ImportDeclaration | ExportDeclaration | ModuleDeclaration;
37533754
readonly elements: NodeArray<ImportAttribute>;
37543755
readonly multiLine?: boolean;
37553756
}
@@ -9062,8 +9063,8 @@ export interface NodeFactory {
90629063
updateTypeAliasDeclaration(node: TypeAliasDeclaration, modifiers: readonly ModifierLike[] | undefined, name: Identifier, typeParameters: readonly TypeParameterDeclaration[] | undefined, type: TypeNode): TypeAliasDeclaration;
90639064
createEnumDeclaration(modifiers: readonly ModifierLike[] | undefined, name: string | Identifier, members: readonly EnumMember[]): EnumDeclaration;
90649065
updateEnumDeclaration(node: EnumDeclaration, modifiers: readonly ModifierLike[] | undefined, name: Identifier, members: readonly EnumMember[]): EnumDeclaration;
9065-
createModuleDeclaration(modifiers: readonly ModifierLike[] | undefined, name: ModuleName, body: ModuleBody | undefined, flags?: NodeFlags): ModuleDeclaration;
9066-
updateModuleDeclaration(node: ModuleDeclaration, modifiers: readonly ModifierLike[] | undefined, name: ModuleName, body: ModuleBody | undefined): ModuleDeclaration;
9066+
createModuleDeclaration(modifiers: readonly ModifierLike[] | undefined, name: ModuleName, body: ModuleBody | undefined, flags?: NodeFlags, withClause?: ImportAttributes): ModuleDeclaration;
9067+
updateModuleDeclaration(node: ModuleDeclaration, modifiers: readonly ModifierLike[] | undefined, name: ModuleName, body: ModuleBody | undefined, withClause?: ImportAttributes): ModuleDeclaration;
90679068
createModuleBlock(statements: readonly Statement[]): ModuleBlock;
90689069
updateModuleBlock(node: ModuleBlock, statements: readonly Statement[]): ModuleBlock;
90699070
createCaseBlock(clauses: readonly CaseOrDefaultClause[]): CaseBlock;

src/compiler/visitorPublic.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,6 +1486,7 @@ const visitEachChildTable: VisitEachChildTable = {
14861486
nodesVisitor(node.modifiers, visitor, isModifierLike),
14871487
Debug.checkDefined(nodeVisitor(node.name, visitor, isModuleName)),
14881488
nodeVisitor(node.body, visitor, isModuleBody),
1489+
nodeVisitor(node.withClause, visitor, isImportAttributes),
14891490
);
14901491
},
14911492

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
ambientModuleWithImportAttributes.ts(2,16): error TS2664: Invalid module name in augmentation, module '*.css' cannot be found.
2+
ambientModuleWithImportAttributes.ts(7,16): error TS2664: Invalid module name in augmentation, module '*.json' cannot be found.
3+
ambientModuleWithImportAttributes.ts(13,16): error TS2664: Invalid module name in augmentation, module 'my-module' cannot be found.
4+
ambientModuleWithImportAttributes.ts(19,16): error TS2664: Invalid module name in augmentation, module 'multi-attr' cannot be found.
5+
ambientModuleWithImportAttributes.ts(24,16): error TS2664: Invalid module name in augmentation, module 'regular-module' cannot be found.
6+
7+
8+
==== ambientModuleWithImportAttributes.ts (5 errors) ====
9+
// Ambient module declaration with import attributes
10+
declare module "*.css" with { type: "css" } {
11+
~~~~~~~
12+
!!! error TS2664: Invalid module name in augmentation, module '*.css' cannot be found.
13+
const stylesheet: CSSStyleSheet;
14+
export default stylesheet;
15+
}
16+
17+
declare module "*.json" with { type: "json" } {
18+
~~~~~~~~
19+
!!! error TS2664: Invalid module name in augmentation, module '*.json' cannot be found.
20+
const data: any;
21+
export default data;
22+
}
23+
24+
// Ambient module with specific name and import attributes
25+
declare module "my-module" with { type: "custom" } {
26+
~~~~~~~~~~~
27+
!!! error TS2664: Invalid module name in augmentation, module 'my-module' cannot be found.
28+
export function foo(): void;
29+
export const bar: string;
30+
}
31+
32+
// Ambient module with multiple attributes
33+
declare module "multi-attr" with { type: "json", integrity: "sha384-..." } {
34+
~~~~~~~~~~~~
35+
!!! error TS2664: Invalid module name in augmentation, module 'multi-attr' cannot be found.
36+
export const value: number;
37+
}
38+
39+
// Ambient module without import attributes (should still work)
40+
declare module "regular-module" {
41+
~~~~~~~~~~~~~~~~
42+
!!! error TS2664: Invalid module name in augmentation, module 'regular-module' cannot be found.
43+
export function baz(): void;
44+
}
45+
46+
export {};
47+
48+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//// [tests/cases/conformance/ambient/ambientModuleWithImportAttributes.ts] ////
2+
3+
//// [ambientModuleWithImportAttributes.ts]
4+
// Ambient module declaration with import attributes
5+
declare module "*.css" with { type: "css" } {
6+
const stylesheet: CSSStyleSheet;
7+
export default stylesheet;
8+
}
9+
10+
declare module "*.json" with { type: "json" } {
11+
const data: any;
12+
export default data;
13+
}
14+
15+
// Ambient module with specific name and import attributes
16+
declare module "my-module" with { type: "custom" } {
17+
export function foo(): void;
18+
export const bar: string;
19+
}
20+
21+
// Ambient module with multiple attributes
22+
declare module "multi-attr" with { type: "json", integrity: "sha384-..." } {
23+
export const value: number;
24+
}
25+
26+
// Ambient module without import attributes (should still work)
27+
declare module "regular-module" {
28+
export function baz(): void;
29+
}
30+
31+
export {};
32+
33+
34+
35+
//// [ambientModuleWithImportAttributes.js]
36+
export {};
37+
38+
39+
//// [ambientModuleWithImportAttributes.d.ts]
40+
declare module "*.css" with { type: "css" } {
41+
const stylesheet: CSSStyleSheet;
42+
export default stylesheet;
43+
}
44+
declare module "*.json" with { type: "json" } {
45+
const data: any;
46+
export default data;
47+
}
48+
declare module "my-module" with { type: "custom" } {
49+
function foo(): void;
50+
const bar: string;
51+
}
52+
declare module "multi-attr" with { type: "json", integrity: "sha384-..." } {
53+
const value: number;
54+
}
55+
declare module "regular-module" {
56+
function baz(): void;
57+
}
58+
export {};
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//// [tests/cases/conformance/ambient/ambientModuleWithImportAttributes.ts] ////
2+
3+
=== ambientModuleWithImportAttributes.ts ===
4+
// Ambient module declaration with import attributes
5+
declare module "*.css" with { type: "css" } {
6+
>"*.css" : Symbol("*.css", Decl(ambientModuleWithImportAttributes.ts, 0, 0))
7+
8+
const stylesheet: CSSStyleSheet;
9+
>stylesheet : Symbol(stylesheet, Decl(ambientModuleWithImportAttributes.ts, 2, 9))
10+
>CSSStyleSheet : Symbol(CSSStyleSheet, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
11+
12+
export default stylesheet;
13+
>stylesheet : Symbol(stylesheet, Decl(ambientModuleWithImportAttributes.ts, 2, 9))
14+
}
15+
16+
declare module "*.json" with { type: "json" } {
17+
>"*.json" : Symbol("*.json", Decl(ambientModuleWithImportAttributes.ts, 4, 1))
18+
19+
const data: any;
20+
>data : Symbol(data, Decl(ambientModuleWithImportAttributes.ts, 7, 9))
21+
22+
export default data;
23+
>data : Symbol(data, Decl(ambientModuleWithImportAttributes.ts, 7, 9))
24+
}
25+
26+
// Ambient module with specific name and import attributes
27+
declare module "my-module" with { type: "custom" } {
28+
>"my-module" : Symbol("my-module", Decl(ambientModuleWithImportAttributes.ts, 9, 1))
29+
30+
export function foo(): void;
31+
>foo : Symbol(foo, Decl(ambientModuleWithImportAttributes.ts, 12, 52))
32+
33+
export const bar: string;
34+
>bar : Symbol(bar, Decl(ambientModuleWithImportAttributes.ts, 14, 16))
35+
}
36+
37+
// Ambient module with multiple attributes
38+
declare module "multi-attr" with { type: "json", integrity: "sha384-..." } {
39+
>"multi-attr" : Symbol("multi-attr", Decl(ambientModuleWithImportAttributes.ts, 15, 1))
40+
41+
export const value: number;
42+
>value : Symbol(value, Decl(ambientModuleWithImportAttributes.ts, 19, 16))
43+
}
44+
45+
// Ambient module without import attributes (should still work)
46+
declare module "regular-module" {
47+
>"regular-module" : Symbol("regular-module", Decl(ambientModuleWithImportAttributes.ts, 20, 1))
48+
49+
export function baz(): void;
50+
>baz : Symbol(baz, Decl(ambientModuleWithImportAttributes.ts, 23, 33))
51+
}
52+
53+
export {};
54+
55+
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//// [tests/cases/conformance/ambient/ambientModuleWithImportAttributes.ts] ////
2+
3+
=== ambientModuleWithImportAttributes.ts ===
4+
// Ambient module declaration with import attributes
5+
declare module "*.css" with { type: "css" } {
6+
>"*.css" : typeof import("*.css")
7+
> : ^^^^^^^^^^^^^^^^^^^^^^
8+
9+
const stylesheet: CSSStyleSheet;
10+
>stylesheet : CSSStyleSheet
11+
> : ^^^^^^^^^^^^^
12+
13+
export default stylesheet;
14+
>stylesheet : CSSStyleSheet
15+
> : ^^^^^^^^^^^^^
16+
}
17+
18+
declare module "*.json" with { type: "json" } {
19+
>"*.json" : typeof import("*.json")
20+
> : ^^^^^^^^^^^^^^^^^^^^^^^
21+
22+
const data: any;
23+
>data : any
24+
> : ^^^
25+
26+
export default data;
27+
>data : any
28+
> : ^^^
29+
}
30+
31+
// Ambient module with specific name and import attributes
32+
declare module "my-module" with { type: "custom" } {
33+
>"my-module" : typeof import("my-module")
34+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^
35+
36+
export function foo(): void;
37+
>foo : () => void
38+
> : ^^^^^^
39+
40+
export const bar: string;
41+
>bar : string
42+
> : ^^^^^^
43+
}
44+
45+
// Ambient module with multiple attributes
46+
declare module "multi-attr" with { type: "json", integrity: "sha384-..." } {
47+
>"multi-attr" : typeof import("multi-attr")
48+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
49+
50+
export const value: number;
51+
>value : number
52+
> : ^^^^^^
53+
}
54+
55+
// Ambient module without import attributes (should still work)
56+
declare module "regular-module" {
57+
>"regular-module" : typeof import("regular-module")
58+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
59+
60+
export function baz(): void;
61+
>baz : () => void
62+
> : ^^^^^^
63+
}
64+
65+
export {};
66+
67+

0 commit comments

Comments
 (0)