Skip to content

Commit a72c6fa

Browse files
authored
feat(ts/fast-strip): Support type-only/uninstantiated namespaces (#9983)
**Related issue:** - Closes #9977
1 parent e9843d8 commit a72c6fa

File tree

14 files changed

+333
-58
lines changed

14 files changed

+333
-58
lines changed

.changeset/popular-spiders-bake.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
swc_core: patch
3+
swc_fast_ts_strip: patch
4+
---
5+
6+
feat(ts/fast-strip): Support type-only/uninstantiated namespaces

bindings/binding_typescript_wasm/__tests__/__snapshots__/transform.js.snap

+4-4
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ exports[`transform in strip-only mode should throw an error when it encounters a
122122
"code": "UnsupportedSyntax",
123123
"message": " x TypeScript namespace declaration is not supported in strip-only mode
124124
,----
125-
1 | module foo {}
126-
: ^^^^^^^^^^^^^
125+
1 | module foo { export const m = 1; }
126+
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
127127
\`----
128128
",
129129
}
@@ -134,8 +134,8 @@ exports[`transform in strip-only mode should throw an error when it encounters a
134134
"code": "UnsupportedSyntax",
135135
"message": " x TypeScript namespace declaration is not supported in strip-only mode
136136
,----
137-
1 | namespace Foo {}
138-
: ^^^^^^^^^^^^^^^^
137+
1 | namespace Foo { export const m = 1; }
138+
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
139139
\`----
140140
",
141141
}

bindings/binding_typescript_wasm/__tests__/transform.js

+32-30
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,32 @@ describe("transform", () => {
1313
export const foo: number = 1;
1414
type Foo = number;
1515
`,
16-
{}
16+
{},
1717
);
1818
expect(code).toMatchSnapshot();
1919
});
2020

2121
describe("in strip-only mode", () => {
2222
it("should remove declare enum", async () => {
2323
await expect(
24-
swc.transform(`declare enum Foo {}`, {})
24+
swc.transform(`declare enum Foo {}`, {}),
2525
).resolves.toMatchSnapshot();
2626
await expect(
2727
swc.transform(
2828
`declare enum Foo {
2929
A
3030
}`,
31-
{}
32-
)
31+
{},
32+
),
3333
).resolves.toMatchSnapshot();
3434
expect(
3535
swc.transform(
3636
`declare enum Foo {
3737
a = 2,
3838
b,
3939
}`,
40-
{}
41-
)
40+
{},
41+
),
4242
).resolves.toMatchSnapshot();
4343
});
4444

@@ -49,8 +49,8 @@ describe("transform", () => {
4949
type Foo = number;
5050
type Bar = string;
5151
const bar: Bar = "bar";`,
52-
{}
53-
)
52+
{},
53+
),
5454
).resolves.toMatchSnapshot();
5555
});
5656

@@ -59,8 +59,8 @@ describe("transform", () => {
5959
swc.transform(
6060
`const foo = 1;
6161
const bar: Bar = "bar";`,
62-
{}
63-
)
62+
{},
63+
),
6464
).resolves.toMatchSnapshot();
6565
});
6666

@@ -69,8 +69,8 @@ describe("transform", () => {
6969
swc.transform(
7070
`const foo = 1 as number;
7171
const bar = "bar";`,
72-
{}
73-
)
72+
{},
73+
),
7474
).resolves.toMatchSnapshot();
7575
});
7676

@@ -79,8 +79,8 @@ describe("transform", () => {
7979
swc.transform(
8080
`const foo = 1!;
8181
const bar = "bar";`,
82-
{}
83-
)
82+
{},
83+
),
8484
).resolves.toMatchSnapshot();
8585
});
8686

@@ -89,8 +89,8 @@ describe("transform", () => {
8989
swc.transform(
9090
`const foo = 1 satisfies number;
9191
const bar = "bar";`,
92-
{}
93-
)
92+
{},
93+
),
9494
).resolves.toMatchSnapshot();
9595
});
9696

@@ -102,59 +102,61 @@ describe("transform", () => {
102102
bar: "bar" as any as number,
103103
} satisfies number;
104104
const bar = "bar";`,
105-
{}
106-
)
105+
{},
106+
),
107107
).resolves.toMatchSnapshot();
108108
});
109109

110110
it("should throw an error when it encounters an enum", async () => {
111111
await expect(
112112
swc.transform("enum Foo {}", {
113113
mode: "strip-only",
114-
})
114+
}),
115115
).rejects.toMatchSnapshot();
116116
});
117117

118118
it("should throw an error when it encounters a namespace", async () => {
119119
await expect(
120-
swc.transform("namespace Foo {}", {
120+
swc.transform("namespace Foo { export const m = 1; }", {
121121
mode: "strip-only",
122-
})
122+
}),
123123
).rejects.toMatchSnapshot();
124124
});
125125

