aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-11-27 07:27:21 -0800
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-11-27 07:27:21 -0800
commit5e6e8ece4e692127f29224aaa00c33e0496bbabb (patch)
treeb7e99b0092e6391382c2085cfb03df81e908961a
parent964c4037deb90283f12733ea7d8a660bc0430f88 (diff)
downloadbun-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.cpp177
-rw-r--r--src/bun.js/bindings/bindings.zig25
-rw-r--r--src/bun.js/bindings/exports.zig379
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),
};