Skip to content

Commit

Permalink
feat(ts/fast-strip): Emit json errors (#10144)
Browse files Browse the repository at this point in the history
**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
  • Loading branch information
kdy1 authored Mar 4, 2025
1 parent 696f053 commit 740bd57
Show file tree
Hide file tree
Showing 23 changed files with 329 additions and 145 deletions.
7 changes: 7 additions & 0 deletions .changeset/wild-roses-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
swc_core: minor
swc_fast_ts_strip: minor
swc_error_reporters: minor
---

feat(ts/fast-strip): Emit json errors
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

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

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

exports[`transform in strip-only mode should report correct error for syntax error 1`] = `
{
"code": "InvalidSyntax",
"message": " x Expected ';', '}' or <eof>
,----
1 | function foo() { invalid syntax }
: ^^^|^^^ ^^^^^^
: \`-- This is the expression part of an expression statement
\`----
",
}
"{"code":"InvalidSyntax","message":"Expected ';', '}' or <eof>","snippet":"syntax","filename":"test.ts","line":1,"column":25}
"
`;

exports[`transform in strip-only mode should report correct error for unsupported syntax 1`] = `
{
"code": "UnsupportedSyntax",
"message": " x TypeScript enum is not supported in strip-only mode
,-[1:1]
1 | ,-> enum Foo {
2 | | a, b
3 | \`-> }
\`----
",
}
"{"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}
"
`;

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

exports[`transform in strip-only mode should throw an error when it encounters a module 1`] = `
{
"code": "UnsupportedSyntax",
"message": " x \`module\` keyword is not supported. Use \`namespace\` instead.
,----
1 | module foo { }
: ^^^^^^
\`----
",
}
"{"code":"UnsupportedSyntax","message":"\`module\` keyword is not supported. Use \`namespace\` instead.","snippet":"module foo","filename":"test.ts","line":1,"column":0}
"
`;

exports[`transform in strip-only mode should throw an error when it encounters a module 2`] = `
{
"code": "UnsupportedSyntax",
"message": " x \`module\` keyword is not supported. Use \`namespace\` instead.
,----
1 | declare module foo { }
: ^^^^^^
\`----
",
}
"{"code":"UnsupportedSyntax","message":"\`module\` keyword is not supported. Use \`namespace\` instead.","snippet":"module foo","filename":"test.ts","line":1,"column":8}
"
`;

exports[`transform in strip-only mode should throw an error when it encounters a namespace 1`] = `
{
"code": "UnsupportedSyntax",
"message": " x TypeScript namespace declaration is not supported in strip-only mode
,----
1 | namespace Foo { export const m = 1; }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
\`----
",
}
"{"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}
"
`;

exports[`transform in strip-only mode should throw an error when it encounters an enum 1`] = `
{
"code": "UnsupportedSyntax",
"message": " x TypeScript enum is not supported in strip-only mode
,----
1 | enum Foo {}
: ^^^^^^^^^^^
\`----
",
}
"{"code":"UnsupportedSyntax","message":"TypeScript enum is not supported in strip-only mode","snippet":"enum Foo {}","filename":"test.ts","line":1,"column":0}
"
`;

exports[`transform in transform mode shoud throw an object even with deprecatedTsModuleAsError = true 1`] = `
"{"code":"UnsupportedSyntax","message":"\`module\` keyword is not supported. Use \`namespace\` instead.","snippet":"module F","filename":"<anon>","line":1,"column":0}
"
`;

exports[`transform in transform mode should throw an error when it encounters a declared module 1`] = `
{
"code": "UnsupportedSyntax",
"message": " x \`module\` keyword is not supported. Use \`namespace\` instead.
,----
1 | declare module foo { }
: ^^^^^^
\`----
",
}
"{"code":"UnsupportedSyntax","message":"\`module\` keyword is not supported. Use \`namespace\` instead.","snippet":"module foo","filename":"test.ts","line":1,"column":8}
"
`;

exports[`transform in transform mode should throw an error when it encounters a module 1`] = `
{
"code": "UnsupportedSyntax",
"message": " x \`module\` keyword is not supported. Use \`namespace\` instead.
,----
1 | module foo { }
: ^^^^^^
\`----
",
}
"{"code":"UnsupportedSyntax","message":"\`module\` keyword is not supported. Use \`namespace\` instead.","snippet":"module foo","filename":"test.ts","line":1,"column":0}
"
`;

exports[`transform should strip types 1`] = `
Expand Down
13 changes: 10 additions & 3 deletions bindings/binding_typescript_wasm/__tests__/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ describe("transform", () => {
await expect(
swc.transform("enum Foo {}", {
mode: "strip-only",
filename: "test.ts",
}),
).rejects.toMatchSnapshot();
});
Expand All @@ -119,6 +120,7 @@ describe("transform", () => {
await expect(
swc.transform("namespace Foo { export const m = 1; }", {
mode: "strip-only",
filename: "test.ts",
}),
).rejects.toMatchSnapshot();
});
Expand All @@ -128,6 +130,7 @@ describe("transform", () => {
swc.transform("module foo { }", {
mode: "strip-only",
deprecatedTsModuleAsError: true,
filename: "test.ts",
}),
).rejects.toMatchSnapshot();
});
Expand All @@ -137,13 +140,15 @@ describe("transform", () => {
swc.transform("declare module foo { }", {
mode: "strip-only",
deprecatedTsModuleAsError: true,
filename: "test.ts",
}),
).rejects.toMatchSnapshot();
});

it("should not emit 'Caused by: failed to parse'", async () => {
await expect(
swc.transform("function foo() { await Promise.resolve(1); }", {
filename: "test.ts",
mode: "strip-only",
}),
).rejects.toMatchSnapshot();
Expand All @@ -153,6 +158,7 @@ describe("transform", () => {
await expect(
swc.transform("function foo() { invalid syntax }", {
mode: "strip-only",
filename: "test.ts"
}),
).rejects.toMatchSnapshot();
});
Expand All @@ -165,6 +171,7 @@ describe("transform", () => {
}`,
{
mode: "strip-only",
filename: "test.ts"
},
),
).rejects.toMatchSnapshot();
Expand All @@ -177,6 +184,7 @@ describe("transform", () => {
swc.transform("module foo { }", {
mode: "transform",
deprecatedTsModuleAsError: true,
filename: "test.ts"
}),
).rejects.toMatchSnapshot();
});
Expand All @@ -186,6 +194,7 @@ describe("transform", () => {
swc.transform("declare module foo { }", {
mode: "transform",
deprecatedTsModuleAsError: true,
filename: "test.ts",
}),
).rejects.toMatchSnapshot();
});
Expand All @@ -196,9 +205,7 @@ describe("transform", () => {
mode: "transform",
deprecatedTsModuleAsError: true,
}),
).rejects.toMatchObject({
code: "UnsupportedSyntax",
});
).rejects.toMatchSnapshot();
})
});
});
25 changes: 5 additions & 20 deletions bindings/binding_typescript_wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use anyhow::Error;
use js_sys::Uint8Array;
use serde::Serialize;
use swc_common::{errors::ColorConfig, sync::Lrc, SourceMap, GLOBALS};
use swc_error_reporters::handler::{try_with_handler, HandlerOpts};
use swc_fast_ts_strip::{ErrorCode, Options, TransformOutput, TsError};
use swc_error_reporters::handler::{try_with_json_handler, HandlerOpts};
use swc_fast_ts_strip::{Options, TransformOutput};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::{future_to_promise, js_sys::Promise};

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