126126
it("should throw an error when it encounters a module", async () => {
127127
await expect(
128-
swc.transform("module foo {}", {
128+
swc.transform("module foo { export const m = 1; }", {
129129
mode: "strip-only",
130-
})
130+
}),
131131
).rejects.toMatchSnapshot();
132132
});
133133

134134
it("should not emit 'Caused by: failed to parse'", async () => {
135135
await expect(
136136
swc.transform("function foo() { await Promise.resolve(1); }", {
137137
mode: "strip-only",
138-
})
138+
}),
139139
).rejects.toMatchSnapshot();
140140
});
141141

142-
143142
it("should report correct error for syntax error", async () => {
144143
await expect(
145144
swc.transform("function foo() { invalid syntax }", {
146145
mode: "strip-only",
147-
})
146+
}),
148147
).rejects.toMatchSnapshot();
149148
});
150149

151150
it("should report correct error for unsupported syntax", async () => {
152151
await expect(
153-
swc.transform(`enum Foo {
152+
swc.transform(
153+
`enum Foo {
154154
a, b
155-
}`, {
156-
mode: "strip-only",
157-
})
155+
}`,
156+
{
157+
mode: "strip-only",
158+
},
159+
),
158160
).rejects.toMatchSnapshot();
159161
});
160162
});

crates/swc_fast_ts_strip/src/lib.rs

