Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions src/jsc/web_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1483,14 +1483,49 @@ fn on_unhandled_rejection(
.unwrap_or(error_instance_or_exception);

// A parse failure rejects with a BuildMessage, which doesn't survive structured
// clone. Node reports a SyntaxError; build a real one from the formatted parse
// error so the subtype reaches the parent intact.
// clone. Node reports a SyntaxError; build a real one carrying the file/line/
// column/code-frame from the parser's `Location` so both the subtype and the
// location reach the parent intact.
if let Some(bm) = error_instance.as_::<crate::BuildMessage>() {
use crate::ZigStringJsc as _;
// SAFETY: as_ returned a live BuildMessage cell, read-only on the
// worker (JS) thread that owns it.
let text = unsafe { (*bm).msg.data.text.clone() };
error_instance =
global_object.create_syntax_error_instance(format_args!("{}", bstr::BStr::new(&text)));
let msg = unsafe { (*bm).msg.clone() };
error_instance = global_object
.create_syntax_error_instance(format_args!("{}", bstr::BStr::new(&msg.data.text)));
// No JS frames are on the stack here, so the synthesized error's own
// lazy materialization produces nothing; attach the parser location as
// own properties the structured-clone serializer reads.
let mut stack = std::string::String::new();
let _ = msg.write_format::<false>(&mut stack);
error_instance.put(
global_object,
b"stack",
bun_core::ZigString::from_bytes(stack.as_bytes()).to_js(global_object),
);
if let Some(location) = &msg.data.location {
if !location.file.is_empty() {
error_instance.put(
global_object,
b"sourceURL",
bun_core::ZigString::from_bytes(&location.file).to_js(global_object),
);
}
if location.line > 0 {
error_instance.put(
global_object,
b"line",
JSValue::js_number(location.line as f64),
);
}
if location.column > -1 {
error_instance.put(
global_object,
b"column",
JSValue::js_number(location.column as f64),
);
}
}
}

let mut array: Vec<u8> = Vec::new();
Expand Down
53 changes: 53 additions & 0 deletions test/js/node/worker_threads/worker-syntax-error.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { expect, test } from "bun:test";
import { tempDir } from "harness";
import { join } from "node:path";
import { Worker } from "node:worker_threads";

test("worker entry-point parse errors surface file path, line and code frame", async () => {
using dir = tempDir("worker-syntax-error", {
"bad.js": "// line 1\nconst y = ;\n",
});
const badPath = join(String(dir), "bad.js");
const worker = new Worker(badPath);
const result: any = await new Promise(resolve => {
worker.on("message", resolve);
worker.on("error", resolve);
});
expect({
name: result?.name,
message: result?.message,
hasStack: typeof result?.stack === "string",
sourceURL: typeof result?.sourceURL,
line: result?.line,
column: result?.column,
}).toEqual({
name: "SyntaxError",
message: "Unexpected ;",
hasStack: true,
sourceURL: "string",
line: 2,
column: 11,
});
expect(result.sourceURL.replaceAll("\\", "/")).toEndWith("/bad.js");
expect(result.stack.replaceAll("\\", "/")).toInclude("/bad.js:2:11");
expect(result.stack).toInclude("const y = ;");
await worker.terminate();
});

test("worker eval parse errors surface line, column and code frame", async () => {
const worker = new Worker(`postMessage(throw new Error("boom"))`, { eval: true });
const result: any = await new Promise(resolve => {
worker.on("message", resolve);
worker.on("error", resolve);
});
expect(result.name).toBe("SyntaxError");
expect(result.message).toBe("Unexpected throw");
expect(result.stack).toBeString();
// The formatted parse error (code frame + location) reaches the parent,
// not just the bare message.
expect(result.stack).toInclude("error: Unexpected throw");
expect(result.stack).toMatch(/at .+:1:13/);
expect(result.line).toBe(1);
expect(result.column).toBe(13);
await worker.terminate();
});
Loading