try_with_handler(
try_with_json_handler(
cm.clone(),
HandlerOpts {
color: ColorConfig::Never,
skip_filename: true,
skip_filename: false,
},
|handler| {
swc_fast_ts_strip::operate(&cm, handler, input, options).map_err(anyhow::Error::new)
},
)
}

#[derive(Debug, Serialize)]
struct ErrorObject {
code: ErrorCode,
message: String,
}

pub fn convert_err(err: Error) -> wasm_bindgen::prelude::JsValue {
if let Some(ts_error) = err.downcast_ref::<TsError>() {
return serde_wasm_bindgen::to_value(&ErrorObject {
code: ts_error.code,
message: err.to_string(),
})
.unwrap();
}

format!("{}", err).into()
err.to_string().into()
}
11 changes: 7 additions & 4 deletions crates/swc_error_reporters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ version = "9.0.0"
bench = false

[dependencies]
anyhow = { workspace = true }
miette = { workspace = true, features = ["fancy-no-syscall"] }
once_cell = { workspace = true }
parking_lot = { workspace = true }
anyhow = { workspace = true }
miette = { workspace = true, features = ["fancy-no-syscall"] }
once_cell = { workspace = true }
parking_lot = { workspace = true }
serde = { workspace = true }
serde_derive = { workspace = true }
serde_json = { workspace = true }

