250113.5 · 87641224c949422f1d1b2734309bc1d907df69de
feat: wrap and rethrow swc errors
reading declarations
@@ -19,7 +19,8 @@ "deps/**", "lib/**", "node_modules/**",- "test/snapshot/**"+ "test/snapshot/**",+ "test/fixtures/**" ] } }
@@ -27,6 +27,13 @@ await build({ plugins: [copyPlugin], }); +await build({+ entryPoints: ["src/errors.ts"],+ platform: "node",+ target: "node22",+ outfile: "dist/errors.js",+});+ await build({ entryPoints: ["src/strip-loader.ts"], bundle: false,
@@ -0,0 +1,24 @@+type SwcError = {+ code: "UnsupportedSyntax" | "InvalidSyntax";+ message: string;+};++// Type guard to check if error is SwcError+export function isSwcError(error: unknown): error is SwcError {+ return (error as SwcError).code !== undefined;+}++// Since swc throw an object, we need to wrap it in a proper error+export function wrapAndReThrowSwcError(error: SwcError): never {+ switch (error.code) {+ case "UnsupportedSyntax": {+ const unsupportedSyntaxError = new Error(error.message);+ unsupportedSyntaxError.name = "UnsupportedSyntaxError";+ throw unsupportedSyntaxError;+ }+ case "InvalidSyntax":+ throw new SyntaxError(error.message);+ default:+ throw new Error(error.message);+ }+}
@@ -1,36 +1,42 @@ import type { LoadFnOutput, LoadHookContext } from "node:module";-import type { Options } from "../lib/wasm";+import { isSwcError, wrapAndReThrowSwcError } from "./errors.js"; import { transformSync } from "./index.js"; -type NextLoad = (- url: string,- context?: LoadHookContext,-) => LoadFnOutput | Promise<LoadFnOutput>;- export async function load( url: string, context: LoadHookContext,- nextLoad: NextLoad,+ nextLoad: (+ url: string,+ context?: LoadHookContext,+ ) => LoadFnOutput | Promise<LoadFnOutput>, ) { const { format } = context; if (format.endsWith("-typescript")) { // Use format 'module' so it returns the source as-is, without stripping the types. // Format 'commonjs' would not return the source for historical reasons.- const { source } = await nextLoad(url, {- ...context,- format: "module",- });- // biome-ignore lint/style/noNonNullAssertion: If module exists, it will have a source- const { code } = transformSync(source!.toString(), {- mode: "strip-only",- } as Options);- return {- format: format.replace("-typescript", ""),- // Source map is not necessary in strip-only mode. However, to map the source- // file in debuggers to the original TypeScript source, add a sourceURL magic- // comment to hint that it is a generated source.- source: `${code}\n\n//# sourceURL=${url}`,- };+ try {+ const { source } = await nextLoad(url, {+ ...context,+ format: "module",+ });+ // biome-ignore lint/style/noNonNullAssertion: If module exists, it will have a source+ const { code } = transformSync(source!.toString(), {+ mode: "strip-only",+ });+ return {+ format: format.replace("-typescript", ""),+ // Source map is not necessary in strip-only mode. However, to map the source+ // file in debuggers to the original TypeScript source, add a sourceURL magic+ // comment to hint that it is a generated source.+ source: `${code}\n\n//# sourceURL=${url}`,+ };+ } catch (error: unknown) {+ if (isSwcError(error)) {+ wrapAndReThrowSwcError(error);+ }+ // If the error is not an SwcError, rethrow it+ throw error;+ } } return nextLoad(url, context); }
@@ -1,44 +1,51 @@ import type { LoadFnOutput, LoadHookContext } from "node:module"; import type { Options } from "../lib/wasm";+import { isSwcError, wrapAndReThrowSwcError } from "./errors.js"; import { transformSync } from "./index.js"; -type NextLoad = (- url: string,- context?: LoadHookContext,-) => LoadFnOutput | Promise<LoadFnOutput>;- export async function load( url: string, context: LoadHookContext,- nextLoad: NextLoad,+ nextLoad: (+ url: string,+ context?: LoadHookContext,+ ) => LoadFnOutput | Promise<LoadFnOutput>, ) { const { format } = context; if (format.endsWith("-typescript")) {- // Use format 'module' so it returns the source as-is, without stripping the types.- // Format 'commonjs' would not return the source for historical reasons.- const { source } = await nextLoad(url, {- ...context,- format: "module",- });+ try {+ // Use format 'module' so it returns the source as-is, without stripping the types.+ // Format 'commonjs' would not return the source for historical reasons.+ const { source } = await nextLoad(url, {+ ...context,+ format: "module",+ }); - // biome-ignore lint/style/noNonNullAssertion: If module exists, it will have a source- const { code, map } = transformSync(source!.toString(), {- mode: "transform",- sourceMap: true,- filename: url,- } as Options);+ // biome-ignore lint/style/noNonNullAssertion: If module exists, it will have a source+ const { code, map } = transformSync(source!.toString(), {+ mode: "transform",+ sourceMap: true,+ filename: url,+ } as Options); - let output = code;+ let output = code; - if (map) {- const base64SourceMap = Buffer.from(map).toString("base64");- output = `${code}\n\n//# sourceMappingURL=data:application/json;base64,${base64SourceMap}`;- }+ if (map) {+ const base64SourceMap = Buffer.from(map).toString("base64");+ output = `${code}\n\n//# sourceMappingURL=data:application/json;base64,${base64SourceMap}`;+ } - return {- format: format.replace("-typescript", ""),- source: `${output}\n\n//# sourceURL=${url}`,- };+ return {+ format: format.replace("-typescript", ""),+ source: `${output}\n\n//# sourceURL=${url}`,+ };+ } catch (error) {+ if (isSwcError(error)) {+ wrapAndReThrowSwcError(error);+ }+ // If the error is not an SwcError, rethrow it+ throw error;+ } } return nextLoad(url, context); }
test/fixtures/invalid-syntax.ts
@@ -0,0 +1,3 @@+function foo(){+ await Promise.resolve();+}
@@ -24,6 +24,7 @@ test("should not work with enums", async () => { ]); strictEqual(result.stdout, "");+ match(result.stderr, /UnsupportedSyntaxError/); match(result.stderr, /TypeScript enum is not supported in strip-only mode/); strictEqual(result.code, 1); });@@ -93,3 +94,17 @@ test("should call nextLoader for non-typescript files for transform", async () = match(result.stdout, /Hello, JavaScript!/); strictEqual(result.code, 0); });++test("should throw syntax error for invalid typescript", async () => {+ const result = await spawnPromisified(process.execPath, [+ "--experimental-strip-types",+ "--no-warnings",+ "--import=./dist/register-strip.mjs",+ fixturesPath("invalid-syntax.ts"),+ ]);++ strictEqual(result.stdout, "");+ match(result.stderr, /SyntaxError/);+ match(result.stderr, /await isn't allowed in non-async function/);+ strictEqual(result.code, 1);+});