+67-12
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ use swc_ecma_ast::{
1414
ArrayPat, ArrowExpr, AutoAccessor, BindingIdent, Class, ClassDecl, ClassMethod, ClassProp,
1515
Constructor, Decl, DefaultDecl, DoWhileStmt, EsVersion, ExportAll, ExportDecl,
1616
ExportDefaultDecl, ExportSpecifier, FnDecl, ForInStmt, ForOfStmt, ForStmt, GetterProp, IfStmt,
17-
ImportDecl, ImportSpecifier, NamedExport, ObjectPat, Param, Pat, PrivateMethod, PrivateProp,
18-
Program, ReturnStmt, SetterProp, Stmt, ThrowStmt, TsAsExpr, TsConstAssertion, TsEnumDecl,
19-
TsExportAssignment, TsImportEqualsDecl, TsIndexSignature, TsInstantiation, TsModuleDecl,
20-
TsModuleName, TsNamespaceDecl, TsNonNullExpr, TsParamPropParam, TsSatisfiesExpr,
21-
TsTypeAliasDecl, TsTypeAnn, TsTypeAssertion, TsTypeParamDecl, TsTypeParamInstantiation,
22-
VarDeclarator, WhileStmt, YieldExpr,
17+
ImportDecl, ImportSpecifier, ModuleDecl, ModuleItem, NamedExport, ObjectPat, Param, Pat,
18+
PrivateMethod, PrivateProp, Program, ReturnStmt, SetterProp, Stmt, ThrowStmt, TsAsExpr,
19+
TsConstAssertion, TsEnumDecl, TsExportAssignment, TsImportEqualsDecl, TsIndexSignature,
20+
TsInstantiation, TsModuleDecl, TsModuleName, TsNamespaceBody, TsNamespaceDecl, TsNonNullExpr,
21+
TsParamPropParam, TsSatisfiesExpr, TsTypeAliasDecl, TsTypeAnn, TsTypeAssertion,
22+
TsTypeParamDecl, TsTypeParamInstantiation, VarDeclarator, WhileStmt, YieldExpr,
2323
};
2424
use swc_ecma_parser::{
2525
lexer::Lexer,
@@ -1034,7 +1034,7 @@ impl Visit for TsStrip {
10341034
}
10351035

10361036
fn visit_export_decl(&mut self, n: &ExportDecl) {
1037-
if n.decl.is_ts_declare() {
1037+
if n.decl.is_ts_declare() || n.decl.is_uninstantiated() {
10381038
self.add_replacement(n.span);
10391039
self.fix_asi(n.span);
10401040
return;
@@ -1044,7 +1044,7 @@ impl Visit for TsStrip {
10441044
}
10451045

10461046
fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) {
1047-
if n.decl.is_ts_declare() {
1047+
if n.decl.is_ts_declare() || n.decl.is_uninstantiated() {
10481048
self.add_replacement(n.span);
10491049
self.fix_asi(n.span);
10501050
return;
@@ -1054,7 +1054,7 @@ impl Visit for TsStrip {
10541054
}
10551055

10561056
fn visit_decl(&mut self, n: &Decl) {
1057-
if n.is_ts_declare() {
1057+
if n.is_ts_declare() || n.is_uninstantiated() {
10581058
self.add_replacement(n.span());
10591059
self.fix_asi(n.span());
10601060
return;
@@ -1393,7 +1393,7 @@ trait IsTsDecl {
13931393
impl IsTsDecl for Decl {
13941394
fn is_ts_declare(&self) -> bool {
13951395
match self {
1396-
Self::TsInterface { .. } | Self::TsTypeAlias(..) => true,
1396+
Self::TsInterface(..) | Self::TsTypeAlias(..) => true,
13971397

13981398
Self::TsModule(module) => module.declare || matches!(module.id, TsModuleName::Str(..)),
13991399
Self::TsEnum(ref r#enum) => r#enum.declare,
@@ -1419,12 +1419,67 @@ impl IsTsDecl for DefaultDecl {
14191419
fn is_ts_declare(&self) -> bool {
14201420
match self {
14211421
Self::Class(..) => false,
1422-
DefaultDecl::Fn(r#fn) => r#fn.function.body.is_none(),
1423-
DefaultDecl::TsInterfaceDecl(..) => true,
1422+
Self::Fn(r#fn) => r#fn.function.body.is_none(),
1423+
Self::TsInterfaceDecl(..) => true,
14241424
}
14251425
}
14261426
}
14271427

1428+
trait IsUninstantiated {
1429+
fn is_uninstantiated(&self) -> bool;
1430+
}
1431+
1432+
impl IsUninstantiated for TsNamespaceBody {
1433+
fn is_uninstantiated(&self) -> bool {
1434+
match self {
1435+
Self::TsModuleBlock(block) => {
1436+
block.body.iter().all(IsUninstantiated::is_uninstantiated)
1437+
}
1438+
Self::TsNamespaceDecl(decl) => decl.body.is_uninstantiated(),
1439+
}
1440+
}
1441+
}
1442+
1443+
impl IsUninstantiated for ModuleItem {
1444+
fn is_uninstantiated(&self) -> bool {
1445+
match self {
1446+
Self::Stmt(stmt) => stmt.is_uninstantiated(),
1447+
Self::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, .. })) => {
1448+
decl.is_uninstantiated()
1449+
}
1450+
_ => false,
1451+
}
1452+
}
1453+
}
1454+
1455+
impl IsUninstantiated for Stmt {
1456+
fn is_uninstantiated(&self) -> bool {
1457+
matches!(self, Self::Decl(decl) if decl.is_uninstantiated())
1458+
}
1459+
}
1460+
1461+
impl IsUninstantiated for TsModuleDecl {
1462+
fn is_uninstantiated(&self) -> bool {
1463+
matches!(&self.body, Some(body) if body.is_uninstantiated())
1464+
}
1465+
}
1466+
1467+
impl IsUninstantiated for Decl {
1468+
fn is_uninstantiated(&self) -> bool {
1469+
match self {
1470+
Self::TsInterface(..) | Self::TsTypeAlias(..) => true,
1471+
Self::TsModule(module) => module.is_uninstantiated(),
1472+
_ => false,
1473+
}
1474+
}
1475+
}
1476+
1477+
impl IsUninstantiated for DefaultDecl {
1478+
fn is_uninstantiated(&self) -> bool {
1479+
matches!(self, Self::TsInterfaceDecl(..))
1480+
}
1481+
}
1482+
14281483
trait U8Helper {
14291484
fn is_utf8_char_boundary(&self) -> bool;
14301485
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
x TypeScript parameter property is not supported in strip-only mode
2+
,-[6:1]
3+
5 | // No parameter properties
4+
6 | constructor(public foo: string) { }
5+
: ^^^^^^^^^^^
6+
7 | }
7+
`----
8+
x TypeScript namespace declaration is not supported in strip-only mode
9+
,-[9:1]
10+
8 |
11+
9 | ,-> namespace IllegalBecauseInstantiated {
12+
10 | | export const m = 1;
13+
11 | `-> }
14+
`----
15+
x TypeScript namespace declaration is not supported in strip-only mode
16+
,-[13:1]
17+
12 |
18+
13 | ,-> namespace AlsoIllegalBecauseInstantiated {
19+
14 | | class PrivateClass {
20+
15 | |
21+
16 | | }
22+
17 | `-> }
23+
`----
24+
x TypeScript namespace declaration is not supported in strip-only mode
25+
,-[19:1]
26+
18 |
27+
19 | ,-> namespace IllegalBecauseNestedInstantiated {
28+
20 | | namespace Nested {
29+
21 | | export const m = 1;
30+
22 | | }
31+
23 | `-> }
32+
`----
33+
x TypeScript enum is not supported in strip-only mode
34+
,-[25:1]
35+
24 |
36+
25 | ,-> enum NotLegalEnum {
37+
26 | | B = 1
38+
27 | `-> }
39+
`----
40+
x TypeScript import equals declaration is not supported in strip-only mode
41+
,-[29:1]
42+
28 |
43+
29 | import NoGoodAlias = NotLegalEnum.B;
44+
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45+
`----
46+
x TypeScript enum is not supported in strip-only mode
47+
,-[31:1]
48+
30 |
49+
31 | ,-> const enum NotLegalConstEnum {
50+
32 | | C = 2
51+
33 | `-> }
52+
`----

0 commit comments

Comments
 (0)