diff --git a/.prettierignore b/.prettierignore index 2ddf0ea3fe..6347a9bd76 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,4 @@ src/deps test/snapshots test/js/deno src/react-refresh.js +*.min.js diff --git a/CMakeLists.txt b/CMakeLists.txt index 2897402365..5581f683c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.22) cmake_policy(SET CMP0091 NEW) cmake_policy(SET CMP0067 NEW) -set(Bun_VERSION "1.1.0") +set(Bun_VERSION "1.1.1") set(WEBKIT_TAG 089023cc9078b3aa173869fd6685f3e7bed2a994) set(BUN_WORKDIR "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 74f3cbbb68..663205434f 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -2864,6 +2864,11 @@ pub const VirtualMachine = struct { defer printGithubAnnotation(exception); } + // This is a longer number than necessary because we don't handle this case very well + // At the very least, we shouldn't dump 100 KB of minified code into your terminal. + const max_line_length_with_divot = 512; + const max_line_length = 1024; + const line_numbers = exception.stack.source_lines_numbers[0..exception.stack.source_lines_len]; var max_line: i32 = -1; for (line_numbers) |line| max_line = @max(max_line, line); @@ -2880,13 +2885,27 @@ pub const VirtualMachine = struct { last_pad = pad; try writer.writeByteNTimes(' ', pad); - try writer.print( - comptime Output.prettyFmt("{d} | {}\n", allow_ansi_color), - .{ - display_line, - bun.fmt.fmtJavaScript(std.mem.trimRight(u8, std.mem.trim(u8, source.text.slice(), "\n"), "\t "), allow_ansi_color), - }, - ); + 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)]; + + if (clamped.len != trimmed.len) { + const fmt = if (comptime allow_ansi_color) " | ... truncated \n" else "\n"; + try writer.print( + comptime Output.prettyFmt( + "{d} | {}" ++ fmt, + allow_ansi_color, + ), + .{ display_line, bun.fmt.fmtJavaScript(clamped, allow_ansi_color) }, + ); + } else { + try writer.print( + comptime Output.prettyFmt( + "{d} | {}\n", + allow_ansi_color, + ), + .{ display_line, bun.fmt.fmtJavaScript(clamped, allow_ansi_color) }, + ); + } } const name = exception.name; @@ -2910,17 +2929,28 @@ pub const VirtualMachine = struct { if (top_frame == null or top_frame.?.position.isInvalid()) { defer did_print_name = true; defer source.text.deinit(); - const text = std.mem.trimRight(u8, std.mem.trim(u8, source.text.slice(), "\n"), "\t "); + const trimmed = std.mem.trimRight(u8, std.mem.trim(u8, source.text.slice(), "\n"), "\t "); - try writer.print( - comptime Output.prettyFmt( - "- | {}\n", - allow_ansi_color, - ), - .{ - bun.fmt.fmtJavaScript(text, allow_ansi_color), - }, - ); + const text = trimmed[0..@min(trimmed.len, max_line_length)]; + + if (text.len != trimmed.len) { + const fmt = if (comptime allow_ansi_color) " | ... truncated \n" else "\n"; + try writer.print( + comptime Output.prettyFmt( + "- | {}" ++ fmt, + allow_ansi_color, + ), + .{bun.fmt.fmtJavaScript(text, allow_ansi_color)}, + ); + } else { + try writer.print( + comptime Output.prettyFmt( + "- | {}\n", + allow_ansi_color, + ), + .{bun.fmt.fmtJavaScript(text, allow_ansi_color)}, + ); + } try this.printErrorNameAndMessage(name, message, Writer, writer, allow_ansi_color); } else if (top_frame) |top| { @@ -2931,24 +2961,40 @@ pub const VirtualMachine = struct { try writer.writeByteNTimes(' ', pad); defer source.text.deinit(); const text = source.text.slice(); - const remainder = std.mem.trimRight(u8, std.mem.trim(u8, text, "\n"), "\t "); + const trimmed = std.mem.trimRight(u8, std.mem.trim(u8, text, "\n"), "\t "); - try writer.print( - comptime Output.prettyFmt( - "{d} | {}\n", - allow_ansi_color, - ), - .{ display_line, bun.fmt.fmtJavaScript(remainder, allow_ansi_color) }, - ); + // TODO: preserve the divot position and possibly use stringWidth() to figure out where to put the divot + const clamped = trimmed[0..@min(trimmed.len, max_line_length)]; - if (!top.position.isInvalid()) { - const indent = max_line_number_pad + " | ".len + @as(u64, @intCast(top.position.column_start)); + if (clamped.len != trimmed.len) { + const fmt = if (comptime allow_ansi_color) " | ... truncated \n\n" else "\n\n"; + try writer.print( + comptime Output.prettyFmt( + "{d} | {}" ++ fmt, + allow_ansi_color, + ), + .{ display_line, bun.fmt.fmtJavaScript(clamped, allow_ansi_color) }, + ); + } else { + try writer.print( + comptime Output.prettyFmt( + "{d} | {}\n", + allow_ansi_color, + ), + .{ display_line, bun.fmt.fmtJavaScript(clamped, allow_ansi_color) }, + ); - try writer.writeByteNTimes(' ', indent); - try writer.print(comptime Output.prettyFmt( - "^\n", - allow_ansi_color, - ), .{}); + if (clamped.len < max_line_length_with_divot or top.position.column_start > max_line_length_with_divot) { + const indent = max_line_number_pad + " | ".len + @as(u64, @intCast(top.position.column_start)); + + try writer.writeByteNTimes(' ', indent); + try writer.print(comptime Output.prettyFmt( + "^\n", + allow_ansi_color, + ), .{}); + } else { + try writer.writeAll("\n"); + } } try this.printErrorNameAndMessage(name, message, Writer, writer, allow_ansi_color); diff --git a/test/.prettierignore b/test/.prettierignore index a130a15e9e..d8d94cdf27 100644 --- a/test/.prettierignore +++ b/test/.prettierignore @@ -1,3 +1,4 @@ node_modules snapshots js/deno +*.min.js diff --git a/test/js/bun/util/__snapshots__/inspect-error.test.js.snap b/test/js/bun/util/__snapshots__/inspect-error.test.js.snap index 4ab0d89375..e550b8ca0a 100644 --- a/test/js/bun/util/__snapshots__/inspect-error.test.js.snap +++ b/test/js/bun/util/__snapshots__/inspect-error.test.js.snap @@ -44,3 +44,31 @@ error: "duplicateConstDecl" has already been declared note: "duplicateConstDecl" was originally declared here at [dir]/inspect-error-fixture-bad.js:1:7" `; + +exports[`Error inside minified file (no color) 1`] = ` +"21 | exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=Z; +22 | exports.cache=function(a){return function(){var b=U.current;if(!b)return a.apply(null,arguments);var c=b.getCacheForType(V);b=c.get(a);void 0===b&&(b=W(),c.set(a,b));c=0;for(var f=arguments.length;c { throw new Error("error inside long minified file!")})();exports.startTransition=function(a){var b=Y.transition;Y.transition={};try{a()}finally{Y.transition=b}};exports.unstable_act=function(){throw Error("act(...) is not supported in production builds of React.");};exports.unstable_useCacheRefresh=function(){return X.current.useCacheRefresh()}; +exports.use=function(a){return X.current.use(a)};exports.useCallback=function(a,b){return X.current.useCallback(a,b)};exports.useContext=function(a){return X.current.useContext(a)};exports.useDebugValue=function(){};exports.useDeferredValue=function(a,b){return X.current.useDeferredValue(a,b)};exports.useEffect=function(a,b){return X.current.useEffect(a,b)};exports.useId=function(){return X.current.useId()};exports.useImperativeHandle=function(a,b,c){return X.current.useImperativeHandle(a,b,c)}; +exports.useInsertionEffect=function(a,b){return X.current.useInsertionEffect(a,b)};exports.useLayoutEffect=function(a,b){return X.current.useLayoutEffect(a,b)};exports.useMemo=function(a,b){return X.current.useMemo(a,b)};exports.useOptimistic=function(a,b){return X.current.useOptimistic(a,b)};exports.useReducer=function(a,b,c){return X.current.useReducer(a,b,c)};exports.useRef=function(a){return X.current.useRef(a)};exports.useState=function(a){return X.current.useState(a)}; +exports.useSyncExternalStore=function(a,b,c){return X.current.useSyncExternalStore(a,b,c)};exports.useTransition=function(){return X.current.useTransition()};exports.version="18.3.0-canary-09fbee89d-20231013"; diff --git a/test/js/bun/util/inspect-error.test.js b/test/js/bun/util/inspect-error.test.js index 31f8d79f74..cb02fe1feb 100644 --- a/test/js/bun/util/inspect-error.test.js +++ b/test/js/bun/util/inspect-error.test.js @@ -31,3 +31,64 @@ test("BuildMessage", async () => { ).toMatchSnapshot(); } }); + +function ansiRegex({ onlyFirst = false } = {}) { + const pattern = [ + "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", + "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))", + ].join("|"); + + return new RegExp(pattern, onlyFirst ? undefined : "g"); +} +const stripANSIColors = str => str.replace(ansiRegex(), ""); +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; +}; + +test("Error inside minified file (no color) ", () => { + try { + require("./inspect-error-fixture.min.js"); + expect.unreachable(); + } catch (e) { + expect( + normalizeError( + Bun.inspect(e) + .replaceAll(import.meta.dir, "[dir]") + .replaceAll("\\", "/") + .trim(), + ), + ).toMatchSnapshot(); + } +}); + +test("Error inside minified file (color) ", () => { + try { + require("./inspect-error-fixture.min.js"); + expect.unreachable(); + } catch (e) { + expect( + // TODO: remove this workaround once snapshots work better + normalizeError( + stripANSIColors( + Bun.inspect(e, { colors: true }) + .replaceAll(import.meta.dir, "[dir]") + .replaceAll("\\", "/") + .trim(), + ).trim(), + ), + ).toMatchSnapshot(); + } +});