feat(bindings/ts): Add transform/strip-only mode (#9138)

**Description:**

This PR adds `strip-only`/`transform` mode to `@swc/wasm-typescript`.

 - Both mode errors on decorator usages.
 - In `strip-only` mode, `enum` and TypeScript parameter properties are treated as an invalid syntax.
 - In `transform` mode, those are transpiled.
This commit is contained in:
Donny/강동윤 2024-07-04 17:51:01 +09:00 committed by GitHub
parent db5c25309b
commit a08bb46ebd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 147 additions and 35 deletions

View File

@ -13,9 +13,6 @@ bench = false
crate-type = ["cdylib"]
[features]
default = ["swc_v1"]
swc_v1 = []
swc_v2 = []
[dependencies]
anyhow = "1.0.66"

View File

@ -0,0 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`transform in strip-only mode should throw an error when it encounters an enum 1`] = `
"
x TypeScript enum is not supported in strip-only mode
,----
1 | enum Foo {}
: ^^^^^^^^^^^
\`----
"
`;
exports[`transform in strip-only mode should throw an error with a descriptive message when it encounters a decorator 1`] = `
"
x Decorators are not supported
,----
1 | class Foo { @decorator foo() {} }
: ^^^^^^^^^^
\`----
"
`;
exports[`transform in transform mode should transpile enum 1`] = `
"var Foo;
(function(Foo) {})(Foo || (Foo = {}));
"
`;
exports[`transform should strip types 1`] = `
"export const foo = 1;
"
`;

View File

@ -6,7 +6,7 @@ it("properly reports error", function () {
}).toThrow();
});
describe("trannsform", () => {
describe("transform", () => {
it("should strip types", async () => {
const { code } = await swc.transform(
`
@ -15,26 +15,36 @@ describe("trannsform", () => {
`,
{}
);
expect(code).toMatchInlineSnapshot(`
"export const foo = 1;
"
`);
expect(code).toMatchSnapshot();
});
it("should preserve enum", async () => {
const { code } = await swc.transform(
`
enum Foo {
Bar
}
`,
{}
);
await expect(code).toMatchInlineSnapshot(`
"enum Foo {
Bar
}
"
`);
describe("in strip-only mode", () => {
it("should throw an error when it encounters an enum", async () => {
await expect(
swc.transform("enum Foo {}", {
mode: "strip-only",
})
).rejects.toMatchSnapshot();
});
it('should throw an error with a descriptive message when it encounters a decorator', async () => {
await expect(
swc.transform("class Foo { @decorator foo() {} }", {
mode: "strip-only",
})
).rejects.toMatchSnapshot();
})
});
describe("in transform mode", () => {
it("should transpile enum", async () => {
const { code } = await swc.transform("enum Foo {}", {
mode: "transform",
});
expect(code).toMatchSnapshot();
});
});
});

View File

@ -1,5 +1,5 @@
{
"devDependencies": {
"jest": "^25.1.0"
"jest": "^29.7.0"
}
}

View File

@ -2,11 +2,14 @@ use anyhow::{Context, Error};
use serde::{Deserialize, Serialize};
use swc_core::{
common::{
comments::SingleThreadedComments, errors::ColorConfig, source_map::SourceMapGenConfig,
sync::Lrc, FileName, Mark, SourceMap, GLOBALS,
comments::SingleThreadedComments,
errors::{ColorConfig, HANDLER},
source_map::SourceMapGenConfig,
sync::Lrc,
FileName, Mark, SourceMap, Spanned, GLOBALS,
},
ecma::{
ast::{EsVersion, Program},
ast::{Decorator, EsVersion, Program, TsEnumDecl, TsParamPropParam},
codegen::text_writer::JsWriter,
parser::{
parse_file_as_module, parse_file_as_program, parse_file_as_script, Syntax, TsSyntax,
@ -18,9 +21,9 @@ use swc_core::{
hygiene::hygiene,
resolver,
},
typescript::strip_type,
typescript::{strip_type, typescript},
},
visit::VisitMutWith,
visit::{Visit, VisitMutWith, VisitWith},
},
};
use swc_error_reporters::handler::{try_with_handler, HandlerOpts};
@ -46,7 +49,7 @@ pub struct Options {
#[serde(default)]
pub filename: Option<String>,
#[serde(default)]
#[serde(default = "default_ts_syntax")]
pub parser: TsSyntax,
#[serde(default)]
@ -55,12 +58,31 @@ pub struct Options {
#[serde(default)]
pub source_maps: bool,
// #[serde(default)]
// pub transform: swc_core::ecma::transforms::typescript::Config,
#[serde(default)]
pub mode: Mode,
#[serde(default)]
pub transform: Option<swc_core::ecma::transforms::typescript::Config>,
#[serde(default)]
pub codegen: swc_core::ecma::codegen::Config,
}
fn default_ts_syntax() -> TsSyntax {
TsSyntax {
decorators: true,
..Default::default()
}
}
#[derive(Clone, Copy, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Mode {
#[default]
StripOnly,
Transform,
}
#[derive(Serialize)]
pub struct TransformOutput {
code: String,
@ -142,7 +164,6 @@ fn operate(input: String, options: Options) -> Result<TransformOutput, Error> {
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
HELPERS.set(&Helpers::new(options.external_helpers), || {
// Apply resolver
@ -150,7 +171,19 @@ fn operate(input: String, options: Options) -> Result<TransformOutput, Error> {
// Strip typescript types
program.visit_mut_with(&mut strip_type());
program.visit_with(&mut Validator { mode: options.mode });
match options.mode {
Mode::StripOnly => {
program.visit_mut_with(&mut strip_type());
}
Mode::Transform => {
program.visit_mut_with(&mut typescript(
options.transform.unwrap_or_default(),
top_level_mark,
));
}
}
// Apply external helpers
@ -217,3 +250,43 @@ impl SourceMapGenConfig for TsSourceMapGenConfig {
f.to_string()
}
}
struct Validator {
mode: Mode,
}
impl Visit for Validator {
fn visit_decorator(&mut self, n: &Decorator) {
HANDLER.with(|handler| {
handler.span_err(n.span, "Decorators are not supported");
});
}
fn visit_ts_enum_decl(&mut self, e: &TsEnumDecl) {
if matches!(self.mode, Mode::StripOnly) {
HANDLER.with(|handler| {
handler.span_err(
e.span,
"TypeScript enum is not supported in strip-only mode",
);
});
return;
}
e.visit_children_with(self);
}
fn visit_ts_param_prop_param(&mut self, n: &TsParamPropParam) {
if matches!(self.mode, Mode::StripOnly) {
HANDLER.with(|handler| {
handler.span_err(
n.span(),
"TypeScript parameter property is not supported in strip-only mode",
);
});
return;
}
n.visit_children_with(self);
}
}

View File

@ -6327,7 +6327,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "binding_typescript_wasm-2142ce@workspace:bindings/binding_typescript_wasm"
dependencies:
jest: "npm:^25.1.0"
jest: "npm:^29.7.0"
languageName: unknown
linkType: soft