swc_common = { version = "8.0.0", path = "../swc_common", features = [
"concurrent",
Expand Down
60 changes: 49 additions & 11 deletions crates/swc_error_reporters/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ use miette::{GraphicalReportHandler, GraphicalTheme};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use swc_common::{
errors::{ColorConfig, Handler, HANDLER},
errors::{ColorConfig, Emitter, Handler, HANDLER},
sync::Lrc,
SourceMap,
};

use crate::{PrettyEmitter, PrettyEmitterConfig};
use crate::{
json_emitter::{JsonEmitter, JsonEmitterConfig},
PrettyEmitter, PrettyEmitterConfig,
};

#[derive(Clone, Default)]
struct LockedWriter(Arc<Mutex<Vec<u8>>>);
Expand Down Expand Up @@ -85,22 +88,57 @@ pub fn try_with_handler<F, Ret>(
config: HandlerOpts,
op: F,
) -> Result<Ret, Error>
where
F: FnOnce(&Handler) -> Result<Ret, Error>,
{
try_with_handler_inner(cm, config, op, false)
}

/// Try operation with a [Handler] and prints the errors as a [String] wrapped
/// by [Err].
pub fn try_with_json_handler<F, Ret>(
cm: Lrc<SourceMap>,
config: HandlerOpts,
op: F,
) -> Result<Ret, Error>
where
F: FnOnce(&Handler) -> Result<Ret, Error>,
{
try_with_handler_inner(cm, config, op, true)
}

fn try_with_handler_inner<F, Ret>(
cm: Lrc<SourceMap>,
config: HandlerOpts,
op: F,
json: bool,
) -> Result<Ret, Error>
where
F: FnOnce(&Handler) -> Result<Ret, Error>,
{
let wr = Box::<LockedWriter>::default();

let emitter = PrettyEmitter::new(
cm,
wr.clone(),
to_miette_reporter(config.color),
PrettyEmitterConfig {
skip_filename: config.skip_filename,
},
);
let emitter: Box<dyn Emitter> = if json {
Box::new(JsonEmitter::new(
cm,
wr.clone(),
JsonEmitterConfig {
skip_filename: config.skip_filename,
},
))
} else {
Box::new(PrettyEmitter::new(
cm,
wr.clone(),
to_miette_reporter(config.color),
PrettyEmitterConfig {
skip_filename: config.skip_filename,
},
))
};
// let e_wr = EmitterWriter::new(wr.clone(), Some(cm), false,
// true).skip_filename(skip_filename);
let handler = Handler::with_emitter(true, false, Box::new(emitter));
let handler = Handler::with_emitter(true, false, emitter);

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

Expand Down
Loading

0 comments on commit 740bd57

Please sign in to comment.