diff --git a/src/ast/known_global.rs b/src/ast/known_global.rs index 47fe7ff9dbaf..d03e2533aca2 100644 --- a/src/ast/known_global.rs +++ b/src/ast/known_global.rs @@ -65,18 +65,6 @@ fn lookup(name: &[u8]) -> Option { } impl KnownGlobal { - #[inline(always)] - fn call_from_new(e: &mut E::New, loc: crate::Loc) -> js_ast::Expr { - let call = E::Call { - target: e.target, - args: bun_alloc::AstAlloc::take(&mut e.args), - close_paren_loc: e.close_parens_loc, - can_be_unwrapped_if_unused: e.can_be_unwrapped_if_unused, - ..Default::default() - }; - js_ast::Expr::init(call, loc) - } - // `_bump` is unused; the `Vec` uses the global arena. #[inline(never)] pub fn minify_global_constructor( @@ -102,8 +90,10 @@ impl KnownGlobal { let original_name = symbol.original_name.slice(); let constructor = lookup(original_name)?; + // `new X(...)` is only ever folded into a literal, never rewritten into the call `X(...)`: + // in strict mode `return X(...)` is a proper tail call, so the frame creating the value is + // gone by the time `X` captures a stack trace, reads its source origin (`Function`) or throws. match constructor { - // Error constructors can be called without 'new' with identical behavior KnownGlobal::Error | KnownGlobal::TypeError | KnownGlobal::SyntaxError @@ -111,10 +101,10 @@ impl KnownGlobal { | KnownGlobal::ReferenceError | KnownGlobal::EvalError | KnownGlobal::URIError - | KnownGlobal::AggregateError => { - // Convert `new Error(...)` to `Error(...)` to save bytes - Some(Self::call_from_new(e, loc)) - } + | KnownGlobal::AggregateError + | KnownGlobal::Function + // `RegExp(re)` would also return `re` itself where `new RegExp(re)` copies it. + | KnownGlobal::RegExp => None, KnownGlobal::Object => { let n = e.args.len_u32(); @@ -141,11 +131,20 @@ impl KnownGlobal { } } - // For other cases, just remove 'new' - Some(Self::call_from_new(e, loc)) + None } KnownGlobal::Array => { + // `new Array(5, ...rest)` is `new Array(5)`, a length, when `rest` is empty. + if e + .args + .slice() + .iter() + .any(|arg| matches!(arg.data, js_ast::ExprData::ESpread(_))) + { + return None; + } + let n = e.args.len_u32(); match n { @@ -176,7 +175,6 @@ impl KnownGlobal { // For other types, check via knownPrimitive let primitive = arg.known_primitive(); // Only convert if we know for certain it's not a number - // unknown could be a number at runtime, so we must preserve Array() call match primitive { js_ast::expr::PrimitiveType::Null | js_ast::expr::PrimitiveType::Undefined @@ -195,7 +193,7 @@ impl KnownGlobal { js_ast::expr::PrimitiveType::Number => { let val = match arg.data { js_ast::ExprData::ENumber(num) => num.value(), - _ => return Some(Self::call_from_new(e, loc)), + _ => return None, }; if // only want this with whitespace minification @@ -231,13 +229,11 @@ impl KnownGlobal { loc, )); } - Some(Self::call_from_new(e, loc)) + None } + // Could be a number, so `new Array(x)` may mean a length. js_ast::expr::PrimitiveType::Unknown - | js_ast::expr::PrimitiveType::Mixed => { - // Could be a number, preserve Array() call - Some(Self::call_from_new(e, loc)) - } + | js_ast::expr::PrimitiveType::Mixed => None, } } // > 1 @@ -255,18 +251,6 @@ impl KnownGlobal { } } - KnownGlobal::Function => { - // Just remove 'new' for Function - Some(Self::call_from_new(e, loc)) - } - KnownGlobal::RegExp => { - // Don't optimize RegExp - the semantics are too complex: - // - new RegExp(re) creates a copy, but RegExp(re) returns the same instance - // - This affects object identity and lastIndex behavior - // - The difference only applies when flags are undefined - // Keep the original new RegExp() call to preserve correct semantics - None - } KnownGlobal::WeakSet | KnownGlobal::WeakMap => { let n = e.args.len_u32(); diff --git a/src/jsc/RuntimeTranspilerCache.rs b/src/jsc/RuntimeTranspilerCache.rs index 3373c1402e99..2de2aaec36b9 100644 --- a/src/jsc/RuntimeTranspilerCache.rs +++ b/src/jsc/RuntimeTranspilerCache.rs @@ -51,7 +51,10 @@ bun_core::declare_scope!(cache, visible); /// Version 25: Every ModuleInfo record carries a trailing FetchParameters slot /// so ImportEntry/ExportEntry/StarExportEntry moduleRequestType matches JSC's /// after WebKit 90b2ecf79ae3 keyed m_loadedModules on (specifier, type). -const EXPECTED_VERSION: u32 = 25; +/// Version 26: `new Error(...)` (and the other native error constructors, `Function`, +/// `Object`, `Array`) is no longer rewritten into a plain call; older entries hold the +/// call form, which JSC tail-calls and so drops the creating function from the stack. +const EXPECTED_VERSION: u32 = 26; /// Source files smaller than this are not written to / read from the on-disk /// transpiler cache. Originally 50 KiB, which excluded almost every file in a diff --git a/test/bundler/bundler_minify.test.ts b/test/bundler/bundler_minify.test.ts index f6220592d6d9..bc9af725f8cf 100644 --- a/test/bundler/bundler_minify.test.ts +++ b/test/bundler/bundler_minify.test.ts @@ -819,7 +819,10 @@ describe("bundler", () => { }, }); - itBundled("minify/ErrorConstructorOptimization", { + // `Error(...)` creates the same object as `new Error(...)`, but in strict mode JSC compiles + // `return Error(...)` to a tail call, which removes the creating function from the stack trace. + // `new` is never a tail call, so it has to stay. + itBundled("minify/ErrorConstructorKeepsNew", { files: { "/entry.js": /* js */ ` // Test all Error constructors @@ -862,25 +865,25 @@ describe("bundler", () => { `, }, capture: [ - "Error()", - 'Error("message")', - 'Error("message", { cause: "cause" })', - "TypeError()", - 'TypeError("type error")', - "SyntaxError()", - 'SyntaxError("syntax error")', - "RangeError()", - 'RangeError("range error")', - "ReferenceError()", - 'ReferenceError("ref error")', - "EvalError()", - 'EvalError("eval error")', - "URIError()", - 'URIError("uri error")', - 'AggregateError([], "aggregate error")', - 'AggregateError([Error("e1")], "multiple")', - "Error(msg)", - "TypeError(getErrorMessage())", + "new Error", + 'new Error("message")', + 'new Error("message", { cause: "cause" })', + "new TypeError", + 'new TypeError("type error")', + "new SyntaxError", + 'new SyntaxError("syntax error")', + "new RangeError", + 'new RangeError("range error")', + "new ReferenceError", + 'new ReferenceError("ref error")', + "new EvalError", + 'new EvalError("eval error")', + "new URIError", + 'new URIError("uri error")', + 'new AggregateError([], "aggregate error")', + 'new AggregateError([new Error("e1")], "multiple")', + "new Error(msg)", + "new TypeError(getErrorMessage())", "/* @__PURE__ */ new Date", "/* @__PURE__ */ new Map", "/* @__PURE__ */ new Set", @@ -889,6 +892,41 @@ describe("bundler", () => { target: "bun", }); + itBundled("minify/ReturnedConstructorKeepsItsFrame", { + files: { + "/entry.js": /* js */ ` + function makeError() { + return new Error("made"); + } + function makeTypeError() { + const err = new TypeError("made"); + return err; + } + function makeArray(...lengths) { + return new Array(...lengths); + } + function thrownBy(make) { + try { + make(-1); + } catch (err) { + return err; + } + } + const frames = [ + [makeError, makeError()], + [makeTypeError, makeTypeError()], + [makeArray, thrownBy(makeArray)], + ].map(([fn, err]) => err.stack.includes("at " + fn.name + " ")); + console.log(JSON.stringify(frames)); + `, + }, + minifySyntax: true, + target: "bun", + run: { + stdout: "[true,true,true]", + }, + }); + itBundled("minify/ErrorConstructorWithVariables", { files: { "/entry.js": /* js */ ` @@ -987,6 +1025,9 @@ describe("bundler", () => { // Test Array constructor capture(new Array()); capture(new Array(3)); + capture(new Array(unknownValue)); + capture(new Array(...unknownValue)); + capture(new Array(5, ...unknownValue)); capture(new Array(1, 2, 3)); // Test Array with non-numeric single arguments (should convert to literal) @@ -1000,6 +1041,7 @@ describe("bundler", () => { capture(new Object()); capture(new Object(null)); capture(new Object({ a: 1 })); + capture(new Object(unknownValue)); // Test Function constructor capture(new Function("return 42")); @@ -1022,7 +1064,13 @@ describe("bundler", () => { }, capture: [ "[]", // new Array() -> [] - "Array(3)", // new Array(3) stays as Array(3) because it creates sparse array + // A single argument (possibly what a spread leaves behind at runtime) may be a length, so these + // cannot become literals. They are not turned into `Array(...)` calls either (see + // ErrorConstructorKeepsNew); `new` stays as written. + "new Array(3)", + "new Array(unknownValue)", + "new Array(...unknownValue)", + "new Array(5, ...unknownValue)", `[ 1, 2, @@ -1046,8 +1094,9 @@ describe("bundler", () => { "{}", // new Object() -> {} "{}", // new Object(null) -> {} "{ a: 1 }", // new Object({ a: 1 }) -> { a: 1 } - 'Function("return 42")', - 'Function("a", "b", "return a + b")', + "new Object(unknownValue)", // nothing to fold into a literal; kept as written + 'new Function("return 42")', + 'new Function("a", "b", "return a + b")', 'new RegExp("test")', 'new RegExp("test", "gi")', "new RegExp(/abc/)", @@ -1090,8 +1139,8 @@ describe("bundler", () => { "[,,,,,,,,]", // new Array(8) -> [undefined x 8] "[,,,,,,,,,]", // new Array(9) -> [undefined x 9] "[,,,,,,,,,,]", // new Array(10) -> [undefined x 10] - "Array(11)", // new Array(11) -> Array(11) - "Array(4.5)", // new Array(4.5) is Array(4.5) because it's not an integer + "new Array(11)", // too long to spell out as a literal; kept as written + "new Array(4.5)", // not an integer; kept as written ], minifySyntax: true, minifyWhitespace: true, @@ -1120,6 +1169,12 @@ describe("bundler", () => { const a3 = new Array(n); const a4 = Array(n); capture(a3.length === a4.length && a3.length === 3 && a3[0] === undefined); + + // A spread can leave a single number behind at runtime, and then it is a length + const none = []; + const a5 = new Array(5, ...none); + capture(a5.length === 5); + capture(0 in a5 === false); // Test Object semantics const o1 = new Object(); @@ -1148,6 +1203,8 @@ describe("bundler", () => { "0 in sparse === !1", 'JSON.stringify(sparse) === "[null,null,null,null,null]"', "a3.length === a4.length && a3.length === 3 && a3[0] === void 0", + "a5.length === 5", + "0 in a5 === !1", "typeof o1 === typeof o2", "o1.constructor === o2.constructor", "typeof f1 === typeof f2", @@ -1158,7 +1215,7 @@ describe("bundler", () => { minifySyntax: true, target: "bun", run: { - stdout: "true\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue", + stdout: "true\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue", }, }); diff --git a/test/bundler/bundler_npm.test.ts b/test/bundler/bundler_npm.test.ts index 09807ab33ac5..0e13ba67989d 100644 --- a/test/bundler/bundler_npm.test.ts +++ b/test/bundler/bundler_npm.test.ts @@ -57,17 +57,17 @@ describe("bundler", () => { "../entry.tsx", ], mappings: [ - ["react.development.js:524:'getContextName'", "1:5623:at"], + ["react.development.js:524:'getContextName'", "1:5637:at"], ["react.development.js:2495:'actScopeDepth'", "23:4082:or++"], - ["react.development.js:696:''Component'", '1:7685:\'Component "%s"'], - ["entry.tsx:6:'\"Content-Type\"'", '100:18808:"Content-Type"'], - ["entry.tsx:11:''", "100:19062:void"], - ["entry.tsx:23:'await'", "100:19161:await"], + ["react.development.js:696:''Component'", '1:7699:\'Component "%s"'], + ["entry.tsx:6:'\"Content-Type\"'", '100:18848:"Content-Type"'], + ["entry.tsx:11:''", "100:19102:void"], + ["entry.tsx:23:'await'", "100:19201:await"], ], }, }, expectExactFilesize: { - "out/entry.js": 222000, + "out/entry.js": 222388, }, run: { stdout: "

Hello World

This is an example.

", diff --git a/test/bundler/transpiler/runtime-transpiler.test.ts b/test/bundler/transpiler/runtime-transpiler.test.ts index 5c05e408c92a..d6192b13c40f 100644 --- a/test/bundler/transpiler/runtime-transpiler.test.ts +++ b/test/bundler/transpiler/runtime-transpiler.test.ts @@ -252,3 +252,37 @@ describe("unterminated string literals in large files", () => { expect(exitCode).toBe(1); }); }); + +// Rewriting `return new Function(...)` into `return Function(...)` makes it a proper tail call in +// strict mode code, and the Function constructor then takes the source origin for the new body +// from whichever frame is left: the caller's caller, in another file. +test("a function body built with `return new Function()` resolves import() relative to the returning module", async () => { + using dir = tempDir("transpiler-new-function-origin", { + "main.mjs": /* js */ ` + import { make } from "./lib/make.mjs"; + const { which } = await make()(); + console.log(which); + `, + "lib/make.mjs": /* js */ ` + export function make() { + return new Function("return import('./which.mjs')"); + } + `, + "lib/which.mjs": `export const which = "lib/which.mjs";`, + "which.mjs": `export const which = "which.mjs";`, + }); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "main.mjs"], + env: bunEnv, + cwd: String(dir), + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).toBe(""); + expect(stdout).toBe("lib/which.mjs\n"); + expect(exitCode).toBe(0); +}); diff --git a/test/cli/hot/hot.test.ts b/test/cli/hot/hot.test.ts index 8ab6f31dd9e6..e5713017df3d 100644 --- a/test/cli/hot/hot.test.ts +++ b/test/cli/hot/hot.test.ts @@ -567,7 +567,7 @@ ${Buffer.alloc(counter * 2, " ").toString()}throw new Error(${counter});`, const match = nextLine.match(/\s*at.*?:1003:(\d+)$/); if (!match) throw new Error("invalid string: " + nextLine); const col = match[1]; - expect(Number(col)).toBe(1 + "throw new ".length + counter * 2); + expect(Number(col)).toBe(1 + "throw ".length + counter * 2); }, }); await runner.exited; @@ -614,7 +614,7 @@ ${Buffer.alloc(counter * 2, " ").toString()}throw new Error('${counter}');`, const match = nextLine.match(/\s*at.*?:(\d+):(\d+)\)?$/); if (!match) throw new Error("no :line:col in: " + JSON.stringify(nextLine)); if (match[1] !== "1003") throw new Error("expected :1003: but got: " + JSON.stringify(nextLine)); - expect(Number(match[2])).toBe(1 + "throw new ".length + counter * 2); + expect(Number(match[2])).toBe(1 + "throw ".length + counter * 2); }, }); await runner.exited; diff --git a/test/js/bun/http/serve.test.ts b/test/js/bun/http/serve.test.ts index 3800c8dadd34..4d8181f77132 100644 --- a/test/js/bun/http/serve.test.ts +++ b/test/js/bun/http/serve.test.ts @@ -1950,8 +1950,8 @@ it.concurrent("dev error page embeds the thrown error, its stack, and build/reso expect(exception.stack.frames[0]).toEqual({ function_name: "inner", file: join(String(dir), "server.ts"), - // 1-based, pointing at `TypeError` — the same position `bun` prints to the terminal - position: { line: 3, column: 19 }, + // 1-based, pointing at `new`, the same position `bun` prints to the terminal + position: { line: 3, column: 15 }, scope: 3, // function }); expect(exception.stack.source_lines).toContainEqual({ diff --git a/test/js/bun/test/stack.test.ts b/test/js/bun/test/stack.test.ts index 63b28630a3f6..1aa222b8a08c 100644 --- a/test/js/bun/test/stack.test.ts +++ b/test/js/bun/test/stack.test.ts @@ -77,7 +77,7 @@ test("err.line and err.column are set", async () => { line: 3, column: 17, originalLine: 1, - originalColumn: 18, + originalColumn: 22, }, null, 2, @@ -149,3 +149,62 @@ test("Async functions frame should be included in stack trace", async () => { at async (file:NN:NN)" `); }); + +// Modules are strict mode code, and in strict mode JSC turns `return f()` into a proper tail +// call: the returning function's frame is gone by the time `f` captures the stack. `new f()` is +// never a tail call, so the transpiler must not rewrite `new Error()` into `Error()` to save bytes. +test("a function returning `new Error()` is in the error's stack", () => { + const factories: Record Error> = { + error() { + return new Error("error"); + }, + typeError() { + return new TypeError("typeError"); + }, + syntaxError() { + return new SyntaxError("syntaxError"); + }, + rangeError() { + return new RangeError("rangeError"); + }, + referenceError() { + return new ReferenceError("referenceError"); + }, + evalError() { + return new EvalError("evalError"); + }, + uriError() { + return new URIError("uriError"); + }, + aggregateError() { + return new AggregateError([], "aggregateError"); + }, + viaConst() { + // the single-use binding gets inlined into the return statement + const e = new Error("viaConst"); + return e; + }, + arrow: () => new Error("arrow"), + }; + + const missingOwnFrame = Object.keys(factories).filter( + name => !factories[name]().stack!.includes(`\n at ${name} (`), + ); + + expect(missingOwnFrame).toEqual([]); +}); + +test("a function whose returned `new Array()` throws is in the error's stack", () => { + function makeArray(...lengths: number[]) { + return new Array(...lengths); + } + + let thrown: RangeError | undefined; + try { + makeArray(-1); + } catch (e) { + thrown = e as RangeError; + } + + expect(thrown!.stack).toContain("\n at makeArray ("); +}); diff --git a/test/js/bun/test/test-error-code-done-callback.test.ts b/test/js/bun/test/test-error-code-done-callback.test.ts index 610c4d85e7ec..da45086f2a67 100644 --- a/test/js/bun/test/test-error-code-done-callback.test.ts +++ b/test/js/bun/test/test-error-code-done-callback.test.ts @@ -49,7 +49,7 @@ test("verify we print error messages passed to done callbacks", () => { 27 | done(new Error(msg + "(sync)")); ^ error: you should see this(sync) - at (/test-error-done-callback-fixture.ts:27:12) + at (/test-error-done-callback-fixture.ts:27:8) (fail) error done callback (sync) 27 | done(new Error(msg + "(sync)")); 28 | }); @@ -59,7 +59,7 @@ test("verify we print error messages passed to done callbacks", () => { 32 | done(new Error(msg + "(async with await)")); ^ error: you should see this(async with await) - at (/test-error-done-callback-fixture.ts:32:12) + at (/test-error-done-callback-fixture.ts:32:8) (fail) error done callback (async with await) 32 | done(new Error(msg + "(async with await)")); 33 | }); @@ -69,7 +69,7 @@ test("verify we print error messages passed to done callbacks", () => { 37 | done(new Error(msg + "(async with Bun.sleep)")); ^ error: you should see this(async with Bun.sleep) - at (/test-error-done-callback-fixture.ts:37:12) + at (/test-error-done-callback-fixture.ts:37:8) (fail) error done callback (async with Bun.sleep) 37 | done(new Error(msg + "(async with Bun.sleep)")); 38 | }); @@ -79,7 +79,7 @@ test("verify we print error messages passed to done callbacks", () => { 42 | done(new Error(msg + "(async)")); ^ error: you should see this(async) - at (/test-error-done-callback-fixture.ts:42:14) + at (/test-error-done-callback-fixture.ts:42:10) at (/test-error-done-callback-fixture.ts:37:3) (fail) error done callback (async) 43 | }); @@ -90,7 +90,7 @@ test("verify we print error messages passed to done callbacks", () => { 48 | done(new Error(msg + "(async, setTimeout)")); ^ error: you should see this(async, setTimeout) - at (/test-error-done-callback-fixture.ts:48:14) + at (/test-error-done-callback-fixture.ts:48:10) (fail) error done callback (async, setTimeout) 49 | }, 0); 50 | }); @@ -100,7 +100,7 @@ test("verify we print error messages passed to done callbacks", () => { 54 | done(new Error(msg + "(async, setImmediate)")); ^ error: you should see this(async, setImmediate) - at (/test-error-done-callback-fixture.ts:54:14) + at (/test-error-done-callback-fixture.ts:54:10) (fail) error done callback (async, setImmediate) 55 | }); 56 | }); @@ -110,7 +110,7 @@ test("verify we print error messages passed to done callbacks", () => { 60 | done(new Error(msg + "(async, nextTick)")); ^ error: you should see this(async, nextTick) - at (/test-error-done-callback-fixture.ts:60:14) + at (/test-error-done-callback-fixture.ts:60:10) at (/test-error-done-callback-fixture.ts:54:5) (fail) error done callback (async, nextTick) 62 | }); @@ -121,7 +121,7 @@ test("verify we print error messages passed to done callbacks", () => { 67 | done(new Error(msg + "(async, setTimeout, Promise.resolve)")); ^ error: you should see this(async, setTimeout, Promise.resolve) - at (/test-error-done-callback-fixture.ts:67:16) + at (/test-error-done-callback-fixture.ts:67:12) (fail) error done callback (async, setTimeout, Promise.resolve) 70 | }); 71 | @@ -131,7 +131,7 @@ test("verify we print error messages passed to done callbacks", () => { 75 | done(new Error(msg + "(async, setImmediate, Promise.resolve)")); ^ error: you should see this(async, setImmediate, Promise.resolve) - at (/test-error-done-callback-fixture.ts:75:16) + at (/test-error-done-callback-fixture.ts:75:12) (fail) error done callback (async, setImmediate, Promise.resolve) 0 pass diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts index 1fad55319b9b..d1e9d19f1471 100644 --- a/test/js/bun/test/test-test.test.ts +++ b/test/js/bun/test/test-test.test.ts @@ -735,10 +735,10 @@ test("my-test", () => { const stackLines = output.split("\n").filter(line => line.trim().startsWith("at ")); expect(stackLines.length).toBeGreaterThan(0); if (process.platform === "win32") { - expect(stackLines[0]).toContain(`\\my-test.test.js:5:15`.replace("", test_dir)); + expect(stackLines[0]).toContain(`\\my-test.test.js:5:11`.replace("", test_dir)); } if (process.platform !== "win32") { - expect(stackLines[0]).toContain(`/my-test.test.js:5:15`.replace("", test_dir)); + expect(stackLines[0]).toContain(`/my-test.test.js:5:11`.replace("", test_dir)); } expect(output).toContain("1 pass"); // since the error is unhandled and in a hook, the error does not get attributed to the hook and the test is still allowed to run diff --git a/test/js/bun/util/inspect-error.test.js b/test/js/bun/util/inspect-error.test.js index 4d3f488dac81..0c0290323986 100644 --- a/test/js/bun/util/inspect-error.test.js +++ b/test/js/bun/util/inspect-error.test.js @@ -13,17 +13,17 @@ test("error.cause", () => { 3 | test("error.cause", () => { 4 | const err = new Error("error 1"); 5 | const err2 = new Error("error 2", { cause: err }); - ^ + ^ error: error 2 - at ([dir]/inspect-error.test.js:5:20) + at ([dir]/inspect-error.test.js:5:16) 1 | import { describe, expect, jest, test } from "bun:test"; 2 | 3 | test("error.cause", () => { 4 | const err = new Error("error 1"); - ^ + ^ error: error 1 - at ([dir]/inspect-error.test.js:4:19) + at ([dir]/inspect-error.test.js:4:15) " `); }); @@ -41,9 +41,9 @@ test("Error", () => { 30 | 31 | test("Error", () => { 32 | const err = new Error("my message"); - ^ + ^ error: my message - at ([dir]/inspect-error.test.js:32:19) + at ([dir]/inspect-error.test.js:32:15) " `); }); @@ -71,22 +71,12 @@ note: "duplicateConstDecl" was originally declared here } }); -const normalizeError = str => { - // remove debug-only stack trace frames - // like "at require (:1:21)" - if (str.includes(" (:")) { - const splits = str.split("\n"); - for (let i = 0; i < splits.length; i++) { - if (splits[i].includes(" (:")) { - splits.splice(i, 1); - i--; - } - } - return splits.join("\n"); - } - - return str; -}; +// Debug builds show frames of Bun's internal JS, which have no file: "at require (51:24)". +const normalizeError = str => + str + .split("\n") + .filter(line => !/^\s+at .* \(:?\d+:\d+\)$/.test(line)) + .join("\n"); test("Error inside minified file (no color) ", () => { try { @@ -109,9 +99,9 @@ test("Error inside minified file (no color) ", () => { 26 | exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};expo error: error inside long minified file! - at ([dir]/inspect-error-fixture.min.js:26:2850) + at ([dir]/inspect-error-fixture.min.js:26:2846) at ([dir]/inspect-error-fixture.min.js:26:2890) - at ([dir]/inspect-error.test.js:92:7)" + at ([dir]/inspect-error.test.js:82:7)" `); } }); @@ -138,9 +128,9 @@ test("Error inside minified file (color) ", () => { 26 | exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};expo | ... truncated error: error inside long minified file! - at ([dir]/inspect-error-fixture.min.js:26:2850) + at ([dir]/inspect-error-fixture.min.js:26:2846) at ([dir]/inspect-error-fixture.min.js:26:2890) - at ([dir]/inspect-error.test.js:120:7)" + at ([dir]/inspect-error.test.js:110:7)" `); } }); @@ -154,7 +144,7 @@ test("Inserted originalLine and originalColumn do not appear in node:util.inspec .replaceAll(import.meta.path.replaceAll("\\", "/"), "[file]"), ).toMatchInlineSnapshot(` "Error: my message - at ([file]:149:19)" + at ([file]:139:19)" `); }); diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index 5c91f3dbe05f..514100f4dfb0 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -741,7 +741,7 @@ it("ErrorEvent", () => { NNN | lineno: 42, NNN | colno: 10, NNN | error: new Error("Test error"), - ^ + ^ error: Test error at (file:NN:NN) , diff --git a/test/js/bun/util/reportError.test.ts b/test/js/bun/util/reportError.test.ts index 3075af55f04f..b45f5ab43db8 100644 --- a/test/js/bun/util/reportError.test.ts +++ b/test/js/bun/util/reportError.test.ts @@ -21,9 +21,9 @@ test("reportError", () => { expect(output.replaceAll("\\", "/").replaceAll("/reportError.ts", "[file]")).toMatchInlineSnapshot( ` "1 | reportError(new Error("reportError Test!")); - ^ + ^ error: reportError Test! - at [file]:1:17 + at [file]:1:13 error: true true error: false diff --git a/test/js/web/console/console-log.test.ts b/test/js/web/console/console-log.test.ts index 4356ae993262..5bb2b0438762 100644 --- a/test/js/web/console/console-log.test.ts +++ b/test/js/web/console/console-log.test.ts @@ -122,7 +122,7 @@ Quote"Backslash 55 | console.warn("Warning log"); 56 | console.warn(new Error("console.warn an error")); 57 | console.error(new Error("console.error an error")); - ^ + ^ error: console.error an error at :NN:NN diff --git a/test/regression/issue/minify-new-array-with-if.test.ts b/test/regression/issue/minify-new-array-with-if.test.ts index 4d55201f1a6d..15d696c2e165 100644 --- a/test/regression/issue/minify-new-array-with-if.test.ts +++ b/test/regression/issue/minify-new-array-with-if.test.ts @@ -15,7 +15,7 @@ test("minifying new Array(if (0) 1 else 2) works", async () => { }); expect(await file(join(testDir, "outdir/entry.js")).text()).toMatchInlineSnapshot(` - "console.log(Array(Math.random()>-1?1:2)); + "console.log(new Array(Math.random()>-1?1:2)); " `); });