Add bunfig.toml [runtime] sourceCodePreview option

This adds a configuration option to disable source code previews in
error stack traces via bunfig.toml:

[runtime]
sourceCodePreview = false

When disabled, error stack traces will still show file paths and line
numbers, but will not include the source code snippets with line
numbers and caret indicators that are normally displayed.

Changes:
- Add source_code_preview field to RuntimeOptions in cli.zig
- Add parsing for [runtime].sourceCodePreview in bunfig.zig
- Add source_code_preview field to VirtualMachine struct and Options
- Wire up the config to disable collectSourceLines() in remapZigException
- Add test verifying source code preview can be disabled

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2025-10-16 03:50:56 +00:00
parent 324c0d1a39
commit ab1d20e596
6 changed files with 96 additions and 1 deletions

View File

@@ -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,

View File

@@ -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;

View File

@@ -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| {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
});
});