mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
3410 lines
138 KiB
Zig
3410 lines
138 KiB
Zig
const std = @import("std");
|
|
const bun = @import("root").bun;
|
|
const JSC = bun.JSC;
|
|
const Output = bun.Output;
|
|
const ConsoleObject = @This();
|
|
const Shimmer = @import("./bindings/shimmer.zig").Shimmer;
|
|
const String = bun.String;
|
|
const JSGlobalObject = JSC.JSGlobalObject;
|
|
const JSValue = JSC.JSValue;
|
|
const strings = bun.strings;
|
|
const is_bindgen = JSC.is_bindgen;
|
|
const ZigException = JSC.ZigException;
|
|
const ZigString = JSC.ZigString;
|
|
const VirtualMachine = JSC.VirtualMachine;
|
|
const string = bun.string;
|
|
const JSLexer = bun.js_lexer;
|
|
const ScriptArguments = opaque {};
|
|
const JSPrinter = bun.js_printer;
|
|
const Environment = bun.Environment;
|
|
const default_allocator = bun.default_allocator;
|
|
const JestPrettyFormat = @import("./test/pretty_format.zig").JestPrettyFormat;
|
|
const JSPromise = JSC.JSPromise;
|
|
const EventType = JSC.EventType;
|
|
|
|
pub const shim = Shimmer("Bun", "ConsoleObject", @This());
|
|
pub const Type = *anyopaque;
|
|
pub const name = "Bun::ConsoleObject";
|
|
pub const include = "\"ConsoleObject.h\"";
|
|
pub const namespace = shim.namespace;
|
|
const Counter = std.AutoHashMapUnmanaged(u64, u32);
|
|
|
|
const BufferedWriter = std.io.BufferedWriter(4096, Output.WriterType);
|
|
error_writer: BufferedWriter,
|
|
writer: BufferedWriter,
|
|
|
|
counts: Counter = .{},
|
|
|
|
pub fn format(_: @This(), comptime _: []const u8, _: anytype, _: anytype) !void {}
|
|
|
|
pub fn init(error_writer: Output.WriterType, writer: Output.WriterType) ConsoleObject {
|
|
return ConsoleObject{
|
|
.error_writer = BufferedWriter{ .unbuffered_writer = error_writer },
|
|
.writer = BufferedWriter{ .unbuffered_writer = writer },
|
|
};
|
|
}
|
|
|
|
pub const MessageLevel = enum(u32) {
|
|
Log = 0,
|
|
Warning = 1,
|
|
Error = 2,
|
|
Debug = 3,
|
|
Info = 4,
|
|
_,
|
|
};
|
|
|
|
pub const MessageType = enum(u32) {
|
|
Log = 0,
|
|
Dir = 1,
|
|
DirXML = 2,
|
|
Table = 3,
|
|
Trace = 4,
|
|
StartGroup = 5,
|
|
StartGroupCollapsed = 6,
|
|
EndGroup = 7,
|
|
Clear = 8,
|
|
Assert = 9,
|
|
Timing = 10,
|
|
Profile = 11,
|
|
ProfileEnd = 12,
|
|
Image = 13,
|
|
_,
|
|
};
|
|
|
|
var stderr_mutex: bun.Lock = bun.Lock.init();
|
|
var stdout_mutex: bun.Lock = bun.Lock.init();
|
|
|
|
threadlocal var stderr_lock_count: u16 = 0;
|
|
threadlocal var stdout_lock_count: u16 = 0;
|
|
|
|
/// https://console.spec.whatwg.org/#formatter
|
|
pub fn messageWithTypeAndLevel(
|
|
//console_: ConsoleObject.Type,
|
|
_: ConsoleObject.Type,
|
|
message_type: MessageType,
|
|
//message_level: u32,
|
|
level: MessageLevel,
|
|
global: *JSGlobalObject,
|
|
vals: [*]const JSValue,
|
|
len: usize,
|
|
) callconv(JSC.conv) void {
|
|
if (comptime is_bindgen) {
|
|
return;
|
|
}
|
|
|
|
var console = global.bunVM().console;
|
|
|
|
// Lock/unlock a mutex incase two JS threads are console.log'ing at the same time
|
|
// We do this the slightly annoying way to avoid assigning a pointer
|
|
if (level == .Warning or level == .Error or message_type == .Assert) {
|
|
if (stderr_lock_count == 0) {
|
|
stderr_mutex.lock();
|
|
}
|
|
|
|
stderr_lock_count += 1;
|
|
} else {
|
|
if (stdout_lock_count == 0) {
|
|
stdout_mutex.lock();
|
|
}
|
|
|
|
stdout_lock_count += 1;
|
|
}
|
|
|
|
defer {
|
|
if (level == .Warning or level == .Error or message_type == .Assert) {
|
|
stderr_lock_count -= 1;
|
|
|
|
if (stderr_lock_count == 0) {
|
|
stderr_mutex.unlock();
|
|
}
|
|
} else {
|
|
stdout_lock_count -= 1;
|
|
|
|
if (stdout_lock_count == 0) {
|
|
stdout_mutex.unlock();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (message_type == .Clear) {
|
|
Output.resetTerminal();
|
|
return;
|
|
}
|
|
|
|
if (message_type == .Assert and len == 0) {
|
|
const text = if (Output.enable_ansi_colors_stderr)
|
|
Output.prettyFmt("<r><red>Assertion failed<r>\n", true)
|
|
else
|
|
"Assertion failed\n";
|
|
console.error_writer.unbuffered_writer.writeAll(text) catch {};
|
|
return;
|
|
}
|
|
|
|
const enable_colors = if (level == .Warning or level == .Error)
|
|
Output.enable_ansi_colors_stderr
|
|
else
|
|
Output.enable_ansi_colors_stdout;
|
|
|
|
var buffered_writer = if (level == .Warning or level == .Error)
|
|
&console.error_writer
|
|
else
|
|
&console.writer;
|
|
var writer = buffered_writer.writer();
|
|
const Writer = @TypeOf(writer);
|
|
|
|
var print_length = len;
|
|
var print_options: FormatOptions = .{
|
|
.enable_colors = enable_colors,
|
|
.add_newline = true,
|
|
.flush = true,
|
|
};
|
|
|
|
if (message_type == .Table and len >= 1) {
|
|
// if value is not an object/array/iterable, don't print a table and just print it
|
|
var tabular_data = vals[0];
|
|
if (tabular_data.isObject()) {
|
|
const properties = if (len >= 2 and vals[1].jsType().isArray()) vals[1] else JSValue.undefined;
|
|
var table_printer = TablePrinter.init(
|
|
global,
|
|
level,
|
|
tabular_data,
|
|
properties,
|
|
);
|
|
|
|
switch (enable_colors) {
|
|
inline else => |colors| table_printer.printTable(Writer, writer, colors) catch return,
|
|
}
|
|
buffered_writer.flush() catch {};
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (message_type == .Dir and len >= 2) {
|
|
print_length = 1;
|
|
var opts = vals[1];
|
|
if (opts.isObject()) {
|
|
if (opts.get(global, "depth")) |depth_prop| {
|
|
if (depth_prop.isInt32() or depth_prop.isNumber() or depth_prop.isBigInt())
|
|
print_options.max_depth = depth_prop.toU16()
|
|
else if (depth_prop.isNull())
|
|
print_options.max_depth = std.math.maxInt(u16);
|
|
}
|
|
if (opts.get(global, "colors")) |colors_prop| {
|
|
if (colors_prop.isBoolean())
|
|
print_options.enable_colors = colors_prop.toBoolean();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (print_length > 0)
|
|
format2(
|
|
level,
|
|
global,
|
|
vals,
|
|
print_length,
|
|
@TypeOf(buffered_writer.unbuffered_writer.context),
|
|
Writer,
|
|
writer,
|
|
print_options,
|
|
)
|
|
else if (message_type == .Log) {
|
|
_ = console.writer.write("\n") catch 0;
|
|
console.writer.flush() catch {};
|
|
} else if (message_type != .Trace)
|
|
writer.writeAll("undefined\n") catch {};
|
|
|
|
if (message_type == .Trace) {
|
|
writeTrace(Writer, writer, global);
|
|
buffered_writer.flush() catch {};
|
|
}
|
|
}
|
|
|
|
const TablePrinter = struct {
|
|
const Column = struct {
|
|
name: String,
|
|
width: u32 = 1,
|
|
};
|
|
|
|
const RowKey = union(@This().Type) {
|
|
str: String,
|
|
num: u32,
|
|
|
|
const Type = enum { str, num };
|
|
};
|
|
|
|
const PADDING = 1;
|
|
|
|
globalObject: *JSGlobalObject,
|
|
level: MessageLevel,
|
|
value_formatter: ConsoleObject.Formatter,
|
|
|
|
tabular_data: JSValue,
|
|
properties: JSValue,
|
|
|
|
is_iterable: bool,
|
|
jstype: JSValue.JSType,
|
|
|
|
/// width of the "Values" column.
|
|
/// This column is not appended to "columns" from the start, because it needs to be the last column.
|
|
values_col_width: ?u32 = null,
|
|
|
|
values_col_idx: usize = std.math.maxInt(usize),
|
|
|
|
pub fn init(
|
|
globalObject: *JSGlobalObject,
|
|
level: MessageLevel,
|
|
tabular_data: JSValue,
|
|
properties: JSValue,
|
|
) TablePrinter {
|
|
return TablePrinter{
|
|
.level = level,
|
|
.globalObject = globalObject,
|
|
.tabular_data = tabular_data,
|
|
.properties = properties,
|
|
.is_iterable = tabular_data.isIterable(globalObject),
|
|
.jstype = tabular_data.jsType(),
|
|
.value_formatter = ConsoleObject.Formatter{
|
|
.remaining_values = &[_]JSValue{},
|
|
.globalThis = globalObject,
|
|
.ordered_properties = false,
|
|
.quote_strings = false,
|
|
.single_line = true,
|
|
.max_depth = 5,
|
|
},
|
|
};
|
|
}
|
|
|
|
const VisibleCharacterCounter = struct {
|
|
width: *usize = undefined,
|
|
|
|
pub const WriteError = error{};
|
|
|
|
pub const Writer = std.io.Writer(
|
|
VisibleCharacterCounter,
|
|
VisibleCharacterCounter.WriteError,
|
|
VisibleCharacterCounter.write,
|
|
);
|
|
|
|
pub fn write(this: VisibleCharacterCounter, bytes: []const u8) WriteError!usize {
|
|
this.width.* += strings.visible.width.exclude_ansi_colors.utf8(bytes);
|
|
return bytes.len;
|
|
}
|
|
|
|
pub fn writeAll(this: VisibleCharacterCounter, bytes: []const u8) WriteError!void {
|
|
this.width.* += strings.width.exclude_ansi_colors.utf8(bytes);
|
|
}
|
|
};
|
|
|
|
/// Compute how much horizontal space will take a JSValue when printed
|
|
fn getWidthForValue(this: *TablePrinter, value: JSValue) u32 {
|
|
var width: usize = 0;
|
|
var value_formatter = this.value_formatter;
|
|
|
|
const tag = ConsoleObject.Formatter.Tag.get(value, this.globalObject);
|
|
value_formatter.quote_strings = !(tag.tag == .String or tag.tag == .StringPossiblyFormatted);
|
|
value_formatter.format(
|
|
tag,
|
|
VisibleCharacterCounter.Writer,
|
|
VisibleCharacterCounter.Writer{
|
|
.context = .{
|
|
.width = &width,
|
|
},
|
|
},
|
|
value,
|
|
this.globalObject,
|
|
false,
|
|
);
|
|
|
|
return @truncate(width);
|
|
}
|
|
|
|
/// Update the sizes of the columns for the values of a given row, and create any additional columns as needed
|
|
fn updateColumnsForRow(this: *TablePrinter, columns: *std.ArrayList(Column), row_key: RowKey, row_value: JSValue) !void {
|
|
// update size of "(index)" column
|
|
const row_key_len: u32 = switch (row_key) {
|
|
.str => |value| @intCast(value.visibleWidthExcludeANSIColors(false)),
|
|
.num => |value| @truncate(bun.fmt.fastDigitCount(value)),
|
|
};
|
|
columns.items[0].width = @max(columns.items[0].width, row_key_len);
|
|
|
|
// special handling for Map: column with idx=1 is "Keys"
|
|
if (this.jstype.isMap()) {
|
|
const entry_key = row_value.getIndex(this.globalObject, 0);
|
|
const entry_value = row_value.getIndex(this.globalObject, 1);
|
|
columns.items[1].width = @max(columns.items[1].width, this.getWidthForValue(entry_key));
|
|
this.values_col_width = @max(this.values_col_width orelse 0, this.getWidthForValue(entry_value));
|
|
return;
|
|
}
|
|
|
|
if (row_value.isObject()) {
|
|
// object ->
|
|
// - if "properties" arg was provided: iterate the already-created columns (except for the 0-th which is the index)
|
|
// - otherwise: iterate the object properties, and create the columns on-demand
|
|
if (!this.properties.isUndefined()) {
|
|
for (columns.items[1..]) |*column| {
|
|
if (row_value.getWithString(this.globalObject, column.name)) |value| {
|
|
column.width = @max(column.width, this.getWidthForValue(value));
|
|
}
|
|
}
|
|
} else {
|
|
var cols_iter = JSC.JSPropertyIterator(.{
|
|
.skip_empty_name = false,
|
|
.include_value = true,
|
|
}).init(this.globalObject, row_value);
|
|
defer cols_iter.deinit();
|
|
|
|
while (cols_iter.next()) |col_key| {
|
|
const value = cols_iter.value;
|
|
|
|
// find or create the column for the property
|
|
const column: *Column = brk: {
|
|
const col_str = String.init(col_key);
|
|
|
|
for (columns.items[1..]) |*col| {
|
|
if (col.name.eql(col_str)) {
|
|
break :brk col;
|
|
}
|
|
}
|
|
|
|
// Need to ref this string because JSPropertyIterator
|
|
// uses `toString` instead of `toStringRef` for property names
|
|
col_str.ref();
|
|
|
|
try columns.append(.{ .name = col_str });
|
|
|
|
break :brk &columns.items[columns.items.len - 1];
|
|
};
|
|
|
|
column.width = @max(column.width, this.getWidthForValue(value));
|
|
}
|
|
}
|
|
} else if (this.properties.isUndefined()) {
|
|
// not object -> the value will go to the special "Values" column
|
|
this.values_col_width = @max(this.values_col_width orelse 1, this.getWidthForValue(row_value));
|
|
}
|
|
}
|
|
|
|
fn writeStringNTimes(comptime Writer: type, writer: Writer, comptime str: []const u8, n: usize) !void {
|
|
if (comptime str.len == 1) {
|
|
try writer.writeByteNTimes(str[0], n);
|
|
return;
|
|
}
|
|
|
|
for (0..n) |_| {
|
|
try writer.writeAll(str);
|
|
}
|
|
}
|
|
|
|
fn printRow(
|
|
this: *TablePrinter,
|
|
comptime Writer: type,
|
|
writer: Writer,
|
|
comptime enable_ansi_colors: bool,
|
|
columns: *std.ArrayList(Column),
|
|
row_key: RowKey,
|
|
row_value: JSValue,
|
|
) !void {
|
|
try writer.writeAll("│");
|
|
{
|
|
const len: u32 = switch (row_key) {
|
|
.str => |value| @truncate(value.visibleWidthExcludeANSIColors(false)),
|
|
.num => |value| @truncate(bun.fmt.fastDigitCount(value)),
|
|
};
|
|
const needed = columns.items[0].width -| len;
|
|
|
|
// Right-align the number column
|
|
try writer.writeByteNTimes(' ', needed + PADDING);
|
|
switch (row_key) {
|
|
.str => |value| try writer.print("{}", .{value}),
|
|
.num => |value| try writer.print("{d}", .{value}),
|
|
}
|
|
try writer.writeByteNTimes(' ', PADDING);
|
|
}
|
|
|
|
for (1..columns.items.len) |col_idx| {
|
|
const col = columns.items[col_idx];
|
|
|
|
try writer.writeAll("│");
|
|
|
|
var value = JSValue.zero;
|
|
if (col_idx == 1 and this.jstype.isMap()) { // is the "Keys" column, when iterating a Map?
|
|
value = row_value.getIndex(this.globalObject, 0);
|
|
} else if (col_idx == this.values_col_idx) { // is the "Values" column?
|
|
if (this.jstype.isMap()) {
|
|
value = row_value.getIndex(this.globalObject, 1);
|
|
} else if (!row_value.isObject()) {
|
|
value = row_value;
|
|
}
|
|
} else if (row_value.isObject()) {
|
|
value = row_value.getWithString(this.globalObject, col.name) orelse JSValue.zero;
|
|
}
|
|
|
|
if (value.isEmpty()) {
|
|
try writer.writeByteNTimes(' ', col.width + (PADDING * 2));
|
|
} else {
|
|
const len: u32 = this.getWidthForValue(value);
|
|
const needed = col.width -| len;
|
|
try writer.writeByteNTimes(' ', PADDING);
|
|
const tag = ConsoleObject.Formatter.Tag.get(value, this.globalObject);
|
|
var value_formatter = this.value_formatter;
|
|
|
|
value_formatter.quote_strings = !(tag.tag == .String or tag.tag == .StringPossiblyFormatted);
|
|
|
|
defer {
|
|
if (value_formatter.map_node) |node| {
|
|
node.data = value_formatter.map;
|
|
node.data.clearRetainingCapacity();
|
|
node.release();
|
|
}
|
|
}
|
|
value_formatter.format(
|
|
tag,
|
|
Writer,
|
|
writer,
|
|
value,
|
|
this.globalObject,
|
|
enable_ansi_colors,
|
|
);
|
|
|
|
try writer.writeByteNTimes(' ', needed + PADDING);
|
|
}
|
|
}
|
|
try writer.writeAll("│\n");
|
|
}
|
|
|
|
pub fn printTable(
|
|
this: *TablePrinter,
|
|
comptime Writer: type,
|
|
writer: Writer,
|
|
comptime enable_ansi_colors: bool,
|
|
) !void {
|
|
const globalObject = this.globalObject;
|
|
|
|
var stack_fallback = std.heap.stackFallback(@sizeOf(Column) * 16, this.globalObject.allocator());
|
|
var columns = try std.ArrayList(Column).initCapacity(stack_fallback.get(), 16);
|
|
defer {
|
|
for (columns.items) |*col| {
|
|
col.name.deref();
|
|
}
|
|
columns.deinit();
|
|
}
|
|
|
|
// create the first column " " which is always present
|
|
columns.appendAssumeCapacity(.{
|
|
.name = String.static(" "),
|
|
.width = 1,
|
|
});
|
|
|
|
// special case for Map: create the special "Key" column at index 1
|
|
if (this.jstype.isMap()) {
|
|
columns.appendAssumeCapacity(.{
|
|
.name = String.static("Key"),
|
|
});
|
|
}
|
|
|
|
// if the "properties" arg was provided, pre-populate the columns
|
|
if (!this.properties.isUndefined()) {
|
|
var properties_iter = JSC.JSArrayIterator.init(this.properties, globalObject);
|
|
while (properties_iter.next()) |value| {
|
|
try columns.append(.{
|
|
.name = value.toBunString(globalObject),
|
|
});
|
|
}
|
|
}
|
|
|
|
// rows first pass - calculate the column widths
|
|
{
|
|
if (this.is_iterable) {
|
|
var ctx_: struct { this: *TablePrinter, columns: *@TypeOf(columns), idx: u32 = 0, err: bool = false } = .{ .this = this, .columns = &columns };
|
|
this.tabular_data.forEachWithContext(globalObject, &ctx_, struct {
|
|
fn callback(_: *JSC.VM, _: *JSGlobalObject, ctx: *@TypeOf(ctx_), value: JSValue) callconv(.C) void {
|
|
updateColumnsForRow(ctx.this, ctx.columns, .{ .num = ctx.idx }, value) catch {
|
|
ctx.err = true;
|
|
};
|
|
ctx.idx += 1;
|
|
}
|
|
}.callback);
|
|
if (ctx_.err) return error.JSError;
|
|
} else {
|
|
var rows_iter = JSC.JSPropertyIterator(.{
|
|
.skip_empty_name = false,
|
|
.include_value = true,
|
|
}).init(globalObject, this.tabular_data);
|
|
defer rows_iter.deinit();
|
|
|
|
while (rows_iter.next()) |row_key| {
|
|
try this.updateColumnsForRow(&columns, .{ .str = String.init(row_key) }, rows_iter.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// append the special "Values" column as the last one, if it is present
|
|
if (this.values_col_width) |width| {
|
|
this.values_col_idx = columns.items.len;
|
|
try columns.append(.{
|
|
.name = String.static("Values"),
|
|
.width = width,
|
|
});
|
|
}
|
|
|
|
// print the table header (border line + column names line + border line)
|
|
{
|
|
for (columns.items) |*col| {
|
|
// also update the col width with the length of the column name itself
|
|
col.width = @max(col.width, @as(u32, @intCast(col.name.visibleWidthExcludeANSIColors(false))));
|
|
}
|
|
|
|
try writer.writeAll("┌");
|
|
for (columns.items, 0..) |*col, i| {
|
|
if (i > 0) try writer.writeAll("┬");
|
|
try writeStringNTimes(Writer, writer, "─", col.width + (PADDING * 2));
|
|
}
|
|
|
|
try writer.writeAll("┐\n│");
|
|
|
|
for (columns.items, 0..) |col, i| {
|
|
if (i > 0) try writer.writeAll("│");
|
|
const len = col.name.visibleWidthExcludeANSIColors(false);
|
|
const needed = col.width -| len;
|
|
try writer.writeByteNTimes(' ', 1);
|
|
if (comptime enable_ansi_colors) {
|
|
try writer.writeAll(Output.prettyFmt("<r><b>", true));
|
|
}
|
|
try writer.print("{}", .{col.name});
|
|
if (comptime enable_ansi_colors) {
|
|
try writer.writeAll(Output.prettyFmt("<r>", true));
|
|
}
|
|
try writer.writeByteNTimes(' ', needed + PADDING);
|
|
}
|
|
|
|
try writer.writeAll("│\n├");
|
|
for (columns.items, 0..) |col, i| {
|
|
if (i > 0) try writer.writeAll("┼");
|
|
try writeStringNTimes(Writer, writer, "─", col.width + (PADDING * 2));
|
|
}
|
|
try writer.writeAll("┤\n");
|
|
}
|
|
|
|
// rows second pass - print the actual table rows
|
|
{
|
|
if (this.is_iterable) {
|
|
var ctx_: struct { this: *TablePrinter, columns: *@TypeOf(columns), writer: Writer, idx: u32 = 0, err: bool = false } = .{ .this = this, .columns = &columns, .writer = writer };
|
|
this.tabular_data.forEachWithContext(globalObject, &ctx_, struct {
|
|
fn callback(_: *JSC.VM, _: *JSGlobalObject, ctx: *@TypeOf(ctx_), value: JSValue) callconv(.C) void {
|
|
printRow(ctx.this, Writer, ctx.writer, enable_ansi_colors, ctx.columns, .{ .num = ctx.idx }, value) catch {
|
|
ctx.err = true;
|
|
};
|
|
ctx.idx += 1;
|
|
}
|
|
}.callback);
|
|
if (ctx_.err) return error.JSError;
|
|
} else {
|
|
var rows_iter = JSC.JSPropertyIterator(.{
|
|
.skip_empty_name = false,
|
|
.include_value = true,
|
|
}).init(globalObject, this.tabular_data);
|
|
defer rows_iter.deinit();
|
|
|
|
while (rows_iter.next()) |row_key| {
|
|
try this.printRow(Writer, writer, enable_ansi_colors, &columns, .{ .str = String.init(row_key) }, rows_iter.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// print the table bottom border
|
|
{
|
|
try writer.writeAll("└");
|
|
try writeStringNTimes(Writer, writer, "─", columns.items[0].width + (PADDING * 2));
|
|
for (columns.items[1..]) |*column| {
|
|
try writer.writeAll("┴");
|
|
try writeStringNTimes(Writer, writer, "─", column.width + (PADDING * 2));
|
|
}
|
|
try writer.writeAll("┘\n");
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn writeTrace(comptime Writer: type, writer: Writer, global: *JSGlobalObject) void {
|
|
var holder = ZigException.Holder.init();
|
|
var vm = VirtualMachine.get();
|
|
defer holder.deinit(vm);
|
|
const exception = holder.zigException();
|
|
|
|
var source_code_slice: ?ZigString.Slice = null;
|
|
defer if (source_code_slice) |slice| slice.deinit();
|
|
|
|
var err = ZigString.init("trace output").toErrorInstance(global);
|
|
err.toZigException(global, exception);
|
|
vm.remapZigException(
|
|
exception,
|
|
err,
|
|
null,
|
|
&holder.need_to_clear_parser_arena_on_deinit,
|
|
&source_code_slice,
|
|
);
|
|
|
|
if (Output.enable_ansi_colors_stderr)
|
|
VirtualMachine.printStackTrace(
|
|
Writer,
|
|
writer,
|
|
exception.stack,
|
|
true,
|
|
) catch {}
|
|
else
|
|
VirtualMachine.printStackTrace(
|
|
Writer,
|
|
writer,
|
|
exception.stack,
|
|
false,
|
|
) catch {};
|
|
}
|
|
|
|
pub const FormatOptions = struct {
|
|
enable_colors: bool,
|
|
add_newline: bool,
|
|
flush: bool,
|
|
ordered_properties: bool = false,
|
|
quote_strings: bool = false,
|
|
max_depth: u16 = 2,
|
|
};
|
|
|
|
pub fn format2(
|
|
level: MessageLevel,
|
|
global: *JSGlobalObject,
|
|
vals: [*]const JSValue,
|
|
len: usize,
|
|
comptime RawWriter: type,
|
|
comptime Writer: type,
|
|
writer: Writer,
|
|
options: FormatOptions,
|
|
) void {
|
|
var fmt: ConsoleObject.Formatter = undefined;
|
|
defer {
|
|
if (fmt.map_node) |node| {
|
|
node.data = fmt.map;
|
|
node.data.clearRetainingCapacity();
|
|
node.release();
|
|
}
|
|
}
|
|
|
|
if (len == 1) {
|
|
fmt = ConsoleObject.Formatter{
|
|
.remaining_values = &[_]JSValue{},
|
|
.globalThis = global,
|
|
.ordered_properties = options.ordered_properties,
|
|
.quote_strings = options.quote_strings,
|
|
.max_depth = options.max_depth,
|
|
};
|
|
const tag = ConsoleObject.Formatter.Tag.get(vals[0], global);
|
|
|
|
var unbuffered_writer = if (comptime Writer != RawWriter)
|
|
if (@hasDecl(@TypeOf(writer.context.unbuffered_writer.context), "quietWriter"))
|
|
writer.context.unbuffered_writer.context.quietWriter()
|
|
else
|
|
writer.context.unbuffered_writer.context.writer()
|
|
else
|
|
writer;
|
|
|
|
if (tag.tag == .String) {
|
|
if (options.enable_colors) {
|
|
if (level == .Error) {
|
|
unbuffered_writer.writeAll(comptime Output.prettyFmt("<r><red>", true)) catch {};
|
|
}
|
|
fmt.format(
|
|
tag,
|
|
@TypeOf(unbuffered_writer),
|
|
unbuffered_writer,
|
|
vals[0],
|
|
global,
|
|
true,
|
|
);
|
|
if (level == .Error) {
|
|
unbuffered_writer.writeAll(comptime Output.prettyFmt("<r>", true)) catch {};
|
|
}
|
|
} else {
|
|
fmt.format(
|
|
tag,
|
|
@TypeOf(unbuffered_writer),
|
|
unbuffered_writer,
|
|
vals[0],
|
|
global,
|
|
false,
|
|
);
|
|
}
|
|
if (options.add_newline) _ = unbuffered_writer.write("\n") catch 0;
|
|
} else {
|
|
defer {
|
|
if (comptime Writer != RawWriter) {
|
|
if (options.flush) writer.context.flush() catch {};
|
|
}
|
|
}
|
|
if (options.enable_colors) {
|
|
fmt.format(
|
|
tag,
|
|
Writer,
|
|
writer,
|
|
vals[0],
|
|
global,
|
|
true,
|
|
);
|
|
} else {
|
|
fmt.format(
|
|
tag,
|
|
Writer,
|
|
writer,
|
|
vals[0],
|
|
global,
|
|
false,
|
|
);
|
|
}
|
|
if (options.add_newline) _ = writer.write("\n") catch 0;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
defer {
|
|
if (comptime Writer != RawWriter) {
|
|
if (options.flush) writer.context.flush() catch {};
|
|
}
|
|
}
|
|
|
|
var this_value: JSValue = vals[0];
|
|
fmt = ConsoleObject.Formatter{
|
|
.remaining_values = vals[0..len][1..],
|
|
.globalThis = global,
|
|
.ordered_properties = options.ordered_properties,
|
|
.quote_strings = options.quote_strings,
|
|
};
|
|
var tag: ConsoleObject.Formatter.Tag.Result = undefined;
|
|
|
|
var any = false;
|
|
if (options.enable_colors) {
|
|
if (level == .Error) {
|
|
writer.writeAll(comptime Output.prettyFmt("<r><red>", true)) catch {};
|
|
}
|
|
while (true) {
|
|
if (any) {
|
|
_ = writer.write(" ") catch 0;
|
|
}
|
|
any = true;
|
|
|
|
tag = ConsoleObject.Formatter.Tag.get(this_value, global);
|
|
if (tag.tag == .String and fmt.remaining_values.len > 0) {
|
|
tag.tag = .{ .StringPossiblyFormatted = {} };
|
|
}
|
|
|
|
fmt.format(tag, Writer, writer, this_value, global, true);
|
|
if (fmt.remaining_values.len == 0) {
|
|
break;
|
|
}
|
|
|
|
this_value = fmt.remaining_values[0];
|
|
fmt.remaining_values = fmt.remaining_values[1..];
|
|
}
|
|
if (level == .Error) {
|
|
writer.writeAll(comptime Output.prettyFmt("<r>", true)) catch {};
|
|
}
|
|
} else {
|
|
while (true) {
|
|
if (any) {
|
|
_ = writer.write(" ") catch 0;
|
|
}
|
|
any = true;
|
|
tag = ConsoleObject.Formatter.Tag.get(this_value, global);
|
|
if (tag.tag == .String and fmt.remaining_values.len > 0) {
|
|
tag.tag = .{ .StringPossiblyFormatted = {} };
|
|
}
|
|
|
|
fmt.format(tag, Writer, writer, this_value, global, false);
|
|
if (fmt.remaining_values.len == 0)
|
|
break;
|
|
|
|
this_value = fmt.remaining_values[0];
|
|
fmt.remaining_values = fmt.remaining_values[1..];
|
|
}
|
|
}
|
|
|
|
if (options.add_newline) _ = writer.write("\n") catch 0;
|
|
}
|
|
|
|
const CustomFormattedObject = struct {
|
|
function: JSValue = .zero,
|
|
this: JSValue = .zero,
|
|
};
|
|
|
|
pub const Formatter = struct {
|
|
remaining_values: []const JSValue = &[_]JSValue{},
|
|
map: Visited.Map = undefined,
|
|
map_node: ?*Visited.Pool.Node = null,
|
|
hide_native: bool = false,
|
|
globalThis: *JSGlobalObject,
|
|
indent: u32 = 0,
|
|
depth: u16 = 0,
|
|
max_depth: u16 = 8,
|
|
quote_strings: bool = false,
|
|
quote_keys: bool = false,
|
|
failed: bool = false,
|
|
estimated_line_length: usize = 0,
|
|
always_newline_scope: bool = false,
|
|
single_line: bool = false,
|
|
ordered_properties: bool = false,
|
|
custom_formatted_object: CustomFormattedObject = .{},
|
|
|
|
pub fn goodTimeForANewLine(this: *@This()) bool {
|
|
if (this.estimated_line_length > 80) {
|
|
this.resetLine();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
pub fn resetLine(this: *@This()) void {
|
|
this.estimated_line_length = this.indent * 2;
|
|
}
|
|
|
|
pub fn addForNewLine(this: *@This(), len: usize) void {
|
|
this.estimated_line_length +|= len;
|
|
}
|
|
|
|
pub const ZigFormatter = struct {
|
|
formatter: *ConsoleObject.Formatter,
|
|
value: JSValue,
|
|
|
|
pub const WriteError = error{UhOh};
|
|
pub fn format(self: ZigFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
self.formatter.remaining_values = &[_]JSValue{self.value};
|
|
defer {
|
|
self.formatter.remaining_values = &[_]JSValue{};
|
|
}
|
|
self.formatter.format(
|
|
Tag.get(self.value, self.formatter.globalThis),
|
|
@TypeOf(writer),
|
|
writer,
|
|
self.value,
|
|
self.formatter.globalThis,
|
|
false,
|
|
);
|
|
}
|
|
};
|
|
|
|
// For detecting circular references
|
|
pub const Visited = struct {
|
|
const ObjectPool = @import("../pool.zig").ObjectPool;
|
|
pub const Map = std.AutoHashMap(JSValue.Type, void);
|
|
pub const Pool = ObjectPool(
|
|
Map,
|
|
struct {
|
|
pub fn init(allocator: std.mem.Allocator) anyerror!Map {
|
|
return Map.init(allocator);
|
|
}
|
|
}.init,
|
|
true,
|
|
16,
|
|
);
|
|
};
|
|
|
|
pub const Tag = enum {
|
|
StringPossiblyFormatted,
|
|
String,
|
|
Undefined,
|
|
Double,
|
|
Integer,
|
|
Null,
|
|
Boolean,
|
|
Array,
|
|
Object,
|
|
Function,
|
|
Class,
|
|
Error,
|
|
TypedArray,
|
|
Map,
|
|
MapIterator,
|
|
SetIterator,
|
|
Set,
|
|
BigInt,
|
|
Symbol,
|
|
|
|
CustomFormattedObject,
|
|
|
|
GlobalObject,
|
|
Private,
|
|
Promise,
|
|
|
|
JSON,
|
|
toJSON,
|
|
NativeCode,
|
|
|
|
JSX,
|
|
Event,
|
|
|
|
GetterSetter,
|
|
CustomGetterSetter,
|
|
|
|
Proxy,
|
|
RevokedProxy,
|
|
|
|
pub fn isPrimitive(this: Tag) bool {
|
|
return switch (this) {
|
|
.String,
|
|
.StringPossiblyFormatted,
|
|
.Undefined,
|
|
.Double,
|
|
.Integer,
|
|
.Null,
|
|
.Boolean,
|
|
.Symbol,
|
|
.BigInt,
|
|
=> true,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
pub inline fn canHaveCircularReferences(tag: Tag) bool {
|
|
return tag == .Array or tag == .Object or tag == .Map or tag == .Set;
|
|
}
|
|
|
|
const Result = struct {
|
|
tag: union(Tag) {
|
|
StringPossiblyFormatted: void,
|
|
String: void,
|
|
Undefined: void,
|
|
Double: void,
|
|
Integer: void,
|
|
Null: void,
|
|
Boolean: void,
|
|
Array: void,
|
|
Object: void,
|
|
Function: void,
|
|
Class: void,
|
|
Error: void,
|
|
TypedArray: void,
|
|
Map: void,
|
|
MapIterator: void,
|
|
SetIterator: void,
|
|
Set: void,
|
|
BigInt: void,
|
|
Symbol: void,
|
|
CustomFormattedObject: CustomFormattedObject,
|
|
GlobalObject: void,
|
|
Private: void,
|
|
Promise: void,
|
|
JSON: void,
|
|
toJSON: void,
|
|
NativeCode: void,
|
|
JSX: void,
|
|
Event: void,
|
|
GetterSetter: void,
|
|
CustomGetterSetter: void,
|
|
Proxy: void,
|
|
RevokedProxy: void,
|
|
|
|
pub fn isPrimitive(this: @This()) bool {
|
|
return @as(Tag, this).isPrimitive();
|
|
}
|
|
|
|
pub fn tag(this: @This()) Tag {
|
|
return @as(Tag, this);
|
|
}
|
|
},
|
|
cell: JSValue.JSType = JSValue.JSType.Cell,
|
|
};
|
|
|
|
pub fn get(value: JSValue, globalThis: *JSGlobalObject) Result {
|
|
return getAdvanced(value, globalThis, .{ .hide_global = false });
|
|
}
|
|
|
|
pub const Options = struct {
|
|
hide_global: bool = false,
|
|
};
|
|
|
|
pub fn getAdvanced(value: JSValue, globalThis: *JSGlobalObject, opts: Options) Result {
|
|
switch (@intFromEnum(value)) {
|
|
0, 0xa => return Result{
|
|
.tag = .{ .Undefined = {} },
|
|
},
|
|
0x2 => return Result{
|
|
.tag = .{ .Null = {} },
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
if (value.isInt32()) {
|
|
return .{
|
|
.tag = .{ .Integer = {} },
|
|
};
|
|
} else if (value.isNumber()) {
|
|
return .{
|
|
.tag = .{ .Double = {} },
|
|
};
|
|
} else if (value.isBoolean()) {
|
|
return .{
|
|
.tag = .{ .Boolean = {} },
|
|
};
|
|
}
|
|
|
|
if (!value.isCell())
|
|
return .{
|
|
.tag = .{ .NativeCode = {} },
|
|
};
|
|
|
|
const js_type = value.jsType();
|
|
|
|
if (js_type.isHidden()) return .{
|
|
.tag = .{ .NativeCode = {} },
|
|
.cell = js_type,
|
|
};
|
|
|
|
if (js_type == .Cell) {
|
|
return .{
|
|
.tag = .{ .NativeCode = {} },
|
|
.cell = js_type,
|
|
};
|
|
}
|
|
|
|
if (js_type.canGet()) {
|
|
// Attempt to get custom formatter
|
|
if (value.fastGet(globalThis, .inspectCustom)) |callback_value| {
|
|
if (callback_value.isCallable(globalThis.vm())) {
|
|
return .{
|
|
.tag = .{
|
|
.CustomFormattedObject = .{
|
|
.function = callback_value,
|
|
.this = value,
|
|
},
|
|
},
|
|
.cell = js_type,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (js_type == .DOMWrapper) {
|
|
return .{
|
|
.tag = .{ .Private = {} },
|
|
.cell = js_type,
|
|
};
|
|
}
|
|
|
|
// If we check an Object has a method table and it does not
|
|
// it will crash
|
|
if (js_type != .Object and value.isCallable(globalThis.vm())) {
|
|
if (value.isClass(globalThis)) {
|
|
return .{
|
|
.tag = .{ .Class = {} },
|
|
.cell = js_type,
|
|
};
|
|
}
|
|
|
|
return .{
|
|
// TODO: we print InternalFunction as Object because we have a lot of
|
|
// callable namespaces and printing the contents of it is better than [Function: namespace]
|
|
// ideally, we would print [Function: namespace] { ... } on all functions, internal and js.
|
|
// what we'll do later is rid of .Function and .Class and handle the prefix in the .Object formatter
|
|
.tag = if (js_type == .InternalFunction) .Object else .Function,
|
|
.cell = js_type,
|
|
};
|
|
}
|
|
|
|
if (js_type == .GlobalProxy) {
|
|
if (!opts.hide_global) {
|
|
return Tag.get(
|
|
JSC.JSValue.c(JSC.C.JSObjectGetProxyTarget(value.asObjectRef())),
|
|
globalThis,
|
|
);
|
|
}
|
|
return .{
|
|
.tag = .{ .GlobalObject = {} },
|
|
.cell = js_type,
|
|
};
|
|
}
|
|
|
|
// Is this a react element?
|
|
if (js_type.isObject()) {
|
|
if (value.get(globalThis, "$$typeof")) |typeof_symbol| {
|
|
var reactElement = ZigString.init("react.element");
|
|
var react_fragment = ZigString.init("react.fragment");
|
|
|
|
if (JSValue.isSameValue(typeof_symbol, JSValue.symbolFor(globalThis, &reactElement), globalThis) or JSValue.isSameValue(typeof_symbol, JSValue.symbolFor(globalThis, &react_fragment), globalThis)) {
|
|
return .{ .tag = .{ .JSX = {} }, .cell = js_type };
|
|
}
|
|
}
|
|
}
|
|
|
|
return .{
|
|
.tag = switch (js_type) {
|
|
.ErrorInstance => .Error,
|
|
.NumberObject => .Double,
|
|
.DerivedArray, JSValue.JSType.Array => .Array,
|
|
.DerivedStringObject, JSValue.JSType.String, JSValue.JSType.StringObject => .String,
|
|
.RegExpObject => .String,
|
|
.Symbol => .Symbol,
|
|
.BooleanObject => .Boolean,
|
|
.JSFunction => .Function,
|
|
.JSWeakMap, JSValue.JSType.JSMap => .Map,
|
|
.JSMapIterator => .MapIterator,
|
|
.JSSetIterator => .SetIterator,
|
|
.JSWeakSet, JSValue.JSType.JSSet => .Set,
|
|
.JSDate => .JSON,
|
|
.JSPromise => .Promise,
|
|
|
|
.Object,
|
|
.FinalObject,
|
|
.ModuleNamespaceObject,
|
|
=> .Object,
|
|
|
|
.ProxyObject => tag: {
|
|
const handler = value.getProxyInternalField(.handler);
|
|
if (handler == .zero or handler == .undefined or handler == .null) {
|
|
break :tag .RevokedProxy;
|
|
}
|
|
break :tag .Proxy;
|
|
},
|
|
|
|
.GlobalObject => if (!opts.hide_global)
|
|
.Object
|
|
else
|
|
.GlobalObject,
|
|
|
|
.ArrayBuffer,
|
|
.Int8Array,
|
|
.Uint8Array,
|
|
.Uint8ClampedArray,
|
|
.Int16Array,
|
|
.Uint16Array,
|
|
.Int32Array,
|
|
.Uint32Array,
|
|
.Float32Array,
|
|
.Float64Array,
|
|
.BigInt64Array,
|
|
.BigUint64Array,
|
|
.DataView,
|
|
=> .TypedArray,
|
|
|
|
.HeapBigInt => .BigInt,
|
|
|
|
// None of these should ever exist here
|
|
// But we're going to check anyway
|
|
.APIValueWrapper,
|
|
.NativeExecutable,
|
|
.ProgramExecutable,
|
|
.ModuleProgramExecutable,
|
|
.EvalExecutable,
|
|
.FunctionExecutable,
|
|
.UnlinkedFunctionExecutable,
|
|
.UnlinkedProgramCodeBlock,
|
|
.UnlinkedModuleProgramCodeBlock,
|
|
.UnlinkedEvalCodeBlock,
|
|
.UnlinkedFunctionCodeBlock,
|
|
.CodeBlock,
|
|
.JSImmutableButterfly,
|
|
.JSSourceCode,
|
|
.JSScriptFetcher,
|
|
.JSScriptFetchParameters,
|
|
.JSCallee,
|
|
.GlobalLexicalEnvironment,
|
|
.LexicalEnvironment,
|
|
.ModuleEnvironment,
|
|
.StrictEvalActivation,
|
|
.WithScope,
|
|
=> .NativeCode,
|
|
|
|
.Event => .Event,
|
|
|
|
.GetterSetter => .GetterSetter,
|
|
.CustomGetterSetter => .CustomGetterSetter,
|
|
|
|
.JSAsJSONType => .toJSON,
|
|
|
|
else => .JSON,
|
|
},
|
|
.cell = js_type,
|
|
};
|
|
}
|
|
};
|
|
|
|
const CellType = JSC.C.CellType;
|
|
threadlocal var name_buf: [512]u8 = undefined;
|
|
|
|
/// https://console.spec.whatwg.org/#formatter
|
|
const PercentTag = enum {
|
|
s, // s
|
|
i, // i or d
|
|
f, // f
|
|
o, // o
|
|
O, // O
|
|
c, // c
|
|
};
|
|
|
|
fn writeWithFormatting(
|
|
this: *ConsoleObject.Formatter,
|
|
comptime Writer: type,
|
|
writer_: Writer,
|
|
comptime Slice: type,
|
|
slice_: Slice,
|
|
global: *JSGlobalObject,
|
|
comptime enable_ansi_colors: bool,
|
|
) void {
|
|
var writer = WrappedWriter(Writer){
|
|
.ctx = writer_,
|
|
.estimated_line_length = &this.estimated_line_length,
|
|
};
|
|
var slice = slice_;
|
|
var i: u32 = 0;
|
|
var len: u32 = @as(u32, @truncate(slice.len));
|
|
var hit_percent = false;
|
|
while (i < len) : (i += 1) {
|
|
if (hit_percent) {
|
|
i = 0;
|
|
hit_percent = false;
|
|
}
|
|
|
|
switch (slice[i]) {
|
|
'%' => {
|
|
i += 1;
|
|
if (i >= len)
|
|
break;
|
|
|
|
if (this.remaining_values.len == 0)
|
|
break;
|
|
|
|
const token: PercentTag = switch (slice[i]) {
|
|
's' => .s,
|
|
'f' => .f,
|
|
'o' => .o,
|
|
'O' => .O,
|
|
'd', 'i' => .i,
|
|
'c' => .c,
|
|
'%' => {
|
|
// print up to and including the first %
|
|
const end = slice[0..i];
|
|
writer.writeAll(end);
|
|
// then skip the second % so we dont hit it again
|
|
slice = slice[@min(slice.len, i + 1)..];
|
|
len = @truncate(slice.len);
|
|
i = 0;
|
|
continue;
|
|
},
|
|
else => continue,
|
|
};
|
|
|
|
// Flush everything up to the %
|
|
const end = slice[0 .. i - 1];
|
|
writer.writeAll(end);
|
|
slice = slice[@min(slice.len, i + 1)..];
|
|
i = 0;
|
|
hit_percent = true;
|
|
len = @truncate(slice.len);
|
|
const next_value = this.remaining_values[0];
|
|
this.remaining_values = this.remaining_values[1..];
|
|
|
|
// https://console.spec.whatwg.org/#formatter
|
|
const max_before_e_notation = 1000000000000000000000;
|
|
const min_before_e_notation = 0.000001;
|
|
switch (token) {
|
|
.s => this.printAs(Tag.String, Writer, writer_, next_value, next_value.jsType(), enable_ansi_colors),
|
|
.i => {
|
|
// 1. If Type(current) is Symbol, let converted be NaN
|
|
// 2. Otherwise, let converted be the result of Call(%parseInt%, undefined, current, 10)
|
|
const int: i64 = brk: {
|
|
// This logic is convoluted because %parseInt% will coerce the argument to a string
|
|
// first. As an optimization, we can check if the argument is a number and
|
|
// skip such coercion.
|
|
if (next_value.isInt32()) {
|
|
// Already an int, parseInt will parse to itself.
|
|
break :brk next_value.asInt32();
|
|
}
|
|
|
|
if (next_value.isNumber() or !next_value.isSymbol()) double_convert: {
|
|
var value = next_value.coerceToDouble(global);
|
|
|
|
if (!std.math.isFinite(value)) {
|
|
// for NaN and the string Infinity and -Infinity, parseInt returns NaN
|
|
break :double_convert;
|
|
}
|
|
|
|
// simulate parseInt, which converts the argument to a string and
|
|
// then back to a number, without converting it to a string
|
|
if (value == 0) {
|
|
break :brk 0;
|
|
}
|
|
|
|
const sign: i64 = if (value < 0) -1 else 1;
|
|
value = @abs(value);
|
|
if (value >= max_before_e_notation) {
|
|
// toString prints 1.000+e0, which parseInt will stop at
|
|
// the '.' or the '+', this gives us a single digit value.
|
|
while (value >= 10) value /= 10;
|
|
break :brk @as(i64, @intFromFloat(@floor(value))) * sign;
|
|
} else if (value < min_before_e_notation) {
|
|
// toString prints 1.000-e0, which parseInt will stop at
|
|
// the '.' or the '-', this gives us a single digit value.
|
|
while (value < 1) value *= 10;
|
|
break :brk @as(i64, @intFromFloat(@floor(value))) * sign;
|
|
}
|
|
|
|
// parsing stops at '.', so this is equal to @floor
|
|
break :brk @as(i64, @intFromFloat(@floor(value))) * sign;
|
|
}
|
|
|
|
// for NaN and the string Infinity and -Infinity, parseInt returns NaN
|
|
this.addForNewLine("NaN".len);
|
|
writer.print("NaN", .{});
|
|
continue;
|
|
};
|
|
|
|
if (int < std.math.maxInt(u32)) {
|
|
const is_negative = int < 0;
|
|
const digits = if (i != 0)
|
|
bun.fmt.fastDigitCount(@as(u64, @intCast(@abs(int)))) + @as(u64, @intFromBool(is_negative))
|
|
else
|
|
1;
|
|
this.addForNewLine(digits);
|
|
} else {
|
|
this.addForNewLine(bun.fmt.count("{d}", .{int}));
|
|
}
|
|
writer.print("{d}", .{int});
|
|
},
|
|
|
|
.f => {
|
|
// 1. If Type(current) is Symbol, let converted be NaN
|
|
// 2. Otherwise, let converted be the result of Call(%parseFloat%, undefined, [current]).
|
|
const converted: f64 = brk: {
|
|
if (next_value.isInt32()) {
|
|
const int = next_value.asInt32();
|
|
const is_negative = int < 0;
|
|
const digits = if (i != 0)
|
|
bun.fmt.fastDigitCount(@as(u64, @intCast(@abs(int)))) + @as(u64, @intFromBool(is_negative))
|
|
else
|
|
1;
|
|
this.addForNewLine(digits);
|
|
writer.print("{d}", .{int});
|
|
continue;
|
|
}
|
|
if (next_value.isNumber()) {
|
|
break :brk next_value.asNumber();
|
|
}
|
|
if (next_value.isSymbol()) {
|
|
break :brk std.math.nan(f64);
|
|
}
|
|
// TODO: this is not perfectly emulating parseFloat,
|
|
// because spec says to convert the value to a string
|
|
// and then parse as a number, but we are just coercing
|
|
// a number.
|
|
break :brk next_value.coerceToDouble(global);
|
|
};
|
|
|
|
const abs = @abs(converted);
|
|
if (abs < max_before_e_notation and abs >= min_before_e_notation) {
|
|
this.addForNewLine(bun.fmt.count("{d}", .{converted}));
|
|
writer.print("{d}", .{converted});
|
|
} else if (std.math.isNan(converted)) {
|
|
this.addForNewLine("NaN".len);
|
|
writer.writeAll("NaN");
|
|
} else if (std.math.isInf(converted)) {
|
|
this.addForNewLine("Infinity".len + @as(usize, @intFromBool(converted < 0)));
|
|
if (converted < 0) {
|
|
writer.writeAll("-");
|
|
}
|
|
writer.writeAll("Infinity");
|
|
} else {
|
|
var buf: [124]u8 = undefined;
|
|
const formatted = bun.fmt.FormatDouble.dtoa(&buf, converted);
|
|
this.addForNewLine(formatted.len);
|
|
writer.print("{s}", .{formatted});
|
|
}
|
|
},
|
|
|
|
inline .o, .O => |t| {
|
|
if (t == .o) {
|
|
// TODO: Node.js applies the following extra formatter options.
|
|
//
|
|
// this.max_depth = 4;
|
|
// this.show_proxy = true;
|
|
// this.show_hidden = true;
|
|
//
|
|
// Spec defines %o as:
|
|
// > An object with optimally useful formatting is an
|
|
// > implementation-specific, potentially-interactive representation
|
|
// > of an object judged to be maximally useful and informative.
|
|
}
|
|
this.format(Tag.get(next_value, global), Writer, writer_, next_value, global, enable_ansi_colors);
|
|
},
|
|
|
|
.c => {
|
|
// TODO: Implement %c
|
|
},
|
|
}
|
|
if (this.remaining_values.len == 0) break;
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
if (slice.len > 0) writer.writeAll(slice);
|
|
}
|
|
|
|
pub fn WrappedWriter(comptime Writer: type) type {
|
|
return struct {
|
|
ctx: Writer,
|
|
failed: bool = false,
|
|
estimated_line_length: *usize,
|
|
|
|
pub fn print(self: *@This(), comptime fmt: string, args: anytype) void {
|
|
self.ctx.print(fmt, args) catch {
|
|
self.failed = true;
|
|
};
|
|
}
|
|
|
|
pub fn space(self: *@This()) void {
|
|
self.estimated_line_length.* += 1;
|
|
self.ctx.writeAll(" ") catch {
|
|
self.failed = true;
|
|
};
|
|
}
|
|
|
|
pub fn pretty(self: *@This(), comptime fmt: string, comptime enable_ansi_color: bool, args: anytype) void {
|
|
const length_ignoring_formatted_values = comptime brk: {
|
|
const fmt_str = Output.prettyFmt(fmt, false);
|
|
var length: usize = 0;
|
|
var i: usize = 0;
|
|
while (i < fmt_str.len) : (i += 1) {
|
|
switch (fmt_str[i]) {
|
|
'{' => {
|
|
i += 1;
|
|
if (i >= fmt_str.len)
|
|
break;
|
|
if (fmt_str[i] == '{') {
|
|
@compileError("Format string too complicated for pretty() right now");
|
|
}
|
|
|
|
while (i < fmt_str.len) : (i += 1) {
|
|
if (fmt_str[i] == '}') {
|
|
break;
|
|
}
|
|
}
|
|
if (i >= fmt_str.len)
|
|
break;
|
|
},
|
|
128...255 => {
|
|
@compileError("Format string too complicated for pretty() right now");
|
|
},
|
|
else => {},
|
|
}
|
|
length += 1;
|
|
}
|
|
|
|
break :brk length;
|
|
};
|
|
|
|
self.estimated_line_length.* += length_ignoring_formatted_values;
|
|
self.ctx.print(comptime Output.prettyFmt(fmt, enable_ansi_color), args) catch {
|
|
self.failed = true;
|
|
};
|
|
}
|
|
|
|
pub fn writeLatin1(self: *@This(), buf: []const u8) void {
|
|
var remain = buf;
|
|
while (remain.len > 0) {
|
|
if (strings.firstNonASCII(remain)) |i| {
|
|
if (i > 0) {
|
|
self.ctx.writeAll(remain[0..i]) catch {
|
|
self.failed = true;
|
|
return;
|
|
};
|
|
}
|
|
self.ctx.writeAll(&strings.latin1ToCodepointBytesAssumeNotASCII(remain[i])) catch {
|
|
self.failed = true;
|
|
};
|
|
remain = remain[i + 1 ..];
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
self.ctx.writeAll(remain) catch return;
|
|
}
|
|
|
|
pub inline fn writeAll(self: *@This(), buf: []const u8) void {
|
|
self.ctx.writeAll(buf) catch {
|
|
self.failed = true;
|
|
};
|
|
}
|
|
|
|
pub inline fn writeString(self: *@This(), str: ZigString) void {
|
|
self.print("{}", .{str});
|
|
}
|
|
|
|
pub inline fn write16Bit(self: *@This(), input: []const u16) void {
|
|
bun.fmt.formatUTF16Type([]const u16, input, self.ctx) catch {
|
|
self.failed = true;
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn writeIndent(
|
|
this: *ConsoleObject.Formatter,
|
|
comptime Writer: type,
|
|
writer: Writer,
|
|
) !void {
|
|
var buf = [_]u8{' '} ** 64;
|
|
var total_remain: u32 = this.indent;
|
|
while (total_remain > 0) {
|
|
const written: u8 = @min(32, total_remain);
|
|
try writer.writeAll(buf[0 .. written * 2]);
|
|
total_remain -|= written;
|
|
}
|
|
}
|
|
|
|
pub fn printComma(this: *ConsoleObject.Formatter, comptime Writer: type, writer: Writer, comptime enable_ansi_colors: bool) !void {
|
|
try writer.writeAll(comptime Output.prettyFmt("<r><d>,<r>", enable_ansi_colors));
|
|
this.estimated_line_length += 1;
|
|
}
|
|
|
|
pub fn MapIterator(comptime Writer: type, comptime enable_ansi_colors: bool, comptime is_iterator: bool, comptime single_line: bool) type {
|
|
return struct {
|
|
formatter: *ConsoleObject.Formatter,
|
|
writer: Writer,
|
|
count: usize = 0,
|
|
pub fn forEach(_: [*c]JSC.VM, globalObject: [*c]JSGlobalObject, ctx: ?*anyopaque, nextValue: JSValue) callconv(.C) void {
|
|
var this: *@This() = bun.cast(*@This(), ctx orelse return);
|
|
if (single_line and this.count > 0) {
|
|
this.formatter.printComma(Writer, this.writer, enable_ansi_colors) catch unreachable;
|
|
this.writer.writeAll(" ") catch unreachable;
|
|
}
|
|
if (!is_iterator) {
|
|
const key = JSC.JSObject.getIndex(nextValue, globalObject, 0);
|
|
const value = JSC.JSObject.getIndex(nextValue, globalObject, 1);
|
|
|
|
if (!single_line) {
|
|
this.formatter.writeIndent(Writer, this.writer) catch unreachable;
|
|
}
|
|
const key_tag = Tag.getAdvanced(key, globalObject, .{ .hide_global = true });
|
|
|
|
this.formatter.format(
|
|
key_tag,
|
|
Writer,
|
|
this.writer,
|
|
key,
|
|
this.formatter.globalThis,
|
|
enable_ansi_colors,
|
|
);
|
|
this.writer.writeAll(": ") catch unreachable;
|
|
const value_tag = Tag.getAdvanced(value, globalObject, .{ .hide_global = true });
|
|
this.formatter.format(
|
|
value_tag,
|
|
Writer,
|
|
this.writer,
|
|
value,
|
|
this.formatter.globalThis,
|
|
enable_ansi_colors,
|
|
);
|
|
} else {
|
|
if (!single_line) {
|
|
this.writer.writeAll("\n") catch unreachable;
|
|
this.formatter.writeIndent(Writer, this.writer) catch unreachable;
|
|
}
|
|
const tag = Tag.getAdvanced(nextValue, globalObject, .{ .hide_global = true });
|
|
this.formatter.format(
|
|
tag,
|
|
Writer,
|
|
this.writer,
|
|
nextValue,
|
|
this.formatter.globalThis,
|
|
enable_ansi_colors,
|
|
);
|
|
}
|
|
this.count += 1;
|
|
if (!single_line) {
|
|
this.formatter.printComma(Writer, this.writer, enable_ansi_colors) catch unreachable;
|
|
if (!is_iterator) {
|
|
this.writer.writeAll("\n") catch unreachable;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn SetIterator(comptime Writer: type, comptime enable_ansi_colors: bool, comptime single_line: bool) type {
|
|
return struct {
|
|
formatter: *ConsoleObject.Formatter,
|
|
writer: Writer,
|
|
is_first: bool = true,
|
|
pub fn forEach(_: [*c]JSC.VM, globalObject: [*c]JSGlobalObject, ctx: ?*anyopaque, nextValue: JSValue) callconv(.C) void {
|
|
var this: *@This() = bun.cast(*@This(), ctx orelse return);
|
|
if (single_line) {
|
|
if (!this.is_first) {
|
|
this.formatter.printComma(Writer, this.writer, enable_ansi_colors) catch unreachable;
|
|
this.writer.writeAll(" ") catch unreachable;
|
|
}
|
|
this.is_first = false;
|
|
} else {
|
|
this.formatter.writeIndent(Writer, this.writer) catch {};
|
|
}
|
|
const key_tag = Tag.getAdvanced(nextValue, globalObject, .{ .hide_global = true });
|
|
this.formatter.format(
|
|
key_tag,
|
|
Writer,
|
|
this.writer,
|
|
nextValue,
|
|
this.formatter.globalThis,
|
|
enable_ansi_colors,
|
|
);
|
|
|
|
if (!single_line) {
|
|
this.formatter.printComma(Writer, this.writer, enable_ansi_colors) catch unreachable;
|
|
this.writer.writeAll("\n") catch unreachable;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn PropertyIterator(comptime Writer: type, comptime enable_ansi_colors_: bool) type {
|
|
return struct {
|
|
formatter: *ConsoleObject.Formatter,
|
|
writer: Writer,
|
|
i: usize = 0,
|
|
single_line: bool,
|
|
always_newline: bool = false,
|
|
parent: JSValue,
|
|
const enable_ansi_colors = enable_ansi_colors_;
|
|
pub fn handleFirstProperty(this: *@This(), globalThis: *JSC.JSGlobalObject, value: JSValue) void {
|
|
if (value.isCell() and !value.jsType().isFunction()) {
|
|
var writer = WrappedWriter(Writer){
|
|
.ctx = this.writer,
|
|
.failed = false,
|
|
.estimated_line_length = &this.formatter.estimated_line_length,
|
|
};
|
|
|
|
if (getObjectName(globalThis, value)) |name_str| {
|
|
writer.print("{} ", .{name_str});
|
|
}
|
|
}
|
|
|
|
if (!this.single_line) {
|
|
this.always_newline = true;
|
|
}
|
|
this.formatter.estimated_line_length = this.formatter.indent * 2 + 1;
|
|
this.formatter.indent += 1;
|
|
this.formatter.depth += 1;
|
|
if (this.single_line) {
|
|
this.writer.writeAll("{ ") catch {};
|
|
} else {
|
|
this.writer.writeAll("{\n") catch {};
|
|
this.formatter.writeIndent(Writer, this.writer) catch {};
|
|
}
|
|
}
|
|
|
|
pub fn forEach(
|
|
globalThis: *JSGlobalObject,
|
|
ctx_ptr: ?*anyopaque,
|
|
key: *ZigString,
|
|
value: JSValue,
|
|
is_symbol: bool,
|
|
is_private_symbol: bool,
|
|
) callconv(.C) void {
|
|
if (key.eqlComptime("constructor")) return;
|
|
|
|
var ctx: *@This() = bun.cast(*@This(), ctx_ptr orelse return);
|
|
var this = ctx.formatter;
|
|
const writer_ = ctx.writer;
|
|
var writer = WrappedWriter(Writer){
|
|
.ctx = writer_,
|
|
.failed = false,
|
|
.estimated_line_length = &this.estimated_line_length,
|
|
};
|
|
|
|
const tag = Tag.getAdvanced(value, globalThis, .{ .hide_global = true });
|
|
|
|
if (tag.cell.isHidden()) return;
|
|
if (ctx.i == 0) {
|
|
handleFirstProperty(ctx, globalThis, ctx.parent);
|
|
} else {
|
|
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
|
|
}
|
|
|
|
defer ctx.i += 1;
|
|
if (ctx.i > 0) {
|
|
if (!this.single_line and (ctx.always_newline or this.always_newline_scope or this.goodTimeForANewLine())) {
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch {};
|
|
this.resetLine();
|
|
} else {
|
|
writer.space();
|
|
}
|
|
}
|
|
|
|
if (!is_symbol) {
|
|
|
|
// TODO: make this one pass?
|
|
if (!key.is16Bit() and (!this.quote_keys and JSLexer.isLatin1Identifier(@TypeOf(key.slice()), key.slice()))) {
|
|
this.addForNewLine(key.len + 1);
|
|
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r>{}<d>:<r> ", enable_ansi_colors),
|
|
.{key},
|
|
);
|
|
} else if (key.is16Bit() and (!this.quote_keys and JSLexer.isLatin1Identifier(@TypeOf(key.utf16SliceAligned()), key.utf16SliceAligned()))) {
|
|
this.addForNewLine(key.len + 1);
|
|
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r>{}<d>:<r> ", enable_ansi_colors),
|
|
.{key},
|
|
);
|
|
} else if (key.is16Bit()) {
|
|
var utf16Slice = key.utf16SliceAligned();
|
|
|
|
this.addForNewLine(utf16Slice.len + 2);
|
|
|
|
if (comptime enable_ansi_colors) {
|
|
writer.writeAll(comptime Output.prettyFmt("<r><green>", true));
|
|
}
|
|
|
|
writer.writeAll("\"");
|
|
|
|
while (strings.indexOfAny16(utf16Slice, "\"")) |j| {
|
|
writer.write16Bit(utf16Slice[0..j]);
|
|
writer.writeAll("\"");
|
|
utf16Slice = utf16Slice[j + 1 ..];
|
|
}
|
|
|
|
writer.write16Bit(utf16Slice);
|
|
|
|
writer.print(
|
|
comptime Output.prettyFmt("\"<r><d>:<r> ", enable_ansi_colors),
|
|
.{},
|
|
);
|
|
} else {
|
|
this.addForNewLine(key.len + 2);
|
|
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r><green>{s}<r><d>:<r> ", enable_ansi_colors),
|
|
.{JSPrinter.formatJSONString(key.slice())},
|
|
);
|
|
}
|
|
} else if (Environment.isDebug and is_private_symbol) {
|
|
this.addForNewLine(1 + "$:".len + key.len);
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r><magenta>{s}{any}<r><d>:<r> ", enable_ansi_colors),
|
|
.{
|
|
if (key.len > 0 and key.charAt(0) == '#') "" else "$",
|
|
key,
|
|
},
|
|
);
|
|
} else {
|
|
this.addForNewLine(1 + "[Symbol()]:".len + key.len);
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r><d>[<r><blue>Symbol({any})<r><d>]:<r> ", enable_ansi_colors),
|
|
.{key},
|
|
);
|
|
}
|
|
|
|
if (tag.cell.isStringLike()) {
|
|
if (comptime enable_ansi_colors) {
|
|
writer.writeAll(comptime Output.prettyFmt("<r><green>", true));
|
|
}
|
|
}
|
|
|
|
this.format(tag, Writer, ctx.writer, value, globalThis, enable_ansi_colors);
|
|
|
|
if (tag.cell.isStringLike()) {
|
|
if (comptime enable_ansi_colors) {
|
|
writer.writeAll(comptime Output.prettyFmt("<r>", true));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
fn getObjectName(globalThis: *JSC.JSGlobalObject, value: JSValue) ?ZigString {
|
|
var name_str = ZigString.init("");
|
|
value.getClassName(globalThis, &name_str);
|
|
if (!name_str.eqlComptime("Object")) {
|
|
return name_str;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
extern fn JSC__JSValue__callCustomInspectFunction(
|
|
*JSC.JSGlobalObject,
|
|
*JSC.JSGlobalObject,
|
|
JSValue,
|
|
JSValue,
|
|
depth: u32,
|
|
max_depth: u32,
|
|
colors: bool,
|
|
is_exception: *bool,
|
|
) JSValue;
|
|
|
|
pub fn printAs(
|
|
this: *ConsoleObject.Formatter,
|
|
comptime Format: ConsoleObject.Formatter.Tag,
|
|
comptime Writer: type,
|
|
writer_: Writer,
|
|
value: JSValue,
|
|
jsType: JSValue.JSType,
|
|
comptime enable_ansi_colors: bool,
|
|
) void {
|
|
if (this.failed)
|
|
return;
|
|
var writer = WrappedWriter(Writer){ .ctx = writer_, .estimated_line_length = &this.estimated_line_length };
|
|
defer {
|
|
if (writer.failed) {
|
|
this.failed = true;
|
|
}
|
|
}
|
|
if (comptime Format.canHaveCircularReferences()) {
|
|
if (this.map_node == null) {
|
|
this.map_node = Visited.Pool.get(default_allocator);
|
|
this.map_node.?.data.clearRetainingCapacity();
|
|
this.map = this.map_node.?.data;
|
|
}
|
|
|
|
const entry = this.map.getOrPut(@intFromEnum(value)) catch unreachable;
|
|
if (entry.found_existing) {
|
|
writer.writeAll(comptime Output.prettyFmt("<r><cyan>[Circular]<r>", enable_ansi_colors));
|
|
return;
|
|
}
|
|
}
|
|
|
|
defer {
|
|
if (comptime Format.canHaveCircularReferences()) {
|
|
_ = this.map.remove(@intFromEnum(value));
|
|
}
|
|
}
|
|
|
|
switch (comptime Format) {
|
|
.StringPossiblyFormatted => {
|
|
var str = value.toSlice(this.globalThis, bun.default_allocator);
|
|
defer str.deinit();
|
|
this.addForNewLine(str.len);
|
|
const slice = str.slice();
|
|
this.writeWithFormatting(Writer, writer_, @TypeOf(slice), slice, this.globalThis, enable_ansi_colors);
|
|
},
|
|
.String => {
|
|
// This is called from the '%s' formatter, so it can actually be any value
|
|
const str: bun.String = bun.String.tryFromJS(value, this.globalThis) orelse {
|
|
writer.failed = true;
|
|
return;
|
|
};
|
|
defer str.deref();
|
|
this.addForNewLine(str.length());
|
|
|
|
if (this.quote_strings and jsType != .RegExpObject) {
|
|
if (str.isEmpty()) {
|
|
writer.writeAll("\"\"");
|
|
return;
|
|
}
|
|
|
|
if (comptime enable_ansi_colors) {
|
|
writer.writeAll(Output.prettyFmt("<r><green>", true));
|
|
}
|
|
|
|
defer if (comptime enable_ansi_colors)
|
|
writer.writeAll(Output.prettyFmt("<r>", true));
|
|
|
|
if (str.isUTF16()) {
|
|
this.printAs(.JSON, Writer, writer_, value, .StringObject, enable_ansi_colors);
|
|
return;
|
|
}
|
|
|
|
JSPrinter.writeJSONString(str.latin1(), Writer, writer_, .latin1) catch unreachable;
|
|
|
|
return;
|
|
}
|
|
|
|
if (jsType == .RegExpObject and enable_ansi_colors) {
|
|
writer.print(comptime Output.prettyFmt("<r><red>", enable_ansi_colors), .{});
|
|
}
|
|
|
|
if (str.isUTF16()) {
|
|
// streaming print
|
|
writer.print("{}", .{str});
|
|
} else if (str.asUTF8()) |slice| {
|
|
// fast path
|
|
writer.writeAll(slice);
|
|
} else if (!str.isEmpty()) {
|
|
// slow path
|
|
const buf = strings.allocateLatin1IntoUTF8(bun.default_allocator, []const u8, str.latin1()) catch &[_]u8{};
|
|
if (buf.len > 0) {
|
|
defer bun.default_allocator.free(buf);
|
|
writer.writeAll(buf);
|
|
}
|
|
}
|
|
|
|
if (jsType == .RegExpObject and enable_ansi_colors) {
|
|
writer.print(comptime Output.prettyFmt("<r>", enable_ansi_colors), .{});
|
|
}
|
|
},
|
|
.Integer => {
|
|
const int = value.coerce(i64, this.globalThis);
|
|
if (int < std.math.maxInt(u32)) {
|
|
var i = int;
|
|
const is_negative = i < 0;
|
|
if (is_negative) {
|
|
i = -i;
|
|
}
|
|
const digits = if (i != 0)
|
|
bun.fmt.fastDigitCount(@as(usize, @intCast(i))) + @as(usize, @intFromBool(is_negative))
|
|
else
|
|
1;
|
|
this.addForNewLine(digits);
|
|
} else {
|
|
this.addForNewLine(bun.fmt.count("{d}", .{int}));
|
|
}
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>{d}<r>", enable_ansi_colors), .{int});
|
|
},
|
|
.BigInt => {
|
|
const out_str = value.getZigString(this.globalThis).slice();
|
|
this.addForNewLine(out_str.len);
|
|
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>{s}n<r>", enable_ansi_colors), .{out_str});
|
|
},
|
|
.Double => {
|
|
if (value.isCell()) {
|
|
var number_name = ZigString.Empty;
|
|
value.getClassName(this.globalThis, &number_name);
|
|
|
|
var number_value = ZigString.Empty;
|
|
value.toZigString(&number_value, this.globalThis);
|
|
|
|
if (!strings.eqlComptime(number_name.slice(), "Number")) {
|
|
this.addForNewLine(number_name.len + number_value.len + "[Number ():]".len);
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>[Number ({s}): {s}]<r>", enable_ansi_colors), .{
|
|
number_name,
|
|
number_value,
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.addForNewLine(number_name.len + number_value.len + 4);
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>[{s}: {s}]<r>", enable_ansi_colors), .{
|
|
number_name,
|
|
number_value,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const num = value.asNumber();
|
|
|
|
if (std.math.isPositiveInf(num)) {
|
|
this.addForNewLine("Infinity".len);
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>Infinity<r>", enable_ansi_colors), .{});
|
|
} else if (std.math.isNegativeInf(num)) {
|
|
this.addForNewLine("-Infinity".len);
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>-Infinity<r>", enable_ansi_colors), .{});
|
|
} else if (std.math.isNan(num)) {
|
|
this.addForNewLine("NaN".len);
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>NaN<r>", enable_ansi_colors), .{});
|
|
} else {
|
|
var buf: [124]u8 = undefined;
|
|
const formatted = bun.fmt.FormatDouble.dtoaWithNegativeZero(&buf, num);
|
|
this.addForNewLine(formatted.len);
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>{s}<r>", enable_ansi_colors), .{formatted});
|
|
}
|
|
},
|
|
.Undefined => {
|
|
this.addForNewLine(9);
|
|
writer.print(comptime Output.prettyFmt("<r><d>undefined<r>", enable_ansi_colors), .{});
|
|
},
|
|
.Null => {
|
|
this.addForNewLine(4);
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>null<r>", enable_ansi_colors), .{});
|
|
},
|
|
.CustomFormattedObject => {
|
|
var is_exception = false;
|
|
// Call custom inspect function. Will return the error if there is one
|
|
// we'll need to pass the callback through to the "this" value in here
|
|
const result = JSC__JSValue__callCustomInspectFunction(
|
|
JSC.VirtualMachine.get().global,
|
|
this.globalThis,
|
|
this.custom_formatted_object.function,
|
|
this.custom_formatted_object.this,
|
|
this.max_depth -| this.depth,
|
|
this.max_depth,
|
|
enable_ansi_colors,
|
|
&is_exception,
|
|
);
|
|
if (is_exception) {
|
|
// Previously, this printed [native code]
|
|
// TODO: in the future, should this throw when in Bun.inspect?
|
|
writer.print("[custom formatter threw an exception]", .{});
|
|
return;
|
|
}
|
|
// Strings are printed directly, otherwise we recurse. It is possible to end up in an infinite loop.
|
|
if (result.isString()) {
|
|
writer.print("{}", .{result.fmtString(this.globalThis)});
|
|
} else {
|
|
this.format(ConsoleObject.Formatter.Tag.get(result, this.globalThis), Writer, writer_, result, this.globalThis, enable_ansi_colors);
|
|
}
|
|
},
|
|
.Symbol => {
|
|
const description = value.getDescription(this.globalThis);
|
|
this.addForNewLine("Symbol".len);
|
|
|
|
if (description.len > 0) {
|
|
this.addForNewLine(description.len + "()".len);
|
|
writer.print(comptime Output.prettyFmt("<r><blue>Symbol({any})<r>", enable_ansi_colors), .{description});
|
|
} else {
|
|
writer.print(comptime Output.prettyFmt("<r><blue>Symbol<r>", enable_ansi_colors), .{});
|
|
}
|
|
},
|
|
.Error => {
|
|
VirtualMachine.get().printErrorlikeObject(
|
|
value,
|
|
null,
|
|
null,
|
|
Writer,
|
|
writer_,
|
|
enable_ansi_colors,
|
|
false,
|
|
);
|
|
},
|
|
.Class => {
|
|
var printable = ZigString.init(&name_buf);
|
|
value.getClassName(this.globalThis, &printable);
|
|
this.addForNewLine(printable.len);
|
|
|
|
if (printable.len == 0) {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[class (anonymous)]<r>", enable_ansi_colors), .{});
|
|
} else {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[class {}]<r>", enable_ansi_colors), .{printable});
|
|
}
|
|
},
|
|
.Function => {
|
|
var printable = value.getName(this.globalThis);
|
|
defer printable.deref();
|
|
|
|
const proto = value.getPrototype(this.globalThis);
|
|
const func_name = proto.getName(this.globalThis); // "Function" | "AsyncFunction" | "GeneratorFunction" | "AsyncGeneratorFunction"
|
|
defer func_name.deref();
|
|
|
|
if (printable.isEmpty()) {
|
|
if (func_name.isEmpty()) {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[Function]<r>", enable_ansi_colors), .{});
|
|
} else {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[{}]<r>", enable_ansi_colors), .{func_name});
|
|
}
|
|
} else {
|
|
if (func_name.isEmpty()) {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[Function: {}]<r>", enable_ansi_colors), .{printable});
|
|
} else {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[{}: {}]<r>", enable_ansi_colors), .{ func_name, printable });
|
|
}
|
|
}
|
|
},
|
|
.GetterSetter => {
|
|
const cell = value.asCell();
|
|
const getterSetter = cell.getGetterSetter();
|
|
const hasGetter = !getterSetter.isGetterNull();
|
|
const hasSetter = !getterSetter.isSetterNull();
|
|
if (hasGetter and hasSetter) {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[Getter/Setter]<r>", enable_ansi_colors), .{});
|
|
} else if (hasGetter) {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[Getter]<r>", enable_ansi_colors), .{});
|
|
} else if (hasSetter) {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[Setter]<r>", enable_ansi_colors), .{});
|
|
}
|
|
},
|
|
.CustomGetterSetter => {
|
|
const cell = value.asCell();
|
|
const getterSetter = cell.getCustomGetterSetter();
|
|
const hasGetter = !getterSetter.isGetterNull();
|
|
const hasSetter = !getterSetter.isSetterNull();
|
|
if (hasGetter and hasSetter) {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[Getter/Setter]<r>", enable_ansi_colors), .{});
|
|
} else if (hasGetter) {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[Getter]<r>", enable_ansi_colors), .{});
|
|
} else if (hasSetter) {
|
|
writer.print(comptime Output.prettyFmt("<cyan>[Setter]<r>", enable_ansi_colors), .{});
|
|
}
|
|
},
|
|
.Array => {
|
|
const len = @as(u32, @truncate(value.getLength(this.globalThis)));
|
|
|
|
// TODO: DerivedArray does not get passed along in JSType, and it's not clear why.
|
|
// if (jsType == .DerivedArray) {
|
|
// var printable = value.className(this.globalThis);
|
|
|
|
// if (!printable.isEmpty()) {
|
|
// writer.print(comptime Output.prettyFmt("{}<r><d>(<r><yellow><r>{d}<r><d>)<r> ", enable_ansi_colors), .{ printable, len });
|
|
// }
|
|
// }
|
|
|
|
if (len == 0) {
|
|
writer.writeAll("[]");
|
|
this.addForNewLine(2);
|
|
return;
|
|
}
|
|
|
|
var was_good_time = this.always_newline_scope or
|
|
// heuristic: more than 10, probably should have a newline before it
|
|
len > 10;
|
|
{
|
|
this.indent += 1;
|
|
this.depth += 1;
|
|
defer this.depth -|= 1;
|
|
defer this.indent -|= 1;
|
|
|
|
this.addForNewLine(2);
|
|
|
|
const prev_quote_strings = this.quote_strings;
|
|
this.quote_strings = true;
|
|
defer this.quote_strings = prev_quote_strings;
|
|
var empty_start: ?u32 = null;
|
|
first: {
|
|
const element = value.getDirectIndex(this.globalThis, 0);
|
|
|
|
const tag = Tag.getAdvanced(element, this.globalThis, .{ .hide_global = true });
|
|
|
|
was_good_time = was_good_time or !tag.tag.isPrimitive() or this.goodTimeForANewLine();
|
|
|
|
if (!this.single_line and (this.ordered_properties or was_good_time)) {
|
|
this.resetLine();
|
|
writer.writeAll("[");
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
this.addForNewLine(1);
|
|
} else {
|
|
writer.writeAll("[ ");
|
|
this.addForNewLine(2);
|
|
}
|
|
|
|
if (element.isEmpty()) {
|
|
empty_start = 0;
|
|
break :first;
|
|
}
|
|
|
|
this.format(tag, Writer, writer_, element, this.globalThis, enable_ansi_colors);
|
|
|
|
if (tag.cell.isStringLike()) {
|
|
if (comptime enable_ansi_colors) {
|
|
writer.writeAll(comptime Output.prettyFmt("<r>", true));
|
|
}
|
|
}
|
|
}
|
|
|
|
var i: u32 = 1;
|
|
|
|
while (i < len) : (i += 1) {
|
|
const element = value.getDirectIndex(this.globalThis, i);
|
|
if (element.isEmpty()) {
|
|
if (empty_start == null) {
|
|
empty_start = i;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (empty_start) |empty| {
|
|
if (empty > 0) {
|
|
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
|
|
if (!this.single_line and (this.ordered_properties or this.goodTimeForANewLine())) {
|
|
was_good_time = true;
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
} else {
|
|
writer.space();
|
|
}
|
|
}
|
|
const empty_count = i - empty;
|
|
if (empty_count == 1) {
|
|
writer.pretty("<r><d>empty item<r>", enable_ansi_colors, .{});
|
|
} else {
|
|
this.estimated_line_length += bun.fmt.fastDigitCount(empty_count);
|
|
writer.pretty("<r><d>{d} x empty items<r>", enable_ansi_colors, .{empty_count});
|
|
}
|
|
empty_start = null;
|
|
}
|
|
|
|
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
|
|
if (!this.single_line and (this.ordered_properties or this.goodTimeForANewLine())) {
|
|
writer.writeAll("\n");
|
|
was_good_time = true;
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
} else {
|
|
writer.space();
|
|
}
|
|
|
|
const tag = Tag.getAdvanced(element, this.globalThis, .{ .hide_global = true });
|
|
|
|
this.format(tag, Writer, writer_, element, this.globalThis, enable_ansi_colors);
|
|
|
|
if (tag.cell.isStringLike()) {
|
|
if (comptime enable_ansi_colors) {
|
|
writer.writeAll(comptime Output.prettyFmt("<r>", true));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty_start) |empty| {
|
|
if (empty > 0) {
|
|
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
|
|
if (!this.single_line and (this.ordered_properties or this.goodTimeForANewLine())) {
|
|
writer.writeAll("\n");
|
|
was_good_time = true;
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
} else {
|
|
writer.space();
|
|
}
|
|
}
|
|
|
|
empty_start = null;
|
|
|
|
const empty_count = len - empty;
|
|
if (empty_count == 1) {
|
|
writer.pretty("<r><d>empty item<r>", enable_ansi_colors, .{});
|
|
} else {
|
|
this.estimated_line_length += bun.fmt.fastDigitCount(empty_count);
|
|
writer.pretty("<r><d>{d} x empty items<r>", enable_ansi_colors, .{empty_count});
|
|
}
|
|
}
|
|
|
|
const Iterator = PropertyIterator(Writer, enable_ansi_colors);
|
|
var iter = Iterator{
|
|
.formatter = this,
|
|
.writer = writer_,
|
|
.always_newline = !this.single_line and (this.always_newline_scope or this.goodTimeForANewLine()),
|
|
.single_line = this.single_line,
|
|
.parent = value,
|
|
.i = i,
|
|
};
|
|
value.forEachPropertyNonIndexed(this.globalThis, &iter, Iterator.forEach);
|
|
}
|
|
|
|
if (!this.single_line and (this.ordered_properties or was_good_time or this.goodTimeForANewLine())) {
|
|
this.resetLine();
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch {};
|
|
writer.writeAll("]");
|
|
this.resetLine();
|
|
this.addForNewLine(1);
|
|
} else {
|
|
writer.writeAll(" ]");
|
|
this.addForNewLine(2);
|
|
}
|
|
},
|
|
.Private => {
|
|
if (value.as(JSC.WebCore.Response)) |response| {
|
|
response.writeFormat(ConsoleObject.Formatter, this, writer_, enable_ansi_colors) catch {};
|
|
return;
|
|
} else if (value.as(JSC.WebCore.Request)) |request| {
|
|
request.writeFormat(ConsoleObject.Formatter, this, writer_, enable_ansi_colors) catch {};
|
|
return;
|
|
} else if (value.as(JSC.API.BuildArtifact)) |build| {
|
|
build.writeFormat(ConsoleObject.Formatter, this, writer_, enable_ansi_colors) catch {};
|
|
return;
|
|
} else if (value.as(JSC.WebCore.Blob)) |blob| {
|
|
blob.writeFormat(ConsoleObject.Formatter, this, writer_, enable_ansi_colors) catch {};
|
|
return;
|
|
} else if (value.as(JSC.FetchHeaders) != null) {
|
|
if (value.get(this.globalThis, "toJSON")) |toJSONFunction| {
|
|
this.addForNewLine("Headers ".len);
|
|
writer.writeAll(comptime Output.prettyFmt("<r>Headers ", enable_ansi_colors));
|
|
const prev_quote_keys = this.quote_keys;
|
|
this.quote_keys = true;
|
|
defer this.quote_keys = prev_quote_keys;
|
|
|
|
return this.printAs(
|
|
.Object,
|
|
Writer,
|
|
writer_,
|
|
toJSONFunction.call(this.globalThis, value, &.{}),
|
|
.Object,
|
|
enable_ansi_colors,
|
|
);
|
|
}
|
|
} else if (value.as(JSC.DOMFormData) != null) {
|
|
if (value.get(this.globalThis, "toJSON")) |toJSONFunction| {
|
|
const prev_quote_keys = this.quote_keys;
|
|
this.quote_keys = true;
|
|
defer this.quote_keys = prev_quote_keys;
|
|
|
|
return this.printAs(
|
|
.Object,
|
|
Writer,
|
|
writer_,
|
|
toJSONFunction.call(this.globalThis, value, &.{}),
|
|
.Object,
|
|
enable_ansi_colors,
|
|
);
|
|
}
|
|
|
|
// this case should never happen
|
|
return this.printAs(.Undefined, Writer, writer_, .undefined, .Cell, enable_ansi_colors);
|
|
} else if (value.as(JSC.API.Bun.Timer.TimerObject)) |timer| {
|
|
this.addForNewLine("Timeout(# ) ".len + bun.fmt.fastDigitCount(@as(u64, @intCast(@max(timer.id, 0)))));
|
|
if (timer.kind == .setInterval) {
|
|
this.addForNewLine("repeats ".len + bun.fmt.fastDigitCount(@as(u64, @intCast(@max(timer.id, 0)))));
|
|
writer.print(comptime Output.prettyFmt("<r><blue>Timeout<r> <d>(#<yellow>{d}<r><d>, repeats)<r>", enable_ansi_colors), .{
|
|
timer.id,
|
|
});
|
|
} else {
|
|
writer.print(comptime Output.prettyFmt("<r><blue>Timeout<r> <d>(#<yellow>{d}<r><d>)<r>", enable_ansi_colors), .{
|
|
timer.id,
|
|
});
|
|
}
|
|
|
|
return;
|
|
} else if (value.as(JSC.BuildMessage)) |build_log| {
|
|
build_log.msg.writeFormat(writer_, enable_ansi_colors) catch {};
|
|
return;
|
|
} else if (value.as(JSC.ResolveMessage)) |resolve_log| {
|
|
resolve_log.msg.writeFormat(writer_, enable_ansi_colors) catch {};
|
|
return;
|
|
} else if (JestPrettyFormat.printAsymmetricMatcher(this, Format, &writer, writer_, name_buf, value, enable_ansi_colors)) {
|
|
return;
|
|
} else if (jsType != .DOMWrapper) {
|
|
if (value.isCallable(this.globalThis.vm())) {
|
|
return this.printAs(.Function, Writer, writer_, value, jsType, enable_ansi_colors);
|
|
}
|
|
|
|
return this.printAs(.Object, Writer, writer_, value, jsType, enable_ansi_colors);
|
|
}
|
|
return this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors);
|
|
},
|
|
.NativeCode => {
|
|
this.addForNewLine("[native code]".len);
|
|
writer.writeAll("[native code]");
|
|
},
|
|
.Promise => {
|
|
if (!this.single_line and this.goodTimeForANewLine()) {
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch {};
|
|
}
|
|
|
|
writer.writeAll("Promise { " ++ comptime Output.prettyFmt("<r><cyan>", enable_ansi_colors));
|
|
|
|
switch (JSPromise.status(@as(*JSPromise, @ptrCast(value.asObjectRef().?)), this.globalThis.vm())) {
|
|
JSPromise.Status.Pending => {
|
|
writer.writeAll("<pending>");
|
|
},
|
|
JSPromise.Status.Fulfilled => {
|
|
writer.writeAll("<resolved>");
|
|
},
|
|
JSPromise.Status.Rejected => {
|
|
writer.writeAll("<rejected>");
|
|
},
|
|
}
|
|
|
|
writer.writeAll(comptime Output.prettyFmt("<r>", enable_ansi_colors) ++ " }");
|
|
},
|
|
.Boolean => {
|
|
if (value.isCell()) {
|
|
var bool_name = ZigString.Empty;
|
|
value.getClassName(this.globalThis, &bool_name);
|
|
var bool_value = ZigString.Empty;
|
|
value.toZigString(&bool_value, this.globalThis);
|
|
|
|
if (!strings.eqlComptime(bool_name.slice(), "Boolean")) {
|
|
this.addForNewLine(bool_value.len + bool_name.len + "[Boolean (): ]".len);
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>[Boolean ({s}): {s}]<r>", enable_ansi_colors), .{
|
|
bool_name,
|
|
bool_value,
|
|
});
|
|
return;
|
|
}
|
|
this.addForNewLine(bool_value.len + "[Boolean: ]".len);
|
|
writer.print(comptime Output.prettyFmt("<r><yellow>[Boolean: {s}]<r>", enable_ansi_colors), .{bool_value});
|
|
return;
|
|
}
|
|
if (value.toBoolean()) {
|
|
this.addForNewLine(4);
|
|
writer.writeAll(comptime Output.prettyFmt("<r><yellow>true<r>", enable_ansi_colors));
|
|
} else {
|
|
this.addForNewLine(5);
|
|
writer.writeAll(comptime Output.prettyFmt("<r><yellow>false<r>", enable_ansi_colors));
|
|
}
|
|
},
|
|
.GlobalObject => {
|
|
const fmt = "[Global Object]";
|
|
this.addForNewLine(fmt.len);
|
|
writer.writeAll(comptime Output.prettyFmt("<cyan>" ++ fmt ++ "<r>", enable_ansi_colors));
|
|
},
|
|
.Map => {
|
|
const length_value = value.get(this.globalThis, "size") orelse JSC.JSValue.jsNumberFromInt32(0);
|
|
const length = length_value.toInt32();
|
|
|
|
const prev_quote_strings = this.quote_strings;
|
|
this.quote_strings = true;
|
|
defer this.quote_strings = prev_quote_strings;
|
|
|
|
const map_name = if (value.jsType() == .JSWeakMap) "WeakMap" else "Map";
|
|
|
|
if (length == 0) {
|
|
return writer.print("{s} {{}}", .{map_name});
|
|
}
|
|
|
|
switch (this.single_line) {
|
|
inline else => |single_line| {
|
|
writer.print("{s}({d}) {{" ++ (if (single_line) " " else "\n"), .{ map_name, length });
|
|
},
|
|
}
|
|
{
|
|
this.indent += 1;
|
|
this.depth +|= 1;
|
|
defer this.indent -|= 1;
|
|
defer this.depth -|= 1;
|
|
switch (this.single_line) {
|
|
inline else => |single_line| {
|
|
var iter = MapIterator(Writer, enable_ansi_colors, false, single_line){
|
|
.formatter = this,
|
|
.writer = writer_,
|
|
};
|
|
value.forEach(this.globalThis, &iter, @TypeOf(iter).forEach);
|
|
if (single_line and iter.count > 0) {
|
|
writer.writeAll(" ");
|
|
}
|
|
},
|
|
}
|
|
}
|
|
if (!this.single_line) {
|
|
this.writeIndent(Writer, writer_) catch {};
|
|
}
|
|
writer.writeAll("}");
|
|
},
|
|
.MapIterator => {
|
|
const prev_quote_strings = this.quote_strings;
|
|
this.quote_strings = true;
|
|
defer this.quote_strings = prev_quote_strings;
|
|
|
|
writer.print("MapIterator {{ ", .{});
|
|
{
|
|
this.indent += 1;
|
|
this.depth +|= 1;
|
|
defer this.indent -|= 1;
|
|
defer this.depth -|= 1;
|
|
switch (this.single_line) {
|
|
inline else => |single_line| {
|
|
var iter = MapIterator(Writer, enable_ansi_colors, true, single_line){
|
|
.formatter = this,
|
|
.writer = writer_,
|
|
};
|
|
value.forEach(this.globalThis, &iter, @TypeOf(iter).forEach);
|
|
if (iter.count > 0) {
|
|
if (single_line) {
|
|
writer.writeAll(" ");
|
|
} else {
|
|
writer.writeAll("\n");
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
if (!this.single_line) {
|
|
this.writeIndent(Writer, writer_) catch {};
|
|
}
|
|
writer.writeAll("}");
|
|
},
|
|
.SetIterator => {
|
|
const prev_quote_strings = this.quote_strings;
|
|
this.quote_strings = true;
|
|
defer this.quote_strings = prev_quote_strings;
|
|
|
|
writer.print("SetIterator {{ ", .{});
|
|
{
|
|
this.indent += 1;
|
|
this.depth +|= 1;
|
|
defer this.indent -|= 1;
|
|
defer this.depth -|= 1;
|
|
switch (this.single_line) {
|
|
inline else => |single_line| {
|
|
var iter = MapIterator(Writer, enable_ansi_colors, true, single_line){
|
|
.formatter = this,
|
|
.writer = writer_,
|
|
};
|
|
value.forEach(this.globalThis, &iter, @TypeOf(iter).forEach);
|
|
if (iter.count > 0 and !single_line) {
|
|
writer.writeAll("\n");
|
|
}
|
|
},
|
|
}
|
|
}
|
|
if (!this.single_line) {
|
|
this.writeIndent(Writer, writer_) catch {};
|
|
}
|
|
writer.writeAll("}");
|
|
},
|
|
.Set => {
|
|
const length_value = value.get(this.globalThis, "size") orelse JSC.JSValue.jsNumberFromInt32(0);
|
|
const length = length_value.toInt32();
|
|
|
|
const prev_quote_strings = this.quote_strings;
|
|
this.quote_strings = true;
|
|
defer this.quote_strings = prev_quote_strings;
|
|
|
|
if (!this.single_line) {
|
|
this.writeIndent(Writer, writer_) catch {};
|
|
}
|
|
|
|
const set_name = if (value.jsType() == .JSWeakSet) "WeakSet" else "Set";
|
|
|
|
if (length == 0) {
|
|
return writer.print("{s} {{}}", .{set_name});
|
|
}
|
|
|
|
switch (this.single_line) {
|
|
inline else => |single_line| {
|
|
writer.print("{s}({d}) {{" ++ (if (single_line) " " else "\n"), .{ set_name, length });
|
|
},
|
|
}
|
|
{
|
|
this.indent += 1;
|
|
this.depth +|= 1;
|
|
defer this.indent -|= 1;
|
|
defer this.depth -|= 1;
|
|
switch (this.single_line) {
|
|
inline else => |single_line| {
|
|
var iter = SetIterator(Writer, enable_ansi_colors, single_line){
|
|
.formatter = this,
|
|
.writer = writer_,
|
|
};
|
|
value.forEach(this.globalThis, &iter, @TypeOf(iter).forEach);
|
|
if (single_line and !iter.is_first) {
|
|
writer.writeAll(" ");
|
|
}
|
|
},
|
|
}
|
|
}
|
|
if (this.single_line) {
|
|
this.writeIndent(Writer, writer_) catch {};
|
|
}
|
|
writer.writeAll("}");
|
|
},
|
|
.toJSON => {
|
|
if (value.get(this.globalThis, "toJSON")) |func| {
|
|
const result = func.call(this.globalThis, value, &.{});
|
|
if (result.toError() == null) {
|
|
const prev_quote_keys = this.quote_keys;
|
|
this.quote_keys = true;
|
|
defer this.quote_keys = prev_quote_keys;
|
|
this.printAs(.Object, Writer, writer_, result, value.jsType(), enable_ansi_colors);
|
|
return;
|
|
}
|
|
}
|
|
|
|
writer.writeAll("{}");
|
|
},
|
|
.JSON => {
|
|
var str = bun.String.empty;
|
|
defer str.deref();
|
|
|
|
value.jsonStringify(this.globalThis, this.indent, &str);
|
|
this.addForNewLine(str.length());
|
|
if (jsType == JSValue.JSType.JSDate) {
|
|
// in the code for printing dates, it never exceeds this amount
|
|
var iso_string_buf: [36]u8 = undefined;
|
|
var out_buf: []const u8 = std.fmt.bufPrint(&iso_string_buf, "{}", .{str}) catch "";
|
|
|
|
if (strings.eql(out_buf, "null")) {
|
|
out_buf = "Invalid Date";
|
|
} else if (out_buf.len > 2) {
|
|
// trim the quotes
|
|
out_buf = out_buf[1 .. out_buf.len - 1];
|
|
}
|
|
|
|
writer.print(comptime Output.prettyFmt("<r><magenta>{s}<r>", enable_ansi_colors), .{out_buf});
|
|
return;
|
|
}
|
|
|
|
writer.print("{}", .{str});
|
|
},
|
|
.Event => {
|
|
const event_type_value = brk: {
|
|
const value_ = value.get(this.globalThis, "type") orelse break :brk JSValue.undefined;
|
|
if (value_.isString()) {
|
|
break :brk value_;
|
|
}
|
|
|
|
break :brk JSValue.undefined;
|
|
};
|
|
|
|
const event_type = switch (EventType.map.fromJS(this.globalThis, event_type_value) orelse .unknown) {
|
|
.MessageEvent, .ErrorEvent => |evt| evt,
|
|
else => {
|
|
return this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors);
|
|
},
|
|
};
|
|
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r><cyan>{s}<r> {{\n", enable_ansi_colors),
|
|
.{
|
|
@tagName(event_type),
|
|
},
|
|
);
|
|
{
|
|
this.indent += 1;
|
|
this.depth +|= 1;
|
|
defer this.indent -|= 1;
|
|
defer this.depth -|= 1;
|
|
const old_quote_strings = this.quote_strings;
|
|
this.quote_strings = true;
|
|
defer this.quote_strings = old_quote_strings;
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
|
|
switch (this.single_line) {
|
|
inline else => |single_line| {
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r>type: <green>\"{s}\"<r><d>,<r>" ++ (if (single_line) " " else "\n"), enable_ansi_colors),
|
|
.{
|
|
event_type.label(),
|
|
},
|
|
);
|
|
},
|
|
}
|
|
|
|
if (value.fastGet(this.globalThis, .message)) |message_value| {
|
|
if (message_value.isString()) {
|
|
if (!this.single_line) {
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
}
|
|
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r><blue>message<d>:<r> ", enable_ansi_colors),
|
|
.{},
|
|
);
|
|
|
|
const tag = Tag.getAdvanced(message_value, this.globalThis, .{ .hide_global = true });
|
|
this.format(tag, Writer, writer_, message_value, this.globalThis, enable_ansi_colors);
|
|
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
|
|
if (!this.single_line) {
|
|
writer.writeAll("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (event_type) {
|
|
.MessageEvent => {
|
|
if (!this.single_line) {
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
}
|
|
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r><blue>data<d>:<r> ", enable_ansi_colors),
|
|
.{},
|
|
);
|
|
const data = value.fastGet(this.globalThis, .data) orelse JSValue.undefined;
|
|
const tag = Tag.getAdvanced(data, this.globalThis, .{ .hide_global = true });
|
|
this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
|
|
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
|
|
if (!this.single_line) {
|
|
writer.writeAll("\n");
|
|
}
|
|
},
|
|
.ErrorEvent => {
|
|
if (value.fastGet(this.globalThis, .@"error")) |error_value| {
|
|
if (!this.single_line) {
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
}
|
|
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r><blue>error<d>:<r> ", enable_ansi_colors),
|
|
.{},
|
|
);
|
|
|
|
const tag = Tag.getAdvanced(error_value, this.globalThis, .{ .hide_global = true });
|
|
this.format(tag, Writer, writer_, error_value, this.globalThis, enable_ansi_colors);
|
|
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
|
|
if (!this.single_line) {
|
|
writer.writeAll("\n");
|
|
}
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
if (!this.single_line) {
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
}
|
|
writer.writeAll("}");
|
|
},
|
|
.JSX => {
|
|
writer.writeAll(comptime Output.prettyFmt("<r>", enable_ansi_colors));
|
|
|
|
writer.writeAll("<");
|
|
|
|
var needs_space = false;
|
|
var tag_name_str = ZigString.init("");
|
|
|
|
var tag_name_slice: ZigString.Slice = ZigString.Slice.empty;
|
|
var is_tag_kind_primitive = false;
|
|
|
|
defer if (tag_name_slice.isAllocated()) tag_name_slice.deinit();
|
|
|
|
if (value.get(this.globalThis, "type")) |type_value| {
|
|
const _tag = Tag.getAdvanced(type_value, this.globalThis, .{ .hide_global = true });
|
|
|
|
if (_tag.cell == .Symbol) {} else if (_tag.cell.isStringLike()) {
|
|
type_value.toZigString(&tag_name_str, this.globalThis);
|
|
is_tag_kind_primitive = true;
|
|
} else if (_tag.cell.isObject() or type_value.isCallable(this.globalThis.vm())) {
|
|
type_value.getNameProperty(this.globalThis, &tag_name_str);
|
|
if (tag_name_str.len == 0) {
|
|
tag_name_str = ZigString.init("NoName");
|
|
}
|
|
} else {
|
|
type_value.toZigString(&tag_name_str, this.globalThis);
|
|
}
|
|
|
|
tag_name_slice = tag_name_str.toSlice(default_allocator);
|
|
needs_space = true;
|
|
} else {
|
|
tag_name_slice = ZigString.init("unknown").toSlice(default_allocator);
|
|
|
|
needs_space = true;
|
|
}
|
|
|
|
if (!is_tag_kind_primitive)
|
|
writer.writeAll(comptime Output.prettyFmt("<cyan>", enable_ansi_colors))
|
|
else
|
|
writer.writeAll(comptime Output.prettyFmt("<green>", enable_ansi_colors));
|
|
writer.writeAll(tag_name_slice.slice());
|
|
if (enable_ansi_colors) writer.writeAll(comptime Output.prettyFmt("<r>", enable_ansi_colors));
|
|
|
|
if (value.get(this.globalThis, "key")) |key_value| {
|
|
if (!key_value.isUndefinedOrNull()) {
|
|
if (needs_space)
|
|
writer.writeAll(" key=")
|
|
else
|
|
writer.writeAll("key=");
|
|
|
|
const old_quote_strings = this.quote_strings;
|
|
this.quote_strings = true;
|
|
defer this.quote_strings = old_quote_strings;
|
|
|
|
this.format(Tag.getAdvanced(key_value, this.globalThis, .{ .hide_global = true }), Writer, writer_, key_value, this.globalThis, enable_ansi_colors);
|
|
|
|
needs_space = true;
|
|
}
|
|
}
|
|
|
|
if (value.get(this.globalThis, "props")) |props| {
|
|
const prev_quote_strings = this.quote_strings;
|
|
this.quote_strings = true;
|
|
defer this.quote_strings = prev_quote_strings;
|
|
|
|
var props_iter = JSC.JSPropertyIterator(.{
|
|
.skip_empty_name = true,
|
|
|
|
.include_value = true,
|
|
}).init(this.globalThis, props);
|
|
defer props_iter.deinit();
|
|
|
|
const children_prop = props.get(this.globalThis, "children");
|
|
if (props_iter.len > 0) {
|
|
{
|
|
this.indent += 1;
|
|
defer this.indent -|= 1;
|
|
const count_without_children = props_iter.len - @as(usize, @intFromBool(children_prop != null));
|
|
|
|
while (props_iter.next()) |prop| {
|
|
if (prop.eqlComptime("children"))
|
|
continue;
|
|
|
|
const property_value = props_iter.value;
|
|
const tag = Tag.getAdvanced(property_value, this.globalThis, .{ .hide_global = true });
|
|
|
|
if (tag.cell.isHidden()) continue;
|
|
|
|
if (needs_space) writer.space();
|
|
needs_space = false;
|
|
|
|
writer.print(
|
|
comptime Output.prettyFmt("<r><blue>{s}<d>=<r>", enable_ansi_colors),
|
|
.{prop.trunc(128)},
|
|
);
|
|
|
|
if (tag.cell.isStringLike()) {
|
|
if (comptime enable_ansi_colors) {
|
|
writer.writeAll(comptime Output.prettyFmt("<r><green>", true));
|
|
}
|
|
}
|
|
|
|
this.format(tag, Writer, writer_, property_value, this.globalThis, enable_ansi_colors);
|
|
|
|
if (tag.cell.isStringLike()) {
|
|
if (comptime enable_ansi_colors) {
|
|
writer.writeAll(comptime Output.prettyFmt("<r>", true));
|
|
}
|
|
}
|
|
|
|
if (!this.single_line and (
|
|
// count_without_children is necessary to prevent printing an extra newline
|
|
// if there are children and one prop and the child prop is the last prop
|
|
props_iter.i + 1 < count_without_children and
|
|
// 3 is arbitrary but basically
|
|
// <input type="text" value="foo" />
|
|
// ^ should be one line
|
|
// <input type="text" value="foo" bar="true" baz={false} />
|
|
// ^ should be multiple lines
|
|
props_iter.i > 3))
|
|
{
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
} else if (props_iter.i + 1 < count_without_children) {
|
|
writer.space();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (children_prop) |children| {
|
|
const tag = Tag.get(children, this.globalThis);
|
|
|
|
const print_children = switch (tag.tag) {
|
|
.String, .JSX, .Array => true,
|
|
else => false,
|
|
};
|
|
|
|
if (print_children and !this.single_line) {
|
|
print_children: {
|
|
switch (tag.tag) {
|
|
.String => {
|
|
const children_string = children.getZigString(this.globalThis);
|
|
if (children_string.len == 0) break :print_children;
|
|
if (comptime enable_ansi_colors) writer.writeAll(comptime Output.prettyFmt("<r>", true));
|
|
|
|
writer.writeAll(">");
|
|
if (children_string.len < 128) {
|
|
writer.writeString(children_string);
|
|
} else {
|
|
this.indent += 1;
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
this.indent -|= 1;
|
|
writer.writeString(children_string);
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
}
|
|
},
|
|
.JSX => {
|
|
writer.writeAll(">\n");
|
|
|
|
{
|
|
this.indent += 1;
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
defer this.indent -|= 1;
|
|
this.format(Tag.get(children, this.globalThis), Writer, writer_, children, this.globalThis, enable_ansi_colors);
|
|
}
|
|
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
},
|
|
.Array => {
|
|
const length = children.getLength(this.globalThis);
|
|
if (length == 0) break :print_children;
|
|
writer.writeAll(">\n");
|
|
|
|
{
|
|
this.indent += 1;
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
const _prev_quote_strings = this.quote_strings;
|
|
this.quote_strings = false;
|
|
defer this.quote_strings = _prev_quote_strings;
|
|
|
|
defer this.indent -|= 1;
|
|
|
|
var j: usize = 0;
|
|
while (j < length) : (j += 1) {
|
|
const child = JSC.JSObject.getIndex(children, this.globalThis, @as(u32, @intCast(j)));
|
|
this.format(Tag.getAdvanced(child, this.globalThis, .{ .hide_global = true }), Writer, writer_, child, this.globalThis, enable_ansi_colors);
|
|
if (j + 1 < length) {
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
}
|
|
}
|
|
}
|
|
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch unreachable;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
|
|
writer.writeAll("</");
|
|
if (!is_tag_kind_primitive)
|
|
writer.writeAll(comptime Output.prettyFmt("<r><cyan>", enable_ansi_colors))
|
|
else
|
|
writer.writeAll(comptime Output.prettyFmt("<r><green>", enable_ansi_colors));
|
|
writer.writeAll(tag_name_slice.slice());
|
|
if (enable_ansi_colors) writer.writeAll(comptime Output.prettyFmt("<r>", enable_ansi_colors));
|
|
writer.writeAll(">");
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
writer.writeAll(" />");
|
|
},
|
|
.Object => {
|
|
bun.assert(value.isCell());
|
|
const prev_quote_strings = this.quote_strings;
|
|
this.quote_strings = true;
|
|
defer this.quote_strings = prev_quote_strings;
|
|
|
|
const Iterator = PropertyIterator(Writer, enable_ansi_colors);
|
|
|
|
// We want to figure out if we should print this object
|
|
// on one line or multiple lines
|
|
//
|
|
// The 100% correct way would be to print everything to
|
|
// a temporary buffer and then check how long each line was
|
|
//
|
|
// But it's important that console.log() is fast. So we
|
|
// do a small compromise to avoid multiple passes over input
|
|
//
|
|
// We say:
|
|
//
|
|
// If the object has at least 2 properties and ANY of the following conditions are met:
|
|
// - total length of all the property names is more than
|
|
// 14 characters
|
|
// - the parent object is printing each property on a new line
|
|
// - The first property is a DOM object, ESM namespace, Map, Set, or Blob
|
|
//
|
|
// Then, we print it each property on a new line, recursively.
|
|
//
|
|
const prev_always_newline_scope = this.always_newline_scope;
|
|
defer this.always_newline_scope = prev_always_newline_scope;
|
|
var iter = Iterator{
|
|
.formatter = this,
|
|
.writer = writer_,
|
|
.always_newline = !this.single_line and (this.always_newline_scope or this.goodTimeForANewLine()),
|
|
.single_line = this.single_line,
|
|
.parent = value,
|
|
};
|
|
|
|
if (this.depth > this.max_depth) {
|
|
if (this.single_line) {
|
|
writer.writeAll(" ");
|
|
} else if (this.always_newline_scope or this.goodTimeForANewLine()) {
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch {};
|
|
this.resetLine();
|
|
}
|
|
|
|
var display_name = value.getName(this.globalThis);
|
|
if (display_name.isEmpty()) {
|
|
display_name = String.static("Object");
|
|
}
|
|
writer.print(comptime Output.prettyFmt("<r><cyan>[{} ...]<r>", enable_ansi_colors), .{
|
|
display_name,
|
|
});
|
|
return;
|
|
} else if (this.ordered_properties) {
|
|
value.forEachPropertyOrdered(this.globalThis, &iter, Iterator.forEach);
|
|
} else {
|
|
value.forEachProperty(this.globalThis, &iter, Iterator.forEach);
|
|
}
|
|
|
|
if (iter.i == 0) {
|
|
if (value.isClass(this.globalThis))
|
|
this.printAs(.Class, Writer, writer_, value, jsType, enable_ansi_colors)
|
|
else if (value.isCallable(this.globalThis.vm()))
|
|
this.printAs(.Function, Writer, writer_, value, jsType, enable_ansi_colors)
|
|
else {
|
|
if (getObjectName(this.globalThis, value)) |name_str| {
|
|
writer.print("{} ", .{name_str});
|
|
}
|
|
writer.writeAll("{}");
|
|
}
|
|
} else {
|
|
this.depth -= 1;
|
|
|
|
if (iter.always_newline) {
|
|
this.indent -|= 1;
|
|
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
|
|
writer.writeAll("\n");
|
|
this.writeIndent(Writer, writer_) catch {};
|
|
writer.writeAll("}");
|
|
this.estimated_line_length += 1;
|
|
} else {
|
|
this.estimated_line_length += 2;
|
|
writer.writeAll(" }");
|
|
}
|
|
}
|
|
},
|
|
.TypedArray => {
|
|
const arrayBuffer = value.asArrayBuffer(this.globalThis).?;
|
|
const slice = arrayBuffer.byteSlice();
|
|
|
|
writer.writeAll(
|
|
if (arrayBuffer.typed_array_type == .Uint8Array and
|
|
arrayBuffer.value.isBuffer(this.globalThis))
|
|
"Buffer"
|
|
else
|
|
bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)),
|
|
);
|
|
writer.print("({d}) [ ", .{arrayBuffer.len});
|
|
|
|
if (slice.len > 0) {
|
|
switch (jsType) {
|
|
.Int8Array => this.writeTypedArray(
|
|
*@TypeOf(writer),
|
|
&writer,
|
|
i8,
|
|
@as([]align(std.meta.alignment([]i8)) i8, @alignCast(std.mem.bytesAsSlice(i8, slice))),
|
|
enable_ansi_colors,
|
|
),
|
|
.Int16Array => this.writeTypedArray(
|
|
*@TypeOf(writer),
|
|
&writer,
|
|
i16,
|
|
@as([]align(std.meta.alignment([]i16)) i16, @alignCast(std.mem.bytesAsSlice(i16, slice))),
|
|
enable_ansi_colors,
|
|
),
|
|
.Uint16Array => this.writeTypedArray(
|
|
*@TypeOf(writer),
|
|
&writer,
|
|
u16,
|
|
@as([]align(std.meta.alignment([]u16)) u16, @alignCast(std.mem.bytesAsSlice(u16, slice))),
|
|
enable_ansi_colors,
|
|
),
|
|
.Int32Array => this.writeTypedArray(
|
|
*@TypeOf(writer),
|
|
&writer,
|
|
i32,
|
|
@as([]align(std.meta.alignment([]i32)) i32, @alignCast(std.mem.bytesAsSlice(i32, slice))),
|
|
enable_ansi_colors,
|
|
),
|
|
.Uint32Array => this.writeTypedArray(
|
|
*@TypeOf(writer),
|
|
&writer,
|
|
u32,
|
|
@as([]align(std.meta.alignment([]u32)) u32, @alignCast(std.mem.bytesAsSlice(u32, slice))),
|
|
enable_ansi_colors,
|
|
),
|
|
.Float32Array => this.writeTypedArray(
|
|
*@TypeOf(writer),
|
|
&writer,
|
|
f32,
|
|
@as([]align(std.meta.alignment([]f32)) f32, @alignCast(std.mem.bytesAsSlice(f32, slice))),
|
|
enable_ansi_colors,
|
|
),
|
|
.Float64Array => this.writeTypedArray(
|
|
*@TypeOf(writer),
|
|
&writer,
|
|
f64,
|
|
@as([]align(std.meta.alignment([]f64)) f64, @alignCast(std.mem.bytesAsSlice(f64, slice))),
|
|
enable_ansi_colors,
|
|
),
|
|
.BigInt64Array => this.writeTypedArray(
|
|
*@TypeOf(writer),
|
|
&writer,
|
|
i64,
|
|
@as([]align(std.meta.alignment([]i64)) i64, @alignCast(std.mem.bytesAsSlice(i64, slice))),
|
|
enable_ansi_colors,
|
|
),
|
|
.BigUint64Array => {
|
|
this.writeTypedArray(
|
|
*@TypeOf(writer),
|
|
&writer,
|
|
u64,
|
|
@as([]align(std.meta.alignment([]u64)) u64, @alignCast(std.mem.bytesAsSlice(u64, slice))),
|
|
enable_ansi_colors,
|
|
);
|
|
},
|
|
|
|
// Uint8Array, Uint8ClampedArray, DataView, ArrayBuffer
|
|
else => this.writeTypedArray(*@TypeOf(writer), &writer, u8, slice, enable_ansi_colors),
|
|
}
|
|
}
|
|
|
|
writer.writeAll(" ]");
|
|
},
|
|
.RevokedProxy => {
|
|
this.addForNewLine("<Revoked Proxy>".len);
|
|
writer.print(comptime Output.prettyFmt("<r><cyan>\\<Revoked Proxy\\><r>", enable_ansi_colors), .{});
|
|
},
|
|
.Proxy => {
|
|
const target = value.getProxyInternalField(.target);
|
|
if (Environment.allow_assert) {
|
|
// Proxy does not allow non-objects here.
|
|
bun.assert(target.isCell());
|
|
}
|
|
// TODO: if (options.showProxy), print like `Proxy { target: ..., handlers: ... }`
|
|
// this is default off so it is not used.
|
|
this.format(ConsoleObject.Formatter.Tag.get(target, this.globalThis), Writer, writer_, target, this.globalThis, enable_ansi_colors);
|
|
},
|
|
}
|
|
}
|
|
|
|
fn writeTypedArray(this: *ConsoleObject.Formatter, comptime WriterWrapped: type, writer: WriterWrapped, comptime Number: type, slice: []const Number, comptime enable_ansi_colors: bool) void {
|
|
const fmt_ = if (Number == i64 or Number == u64)
|
|
"<r><yellow>{d}n<r>"
|
|
else
|
|
"<r><yellow>{d}<r>";
|
|
const more = if (Number == i64 or Number == u64)
|
|
"<r><d>n, ... {d} more<r>"
|
|
else
|
|
"<r><d>, ... {d} more<r>";
|
|
|
|
writer.print(comptime Output.prettyFmt(fmt_, enable_ansi_colors), .{
|
|
if (@typeInfo(Number) == .Float) bun.fmt.fmtDouble(@floatCast(slice[0])) else slice[0],
|
|
});
|
|
var leftover = slice[1..];
|
|
const max = 512;
|
|
leftover = leftover[0..@min(leftover.len, max)];
|
|
for (leftover) |el| {
|
|
this.printComma(@TypeOf(&writer.ctx), &writer.ctx, enable_ansi_colors) catch return;
|
|
writer.space();
|
|
|
|
writer.print(comptime Output.prettyFmt(fmt_, enable_ansi_colors), .{
|
|
if (@typeInfo(Number) == .Float) bun.fmt.fmtDouble(@floatCast(el)) else el,
|
|
});
|
|
}
|
|
|
|
if (slice.len > max + 1) {
|
|
writer.print(comptime Output.prettyFmt(more, enable_ansi_colors), .{slice.len - max - 1});
|
|
}
|
|
}
|
|
|
|
pub fn format(this: *ConsoleObject.Formatter, result: Tag.Result, comptime Writer: type, writer: Writer, value: JSValue, globalThis: *JSGlobalObject, comptime enable_ansi_colors: bool) void {
|
|
const prevGlobalThis = this.globalThis;
|
|
defer this.globalThis = prevGlobalThis;
|
|
this.globalThis = globalThis;
|
|
|
|
// This looks incredibly redundant. We make the ConsoleObject.Formatter.Tag a
|
|
// comptime var so we have to repeat it here. The rationale there is
|
|
// it _should_ limit the stack usage because each version of the
|
|
// function will be relatively small
|
|
switch (result.tag.tag()) {
|
|
inline else => |tag| this.printAs(tag, Writer, writer, value, result.cell, enable_ansi_colors),
|
|
|
|
.CustomFormattedObject => {
|
|
this.custom_formatted_object = result.tag.CustomFormattedObject;
|
|
this.printAs(.CustomFormattedObject, Writer, writer, value, result.cell, enable_ansi_colors);
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn count(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
globalThis: *JSGlobalObject,
|
|
// chars
|
|
ptr: [*]const u8,
|
|
// len
|
|
len: usize,
|
|
) callconv(JSC.conv) void {
|
|
var this = globalThis.bunVM().console;
|
|
const slice = ptr[0..len];
|
|
const hash = bun.hash(slice);
|
|
// we don't want to store these strings, it will take too much memory
|
|
const counter = this.counts.getOrPut(globalThis.allocator(), hash) catch unreachable;
|
|
const current = @as(u32, if (counter.found_existing) counter.value_ptr.* else @as(u32, 0)) + 1;
|
|
counter.value_ptr.* = current;
|
|
|
|
var writer_ctx = &this.writer;
|
|
var writer = &writer_ctx.writer();
|
|
if (Output.enable_ansi_colors_stdout)
|
|
writer.print(comptime Output.prettyFmt("<r>{s}<d>: <r><yellow>{d}<r>\n", true), .{ slice, current }) catch unreachable
|
|
else
|
|
writer.print(comptime Output.prettyFmt("<r>{s}<d>: <r><yellow>{d}<r>\n", false), .{ slice, current }) catch unreachable;
|
|
writer_ctx.flush() catch unreachable;
|
|
}
|
|
pub fn countReset(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
globalThis: *JSGlobalObject,
|
|
// chars
|
|
ptr: [*]const u8,
|
|
// len
|
|
len: usize,
|
|
) callconv(JSC.conv) void {
|
|
var this = globalThis.bunVM().console;
|
|
const slice = ptr[0..len];
|
|
const hash = bun.hash(slice);
|
|
// we don't delete it because deleting is implemented via tombstoning
|
|
const entry = this.counts.getEntry(hash) orelse return;
|
|
entry.value_ptr.* = 0;
|
|
}
|
|
|
|
const PendingTimers = std.AutoHashMap(u64, ?std.time.Timer);
|
|
threadlocal var pending_time_logs: PendingTimers = undefined;
|
|
threadlocal var pending_time_logs_loaded = false;
|
|
|
|
pub fn time(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
_: *JSGlobalObject,
|
|
chars: [*]const u8,
|
|
len: usize,
|
|
) callconv(JSC.conv) void {
|
|
const id = bun.hash(chars[0..len]);
|
|
if (!pending_time_logs_loaded) {
|
|
pending_time_logs = PendingTimers.init(default_allocator);
|
|
pending_time_logs_loaded = true;
|
|
}
|
|
|
|
const result = pending_time_logs.getOrPut(id) catch unreachable;
|
|
|
|
if (!result.found_existing or (result.found_existing and result.value_ptr.* == null)) {
|
|
result.value_ptr.* = std.time.Timer.start() catch unreachable;
|
|
}
|
|
}
|
|
pub fn timeEnd(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
_: *JSGlobalObject,
|
|
chars: [*]const u8,
|
|
len: usize,
|
|
) callconv(JSC.conv) void {
|
|
if (!pending_time_logs_loaded) {
|
|
return;
|
|
}
|
|
|
|
const id = bun.hash(chars[0..len]);
|
|
const result = (pending_time_logs.fetchPut(id, null) catch null) orelse return;
|
|
var value: std.time.Timer = result.value orelse return;
|
|
// get the duration in microseconds
|
|
// then display it in milliseconds
|
|
Output.printElapsed(@as(f64, @floatFromInt(value.read() / std.time.ns_per_us)) / std.time.us_per_ms);
|
|
switch (len) {
|
|
0 => Output.printErrorln("\n", .{}),
|
|
else => Output.printErrorln(" {s}", .{chars[0..len]}),
|
|
}
|
|
|
|
Output.flush();
|
|
}
|
|
|
|
pub fn timeLog(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
global: *JSGlobalObject,
|
|
// chars
|
|
chars: [*]const u8,
|
|
// len
|
|
len: usize,
|
|
// args
|
|
args: [*]JSValue,
|
|
args_len: usize,
|
|
) callconv(JSC.conv) void {
|
|
if (!pending_time_logs_loaded) {
|
|
return;
|
|
}
|
|
|
|
const id = bun.hash(chars[0..len]);
|
|
var value: std.time.Timer = (pending_time_logs.get(id) orelse return) orelse return;
|
|
// get the duration in microseconds
|
|
// then display it in milliseconds
|
|
Output.printElapsed(@as(f64, @floatFromInt(value.read() / std.time.ns_per_us)) / std.time.us_per_ms);
|
|
switch (len) {
|
|
0 => {},
|
|
else => Output.printError(" {s}", .{chars[0..len]}),
|
|
}
|
|
Output.flush();
|
|
|
|
// print the arguments
|
|
var fmt = ConsoleObject.Formatter{
|
|
.remaining_values = &[_]JSValue{},
|
|
.globalThis = global,
|
|
.ordered_properties = false,
|
|
.quote_strings = false,
|
|
};
|
|
var console = global.bunVM().console;
|
|
var writer = console.error_writer.writer();
|
|
const Writer = @TypeOf(writer);
|
|
for (args[0..args_len]) |arg| {
|
|
const tag = ConsoleObject.Formatter.Tag.get(arg, global);
|
|
_ = writer.write(" ") catch 0;
|
|
if (Output.enable_ansi_colors_stderr) {
|
|
fmt.format(tag, Writer, writer, arg, global, true);
|
|
} else {
|
|
fmt.format(tag, Writer, writer, arg, global, false);
|
|
}
|
|
}
|
|
_ = writer.write("\n") catch 0;
|
|
writer.context.flush() catch {};
|
|
}
|
|
pub fn profile(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
_: *JSGlobalObject,
|
|
// chars
|
|
_: [*]const u8,
|
|
// len
|
|
_: usize,
|
|
) callconv(JSC.conv) void {}
|
|
pub fn profileEnd(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
_: *JSGlobalObject,
|
|
// chars
|
|
_: [*]const u8,
|
|
// len
|
|
_: usize,
|
|
) callconv(JSC.conv) void {}
|
|
pub fn takeHeapSnapshot(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
globalThis: *JSGlobalObject,
|
|
// chars
|
|
_: [*]const u8,
|
|
// len
|
|
_: usize,
|
|
) callconv(JSC.conv) void {
|
|
// TODO: this does an extra JSONStringify and we don't need it to!
|
|
var snapshot: [1]JSValue = .{globalThis.generateHeapSnapshot()};
|
|
ConsoleObject.messageWithTypeAndLevel(undefined, MessageType.Log, MessageLevel.Debug, globalThis, &snapshot, 1);
|
|
}
|
|
pub fn timeStamp(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
_: *JSGlobalObject,
|
|
// args
|
|
_: *ScriptArguments,
|
|
) callconv(JSC.conv) void {}
|
|
pub fn record(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
_: *JSGlobalObject,
|
|
// args
|
|
_: *ScriptArguments,
|
|
) callconv(JSC.conv) void {}
|
|
pub fn recordEnd(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
_: *JSGlobalObject,
|
|
// args
|
|
_: *ScriptArguments,
|
|
) callconv(JSC.conv) void {}
|
|
pub fn screenshot(
|
|
// console
|
|
_: ConsoleObject.Type,
|
|
// global
|
|
_: *JSGlobalObject,
|
|
// args
|
|
_: *ScriptArguments,
|
|
) callconv(JSC.conv) void {}
|
|
|
|
comptime {
|
|
@export(messageWithTypeAndLevel, .{ .name = shim.symbolName("messageWithTypeAndLevel") });
|
|
@export(count, .{ .name = shim.symbolName("count") });
|
|
@export(countReset, .{ .name = shim.symbolName("countReset") });
|
|
@export(time, .{ .name = shim.symbolName("time") });
|
|
@export(timeLog, .{ .name = shim.symbolName("timeLog") });
|
|
@export(timeEnd, .{ .name = shim.symbolName("timeEnd") });
|
|
@export(profile, .{ .name = shim.symbolName("profile") });
|
|
@export(profileEnd, .{ .name = shim.symbolName("profileEnd") });
|
|
@export(takeHeapSnapshot, .{ .name = shim.symbolName("takeHeapSnapshot") });
|
|
@export(timeStamp, .{ .name = shim.symbolName("timeStamp") });
|
|
@export(record, .{ .name = shim.symbolName("record") });
|
|
@export(recordEnd, .{ .name = shim.symbolName("recordEnd") });
|
|
@export(screenshot, .{ .name = shim.symbolName("screenshot") });
|
|
}
|