diff options
author | 2022-11-27 07:27:21 -0800 | |
---|---|---|
committer | 2022-11-27 07:27:21 -0800 | |
commit | 5e6e8ece4e692127f29224aaa00c33e0496bbabb (patch) | |
tree | b7e99b0092e6391382c2085cfb03df81e908961a | |
parent | 964c4037deb90283f12733ea7d8a660bc0430f88 (diff) | |
download | bun-5e6e8ece4e692127f29224aaa00c33e0496bbabb.tar.gz bun-5e6e8ece4e692127f29224aaa00c33e0496bbabb.tar.zst bun-5e6e8ece4e692127f29224aaa00c33e0496bbabb.zip |
[console.log] Improve Object formatting and error handling
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 177 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 25 | ||||
-rw-r--r-- | src/bun.js/bindings/exports.zig | 379 |
3 files changed, 399 insertions, 182 deletions
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 4cb163f88..4c65cedf5 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -47,7 +47,7 @@ #include "wtf/text/StringImpl.h" #include "wtf/text/StringView.h" #include "wtf/text/WTFString.h" - +#include "JavaScriptCore/FunctionPrototype.h" #include "JSFetchHeaders.h" #include "FetchHeaders.h" #include "DOMURL.h" @@ -128,7 +128,7 @@ static void handlePromise(PromiseType* promise, JSC__JSGlobalObject* globalObjec } } -static bool canPerformFastPropertyEnumerationForObjectAssignBun(Structure* s) +static bool canPerformFastPropertyEnumerationForIterationBun(Structure* s) { if (s->typeInfo().overridesGetOwnPropertySlot()) return false; @@ -448,7 +448,6 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2, case JSFunctionType: { return false; } - default: { break; } @@ -547,14 +546,14 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2, } JSC::Structure* o1Structure = o1->structure(); - if (canPerformFastPropertyEnumerationForObjectAssignBun(o1Structure)) { + if (canPerformFastPropertyEnumerationForIterationBun(o1Structure)) { JSC::Structure* o2Structure = o2->structure(); - if (canPerformFastPropertyEnumerationForObjectAssignBun(o2Structure)) { + if (canPerformFastPropertyEnumerationForIterationBun(o2Structure)) { size_t count1 = 0; bool result = true; - if (o2Structure->maxOffset() != o1Structure->maxOffset()) { + if (o2Structure->inlineSize() + o2Structure->outOfLineSize() != o1Structure->inlineSize() + o1Structure->outOfLineSize()) { return false; } @@ -1819,7 +1818,15 @@ void JSC__JSValue__toZigString(JSC__JSValue JSValue0, ZigString* arg1, JSC__JSGl // return; // } - auto str = value.toWTFString(arg2); + auto* strValue = value.toStringOrNull(arg2); + + if (UNLIKELY(!strValue)) { + arg1->len = 0; + arg1->ptr = nullptr; + return; + } + + auto str = strValue->value(arg2); if (str.is8Bit()) { arg1->ptr = str.characters8(); @@ -3724,3 +3731,159 @@ bool JSC__JSValue__toBooleanSlow(JSC__JSValue JSValue0, JSC__JSGlobalObject* glo { return JSValue::decode(JSValue0).toBoolean(globalObject); } + +void JSC__JSValue__forEachProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, void* arg2, void (*iter)(JSC__JSGlobalObject* arg0, void* ctx, ZigString* arg2, JSC__JSValue JSValue3, bool isSymbol)) +{ + JSC::JSValue value = JSC::JSValue::decode(JSValue0); + JSC::JSObject* object = value.getObject(); + if (!object) + return; + + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + + JSC::Structure* structure = object->structure(); + bool fast = canPerformFastPropertyEnumerationForIterationBun(structure); + if (fast) { + if (structure->outOfLineSize() == 0 && structure->inlineSize() == 0) { + fast = false; + if (JSValue proto = object->getPrototype(vm, globalObject)) { + if (structure = proto.structureOrNull()) { + fast = canPerformFastPropertyEnumerationForIterationBun(structure); + } + } + } + } + + if (fast) { + bool anyHits = false; + + structure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { + if (entry.attributes() & PropertyAttribute::DontEnum) { + + if ((entry.attributes() & PropertyAttribute::Accessor) != 0) { + return true; + } + if (!(entry.attributes() & (PropertyAttribute::BuiltinOrFunction | PropertyAttribute::CustomAccessorOrValue))) { + return true; + } + + // ignore constructor + if (entry.key() == vm.propertyNames->constructor) { + return true; + } + } + + ZigString key = toZigString(entry.key()); + + JSC::JSValue propertyValue = object->getDirect(entry.offset()); + if (!propertyValue) { + propertyValue = object->get(globalObject, entry.key()); + } + + if (scope.exception()) + scope.clearException(); + + if (!propertyValue) + return true; + + anyHits = true; + JSC::EnsureStillAliveScope ensureStillAliveScope(propertyValue); + iter(globalObject, arg2, &key, JSC::JSValue::encode(propertyValue), entry.key()->isSymbol()); + return true; + }); + if (anyHits) { + if (scope.exception()) { + scope.clearException(); + return; + } + return; + } + } + + JSC::PropertyNameArray properties(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude); + + { + auto scope = DECLARE_THROW_SCOPE(vm); + + JSObject* iterating = object; + unsigned prototypeCount = 0; + size_t count = 0; + + while (count == 0) { + iterating->methodTable()->getOwnPropertyNames(iterating, globalObject, properties, DontEnumPropertiesMode::Include); + RETURN_IF_EXCEPTION(scope, void()); + + for (auto& property : properties) { + if (UNLIKELY(property.isEmpty() || property.isNull())) + continue; + + // ignore constructor + if (property == vm.propertyNames->constructor) { + continue; + } + + JSC::PropertySlot slot(object, PropertySlot::InternalMethodType::Get); + if (!object->getPropertySlot(globalObject, property, slot)) + continue; + + if ((slot.attributes() & PropertyAttribute::Accessor) != 0) { + continue; + } + + if ((slot.attributes() & PropertyAttribute::DontEnum) != 0) { + if (property == vm.propertyNames->length || property == vm.propertyNames->name || property == vm.propertyNames->underscoreProto) + continue; + } + + ZigString key = toZigString(property.isSymbol() ? property.impl() : property.string()); + + JSC::JSValue propertyValue = jsUndefined(); + + if ((slot.attributes() & PropertyAttribute::DontEnum) != 0) { + if (slot.attributes() & PropertyAttribute::BuiltinOrFunction) { + propertyValue = slot.getValue(globalObject, property); + } else if (slot.isCustom()) { + propertyValue = slot.internalMethodType() == PropertySlot::InternalMethodType::Get || slot.internalMethodType() == PropertySlot::InternalMethodType::HasProperty ? slot.getValue(globalObject, property) : jsUndefined(); + } else if (slot.isValue()) { + propertyValue = slot.getValue(globalObject, property); + } else if (object->getOwnPropertySlot(object, globalObject, property, slot)) { + propertyValue = slot.getValue(globalObject, property); + } + } else { + propertyValue = slot.getValue(globalObject, property); + } + + if (scope.exception()) { + scope.clearException(); + propertyValue = jsUndefined(); + } + + JSC::EnsureStillAliveScope ensureStillAliveScope(propertyValue); + count++; + iter(globalObject, arg2, &key, JSC::JSValue::encode(propertyValue), property.isSymbol()); + } + // reuse memory + properties.data()->propertyNameVector().shrink(0); + + JSValue prototype = iterating->getPrototype(vm, globalObject); + RETURN_IF_EXCEPTION(scope, void()); + if (prototype.isNull() || prototype == JSValue(globalObject->functionPrototype()) || prototype == JSValue(globalObject->objectPrototype()) || prototype == JSValue(globalObject->objectPrototype())) + break; + + if (++prototypeCount > 1) { + break; + } + + iterating = asObject(prototype); + if (iterating->structure()->typeInfo().overridesAnyFormOfGetOwnPropertyNames()) + break; + } + } + properties.releaseData(); + + if (scope.exception()) { + scope.clearException(); + return; + } +} diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index c59aeb1a2..22855ef01 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -2692,7 +2692,7 @@ pub const JSValue = enum(JSValueReprInt) { pub fn isFunction(this: JSType) bool { return switch (this) { - .FunctionExecutable, .InternalFunction => true, + .JSFunction, .FunctionExecutable, .InternalFunction => true, else => false, }; } @@ -2805,6 +2805,23 @@ pub const JSValue = enum(JSValueReprInt) { return cppFn("coerceToInt32", .{ this, globalThis }); } + const PropertyIteratorFn = fn ( + globalObject_: [*c]JSGlobalObject, + ctx_ptr: ?*anyopaque, + key: *ZigString, + value: JSValue, + is_symbol: bool, + ) callconv(.C) void; + + pub fn forEachProperty( + this: JSValue, + globalThis: *JSC.JSGlobalObject, + ctx: ?*anyopaque, + callback: PropertyIteratorFn, + ) void { + cppFn("forEachProperty", .{ this, globalThis, ctx, callback }); + } + pub fn coerce(this: JSValue, comptime T: type, globalThis: *JSC.JSGlobalObject) T { return switch (T) { ZigString => this.getZigString(globalThis), @@ -3208,6 +3225,10 @@ pub const JSValue = enum(JSValueReprInt) { } pub fn getNameProperty(this: JSValue, global: *JSGlobalObject, ret: *ZigString) void { + if (this.isEmptyOrUndefinedOrNull()) { + return; + } + cppFn("getNameProperty", .{ this, global, ret }); } @@ -3615,7 +3636,7 @@ pub const JSValue = enum(JSValueReprInt) { return this.asNullableVoid().?; } - pub const Extern = [_][]const u8{ "coerceToInt32", "fastGet_", "getStaticProperty", "createUninitializedUint8Array", "fromInt64NoTruncate", "fromUInt64NoTruncate", "toUInt64NoTruncate", "asPromise", "toInt64", "_then", "put", "makeWithNameAndPrototype", "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt64", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable", "toBooleanSlow", "deepEquals" }; + pub const Extern = [_][]const u8{ "forEachProperty", "coerceToInt32", "fastGet_", "getStaticProperty", "createUninitializedUint8Array", "fromInt64NoTruncate", "fromUInt64NoTruncate", "toUInt64NoTruncate", "asPromise", "toInt64", "_then", "put", "makeWithNameAndPrototype", "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt64", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable", "toBooleanSlow", "deepEquals" }; }; extern "c" fn Microtask__run(*Microtask, *JSGlobalObject) void; diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index f18d47329..917a62ee0 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -1680,6 +1680,156 @@ pub const ZigConsoleClient = struct { }; } + pub fn PropertyIterator(comptime Writer: type, comptime enable_ansi_colors_: bool) type { + return struct { + formatter: *ZigConsoleClient.Formatter, + writer: Writer, + i: usize = 0, + 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.jsType().isFunction() and !value.isClass(globalThis)) { + var name_str = ZigString.init(""); + + value.getPrototype(globalThis).getNameProperty(globalThis, &name_str); + var writer = WrappedWriter(Writer){ + .ctx = this.writer, + .failed = false, + }; + + if (name_str.len > 0 and !strings.eqlComptime(name_str.slice(), "Object")) { + writer.print("{} ", .{name_str}); + } else { + value.getNameProperty(globalThis, &name_str); + if (name_str.len > 0 and !strings.eqlComptime(name_str.slice(), "Object")) { + writer.print("{} ", .{name_str}); + } + } + } + + this.always_newline = true; + this.formatter.estimated_line_length = this.formatter.indent * 2 + 1; + this.writer.writeAll("{\n") catch {}; + this.formatter.indent += 1; + this.formatter.writeIndent(Writer, this.writer) catch {}; + } + + pub fn forEach( + globalObject_: [*c]JSGlobalObject, + ctx_ptr: ?*anyopaque, + key: *ZigString, + value: JSValue, + is_symbol: bool, + ) callconv(.C) void { + if (key.eqlComptime("constructor")) return; + if (key.eqlComptime("call")) return; + + var globalThis = globalObject_.?; + var ctx: *@This() = bun.cast(*@This(), ctx_ptr orelse return); + var this = ctx.formatter; + var writer_ = ctx.writer; + var writer = WrappedWriter(Writer){ + .ctx = writer_, + .failed = false, + }; + + const tag = Tag.get(value, globalThis); + + 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 (ctx.always_newline or this.always_newline_scope or this.goodTimeForANewLine()) { + writer.writeAll("\n"); + this.writeIndent(Writer, writer_) catch {}; + this.resetLine(); + } else { + this.estimated_line_length += 1; + writer.writeAll(" "); + } + } + + if (!is_symbol) { + + // TODO: make this one pass? + if (!key.is16Bit() and JSLexer.isLatin1Identifier(@TypeOf(key.slice()), key.slice())) { + this.addForNewLine(key.len + 1); + + writer.print( + comptime Output.prettyFmt("{}<d>:<r> ", enable_ansi_colors), + .{key}, + ); + } else if (key.is16Bit() and JSLexer.isLatin1Identifier(@TypeOf(key.utf16SliceAligned()), key.utf16SliceAligned())) { + this.addForNewLine(key.len + 1); + + writer.print( + comptime Output.prettyFmt("{}<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 + 1); + + writer.print( + comptime Output.prettyFmt("{s}<d>:<r> ", enable_ansi_colors), + .{JSPrinter.formatJSONString(key.slice())}, + ); + } + } 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)); + } + } + } + }; + } + pub fn printAs( this: *ZigConsoleClient.Formatter, comptime Format: ZigConsoleClient.Formatter.Tag, @@ -1822,12 +1972,13 @@ pub const ZigConsoleClient = struct { }, .Symbol => { const description = value.getDescription(this.globalThis); - this.addForNewLine(description.len + "Symbol".len); + this.addForNewLine("Symbol".len); if (description.len > 0) { - writer.print(comptime Output.prettyFmt("<r><cyan>Symbol<r><d>(<green>{}<r><d>)<r>", enable_ansi_colors), .{description}); + 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><cyan>Symbol<r>", enable_ansi_colors), .{}); + writer.print(comptime Output.prettyFmt("<r><blue>Symbol<r>", enable_ansi_colors), .{}); } }, .Error => { @@ -1970,6 +2121,12 @@ pub const ZigConsoleClient = struct { else => {}, } } + + 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); }, @@ -2361,178 +2518,55 @@ pub const ZigConsoleClient = struct { writer.writeAll(" />"); }, .Object => { - var object = value.asObjectRef(); - - { - var props_iter = JSC.JSPropertyIterator(.{ - .skip_empty_name = true, - .include_value = true, - }).init(this.globalThis, object); - defer props_iter.deinit(); - - const prev_quote_strings = this.quote_strings; - this.quote_strings = true; - defer this.quote_strings = prev_quote_strings; - - var name_str = ZigString.init(""); - value.getPrototype(this.globalThis).getNameProperty(this.globalThis, &name_str); - - if (name_str.len > 0 and !strings.eqlComptime(name_str.slice(), "Object")) { - writer.print("{} ", .{name_str}); - } else { - value.getNameProperty(this.globalThis, &name_str); - if (name_str.len > 0 and !strings.eqlComptime(name_str.slice(), "Object")) { - writer.print("{} ", .{name_str}); - } - } - - if (props_iter.len == 0) { - writer.writeAll("{ }"); - return; - } - - // 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; - const first_value = JSC.JSValue.fromRef( - JSC.C.JSObjectGetProperty( - this.globalThis, - value.asObjectRef(), - props_iter.array_ref.?.at(0), - null, - ), - ); + const prev_quote_strings = this.quote_strings; + this.quote_strings = true; + defer this.quote_strings = prev_quote_strings; - const always_newline = props_iter.len > 1 and - (prev_always_newline_scope or - props_iter.hasLongNames() or - switch (first_value.jsTypeLoose()) { - .DOMWrapper, .ModuleNamespaceObject, .JSMap, .JSSet, .Blob => true, - else => false, - }); + 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.always_newline_scope or this.goodTimeForANewLine(), + .parent = value, + }; - defer this.always_newline_scope = prev_always_newline_scope; - this.always_newline_scope = always_newline; + value.forEachProperty(this.globalThis, &iter, Iterator.forEach); - const had_newline = this.always_newline_scope or this.goodTimeForANewLine(); - if (had_newline) { - this.estimated_line_length = this.indent * 2 + 1; - writer.writeAll("{\n"); - this.indent += 1; - this.writeIndent(Writer, writer_) catch {}; - } else { - writer.writeAll("{ "); - this.estimated_line_length += 3; + if (iter.i == 0) { + if (value.isClass(this.globalThis) and !value.isCallable(this.globalThis.vm())) + 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 + writer.writeAll("{}"); + } else { + if (iter.always_newline) { + this.indent -|= 1; } - { - defer { - if (had_newline) { - this.indent -|= 1; - } - } - - while (props_iter.nextMaybeFirstValue(first_value)) |key| { - const property_value = props_iter.value; - const tag = Tag.get(property_value, this.globalThis); - - if (tag.cell.isHidden()) continue; - - if (props_iter.iter_i > 1) { - if (always_newline or this.always_newline_scope or this.goodTimeForANewLine()) { - writer.writeAll("\n"); - this.writeIndent(Writer, writer_) catch {}; - this.resetLine(); - } else { - this.estimated_line_length += 1; - writer.writeAll(" "); - } - } - - // TODO: make this one pass? - if (!key.is16Bit() and JSLexer.isLatin1Identifier(@TypeOf(key.slice()), key.slice())) { - this.addForNewLine(key.len + 1); - - writer.print( - comptime Output.prettyFmt("{}<d>:<r> ", enable_ansi_colors), - .{key}, - ); - } else if (key.is16Bit() and JSLexer.isLatin1Identifier(@TypeOf(key.utf16SliceAligned()), key.utf16SliceAligned())) { - this.addForNewLine(key.len + 1); - - writer.print( - comptime Output.prettyFmt("{}<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 + 1); - - writer.print( - comptime Output.prettyFmt("{s}<d>:<r> ", enable_ansi_colors), - .{JSPrinter.formatJSONString(key.slice())}, - ); - } - - 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 (props_iter.i + 1 < props_iter.len) { - this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable; - } - } - } - if (had_newline) { + if (iter.always_newline) { writer.writeAll("\n"); this.writeIndent(Writer, writer_) catch {}; writer.writeAll("}"); @@ -2616,7 +2650,7 @@ pub const ZigConsoleClient = struct { enable_ansi_colors, ), - // Uint8Array, Uint8ClampedArray, DataView + // Uint8Array, Uint8ClampedArray, DataView, ArrayBuffer else => this.writeTypedArray(*@TypeOf(writer), &writer, u8, slice, enable_ansi_colors), } } @@ -2678,7 +2712,7 @@ pub const ZigConsoleClient = struct { .Function => this.printAs(.Function, Writer, writer, value, result.cell, enable_ansi_colors), .Class => this.printAs(.Class, Writer, writer, value, result.cell, enable_ansi_colors), .Error => this.printAs(.Error, Writer, writer, value, result.cell, enable_ansi_colors), - .TypedArray => this.printAs(.TypedArray, Writer, writer, value, result.cell, enable_ansi_colors), + .ArrayBuffer, .TypedArray => this.printAs(.TypedArray, Writer, writer, value, result.cell, enable_ansi_colors), .Map => this.printAs(.Map, Writer, writer, value, result.cell, enable_ansi_colors), .Set => this.printAs(.Set, Writer, writer, value, result.cell, enable_ansi_colors), .Symbol => this.printAs(.Symbol, Writer, writer, value, result.cell, enable_ansi_colors), @@ -2688,7 +2722,6 @@ pub const ZigConsoleClient = struct { .Promise => this.printAs(.Promise, Writer, writer, value, result.cell, enable_ansi_colors), .JSON => this.printAs(.JSON, Writer, writer, value, result.cell, enable_ansi_colors), .NativeCode => this.printAs(.NativeCode, Writer, writer, value, result.cell, enable_ansi_colors), - .ArrayBuffer => this.printAs(.ArrayBuffer, Writer, writer, value, result.cell, enable_ansi_colors), .JSX => this.printAs(.JSX, Writer, writer, value, result.cell, enable_ansi_colors), .Event => this.printAs(.Event, Writer, writer, value, result.cell, enable_ansi_colors), }; |