From 4cb8e37474276283ebea529c697355d5776608e8 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 16 Jan 2025 00:43:26 -0800 Subject: [PATCH] Make console.warn yellow, make console.log(error) not red (#16435) --- .vscode/launch.json | 4 +- src/bun.js/ConsoleObject.zig | 62 ++++++++++++++ src/bun.js/bindings/BunProcess.cpp | 24 ++++-- src/bun.js/bindings/bindings.cpp | 70 +++++++++++---- src/bun.js/bindings/exports.zig | 6 ++ src/bun.js/javascript.zig | 52 +++++++++--- test/js/web/console/console-group.fixture.js | 11 +++ test/js/web/console/console-log.test.ts | 89 +++++++++++++++++++- 8 files changed, 277 insertions(+), 41 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 4897a4b1ec..ea22c29cb5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -160,14 +160,14 @@ "request": "launch", "name": "bun run [file]", "program": "${workspaceFolder}/build/debug/bun-debug", - "args": ["run", "${file}"], + "args": ["--eval", "console.warn(new Error(\"hello\"))"], "cwd": "${fileDirname}", "env": { "FORCE_COLOR": "0", "BUN_DEBUG_QUIET_LOGS": "1", "BUN_GARBAGE_COLLECTOR_LEVEL": "2", }, - "console": "internalConsole", + "console": "integratedTerminal", // Don't pause when the GC runs while the debugger is open. "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index f79efc038a..39fafacf6e 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -185,6 +185,11 @@ fn messageWithTypeAndLevel_( .add_newline = true, .flush = true, .default_indent = console.default_indent, + .error_display_level = switch (level) { + .Error => .full, + else => .normal, + .Warning => .warn, + }, }; if (message_type == .Table and len >= 1) { @@ -676,6 +681,7 @@ pub fn writeTrace(comptime Writer: type, writer: Writer, global: *JSGlobalObject null, &holder.need_to_clear_parser_arena_on_deinit, &source_code_slice, + false, ); if (Output.enable_ansi_colors_stderr) @@ -703,6 +709,59 @@ pub const FormatOptions = struct { max_depth: u16 = 2, single_line: bool = false, default_indent: u16 = 0, + error_display_level: ErrorDisplayLevel = .full, + pub const ErrorDisplayLevel = enum { + normal, + warn, + full, + + const Formatter = struct { + name: bun.String, + level: ErrorDisplayLevel, + enable_colors: bool, + colon: Colon, + pub fn format(this: @This(), comptime _: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + if (this.enable_colors) { + switch (this.level) { + .normal => try writer.writeAll(Output.prettyFmt("", true)), + .warn => try writer.writeAll(Output.prettyFmt("", true)), + .full => try writer.writeAll(Output.prettyFmt("", true)), + } + } + + if (!this.name.isEmpty()) { + try this.name.format("", opts, writer); + } else if (this.level == .warn) { + try writer.writeAll("warn"); + } else { + try writer.writeAll("error"); + } + + if (this.colon == .exclude_colon) { + if (this.enable_colors) { + try writer.writeAll(Output.prettyFmt("", true)); + } + return; + } + + if (this.enable_colors) { + try writer.writeAll(Output.prettyFmt(": ", true)); + } else { + try writer.writeAll(": "); + } + } + }; + + pub const Colon = enum { include_colon, exclude_colon }; + pub fn formatter(this: ErrorDisplayLevel, error_name: bun.String, enable_colors: bool, colon: Colon) ErrorDisplayLevel.Formatter { + return .{ + .name = error_name, + .level = this, + .enable_colors = enable_colors, + .colon = colon, + }; + } + }; pub fn fromJS(formatOptions: *FormatOptions, globalThis: *JSC.JSGlobalObject, arguments: []const JSC.JSValue) bun.JSError!void { const arg1 = arguments[0]; @@ -785,6 +844,7 @@ pub fn format2( .indent = options.default_indent, .stack_check = bun.StackCheck.init(), .can_throw_stack_overflow = true, + .error_display_level = options.error_display_level, }; defer fmt.deinit(); const tag = ConsoleObject.Formatter.Tag.get(vals[0], global); @@ -868,6 +928,7 @@ pub fn format2( .indent = options.default_indent, .stack_check = bun.StackCheck.init(), .can_throw_stack_overflow = true, + .error_display_level = options.error_display_level, }; defer fmt.deinit(); var tag: ConsoleObject.Formatter.Tag.Result = undefined; @@ -949,6 +1010,7 @@ pub const Formatter = struct { disable_inspect_custom: bool = false, stack_check: bun.StackCheck = .{ .cached_stack_end = std.math.maxInt(usize) }, can_throw_stack_overflow: bool = false, + error_display_level: FormatOptions.ErrorDisplayLevel = .full, pub fn deinit(this: *Formatter) void { if (bun.take(&this.map_node)) |node| { diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 322d774bc6..d00195efab 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -1122,9 +1122,9 @@ Process::~Process() { } -JSC_DEFINE_HOST_FUNCTION(jsFunction_emitWarning, (Zig::JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(jsFunction_emitWarning, (JSC::JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { - auto* globalObject = jsCast(lexicalGlobalObject); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); auto& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* process = jsCast(globalObject->processObject()); @@ -1163,6 +1163,20 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionAbort, (JSGlobalObject * globalObject, abort(); } +static bool isJSValueEqualToASCIILiteral(JSC::JSGlobalObject* globalObject, JSC::JSValue value, const ASCIILiteral literal) +{ + if (!value.isString()) { + return false; + } + + auto* str = value.toStringOrNull(globalObject); + if (!str) { + return false; + } + auto view = str->view(globalObject); + return view == literal; +} + JSC_DEFINE_HOST_FUNCTION(Process_emitWarning, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { Zig::GlobalObject* globalObject = jsCast(lexicalGlobalObject); @@ -1176,9 +1190,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_emitWarning, (JSGlobalObject * lexicalGlobalObj auto ctor = callFrame->argument(3); auto detail = jsUndefined(); - auto dep_warning = jsString(vm, String("DeprecationWarning"_s)); - - if (Bun__Node__ProcessNoDeprecation && JSC::JSValue::strictEqual(globalObject, type, dep_warning)) { + if (Bun__Node__ProcessNoDeprecation && isJSValueEqualToASCIILiteral(globalObject, type, "DeprecationWarning"_s)) { return JSValue::encode(jsUndefined()); } @@ -1233,7 +1245,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_emitWarning, (JSGlobalObject * lexicalGlobalObj if (!detail.isUndefined()) errorInstance->putDirect(vm, vm.propertyNames->detail, detail, JSC::PropertyAttribute::DontEnum | 0); // ErrorCaptureStackTrace(warning, ctor || process.emitWarning); - if (JSC::JSValue::strictEqual(globalObject, type, dep_warning)) { + if (isJSValueEqualToASCIILiteral(globalObject, type, "DeprecationWarning"_s)) { if (Bun__Node__ProcessNoDeprecation) { return JSValue::encode(jsUndefined()); } diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index d553e86f4a..78cc6298bd 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -208,6 +208,11 @@ using namespace JSC; using namespace WebCore; +enum PopulateStackTraceFlags { + OnlyPosition, + OnlySourceLines, +}; + typedef uint8_t ExpectFlags; // Note: keep this in sync with Expect.Flags implementation in zig (at expect.zig) @@ -4216,7 +4221,7 @@ static void populateStackFrameMetadata(JSC::VM& vm, JSC::JSGlobalObject* globalO static void populateStackFramePosition(const JSC::StackFrame* stackFrame, BunString* source_lines, OrdinalNumber* source_line_numbers, uint8_t source_lines_count, - ZigStackFramePosition* position, JSC::SourceProvider** referenced_source_provider) + ZigStackFramePosition* position, JSC::SourceProvider** referenced_source_provider, PopulateStackTraceFlags flags) { auto code = stackFrame->codeBlock(); if (!code) @@ -4244,6 +4249,8 @@ static void populateStackFramePosition(const JSC::StackFrame* stackFrame, BunStr auto location = Bun::getAdjustedPositionForBytecode(code, stackFrame->bytecodeIndex()); *position = location; + if (flags == PopulateStackTraceFlags::OnlyPosition) + return; if (source_lines_count > 1 && source_lines != nullptr && sourceString.is8Bit()) { // Search for the beginning of the line @@ -4310,12 +4317,18 @@ static void populateStackFramePosition(const JSC::StackFrame* stackFrame, BunStr } static void populateStackFrame(JSC::VM& vm, ZigStackTrace* trace, const JSC::StackFrame* stackFrame, - ZigStackFrame* frame, bool is_top, JSC::SourceProvider** referenced_source_provider, JSC::JSGlobalObject* globalObject) + ZigStackFrame* frame, bool is_top, JSC::SourceProvider** referenced_source_provider, JSC::JSGlobalObject* globalObject, PopulateStackTraceFlags flags) { - populateStackFrameMetadata(vm, globalObject, stackFrame, frame); - populateStackFramePosition(stackFrame, is_top ? trace->source_lines_ptr : nullptr, - is_top ? trace->source_lines_numbers : nullptr, - is_top ? trace->source_lines_to_collect : 0, &frame->position, referenced_source_provider); + if (flags == PopulateStackTraceFlags::OnlyPosition) { + populateStackFrameMetadata(vm, globalObject, stackFrame, frame); + populateStackFramePosition(stackFrame, nullptr, + nullptr, + 0, &frame->position, referenced_source_provider, flags); + } else if (flags == PopulateStackTraceFlags::OnlySourceLines) { + populateStackFramePosition(stackFrame, is_top ? trace->source_lines_ptr : nullptr, + is_top ? trace->source_lines_numbers : nullptr, + is_top ? trace->source_lines_to_collect : 0, &frame->position, referenced_source_provider, flags); + } } class V8StackTraceIterator { @@ -4485,7 +4498,7 @@ public: } }; -static void populateStackTrace(JSC::VM& vm, const WTF::Vector& frames, ZigStackTrace* trace, JSC::JSGlobalObject* globalObject) +static void populateStackTrace(JSC::VM& vm, const WTF::Vector& frames, ZigStackTrace* trace, JSC::JSGlobalObject* globalObject, PopulateStackTraceFlags flags) { uint8_t frame_i = 0; size_t stack_frame_i = 0; @@ -4501,7 +4514,7 @@ static void populateStackTrace(JSC::VM& vm, const WTF::Vector& break; ZigStackFrame* frame = &trace->frames_ptr[frame_i]; - populateStackFrame(vm, trace, &frames[stack_frame_i], frame, frame_i == 0, &trace->referenced_source_provider, globalObject); + populateStackFrame(vm, trace, &frames[stack_frame_i], frame, frame_i == 0, &trace->referenced_source_provider, globalObject, flags); stack_frame_i++; frame_i++; } @@ -4529,7 +4542,7 @@ static JSC::JSValue getNonObservable(JSC::VM& vm, JSC::JSGlobalObject* global, J static void fromErrorInstance(ZigException* except, JSC::JSGlobalObject* global, JSC::ErrorInstance* err, const Vector* stackTrace, - JSC::JSValue val) + JSC::JSValue val, PopulateStackTraceFlags flags) { JSC::JSObject* obj = JSC::jsDynamicCast(val); JSC::VM& vm = global->vm(); @@ -4537,12 +4550,12 @@ static void fromErrorInstance(ZigException* except, JSC::JSGlobalObject* global, bool getFromSourceURL = false; if (stackTrace != nullptr && stackTrace->size() > 0) { - populateStackTrace(vm, *stackTrace, &except->stack, global); + populateStackTrace(vm, *stackTrace, &except->stack, global, flags); if (UNLIKELY(scope.exception())) { scope.clearExceptionExceptTermination(); } } else if (err->stackTrace() != nullptr && err->stackTrace()->size() > 0) { - populateStackTrace(vm, *err->stackTrace(), &except->stack, global); + populateStackTrace(vm, *err->stackTrace(), &except->stack, global, flags); if (UNLIKELY(scope.exception())) { scope.clearExceptionExceptTermination(); } @@ -4998,12 +5011,12 @@ void JSC__JSValue__toZigException(JSC__JSValue jsException, JSC__JSGlobalObject* JSValue unwrapped = jscException->value(); if (JSC::ErrorInstance* error = JSC::jsDynamicCast(unwrapped)) { - fromErrorInstance(exception, global, error, &jscException->stack(), unwrapped); + fromErrorInstance(exception, global, error, &jscException->stack(), unwrapped, PopulateStackTraceFlags::OnlyPosition); return; } if (jscException->stack().size() > 0) { - populateStackTrace(global->vm(), jscException->stack(), &exception->stack, global); + populateStackTrace(global->vm(), jscException->stack(), &exception->stack, global, PopulateStackTraceFlags::OnlyPosition); } exceptionFromString(exception, unwrapped, global); @@ -5011,13 +5024,40 @@ void JSC__JSValue__toZigException(JSC__JSValue jsException, JSC__JSGlobalObject* } if (JSC::ErrorInstance* error = JSC::jsDynamicCast(value)) { - fromErrorInstance(exception, global, error, nullptr, value); + fromErrorInstance(exception, global, error, nullptr, value, PopulateStackTraceFlags::OnlyPosition); return; } exceptionFromString(exception, value, global); } +void ZigException__collectSourceLines(JSC__JSValue jsException, JSC__JSGlobalObject* global, ZigException* exception) +{ + JSC::JSValue value = JSC::JSValue::decode(jsException); + if (value == JSC::JSValue {}) { + return; + } + + if (value.classInfoOrNull() == JSC::Exception::info()) { + auto* jscException = jsCast(value); + JSValue unwrapped = jscException->value(); + + if (jscException->stack().size() > 0) { + populateStackTrace(global->vm(), jscException->stack(), &exception->stack, global, PopulateStackTraceFlags::OnlySourceLines); + } + + exceptionFromString(exception, unwrapped, global); + return; + } + + if (JSC::ErrorInstance* error = JSC::jsDynamicCast(value)) { + if (error->stackTrace() != nullptr && error->stackTrace()->size() > 0) { + populateStackTrace(global->vm(), *error->stackTrace(), &exception->stack, global, PopulateStackTraceFlags::OnlySourceLines); + } + return; + } +} + #pragma mark - JSC::VM size_t JSC__VM__runGC(JSC__VM* vm, bool sync) @@ -5078,7 +5118,7 @@ bool JSC__JSValue__isTerminationException(JSC__JSValue JSValue0, JSC__VM* arg1) extern "C" void JSC__Exception__getStackTrace(JSC::Exception* arg0, JSC::JSGlobalObject* global, ZigStackTrace* trace) { - populateStackTrace(arg0->vm(), arg0->stack(), trace, global); + populateStackTrace(arg0->vm(), arg0->stack(), trace, global, PopulateStackTraceFlags::OnlyPosition); } void JSC__VM__shrinkFootprint(JSC__VM* arg0) { arg0->shrinkFootprintWhenIdle(); }; diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index a6b716c523..9aaae2a718 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -811,6 +811,12 @@ pub const ZigException = extern struct { fd: i32 = -1, + pub extern fn ZigException__collectSourceLines(jsValue: JSValue, global: *JSGlobalObject, exception: *ZigException) void; + + pub fn collectSourceLines(this: *ZigException, value: JSValue, global: *JSGlobalObject) void { + ZigException__collectSourceLines(value, global, this); + } + pub fn deinit(this: *ZigException) void { this.syscall.deref(); this.system_code.deref(); diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 342e33c358..3e11e7e3fb 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -2983,6 +2983,7 @@ pub const VirtualMachine = struct { .quote_strings = false, .single_line = false, .stack_check = bun.StackCheck.init(), + .error_display_level = .full, }; defer formatter.deinit(); switch (Output.enable_ansi_colors) { @@ -3579,8 +3580,22 @@ pub const VirtualMachine = struct { exception_list: ?*ExceptionList, must_reset_parser_arena_later: *bool, source_code_slice: *?ZigString.Slice, + allow_source_code_preview: bool, ) void { error_instance.toZigException(this.global, exception); + const enable_source_code_preview = allow_source_code_preview and + !(bun.getRuntimeFeatureFlag("BUN_DISABLE_SOURCE_CODE_PREVIEW") or + bun.getRuntimeFeatureFlag("BUN_DISABLE_TRANSPILED_SOURCE_CODE_PREVIEW")); + + defer { + if (Environment.isDebug) { + if (!enable_source_code_preview and source_code_slice.* != null) { + Output.panic("Do not collect source code when we don't need to", .{}); + } else if (!enable_source_code_preview and exception.stack.source_lines_numbers[0] != -1) { + Output.panic("Do not collect source code when we don't need to", .{}); + } + } + } // defer this so that it copies correctly defer { @@ -3696,7 +3711,10 @@ pub const VirtualMachine = struct { } const code = code: { - if (bun.getRuntimeFeatureFlag("BUN_DISABLE_SOURCE_CODE_PREVIEW") or bun.getRuntimeFeatureFlag("BUN_DISABLE_TRANSPILED_SOURCE_CODE_PREVIEW")) break :code ZigString.Slice.empty; + if (!enable_source_code_preview) { + break :code ZigString.Slice.empty; + } + if (!top.remapped and lookup.source_map != null and lookup.source_map.?.isExternal()) { if (lookup.getSourceCode(top_source_url.slice())) |src| { break :code src; @@ -3715,7 +3733,13 @@ pub const VirtualMachine = struct { must_reset_parser_arena_later.* = true; break :code original_source.source_code.toUTF8(bun.default_allocator); }; - source_code_slice.* = code; + + if (enable_source_code_preview and code.len == 0) { + exception.collectSourceLines(error_instance, this.global); + } + + if (code.len > 0) + source_code_slice.* = code; top.position.line = Ordinal.fromZeroBased(mapping.original.lines); top.position.column = Ordinal.fromZeroBased(mapping.original.columns); @@ -3747,6 +3771,8 @@ pub const VirtualMachine = struct { exception.stack.source_lines_len = @as(u8, @truncate(lines.len)); } + } else if (enable_source_code_preview) { + exception.collectSourceLines(error_instance, this.global); } if (frames.len > 1) { @@ -3792,6 +3818,7 @@ pub const VirtualMachine = struct { exception_list, &exception_holder.need_to_clear_parser_arena_on_deinit, &source_code_slice, + formatter.error_display_level != .warn, ); const prev_had_errors = this.had_errors; this.had_errors = true; @@ -3913,7 +3940,7 @@ pub const VirtualMachine = struct { ); } - try this.printErrorNameAndMessage(name, message, code, Writer, writer, allow_ansi_color); + try this.printErrorNameAndMessage(name, message, code, Writer, writer, allow_ansi_color, formatter.error_display_level); } else if (top_frame) |top| { defer did_print_name = true; const display_line = source.line + 1; @@ -3958,12 +3985,12 @@ pub const VirtualMachine = struct { } } - try this.printErrorNameAndMessage(name, message, code, Writer, writer, allow_ansi_color); + try this.printErrorNameAndMessage(name, message, code, Writer, writer, allow_ansi_color, formatter.error_display_level); } } if (!did_print_name) { - try this.printErrorNameAndMessage(name, message, code, Writer, writer, allow_ansi_color); + try this.printErrorNameAndMessage(name, message, code, Writer, writer, allow_ansi_color, formatter.error_display_level); } // This is usually unsafe to do, but we are protecting them each time first @@ -4126,6 +4153,7 @@ pub const VirtualMachine = struct { comptime Writer: type, writer: Writer, comptime allow_ansi_color: bool, + error_display_level: ConsoleObject.FormatOptions.ErrorDisplayLevel, ) !void { if (!name.isEmpty() and !message.isEmpty()) { const display_name, const display_message = if (name.eqlComptime("Error")) brk: { @@ -4156,19 +4184,15 @@ pub const VirtualMachine = struct { }; }; - break :brk .{ String.static("error"), message }; + break :brk .{ String.empty, message }; } else .{ name, message }; - try writer.print(comptime Output.prettyFmt("{}: {s}\n", allow_ansi_color), .{ display_name, display_message }); + try writer.print(comptime Output.prettyFmt("{}{}\n", allow_ansi_color), .{ error_display_level.formatter(display_name, allow_ansi_color, .include_colon), display_message }); } else if (!name.isEmpty()) { - if (!name.hasPrefixComptime("error")) { - try writer.print(comptime Output.prettyFmt("error: {}\n", allow_ansi_color), .{name}); - } else { - try writer.print(comptime Output.prettyFmt("{}\n", allow_ansi_color), .{name}); - } + try writer.print("{}\n", .{error_display_level.formatter(name, allow_ansi_color, .include_colon)}); } else if (!message.isEmpty()) { - try writer.print(comptime Output.prettyFmt("error: {}\n", allow_ansi_color), .{message}); + try writer.print(comptime Output.prettyFmt("{}{}\n", allow_ansi_color), .{ error_display_level.formatter(bun.String.empty, allow_ansi_color, .include_colon), message }); } else { - try writer.print(comptime Output.prettyFmt("error\n", allow_ansi_color), .{}); + try writer.print(comptime Output.prettyFmt("{}\n", allow_ansi_color), .{error_display_level.formatter(bun.String.empty, allow_ansi_color, .exclude_colon)}); } } diff --git a/test/js/web/console/console-group.fixture.js b/test/js/web/console/console-group.fixture.js index 34d103720d..7b36024a3c 100644 --- a/test/js/web/console/console-group.fixture.js +++ b/test/js/web/console/console-group.fixture.js @@ -41,11 +41,22 @@ console.groupEnd(); console.groupEnd(); // Extra console.groupEnd(); // Extra +class NamedError extends Error { + constructor(message) { + super(message); + this.name = "NamedError"; + } +} + // Group with different log types console.group("Different logs"); console.log("Regular log"); console.info("Info log"); console.warn("Warning log"); +console.warn(new Error("console.warn an error")); +console.error(new Error("console.error an error")); +console.error(new NamedError("console.error a named error")); +console.warn(new NamedError("console.warn a named error")); console.error("Error log"); console.debug("Debug log"); console.groupEnd(); diff --git a/test/js/web/console/console-log.test.ts b/test/js/web/console/console-log.test.ts index 64e94a2069..17cb8994cd 100644 --- a/test/js/web/console/console-log.test.ts +++ b/test/js/web/console/console-log.test.ts @@ -58,12 +58,93 @@ it("long arrays get cutoff", () => { }); it("console.group", async () => { + const filepath = join(import.meta.dir, "console-group.fixture.js").replaceAll("\\", "/"); const proc = Bun.spawnSync({ - cmd: [bunExe(), join(import.meta.dir, "console-group.fixture.js")], - env: bunEnv, + cmd: [bunExe(), filepath], + env: { ...bunEnv, "BUN_JSC_showPrivateScriptsInStackTraces": "0" }, stdio: ["inherit", "pipe", "pipe"], }); expect(proc.exitCode).toBe(0); - expect(proc.stderr.toString("utf8").replaceAll("\r\n", "\n").trim()).toMatchSnapshot("console-group-error"); - expect(proc.stdout.toString("utf8").replaceAll("\r\n", "\n").trim()).toMatchSnapshot("console-group-output"); + let stdout = proc.stdout + .toString("utf8") + .replaceAll("\r\n", "\n") + .replaceAll("\\", "/") + .trim() + .replaceAll(filepath, ""); + let stderr = proc.stderr + .toString("utf8") + .replaceAll("\r\n", "\n") + .replaceAll("\\", "/") + .trim() + .replaceAll(filepath, ""); + expect(stdout).toMatchInlineSnapshot(` +"Basic group + Inside basic group +Outer group + Inside outer group + Inner group + Inside inner group + Back to outer group +Level 1 + Level 2 + Level 3 + Deep inside +undefined +Empty nested +Test extra end + Inside +Different logs + Regular log + Info log + Debug log +Complex types + { + a: 1, + b: 2, + } + [ 1, 2, 3 ] +null + undefined + 0 + false + + Inside falsy groups +🎉 Unicode! + Inside unicode group + Tab\tNewline +Quote"Backslash + Special chars" +`); + expect(stderr).toMatchInlineSnapshot(` +"Warning log + warn: console.warn an error + at :56:14 + + 52 | console.group("Different logs"); +53 | console.log("Regular log"); +54 | console.info("Info log"); +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 :57:15 + + 41 | console.groupEnd(); // Extra +42 | console.groupEnd(); // Extra +43 | +44 | class NamedError extends Error { +45 | constructor(message) { +46 | super(message); + ^ +NamedError: console.error a named error + at new NamedError (:46:5) + at :58:15 + + NamedError: console.warn a named error + at new NamedError (:46:5) + at :59:14 + + Error log" +`); });