Skip to content

Commit 740bd57

Browse files
authored
feat(ts/fast-strip): Emit json errors (#10144)
**Description:** - Improves span for `swc_fast_ts_strip`. - Add `try_with_json_handler` to `swc_error_reporters`. - `@swc/wasm-typescript` now throws a string separated by `\n`. **Related issue:** - Closes #9884
1 parent 696f053 commit 740bd57

23 files changed

+329
-145
lines changed

.changeset/wild-roses-taste.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
swc_core: minor
3+
swc_fast_ts_strip: minor
4+
swc_error_reporters: minor
5+
---
6+
7+
feat(ts/fast-strip): Emit json errors

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 23 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`transform in strip-only mode should not emit 'Caused by: failed to parse' 1`] = `
4-
{
5-
"code": "InvalidSyntax",
6-
"message": " x await isn't allowed in non-async function
7-
,----
8-
1 | function foo() { await Promise.resolve(1); }
9-
: ^^^^^^^
10-
\`----
11-
",
12-
}
4+
"{"code":"InvalidSyntax","message":"await isn't allowed in non-async function","snippet":"Promise","filename":"test.ts","line":1,"column":23}
5+
"
136
`;
147

158
exports[`transform in strip-only mode should remove declare enum 1`] = `
@@ -39,29 +32,13 @@ exports[`transform in strip-only mode should remove declare enum 3`] = `
3932
`;
4033

4134
exports[`transform in strip-only mode should report correct error for syntax error 1`] = `
42-
{
43-
"code": "InvalidSyntax",
44-
"message": " x Expected ';', '}' or <eof>
45-
,----
46-
1 | function foo() { invalid syntax }
47-
: ^^^|^^^ ^^^^^^
48-
: \`-- This is the expression part of an expression statement
49-
\`----
50-
",
51-
}
35+
"{"code":"InvalidSyntax","message":"Expected ';', '}' or <eof>","snippet":"syntax","filename":"test.ts","line":1,"column":25}
36+
"
5237
`;
5338

5439
exports[`transform in strip-only mode should report correct error for unsupported syntax 1`] = `
55-
{
56-
"code": "UnsupportedSyntax",
57-
"message": " x TypeScript enum is not supported in strip-only mode
58-
,-[1:1]
59-
1 | ,-> enum Foo {
60-
2 | | a, b
61-
3 | \`-> }
62-
\`----
63-
",
64-
}
40+
"{"code":"UnsupportedSyntax","message":"TypeScript enum is not supported in strip-only mode","snippet":"enum Foo {\\n a, b \\n }","filename":"test.ts","line":1,"column":0}
41+
"
6542
`;
6643

6744
exports[`transform in strip-only mode should strip complex expressions 1`] = `
@@ -118,75 +95,38 @@ exports[`transform in strip-only mode should strip type declarations 1`] = `
11895
`;
11996

12097
exports[`transform in strip-only mode should throw an error when it encounters a module 1`] = `
121-
{
122-
"code": "UnsupportedSyntax",
123-
"message": " x \`module\` keyword is not supported. Use \`namespace\` instead.
124-
,----
125-
1 | module foo { }
126-
: ^^^^^^
127-
\`----
128-
",
129-
}
98+
"{"code":"UnsupportedSyntax","message":"\`module\` keyword is not supported. Use \`namespace\` instead.","snippet":"module foo","filename":"test.ts","line":1,"column":0}
99+
"
130100
`;
131101

132102
exports[`transform in strip-only mode should throw an error when it encounters a module 2`] = `
133-
{
134-
"code": "UnsupportedSyntax",
135-
"message": " x \`module\` keyword is not supported. Use \`namespace\` instead.
136-
,----
137-
1 | declare module foo { }
138-
: ^^^^^^
139-
\`----
140-
",
141-
}
103+
"{"code":"UnsupportedSyntax","message":"\`module\` keyword is not supported. Use \`namespace\` instead.","snippet":"module foo","filename":"test.ts","line":1,"column":8}
104+
"
142105
`;
143106

144107
exports[`transform in strip-only mode should throw an error when it encounters a namespace 1`] = `
145-
{
146-
"code": "UnsupportedSyntax",
147-
"message": " x TypeScript namespace declaration is not supported in strip-only mode
148-
,----
149-
1 | namespace Foo { export const m = 1; }
150-
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
151-
\`----
152-
",
153-
}
108+
"{"code":"UnsupportedSyntax","message":"TypeScript namespace declaration is not supported in strip-only mode","snippet":"namespace Foo { export const m = 1; }","filename":"test.ts","line":1,"column":0}
109+
"
154110
`;
155111

156112
exports[`transform in strip-only mode should throw an error when it encounters an enum 1`] = `
157-
{
158-
"code": "UnsupportedSyntax",
159-
"message": " x TypeScript enum is not supported in strip-only mode
160-
,----
161-
1 | enum Foo {}
162-
: ^^^^^^^^^^^
163-
\`----
164-
",
165-
}
113+
"{"code":"UnsupportedSyntax","message":"TypeScript enum is not supported in strip-only mode","snippet":"enum Foo {}","filename":"test.ts","line":1,"column":0}
114+
"
115+
`;
116+
117+
exports[`transform in transform mode shoud throw an object even with deprecatedTsModuleAsError = true 1`] = `
118+
"{"code":"UnsupportedSyntax","message":"\`module\` keyword is not supported. Use \`namespace\` instead.","snippet":"module F","filename":"<anon>","line":1,"column":0}
119+
"
166120
`;
167121

168122
exports[`transform in transform mode should throw an error when it encounters a declared module 1`] = `
169-
{
170-
"code": "UnsupportedSyntax",
171-
"message": " x \`module\` keyword is not supported. Use \`namespace\` instead.
172-
,----
173-
1 | declare module foo { }
174-
: ^^^^^^
175-
\`----
176-
",
177-
}
123+
"{"code":"UnsupportedSyntax","message":"\`module\` keyword is not supported. Use \`namespace\` instead.","snippet":"module foo","filename":"test.ts","line":1,"column":8}
124+
"
178125
`;
179126

180127
exports[`transform in transform mode should throw an error when it encounters a module 1`] = `
181-
{
182-
"code": "UnsupportedSyntax",
183-
"message": " x \`module\` keyword is not supported. Use \`namespace\` instead.
184-
,----
185-
1 | module foo { }
186-
: ^^^^^^
187-
\`----
188-
",
189-
}
128+
"{"code":"UnsupportedSyntax","message":"\`module\` keyword is not supported. Use \`namespace\` instead.","snippet":"module foo","filename":"test.ts","line":1,"column":0}
129+
"
190130
`;
191131

192132
exports[`transform should strip types 1`] = `

bindings/binding_typescript_wasm/__tests__/transform.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ describe("transform", () => {
111111
await expect(
112112
swc.transform("enum Foo {}", {
113113
mode: "strip-only",
114+
filename: "test.ts",
114115
}),
115116
).rejects.toMatchSnapshot();
116117
});
@@ -119,6 +120,7 @@ describe("transform", () => {
119120
await expect(
120121
swc.transform("namespace Foo { export const m = 1; }", {
121122
mode: "strip-only",
123+
filename: "test.ts",
122124
}),
123125
).rejects.toMatchSnapshot();
124126
});
@@ -128,6 +130,7 @@ describe("transform", () => {
128130
swc.transform("module foo { }", {
129131
mode: "strip-only",
130132
deprecatedTsModuleAsError: true,
133+
filename: "test.ts",
131134
}),
132135
).rejects.toMatchSnapshot();
133136
});
@@ -137,13 +140,15 @@ describe("transform", () => {
137140
swc.transform("declare module foo { }", {
138141
mode: "strip-only",
139142
deprecatedTsModuleAsError: true,
143+
filename: "test.ts",
140144
}),
141145
).rejects.toMatchSnapshot();
142146
});
143147

144148
it("should not emit 'Caused by: failed to parse'", async () => {
145149
await expect(
146150
swc.transform("function foo() { await Promise.resolve(1); }", {
151+
filename: "test.ts",
147152
mode: "strip-only",
148153
}),
149154
).rejects.toMatchSnapshot();
@@ -153,6 +158,7 @@ describe("transform", () => {
153158
await expect(
154159
swc.transform("function foo() { invalid syntax }", {
155160
mode: "strip-only",
161+
filename: "test.ts"
156162
}),
157163
).rejects.toMatchSnapshot();
158164
});
@@ -165,6 +171,7 @@ describe("transform", () => {
165171
}`,
166172
{
167173
mode: "strip-only",
174+
filename: "test.ts"
168175
},
169176
),
170177
).rejects.toMatchSnapshot();
@@ -177,6 +184,7 @@ describe("transform", () => {
177184
swc.transform("module foo { }", {
178185
mode: "transform",
179186
deprecatedTsModuleAsError: true,
187+
filename: "test.ts"
180188
}),
181189
).rejects.toMatchSnapshot();
182190
});
@@ -186,6 +194,7 @@ describe("transform", () => {
186194
swc.transform("declare module foo { }", {
187195
mode: "transform",
188196
deprecatedTsModuleAsError: true,
197+
filename: "test.ts",
189198
}),
190199
).rejects.toMatchSnapshot();
191200
});
@@ -196,9 +205,7 @@ describe("transform", () => {
196205
mode: "transform",
197206
deprecatedTsModuleAsError: true,
198207
}),
199-
).rejects.toMatchObject({
200-
code: "UnsupportedSyntax",
201-
});
208+
).rejects.toMatchSnapshot();
202209
})
203210
});
204211
});

