mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 13:51:47 +00:00
Compare commits
7 Commits
claude/sta
...
claude/ai-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e147b3f7e | ||
|
|
cc65d86af4 | ||
|
|
686627edc5 | ||
|
|
0dddeb9611 | ||
|
|
86f522fb49 | ||
|
|
0a9e6844ee | ||
|
|
c61c699045 |
@@ -2940,6 +2940,16 @@ fn printErrorInstance(
|
||||
for (line_numbers) |line| max_line = @max(max_line, line);
|
||||
const max_line_number_pad = std.fmt.count("{d}", .{max_line + 1});
|
||||
|
||||
// Use dash separator for AI agents (CLAUDECODE=1 or AGENT=1) to make error output
|
||||
// more familiar and easier to parse, similar to diff/patch format.
|
||||
// Normal: "479 | return 42;"
|
||||
// AI: "479- return 42;"
|
||||
const is_ai_agent = Output.isAIAgent();
|
||||
|
||||
// Width from line number to code start, depending on separator:
|
||||
// AI: "- " (2 chars), Normal: " | " (3 chars)
|
||||
const sep_len: u64 = if (is_ai_agent) 2 else 3;
|
||||
|
||||
var source_lines = exception.stack.sourceLineIterator();
|
||||
var last_pad: u64 = 0;
|
||||
while (source_lines.untilLast()) |source| {
|
||||
@@ -2954,23 +2964,51 @@ fn printErrorInstance(
|
||||
const trimmed = std.mem.trimRight(u8, std.mem.trim(u8, source.text.slice(), "\n"), "\t ");
|
||||
const clamped = trimmed[0..@min(trimmed.len, max_line_length)];
|
||||
|
||||
// Print source lines before the error line
|
||||
if (clamped.len != trimmed.len) {
|
||||
const fmt = if (comptime allow_ansi_color) "<r><d> | ... truncated <r>\n" else "\n";
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d} |<r> {}" ++ fmt,
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
// Line was truncated
|
||||
if (is_ai_agent) {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d}-<r> {}",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
if (comptime allow_ansi_color) {
|
||||
try writer.writeAll(Output.prettyFmt("<d> ... truncated<r>\n", true));
|
||||
} else {
|
||||
try writer.writeAll(" ... truncated\n");
|
||||
}
|
||||
} else {
|
||||
const fmt = if (comptime allow_ansi_color) "<r><d> | ... truncated <r>\n" else "\n";
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d} |<r> {}" ++ fmt,
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d} |<r> {}\n",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
// Full line fits
|
||||
if (is_ai_agent) {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d}-<r> {}\n",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
} else {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d} |<r> {}\n",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3012,6 +3050,7 @@ fn printErrorInstance(
|
||||
}
|
||||
}
|
||||
|
||||
// Print the error line itself (no valid position, so we use "-" as line number)
|
||||
if (top_frame == null or top_frame.?.position.isInvalid()) {
|
||||
defer did_print_name = true;
|
||||
defer source.text.deinit();
|
||||
@@ -3020,26 +3059,52 @@ fn printErrorInstance(
|
||||
const text = trimmed[0..@min(trimmed.len, max_line_length)];
|
||||
|
||||
if (text.len != trimmed.len) {
|
||||
const fmt = if (comptime allow_ansi_color) "<r><d> | ... truncated <r>\n" else "\n";
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>- |<r> {}" ++ fmt,
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{bun.fmt.fmtJavaScript(text, .{ .enable_colors = allow_ansi_color })},
|
||||
);
|
||||
if (is_ai_agent) {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>-<r> {}",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{bun.fmt.fmtJavaScript(text, .{ .enable_colors = allow_ansi_color })},
|
||||
);
|
||||
if (comptime allow_ansi_color) {
|
||||
try writer.writeAll(Output.prettyFmt("<d> ... truncated<r>\n", true));
|
||||
} else {
|
||||
try writer.writeAll(" ... truncated\n");
|
||||
}
|
||||
} else {
|
||||
const fmt = if (comptime allow_ansi_color) "<r><d> | ... truncated <r>\n" else "\n";
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>- |<r> {}" ++ fmt,
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{bun.fmt.fmtJavaScript(text, .{ .enable_colors = allow_ansi_color })},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><d>- |<r> {}\n",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{bun.fmt.fmtJavaScript(text, .{ .enable_colors = allow_ansi_color })},
|
||||
);
|
||||
if (is_ai_agent) {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><d>-<r> {}\n",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{bun.fmt.fmtJavaScript(text, .{ .enable_colors = allow_ansi_color })},
|
||||
);
|
||||
} else {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><d>- |<r> {}\n",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{bun.fmt.fmtJavaScript(text, .{ .enable_colors = allow_ansi_color })},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try this.printErrorNameAndMessage(name, message, !exception.browser_url.isEmpty(), code, Writer, writer, allow_ansi_color, formatter.error_display_level);
|
||||
try this.printErrorNameAndMessage(name, message, !exception.browser_url.isEmpty(), code, exception.runtime_type, Writer, writer, allow_ansi_color, formatter.error_display_level);
|
||||
} else if (top_frame) |top| {
|
||||
// Print the error line with a caret (^) pointing to the error position
|
||||
defer did_print_name = true;
|
||||
const display_line = source.line + 1;
|
||||
const int_size = std.fmt.count("{d}", .{display_line});
|
||||
@@ -3053,42 +3118,81 @@ fn printErrorInstance(
|
||||
const clamped = trimmed[0..@min(trimmed.len, max_line_length)];
|
||||
|
||||
if (clamped.len != trimmed.len) {
|
||||
const fmt = if (comptime allow_ansi_color) "<r><d> | ... truncated <r>\n\n" else "\n\n";
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d} |<r> {}" ++ fmt,
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
} else {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d} |<r> {}\n",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
|
||||
if (clamped.len < max_line_length_with_divot or top.position.column.zeroBased() > max_line_length_with_divot) {
|
||||
const indent = max_line_number_pad + " | ".len + @as(u64, @intCast(top.position.column.zeroBased()));
|
||||
|
||||
try writer.writeByteNTimes(' ', indent);
|
||||
try writer.print(comptime Output.prettyFmt(
|
||||
"<red><b>^<r>\n",
|
||||
allow_ansi_color,
|
||||
), .{});
|
||||
if (is_ai_agent) {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d}-<r> {}",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
if (comptime allow_ansi_color) {
|
||||
try writer.writeAll(Output.prettyFmt("<d> ... truncated<r>\n\n", true));
|
||||
} else {
|
||||
try writer.writeAll(" ... truncated\n\n");
|
||||
}
|
||||
} else {
|
||||
try writer.writeAll("\n");
|
||||
const fmt = if (comptime allow_ansi_color) "<r><d> | ... truncated <r>\n\n" else "\n\n";
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d} |<r> {}" ++ fmt,
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (is_ai_agent) {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d}-<r> {}\n",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
|
||||
if (clamped.len < max_line_length_with_divot or top.position.column.zeroBased() > max_line_length_with_divot) {
|
||||
// Calculate indent for caret: line number padding + separator width + column
|
||||
const indent: usize = @intCast(max_line_number_pad + sep_len + @as(u64, @intCast(top.position.column.zeroBased())));
|
||||
|
||||
try writer.writeByteNTimes(' ', indent);
|
||||
try writer.print(comptime Output.prettyFmt(
|
||||
"<red><b>^<r>\n",
|
||||
allow_ansi_color,
|
||||
), .{});
|
||||
} else {
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
} else {
|
||||
try writer.print(
|
||||
comptime Output.prettyFmt(
|
||||
"<r><b>{d} |<r> {}\n",
|
||||
allow_ansi_color,
|
||||
),
|
||||
.{ display_line, bun.fmt.fmtJavaScript(clamped, .{ .enable_colors = allow_ansi_color }) },
|
||||
);
|
||||
|
||||
if (clamped.len < max_line_length_with_divot or top.position.column.zeroBased() > max_line_length_with_divot) {
|
||||
// Calculate indent for caret: line number padding + separator width + column
|
||||
const indent: usize = @intCast(max_line_number_pad + sep_len + @as(u64, @intCast(top.position.column.zeroBased())));
|
||||
|
||||
try writer.writeByteNTimes(' ', indent);
|
||||
try writer.print(comptime Output.prettyFmt(
|
||||
"<red><b>^<r>\n",
|
||||
allow_ansi_color,
|
||||
), .{});
|
||||
} else {
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try this.printErrorNameAndMessage(name, message, !exception.browser_url.isEmpty(), code, Writer, writer, allow_ansi_color, formatter.error_display_level);
|
||||
try this.printErrorNameAndMessage(name, message, !exception.browser_url.isEmpty(), code, exception.runtime_type, Writer, writer, allow_ansi_color, formatter.error_display_level);
|
||||
}
|
||||
}
|
||||
|
||||
if (!did_print_name) {
|
||||
try this.printErrorNameAndMessage(name, message, !exception.browser_url.isEmpty(), code, Writer, writer, allow_ansi_color, formatter.error_display_level);
|
||||
try this.printErrorNameAndMessage(name, message, !exception.browser_url.isEmpty(), code, exception.runtime_type, Writer, writer, allow_ansi_color, formatter.error_display_level);
|
||||
}
|
||||
|
||||
// This is usually unsafe to do, but we are protecting them each time first
|
||||
@@ -3279,11 +3383,35 @@ fn printErrorNameAndMessage(
|
||||
message: String,
|
||||
is_browser_error: bool,
|
||||
optional_code: ?[]const u8,
|
||||
runtime_type: jsc.JSRuntimeType,
|
||||
comptime Writer: type,
|
||||
writer: Writer,
|
||||
comptime allow_ansi_color: bool,
|
||||
error_display_level: ConsoleObject.FormatOptions.ErrorDisplayLevel,
|
||||
) !void {
|
||||
// In AI mode, print runtime type information before the error message
|
||||
if (Output.isAIAgent() and runtime_type != .Nothing) {
|
||||
const type_name = switch (runtime_type) {
|
||||
.Function => "Function",
|
||||
.Undefined => "Undefined",
|
||||
.Null => "Null",
|
||||
.Boolean => "Boolean",
|
||||
.AnyInt => "Integer",
|
||||
.Number => "Number",
|
||||
.String => "String",
|
||||
.Object => "Object",
|
||||
.Symbol => "Symbol",
|
||||
.BigInt => "BigInt",
|
||||
else => null,
|
||||
};
|
||||
if (type_name) |tn| {
|
||||
if (comptime allow_ansi_color) {
|
||||
try writer.print(comptime Output.prettyFmt(" <d>value type: <r><cyan>{s}<r>\n", true), .{tn});
|
||||
} else {
|
||||
try writer.print(" value type: {s}\n", .{tn});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_browser_error) {
|
||||
try writer.writeAll(Output.prettyFmt("<red>frontend<r> ", true));
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
exports[`CLAUDECODE=1 shows quiet test output (only failures) 1`] = `
|
||||
"test2.test.js:
|
||||
4 | test("passing test", () => {
|
||||
5 | expect(1).toBe(1);
|
||||
6 | });
|
||||
7 |
|
||||
8 | test("failing test", () => {
|
||||
9 | expect(1).toBe(2);
|
||||
^
|
||||
4- test("passing test", () => {
|
||||
5- expect(1).toBe(1);
|
||||
6- });
|
||||
7-
|
||||
8- test("failing test", () => {
|
||||
9- expect(1).toBe(2);
|
||||
^
|
||||
error: expect(received).toBe(expected)
|
||||
|
||||
Expected: 2
|
||||
@@ -65,3 +65,78 @@ exports[`CLAUDECODE flag handles no test files found: no-tests-quiet 1`] = `
|
||||
|
||||
bun test <version> (<revision>)"
|
||||
`;
|
||||
|
||||
exports[`CLAUDECODE=1 formats error source lines with dash separator: error-normal 1`] = `
|
||||
"error.test.js:
|
||||
3 |
|
||||
4 | test("error formatting", () => {
|
||||
5 | function foo() {
|
||||
6 | const x = 1;
|
||||
7 | const y = 2;
|
||||
8 | throw new Error("Test error message");
|
||||
^
|
||||
error: Test error message
|
||||
at foo (file:NN:NN)
|
||||
at <anonymous> (file:NN:NN)
|
||||
(fail) error formatting
|
||||
|
||||
0 pass
|
||||
1 fail
|
||||
Ran 1 test across 1 file.
|
||||
bun test <version> (<revision>)"
|
||||
`;
|
||||
|
||||
exports[`CLAUDECODE=1 formats error source lines with dash separator: error-ai-agent 1`] = `
|
||||
"error.test.js:
|
||||
3-
|
||||
4- test("error formatting", () => {
|
||||
5- function foo() {
|
||||
6- const x = 1;
|
||||
7- const y = 2;
|
||||
8- throw new Error("Test error message");
|
||||
^
|
||||
error: Test error message
|
||||
at foo (file:NN:NN)
|
||||
at <anonymous> (file:NN:NN)
|
||||
(fail) error formatting
|
||||
|
||||
0 pass
|
||||
1 fail
|
||||
Ran 1 test across 1 file.
|
||||
bun test <version> (<revision>)"
|
||||
`;
|
||||
|
||||
exports[`CLAUDECODE=1 error format in Bun.inspect: inspect-error-normal 1`] = `
|
||||
"inspect.test.js:
|
||||
(pass) inspect error format
|
||||
|
||||
1 pass
|
||||
0 fail
|
||||
Ran 1 test across 1 file.
|
||||
bun test <version> (<revision>)
|
||||
1 |
|
||||
2 | import { test, expect } from "bun:test";
|
||||
3 |
|
||||
4 | test("inspect error format", () => {
|
||||
5 | const err = new Error("Inspected error");
|
||||
^
|
||||
error: Inspected error
|
||||
at <anonymous> (file:NN:NN)"
|
||||
`;
|
||||
|
||||
exports[`CLAUDECODE=1 error format in Bun.inspect: inspect-error-ai-agent 1`] = `
|
||||
"inspect.test.js:
|
||||
|
||||
1 pass
|
||||
0 fail
|
||||
Ran 1 test across 1 file.
|
||||
bun test <version> (<revision>)
|
||||
1-
|
||||
2- import { test, expect } from "bun:test";
|
||||
3-
|
||||
4- test("inspect error format", () => {
|
||||
5- const err = new Error("Inspected error");
|
||||
^
|
||||
error: Inspected error
|
||||
at <anonymous> (file:NN:NN)"
|
||||
`;
|
||||
|
||||
@@ -144,3 +144,95 @@ test("CLAUDECODE flag handles no test files found", () => {
|
||||
expect(normalizeBunSnapshot(normalOutput, dir)).toMatchSnapshot("no-tests-normal");
|
||||
expect(normalizeBunSnapshot(quietOutput, dir)).toMatchSnapshot("no-tests-quiet");
|
||||
});
|
||||
|
||||
test("CLAUDECODE=1 formats error source lines with dash separator", () => {
|
||||
const dir = tempDirWithFiles("claudecode-error-format", {
|
||||
"error.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("error formatting", () => {
|
||||
function foo() {
|
||||
const x = 1;
|
||||
const y = 2;
|
||||
throw new Error("Test error message");
|
||||
}
|
||||
foo();
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
// Run with CLAUDECODE=0 (normal output with pipe separator)
|
||||
const result1 = spawnSync({
|
||||
cmd: [bunExe(), "test", "error.test.js"],
|
||||
env: { ...testEnv, CLAUDECODE: "0" },
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
// Run with CLAUDECODE=1 (AI agent output with dash separator)
|
||||
const result2 = spawnSync({
|
||||
cmd: [bunExe(), "test", "error.test.js"],
|
||||
env: { ...testEnv, CLAUDECODE: "1" },
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const normalOutput = result1.stderr.toString() + result1.stdout.toString();
|
||||
const aiAgentOutput = result2.stderr.toString() + result2.stdout.toString();
|
||||
|
||||
// Normal output should use pipe separator: "6 |"
|
||||
expect(normalOutput).toMatch(/\d+ \|/);
|
||||
|
||||
// AI agent output should use dash separator: "6-"
|
||||
expect(aiAgentOutput).toMatch(/\d+-/);
|
||||
expect(aiAgentOutput).not.toMatch(/\d+ \|/);
|
||||
|
||||
expect(normalizeBunSnapshot(normalOutput, dir)).toMatchSnapshot("error-normal");
|
||||
expect(normalizeBunSnapshot(aiAgentOutput, dir)).toMatchSnapshot("error-ai-agent");
|
||||
});
|
||||
|
||||
test("CLAUDECODE=1 error format in Bun.inspect", () => {
|
||||
const dir = tempDirWithFiles("claudecode-inspect-error", {
|
||||
"inspect.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("inspect error format", () => {
|
||||
const err = new Error("Inspected error");
|
||||
console.log(Bun.inspect(err));
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
// Run with CLAUDECODE=0 (normal output with pipe separator)
|
||||
const result1 = spawnSync({
|
||||
cmd: [bunExe(), "test", "inspect.test.js"],
|
||||
env: { ...testEnv, CLAUDECODE: "0" },
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
// Run with CLAUDECODE=1 (AI agent output with dash separator)
|
||||
const result2 = spawnSync({
|
||||
cmd: [bunExe(), "test", "inspect.test.js"],
|
||||
env: { ...testEnv, CLAUDECODE: "1" },
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const normalOutput = result1.stderr.toString() + result1.stdout.toString();
|
||||
const aiAgentOutput = result2.stderr.toString() + result2.stdout.toString();
|
||||
|
||||
// Normal output should use pipe separator: "6 |"
|
||||
expect(normalOutput).toMatch(/\d+ \|/);
|
||||
|
||||
// AI agent output should use dash separator: "6-"
|
||||
expect(aiAgentOutput).toMatch(/\d+-/);
|
||||
expect(aiAgentOutput).not.toMatch(/\d+ \|/);
|
||||
|
||||
expect(normalizeBunSnapshot(normalOutput, dir)).toMatchSnapshot("inspect-error-normal");
|
||||
expect(normalizeBunSnapshot(aiAgentOutput, dir)).toMatchSnapshot("inspect-error-ai-agent");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user