aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js
diff options
context:
space:
mode:
authorGravatar dave caruso <me@paperdave.net> 2023-08-17 20:56:52 -0700
committerGravatar GitHub <noreply@github.com> 2023-08-17 20:56:52 -0700
commit6fd0043f6bf766cc488a88339059e8879fa07161 (patch)
treed5289bcaf0880a3bf1e2f0b1c681aff93188fe51 /src/bun.js
parent0424fd8f6e7549ed779788006acdc97a8467e287 (diff)
downloadbun-6fd0043f6bf766cc488a88339059e8879fa07161.tar.gz
bun-6fd0043f6bf766cc488a88339059e8879fa07161.tar.zst
bun-6fd0043f6bf766cc488a88339059e8879fa07161.zip
Add `util.inspect.custom` support to `util.inspect/Bun.inspect/console.log` (#4194)
* start work on util.inspect.custom * asdf * finish util inspect custom inspect * inspect * fix tests * revert * tidy * revert * oops * test * fix issues
Diffstat (limited to 'src/bun.js')
-rw-r--r--src/bun.js/api/bun.zig76
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp38
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h7
-rw-r--r--src/bun.js/bindings/bindings.cpp40
-rw-r--r--src/bun.js/bindings/bindings.zig1
-rw-r--r--src/bun.js/bindings/exports.zig134
6 files changed, 266 insertions, 30 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
index d230028ba..7685b8f80 100644
--- a/src/bun.js/api/bun.zig
+++ b/src/bun.js/api/bun.zig
@@ -227,6 +227,71 @@ pub fn inspect(
}
}
+ var formatOptions = ZigConsoleClient.FormatOptions{
+ .enable_colors = false,
+ .add_newline = false,
+ .flush = false,
+ .max_depth = 8,
+ .quote_strings = true,
+ .ordered_properties = false,
+ };
+ var value = JSC.JSValue.fromRef(arguments[0]);
+
+ if (arguments.len > 1) {
+ var arg1: JSC.JSValue = JSC.JSValue.fromRef(arguments[1]);
+
+ if (arg1.isObject()) {
+ if (arg1.getTruthy(ctx, "depth")) |opt| {
+ if (opt.isInt32()) {
+ const arg = opt.toInt32();
+ if (arg < 0) {
+ ctx.throwInvalidArguments("expected depth to be greater than or equal to 0, got {d}", .{arg});
+ return null;
+ }
+ formatOptions.max_depth = @as(u16, @truncate(@as(u32, @intCast(@min(arg, std.math.maxInt(u16))))));
+ } else if (opt.isNumber()) {
+ const v = opt.asDouble();
+ if (std.math.isInf(v)) {
+ formatOptions.max_depth = std.math.maxInt(u16);
+ } else {
+ ctx.throwInvalidArguments("expected depth to be an integer, got {d}", .{v});
+ return null;
+ }
+ }
+ }
+ if (arg1.getOptional(ctx, "colors", bool) catch return null) |opt| {
+ formatOptions.enable_colors = opt;
+ }
+ if (arg1.getOptional(ctx, "sorted", bool) catch return null) |opt| {
+ formatOptions.ordered_properties = opt;
+ }
+ } else {
+ // formatOptions.show_hidden = arg1.toBoolean();
+ if (arguments.len > 2) {
+ var depthArg = JSC.JSValue.fromRef(arguments[1]);
+ if (depthArg.isInt32()) {
+ const arg = depthArg.toInt32();
+ if (arg < 0) {
+ ctx.throwInvalidArguments("expected depth to be greater than or equal to 0, got {d}", .{arg});
+ return null;
+ }
+ formatOptions.max_depth = @as(u16, @truncate(@as(u32, @intCast(@min(arg, std.math.maxInt(u16))))));
+ } else if (depthArg.isNumber()) {
+ const v = depthArg.asDouble();
+ if (std.math.isInf(v)) {
+ formatOptions.max_depth = std.math.maxInt(u16);
+ } else {
+ ctx.throwInvalidArguments("expected depth to be an integer, got {d}", .{v});
+ return null;
+ }
+ }
+ if (arguments.len > 3) {
+ formatOptions.enable_colors = JSC.JSValue.fromRef(arguments[2]).toBoolean();
+ }
+ }
+ }
+ }
+
// very stable memory address
var array = MutableString.init(getAllocator(ctx), 0) catch unreachable;
var buffered_writer_ = MutableString.BufferedWriter{ .context = &array };
@@ -239,17 +304,12 @@ pub fn inspect(
ZigConsoleClient.format(
.Debug,
ctx.ptr(),
- @as([*]const JSValue, @ptrCast(arguments.ptr)),
- arguments.len,
+ @as([*]const JSValue, @ptrCast(&value)),
+ 1,
Writer,
Writer,
writer,
- .{
- .enable_colors = false,
- .add_newline = false,
- .flush = false,
- .max_depth = 32,
- },
+ formatOptions,
);
buffered_writer.flush() catch {
return JSC.C.JSValueMakeUndefined(ctx);
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 5524166c3..b6ac8e7ea 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -3270,6 +3270,41 @@ void GlobalObject::finishCreation(VM& vm)
init.set(JSFunction::create(init.vm, init.owner, 4, "performMicrotaskVariadic"_s, jsFunctionPerformMicrotaskVariadic, ImplementationVisibility::Public));
});
+ m_utilInspectFunction.initLater(
+ [](const Initializer<JSFunction>& init) {
+ JSValue nodeUtilValue = static_cast<Zig::GlobalObject*>(init.owner)->internalModuleRegistry()->requireId(init.owner, init.vm, Bun::InternalModuleRegistry::Field::NodeUtil);
+ RELEASE_ASSERT(nodeUtilValue.isObject());
+ init.set(jsCast<JSFunction*>(nodeUtilValue.getObject()->getIfPropertyExists(init.owner, Identifier::fromString(init.vm, "inspect"_s))));
+ });
+
+ m_utilInspectStylizeColorFunction.initLater(
+ [](const Initializer<JSFunction>& init) {
+ auto scope = DECLARE_THROW_SCOPE(init.vm);
+ JSC::JSFunction* getStylize = JSC::JSFunction::create(init.vm, utilInspectGetStylizeWithColorCodeGenerator(init.vm), init.owner);
+ // RETURN_IF_EXCEPTION(scope, {});
+
+ JSC::MarkedArgumentBuffer args;
+ args.append(static_cast<Zig::GlobalObject*>(init.owner)->utilInspectFunction());
+
+ auto clientData = WebCore::clientData(init.vm);
+ JSC::CallData callData = JSC::getCallData(getStylize);
+
+ NakedPtr<JSC::Exception> returnedException = nullptr;
+ auto result = JSC::call(init.owner, getStylize, callData, jsNull(), args, returnedException);
+ // RETURN_IF_EXCEPTION(scope, {});
+
+ if (returnedException) {
+ throwException(init.owner, scope, returnedException.get());
+ }
+ // RETURN_IF_EXCEPTION(scope, {});
+ init.set(jsCast<JSFunction*>(result));
+ });
+
+ m_utilInspectStylizeNoColorFunction.initLater(
+ [](const Initializer<JSFunction>& init) {
+ init.set(JSC::JSFunction::create(init.vm, utilInspectStylizeWithNoColorCodeGenerator(init.vm), init.owner));
+ });
+
m_nativeMicrotaskTrampoline.initLater(
[](const Initializer<JSFunction>& init) {
init.set(JSFunction::create(init.vm, init.owner, 2, ""_s, functionNativeMicrotaskTrampoline, ImplementationVisibility::Public));
@@ -4606,6 +4641,9 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_pendingVirtualModuleResultStructure.visit(visitor);
thisObject->m_performMicrotaskFunction.visit(visitor);
thisObject->m_performMicrotaskVariadicFunction.visit(visitor);
+ thisObject->m_utilInspectFunction.visit(visitor);
+ thisObject->m_utilInspectStylizeColorFunction.visit(visitor);
+ thisObject->m_utilInspectStylizeNoColorFunction.visit(visitor);
thisObject->m_lazyReadableStreamPrototypeMap.visit(visitor);
thisObject->m_requireMap.visit(visitor);
thisObject->m_encodeIntoObjectStructure.visit(visitor);
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index 0535b1e8f..68bd8aeb0 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -258,6 +258,10 @@ public:
JSC::JSFunction* performMicrotaskFunction() { return m_performMicrotaskFunction.getInitializedOnMainThread(this); }
JSC::JSFunction* performMicrotaskVariadicFunction() { return m_performMicrotaskVariadicFunction.getInitializedOnMainThread(this); }
+ JSC::JSFunction* utilInspectFunction() { return m_utilInspectFunction.getInitializedOnMainThread(this); }
+ JSC::JSFunction* utilInspectStylizeColorFunction() { return m_utilInspectStylizeColorFunction.getInitializedOnMainThread(this); }
+ JSC::JSFunction* utilInspectStylizeNoColorFunction() { return m_utilInspectStylizeNoColorFunction.getInitializedOnMainThread(this); }
+
JSC::JSFunction* emitReadableNextTickFunction() { return m_emitReadableNextTickFunction.getInitializedOnMainThread(this); }
JSObject* requireFunctionUnbound() { return m_requireFunctionUnbound.getInitializedOnMainThread(this); }
@@ -513,6 +517,9 @@ private:
LazyProperty<JSGlobalObject, JSFunction> m_performMicrotaskFunction;
LazyProperty<JSGlobalObject, JSFunction> m_nativeMicrotaskTrampoline;
LazyProperty<JSGlobalObject, JSFunction> m_performMicrotaskVariadicFunction;
+ LazyProperty<JSGlobalObject, JSFunction> m_utilInspectFunction;
+ LazyProperty<JSGlobalObject, JSFunction> m_utilInspectStylizeColorFunction;
+ LazyProperty<JSGlobalObject, JSFunction> m_utilInspectStylizeNoColorFunction;
LazyProperty<JSGlobalObject, JSFunction> m_emitReadableNextTickFunction;
LazyProperty<JSGlobalObject, JSMap> m_lazyReadableStreamPrototypeMap;
LazyProperty<JSGlobalObject, JSMap> m_requireMap;
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index e1d6ba526..5e5f43c99 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -3957,6 +3957,7 @@ enum class BuiltinNamesMap : uint8_t {
data,
toString,
redirect,
+ inspectCustom,
};
static JSC::Identifier builtinNameMap(JSC::JSGlobalObject* globalObject, unsigned char name)
@@ -3988,7 +3989,46 @@ static JSC::Identifier builtinNameMap(JSC::JSGlobalObject* globalObject, unsigne
case BuiltinNamesMap::redirect: {
return clientData->builtinNames().redirectPublicName();
}
+ case BuiltinNamesMap::inspectCustom: {
+ return Identifier::fromUid(vm.symbolRegistry().symbolForKey("nodejs.util.inspect.custom"_s));
}
+ }
+}
+
+extern "C" EncodedJSValue JSC__JSValue__callCustomInspectFunction(
+ JSC::JSGlobalObject* lexicalGlobalObject,
+ JSC__JSValue encodedFunctionValue,
+ JSC__JSValue encodedThisValue,
+ unsigned depth,
+ unsigned max_depth,
+ bool colors)
+{
+ auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
+ JSValue functionToCall = JSValue::decode(encodedFunctionValue);
+ JSValue thisValue = JSValue::decode(encodedThisValue);
+ JSC::VM& vm = globalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ JSFunction* inspectFn = globalObject->utilInspectFunction();
+ JSFunction* stylizeFn = colors ? globalObject->utilInspectStylizeColorFunction() : globalObject->utilInspectStylizeNoColorFunction();
+
+ JSObject* options = JSC::constructEmptyObject(globalObject);
+ options->putDirect(vm, Identifier::fromString(vm, "stylize"_s), stylizeFn);
+ options->putDirect(vm, Identifier::fromString(vm, "depth"_s), jsNumber(max_depth));
+ options->putDirect(vm, Identifier::fromString(vm, "colors"_s), jsBoolean(colors));
+
+ auto callData = JSC::getCallData(functionToCall);
+ MarkedArgumentBuffer arguments;
+ arguments.append(jsNumber(depth));
+ arguments.append(options);
+ arguments.append(inspectFn);
+
+ auto inspectRet = JSC::call(globalObject, functionToCall, callData, thisValue, arguments);
+ if (auto exe = scope.exception()) {
+ scope.clearException();
+ return JSValue::encode(exe);
+ }
+ RELEASE_AND_RETURN(scope, JSValue::encode(inspectRet));
}
JSC__JSValue JSC__JSValue__fastGetDirect_(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, unsigned char arg2)
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 529315a59..dc9740865 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -4240,6 +4240,7 @@ pub const JSValue = enum(JSValueReprInt) {
data,
toString,
redirect,
+ inspectCustom,
};
// intended to be more lightweight than ZigString
diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig
index bdfabaeee..0f234f876 100644
--- a/src/bun.js/bindings/exports.zig
+++ b/src/bun.js/bindings/exports.zig
@@ -1187,7 +1187,7 @@ pub const ZigConsoleClient = struct {
tag = ZigConsoleClient.Formatter.Tag.get(this_value, global);
if (tag.tag == .String and fmt.remaining_values.len > 0) {
- tag.tag = .StringPossiblyFormatted;
+ tag.tag = .{ .StringPossiblyFormatted = {} };
}
fmt.format(tag, Writer, writer, this_value, global, true);
@@ -1209,7 +1209,7 @@ pub const ZigConsoleClient = struct {
any = true;
tag = ZigConsoleClient.Formatter.Tag.get(this_value, global);
if (tag.tag == .String and fmt.remaining_values.len > 0) {
- tag.tag = .StringPossiblyFormatted;
+ tag.tag = .{ .StringPossiblyFormatted = {} };
}
fmt.format(tag, Writer, writer, this_value, global, false);
@@ -1224,6 +1224,11 @@ pub const ZigConsoleClient = struct {
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,
@@ -1239,6 +1244,7 @@ pub const ZigConsoleClient = struct {
estimated_line_length: usize = 0,
always_newline_scope: bool = false,
ordered_properties: bool = false,
+ custom_formatted_object: CustomFormattedObject = .{},
pub fn goodTimeForANewLine(this: *@This()) bool {
if (this.estimated_line_length > 80) {
@@ -1311,8 +1317,10 @@ pub const ZigConsoleClient = struct {
TypedArray,
Map,
Set,
- Symbol,
BigInt,
+ Symbol,
+
+ CustomFormattedObject,
GlobalObject,
Private,
@@ -1349,7 +1357,41 @@ pub const ZigConsoleClient = struct {
}
const Result = struct {
- tag: Tag,
+ 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,
+ Set: void,
+ BigInt: void,
+ Symbol: void,
+ GlobalObject: void,
+ Private: void,
+ Promise: void,
+ JSON: void,
+ toJSON: void,
+ NativeCode: void,
+ ArrayBuffer: void,
+ JSX: void,
+ Event: void,
+ Getter: void,
+
+ CustomFormattedObject: CustomFormattedObject,
+
+ pub fn isPrimitive(this: @This()) bool {
+ return @as(Tag, this).isPrimitive();
+ }
+ },
cell: JSValue.JSType = JSValue.JSType.Cell,
};
@@ -1364,37 +1406,37 @@ pub const ZigConsoleClient = struct {
pub fn getAdvanced(value: JSValue, globalThis: *JSGlobalObject, opts: Options) Result {
switch (@intFromEnum(value)) {
0, 0xa => return Result{
- .tag = .Undefined,
+ .tag = .{ .Undefined = {} },
},
0x2 => return Result{
- .tag = .Null,
+ .tag = .{ .Null = {} },
},
else => {},
}
if (value.isInt32()) {
return .{
- .tag = .Integer,
+ .tag = .{ .Integer = {} },
};
} else if (value.isNumber()) {
return .{
- .tag = .Double,
+ .tag = .{ .Double = {} },
};
} else if (value.isBoolean()) {
return .{
- .tag = .Boolean,
+ .tag = .{ .Boolean = {} },
};
}
if (!value.isCell())
return .{
- .tag = .NativeCode,
+ .tag = .{ .NativeCode = {} },
};
const js_type = value.jsType();
if (js_type.isHidden()) return .{
- .tag = .NativeCode,
+ .tag = .{ .NativeCode = {} },
.cell = js_type,
};
@@ -1402,21 +1444,38 @@ pub const ZigConsoleClient = struct {
// if we call JSObjectGetPrivate, it can segfault
if (js_type == .Cell) {
return .{
- .tag = .NativeCode,
+ .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,
+ .tag = .{ .Private = {} },
.cell = js_type,
};
}
if (CAPI.JSObjectGetPrivate(value.asObjectRef()) != null)
return .{
- .tag = .Private,
+ .tag = .{ .Private = {} },
.cell = js_type,
};
@@ -1425,7 +1484,7 @@ pub const ZigConsoleClient = struct {
if (js_type != .Object and value.isCallable(globalThis.vm())) {
if (value.isClass(globalThis)) {
return .{
- .tag = .Class,
+ .tag = .{ .Class = {} },
.cell = js_type,
};
}
@@ -1448,7 +1507,7 @@ pub const ZigConsoleClient = struct {
);
}
return .{
- .tag = .GlobalObject,
+ .tag = .{ .GlobalObject = {} },
.cell = js_type,
};
}
@@ -1460,7 +1519,7 @@ pub const ZigConsoleClient = struct {
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 = .{ .JSX = {} }, .cell = js_type };
}
}
}
@@ -1676,11 +1735,10 @@ pub const ZigConsoleClient = struct {
comptime Writer: type,
writer: Writer,
) !void {
- const indent = @min(this.indent, 32);
var buf = [_]u8{' '} ** 64;
- var total_remain: usize = indent;
+ var total_remain: u32 = this.indent;
while (total_remain > 0) {
- const written = @min(32, total_remain);
+ const written: u8 = @min(32, total_remain);
try writer.writeAll(buf[0 .. written * 2]);
total_remain -|= written;
}
@@ -1899,6 +1957,15 @@ pub const ZigConsoleClient = struct {
};
}
+ extern fn JSC__JSValue__callCustomInspectFunction(
+ *JSC.JSGlobalObject,
+ JSValue,
+ JSValue,
+ depth: u32,
+ max_depth: u32,
+ colors: bool,
+ ) JSValue;
+
pub fn printAs(
this: *ZigConsoleClient.Formatter,
comptime Format: ZigConsoleClient.Formatter.Tag,
@@ -2068,6 +2135,24 @@ pub const ZigConsoleClient = struct {
this.addForNewLine(4);
writer.print(comptime Output.prettyFmt("<r><yellow>null<r>", enable_ansi_colors), .{});
},
+ .CustomFormattedObject => {
+ // 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(
+ this.globalThis,
+ this.custom_formatted_object.function,
+ this.custom_formatted_object.this,
+ this.max_depth - this.depth,
+ this.max_depth,
+ enable_ansi_colors,
+ );
+ // Strings are printed directly, otherwise we recurse. It is possible to end up in an infinite loop.
+ if (result.isString()) {
+ writer.print("{}", .{result.toBunString(this.globalThis)});
+ } else {
+ this.format(ZigConsoleClient.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);
@@ -2973,7 +3058,7 @@ pub const ZigConsoleClient = struct {
// 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
- return switch (result.tag) {
+ switch (result.tag) {
.StringPossiblyFormatted => this.printAs(.StringPossiblyFormatted, Writer, writer, value, result.cell, enable_ansi_colors),
.String => this.printAs(.String, Writer, writer, value, result.cell, enable_ansi_colors),
.Undefined => this.printAs(.Undefined, Writer, writer, value, result.cell, enable_ansi_colors),
@@ -3005,7 +3090,12 @@ pub const ZigConsoleClient = struct {
.JSX => this.printAs(.JSX, Writer, writer, value, result.cell, enable_ansi_colors),
.Event => this.printAs(.Event, Writer, writer, value, result.cell, enable_ansi_colors),
.Getter => this.printAs(.Getter, Writer, writer, value, result.cell, enable_ansi_colors),
- };
+
+ .CustomFormattedObject => |callback| {
+ this.custom_formatted_object = callback;
+ this.printAs(.CustomFormattedObject, Writer, writer, value, result.cell, enable_ansi_colors);
+ },
+ }
}
};