bindings/binding_typescript_wasm/src/lib.rs

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use anyhow::Error;
22
use js_sys::Uint8Array;
3-
use serde::Serialize;
43
use swc_common::{errors::ColorConfig, sync::Lrc, SourceMap, GLOBALS};
5-
use swc_error_reporters::handler::{try_with_handler, HandlerOpts};
6-
use swc_fast_ts_strip::{ErrorCode, Options, TransformOutput, TsError};
4+
use swc_error_reporters::handler::{try_with_json_handler, HandlerOpts};
5+
use swc_fast_ts_strip::{Options, TransformOutput};
76
use wasm_bindgen::prelude::*;
87
use wasm_bindgen_futures::{future_to_promise, js_sys::Promise};
98

@@ -54,32 +53,18 @@ pub fn transform_sync(input: JsValue, options: JsValue) -> Result<JsValue, JsVal
5453
fn operate(input: String, options: Options) -> Result<TransformOutput, Error> {
5554
let cm = Lrc::new(SourceMap::default());
5655

57-
try_with_handler(
56+
try_with_json_handler(
5857
cm.clone(),
5958
HandlerOpts {
6059
color: ColorConfig::Never,
61-
skip_filename: true,
60+
skip_filename: false,
6261
},
6362
|handler| {
6463
swc_fast_ts_strip::operate(&cm, handler, input, options).map_err(anyhow::Error::new)
6564
},
6665
)
6766
}
6867

69-
#[derive(Debug, Serialize)]
70-
struct ErrorObject {
71-
code: ErrorCode,
72-
message: String,
73-
}
74-
7568
pub fn convert_err(err: Error) -> wasm_bindgen::prelude::JsValue {
76-
if let Some(ts_error) = err.downcast_ref::<TsError>() {
77-
return serde_wasm_bindgen::to_value(&ErrorObject {
78-
code: ts_error.code,
79-
message: err.to_string(),
80-
})
81-
.unwrap();
82-
}
83-
84-
format!("{}", err).into()
69+
err.to_string().into()
8570
}

crates/swc_error_reporters/Cargo.toml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ version = "9.0.0"
1212
bench = false
1313

1414
[dependencies]
15-
anyhow = { workspace = true }
16-
miette = { workspace = true, features = ["fancy-no-syscall"] }
17-
once_cell = { workspace = true }
18-
parking_lot = { workspace = true }
15+
anyhow = { workspace = true }
16+
miette = { workspace = true, features = ["fancy-no-syscall"] }
17+
once_cell = { workspace = true }
18+
parking_lot = { workspace = true }
19+
serde = { workspace = true }
20+
serde_derive = { workspace = true }
21+
serde_json = { workspace = true }
1922

2023
swc_common = { version = "8.0.0", path = "../swc_common", features = [
2124
"concurrent",

crates/swc_error_reporters/src/handler.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ use miette::{GraphicalReportHandler, GraphicalTheme};
55
use once_cell::sync::Lazy;
66
use parking_lot::Mutex;
77
use swc_common::{
8-
errors::{ColorConfig, Handler, HANDLER},
8+
errors::{ColorConfig, Emitter, Handler, HANDLER},
99
sync::Lrc,
1010
SourceMap,
1111
};
1212

13-
use crate::{PrettyEmitter, PrettyEmitterConfig};
13+
use crate::{
14+
json_emitter::{JsonEmitter, JsonEmitterConfig},
15+
PrettyEmitter, PrettyEmitterConfig,
16+
};
1417

1518
#[derive(Clone, Default)]
1619
struct LockedWriter(Arc<Mutex<Vec<u8>>>);
@@ -85,22 +88,57 @@ pub fn try_with_handler<F, Ret>(
8588
config: HandlerOpts,
8689
op: F,
8790
) -> Result<Ret, Error>
91+
where
92+
F: FnOnce(&Handler) -> Result<Ret, Error>,
93+
{
94+
try_with_handler_inner(cm, config, op, false)
95+
}
96+
97+
/// Try operation with a [Handler] and prints the errors as a [String] wrapped
98+
/// by [Err].
99+
pub fn try_with_json_handler<F, Ret>(
100+
cm: Lrc<SourceMap>,
101+
config: HandlerOpts,
102+
op: F,
103+
) -> Result<Ret, Error>
104+
where
105+
F: FnOnce(&Handler) -> Result<Ret, Error>,
106+
{
107+
try_with_handler_inner(cm, config, op, true)
108+
}
109+
110+
fn try_with_handler_inner<F, Ret>(
111+
cm: Lrc<SourceMap>,
112+
config: HandlerOpts,
113+
op: F,
114+
json: bool,
115+
) -> Result<Ret, Error>
88116
where
89117
F: FnOnce(&Handler) -> Result<Ret, Error>,
90118
{
91119
let wr = Box::<LockedWriter>::default();
92120

93-
let emitter = PrettyEmitter::new(
94-
cm,
95-
wr.clone(),
96-
to_miette_reporter(config.color),
97-
PrettyEmitterConfig {
98-
skip_filename: config.skip_filename,
99-
},
100-
);
121+
let emitter: Box<dyn Emitter> = if json {
122+
Box::new(JsonEmitter::new(
123+
cm,
124+
wr.clone(),
125+
JsonEmitterConfig {
126+
skip_filename: config.skip_filename,
127+
},
128+
))
129+
} else {
130+
Box::new(PrettyEmitter::new(
131+
cm,
132+
wr.clone(),
133+
to_miette_reporter(config.color),
134+
PrettyEmitterConfig {
135+
skip_filename: config.skip_filename,
136+
},
137+
))
138+
};
101139
// let e_wr = EmitterWriter::new(wr.clone(), Some(cm), false,
102140
// true).skip_filename(skip_filename);
103-
let handler = Handler::with_emitter(true, false, Box::new(emitter));
141+
let handler = Handler::with_emitter(true, false, emitter);
104142

105143
let ret = HANDLER.set(&handler, || op(&handler));
106144

0 commit comments

Comments
 (0)