diff options
author | 2023-08-17 20:56:52 -0700 | |
---|---|---|
committer | 2023-08-17 20:56:52 -0700 | |
commit | 6fd0043f6bf766cc488a88339059e8879fa07161 (patch) | |
tree | d5289bcaf0880a3bf1e2f0b1c681aff93188fe51 /src/bun.js | |
parent | 0424fd8f6e7549ed779788006acdc97a8467e287 (diff) | |
download | bun-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.zig | 76 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 38 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.h | 7 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 40 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 1 | ||||
-rw-r--r-- | src/bun.js/bindings/exports.zig | 134 |
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); + }, + } } }; |