diff --git a/src/bun.js.zig b/src/bun.js.zig index 1a4fafbbd4..91040c11d7 100644 --- a/src/bun.js.zig +++ b/src/bun.js.zig @@ -37,6 +37,7 @@ pub const Run = struct { .args = ctx.args, .graph = graph_ptr, .is_main_thread = true, + .source_code_preview = ctx.runtime_options.source_code_preview, }), .arena = arena, .ctx = ctx, @@ -174,6 +175,7 @@ pub const Run = struct { .debugger = ctx.runtime_options.debugger, .dns_result_order = DNSResolver.Order.fromStringOrDie(ctx.runtime_options.dns_result_order), .is_main_thread = true, + .source_code_preview = ctx.runtime_options.source_code_preview, }, ), .arena = arena, diff --git a/src/bun.js/VirtualMachine.zig b/src/bun.js/VirtualMachine.zig index ea0095dfea..5dc12fe1ed 100644 --- a/src/bun.js/VirtualMachine.zig +++ b/src/bun.js/VirtualMachine.zig @@ -48,6 +48,7 @@ unhandled_pending_rejection_to_capture: ?*JSValue = null, standalone_module_graph: ?*bun.StandaloneModuleGraph = null, smol: bool = false, dns_result_order: DNSResolver.Order = .verbatim, +source_code_preview: bool = true, counters: Counters = .{}, hot_reload: bun.cli.Command.HotReload = .none, @@ -1086,6 +1087,7 @@ pub const Options = struct { /// Worker VMs are always destroyed on exit, regardless of this setting. Setting this to /// true may expose bugs that would otherwise only occur using Workers. destruct_main_thread_on_exit: bool = false, + source_code_preview: bool = true, }; pub var is_smol_mode = false; @@ -1180,6 +1182,7 @@ pub fn init(opts: Options) !*VirtualMachine { uws.Loop.get().internal_loop_data.jsc_vm = vm.jsc_vm; vm.smol = opts.smol; vm.dns_result_order = opts.dns_result_order; + vm.source_code_preview = opts.source_code_preview; if (opts.smol) is_smol_mode = opts.smol; @@ -1330,6 +1333,7 @@ pub fn initWorker( } vm.smol = opts.smol; + vm.source_code_preview = opts.source_code_preview; vm.transpiler.macro_context = js_ast.Macro.MacroContext.init(&vm.transpiler); vm.global = JSGlobalObject.create( @@ -1430,6 +1434,7 @@ pub fn initBake(opts: Options) anyerror!*VirtualMachine { vm.transpiler.macro_context = js_ast.Macro.MacroContext.init(&vm.transpiler); vm.smol = opts.smol; + vm.source_code_preview = opts.source_code_preview; if (opts.smol) is_smol_mode = opts.smol; @@ -2923,7 +2928,7 @@ fn printErrorInstance( exception_list, &exception_holder.need_to_clear_parser_arena_on_deinit, &source_code_slice, - formatter.error_display_level != .warn, + formatter.error_display_level != .warn and this.source_code_preview, ); } const prev_had_errors = this.had_errors; diff --git a/src/bunfig.zig b/src/bunfig.zig index 2041454161..3a45c5f817 100644 --- a/src/bunfig.zig +++ b/src/bunfig.zig @@ -779,6 +779,16 @@ pub const Bunfig = struct { } } } + + if (json.get("runtime")) |runtime_expr| { + if (runtime_expr.get("sourceCodePreview")) |source_code_preview| { + if (source_code_preview.asBool()) |value| { + this.ctx.runtime_options.source_code_preview = value; + } else { + try this.addError(source_code_preview.loc, "Expected boolean"); + } + } + } } if (json.getObject("serve")) |serve_obj2| { diff --git a/src/cli.zig b/src/cli.zig index bccc5c29f1..ae2a04a51e 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -385,6 +385,7 @@ pub const Command = struct { expose_gc: bool = false, preserve_symlinks_main: bool = false, console_depth: ?u16 = null, + source_code_preview: bool = true, }; var global_cli_ctx: Context = undefined; diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index 23b70343f8..f4958fae00 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -1380,6 +1380,7 @@ pub const TestCommand = struct { .smol = ctx.runtime_options.smol, .debugger = ctx.runtime_options.debugger, .is_main_thread = true, + .source_code_preview = ctx.runtime_options.source_code_preview, }, ); vm.argv = ctx.passthrough; diff --git a/test/js/bun/runtime/source-code-preview.test.ts b/test/js/bun/runtime/source-code-preview.test.ts new file mode 100644 index 0000000000..5b4b9616a9 --- /dev/null +++ b/test/js/bun/runtime/source-code-preview.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, test } from "bun:test"; +import { bunEnv, bunExe, tempDir } from "harness"; + +describe("sourceCodePreview config option", () => { + test("default behavior shows source code in error stack traces", async () => { + using dir = tempDir("source-code-preview-default", { + "test.js": ` +function foo() { + console.log(new Error().stack); +} +foo(); + `, + }); + + const proc = Bun.spawn({ + cmd: [bunExe(), "test.js"], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const stdout = await proc.stdout.text(); + const exitCode = await proc.exited; + + expect(exitCode).toBe(0); + // Should contain line numbers and source code + expect(stdout).toContain("test.js:"); + // Should contain the function call location with source code or line number + expect(stdout.length).toBeGreaterThan(10); + }); + + test("sourceCodePreview=false disables source code in error stack traces", async () => { + using dir = tempDir("source-code-preview-disabled", { + "bunfig.toml": ` +[runtime] +sourceCodePreview = false + `, + "test.fixture.ts": ` +function foo() { + console.log(new Error().stack); +} +foo(); + `, + }); + + const proc2 = Bun.spawn({ + cmd: [bunExe(), "test.fixture.ts"], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const stdout = await proc2.stdout.text(); + const stderr = await proc2.stderr.text(); + const exitCode = await proc2.exited; + + expect(exitCode).toBe(0); + + const output = stdout + stderr; + // Should still contain file path and line numbers + expect(output).toContain("test.fixture.ts:"); + + // Should NOT contain source code snippets (no pipe characters from source display) + // The source code preview typically shows lines like: + // 3 | console.log(new Error().stack); + // ^ + // We check that these formatted source lines are not present + const lines = output.split("\n"); + const hasSourceCodeDisplay = lines.some( + line => + /^\s*\d+\s+\|/.test(line) || // Lines with line numbers and pipe + /^\s*\^/.test(line), // Caret indicators + ); + expect(hasSourceCodeDisplay).toBe(false); + }); +});