aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js
diff options
context:
space:
mode:
authorGravatar Dylan Conway <35280289+dylan-conway@users.noreply.github.com> 2023-03-14 16:50:59 -0700
committerGravatar GitHub <noreply@github.com> 2023-03-14 16:50:59 -0700
commit4792abdb7fbceef7e4ec6bf15c671b88f41c65eb (patch)
tree00db7585ea137a1bbb775cfe586e32078b77b13b /src/bun.js
parent76b875e4140b45dd7d70e11dfb3a87d2d9469aa0 (diff)
downloadbun-4792abdb7fbceef7e4ec6bf15c671b88f41c65eb.tar.gz
bun-4792abdb7fbceef7e4ec6bf15c671b88f41c65eb.tar.zst
bun-4792abdb7fbceef7e4ec6bf15c671b88f41c65eb.zip
Implement `toMatchSnapshot()` (#2294)
* buggy snapshot * error output for failed snapshot * missing first * hints * open dir once, better cleanup * update flag * truncate on update * object and class snapshot formatting * array formatting * no function name, single item is empty array * string objects, maps, sets, promise * avoid using invalid memory * handle number objects * handle extending `Number` * boolean objects * snapshot tests and test updates * snapshot format for buffers * safer snapshot parsing * property matchers setup * strings and tests * generate classes with empty prototype * optional `propertyMatchers` parameter * new test folder structure * strings.eqlLong * globalObject.throwPretty() and expect.any tests * add updateSnapshot flag to help * move snapshot format out of `printErrorlikeObject` * empty object snapshot format * separate typed array, remove trailing comma * use `isCell`, object trailing commas * handle unicode * todo for primitive constructors * switch to `JSC.Node.Syscall.open` and `JSC.Maybe` * use js parser for snapshot files * deinit ast, log parse error * copy/paste most of `exports.ZigConsoleClient` * remove snapshot option * remove ordered properties option * remove snapshot format option from `exports.zig` * remove extra newlines * change mode * update test runner output * escape backticks faster * `bunx jest` in temp dir * remove buffered writer * add `toMatchSnapshot` to types * cleanup, switch to `pread` * cli `--update` flag * `--update-snapshots` * remove string object format
Diffstat (limited to 'src/bun.js')
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h3
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h3
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h6
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h7
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.cpp169
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.h56
-rw-r--r--src/bun.js/bindings/bindings.cpp18
-rw-r--r--src/bun.js/bindings/bindings.zig107
-rw-r--r--src/bun.js/bindings/exports.zig108
-rw-r--r--src/bun.js/bindings/generated_classes.zig82
-rw-r--r--src/bun.js/bindings/generated_classes_list.zig1
-rw-r--r--src/bun.js/bindings/headers.h1
-rw-r--r--src/bun.js/bindings/headers.zig1
-rw-r--r--src/bun.js/javascript.zig1
-rw-r--r--src/bun.js/scripts/generate-classes.ts14
-rw-r--r--src/bun.js/test/jest.classes.ts12
-rw-r--r--src/bun.js/test/jest.zig659
-rw-r--r--src/bun.js/test/pretty_format.zig1963
-rw-r--r--src/bun.js/webcore/blob.zig2
-rw-r--r--src/bun.js/webcore/body.zig4
-rw-r--r--src/bun.js/webcore/request.zig6
-rw-r--r--src/bun.js/webcore/response.zig4
22 files changed, 3168 insertions, 59 deletions
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
index d642ae40f..f38c22831 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
@@ -2,7 +2,8 @@ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBlob;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBlobConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCryptoHasher;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCryptoHasherConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDirent;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDirentConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpect;
-std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemRouter;
+std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectAny;
+std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemRouter;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemRouterConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForListener;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD4;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD4Constructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD5;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
index 93cb9bf5e..80ada929c 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
@@ -2,7 +2,8 @@ std::unique_ptr<IsoSubspace> m_subspaceForBlob;
std::unique_ptr<IsoSubspace> m_subspaceForBlobConstructor;std::unique_ptr<IsoSubspace> m_subspaceForCryptoHasher;
std::unique_ptr<IsoSubspace> m_subspaceForCryptoHasherConstructor;std::unique_ptr<IsoSubspace> m_subspaceForDirent;
std::unique_ptr<IsoSubspace> m_subspaceForDirentConstructor;std::unique_ptr<IsoSubspace> m_subspaceForExpect;
-std::unique_ptr<IsoSubspace> m_subspaceForExpectConstructor;std::unique_ptr<IsoSubspace> m_subspaceForFileSystemRouter;
+std::unique_ptr<IsoSubspace> m_subspaceForExpectConstructor;std::unique_ptr<IsoSubspace> m_subspaceForExpectAny;
+std::unique_ptr<IsoSubspace> m_subspaceForFileSystemRouter;
std::unique_ptr<IsoSubspace> m_subspaceForFileSystemRouterConstructor;std::unique_ptr<IsoSubspace> m_subspaceForListener;
std::unique_ptr<IsoSubspace> m_subspaceForMD4;
std::unique_ptr<IsoSubspace> m_subspaceForMD4Constructor;std::unique_ptr<IsoSubspace> m_subspaceForMD5;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
index ab75a9949..ae3e4233a 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
@@ -22,6 +22,12 @@ JSC::Structure* JSExpectStructure() { return m_JSExpect.getInitializedOnMainThre
JSC::LazyClassStructure m_JSExpect;
bool hasJSExpectSetterValue { false };
mutable JSC::WriteBarrier<JSC::Unknown> m_JSExpectSetterValue;
+JSC::Structure* JSExpectAnyStructure() { return m_JSExpectAny.getInitializedOnMainThread(this); }
+ JSC::JSObject* JSExpectAnyConstructor() { return m_JSExpectAny.constructorInitializedOnMainThread(this); }
+ JSC::JSValue JSExpectAnyPrototype() { return m_JSExpectAny.prototypeInitializedOnMainThread(this); }
+ JSC::LazyClassStructure m_JSExpectAny;
+ bool hasJSExpectAnySetterValue { false };
+ mutable JSC::WriteBarrier<JSC::Unknown> m_JSExpectAnySetterValue;
JSC::Structure* JSFileSystemRouterStructure() { return m_JSFileSystemRouter.getInitializedOnMainThread(this); }
JSC::JSObject* JSFileSystemRouterConstructor() { return m_JSFileSystemRouter.constructorInitializedOnMainThread(this); }
JSC::JSValue JSFileSystemRouterPrototype() { return m_JSFileSystemRouter.prototypeInitializedOnMainThread(this); }
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
index aed941a2e..a90753aa8 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
@@ -23,6 +23,12 @@ void GlobalObject::initGeneratedLazyClasses() {
init.setStructure(WebCore::JSExpect::createStructure(init.vm, init.global, init.prototype));
init.setConstructor(WebCore::JSExpect::createConstructor(init.vm, init.global, init.prototype));
});
+ m_JSExpectAny.initLater(
+ [](LazyClassStructure::Initializer& init) {
+ init.setPrototype(WebCore::JSExpectAny::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
+ init.setStructure(WebCore::JSExpectAny::createStructure(init.vm, init.global, init.prototype));
+
+ });
m_JSFileSystemRouter.initLater(
[](LazyClassStructure::Initializer& init) {
init.setPrototype(WebCore::JSFileSystemRouter::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
@@ -163,6 +169,7 @@ void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor&
thisObject->m_JSCryptoHasher.visit(visitor); visitor.append(thisObject->m_JSCryptoHasherSetterValue);
thisObject->m_JSDirent.visit(visitor); visitor.append(thisObject->m_JSDirentSetterValue);
thisObject->m_JSExpect.visit(visitor); visitor.append(thisObject->m_JSExpectSetterValue);
+ thisObject->m_JSExpectAny.visit(visitor); visitor.append(thisObject->m_JSExpectAnySetterValue);
thisObject->m_JSFileSystemRouter.visit(visitor); visitor.append(thisObject->m_JSFileSystemRouterSetterValue);
thisObject->m_JSListener.visit(visitor); visitor.append(thisObject->m_JSListenerSetterValue);
thisObject->m_JSMD4.visit(visitor); visitor.append(thisObject->m_JSMD4SetterValue);
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp
index 716f683e5..a388490b4 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.cpp
+++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp
@@ -2342,6 +2342,175 @@ void JSExpect::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor)
}
DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSExpect);
+class JSExpectAnyPrototype final : public JSC::JSNonFinalObject {
+public:
+ using Base = JSC::JSNonFinalObject;
+
+ static JSExpectAnyPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
+ {
+ JSExpectAnyPrototype* ptr = new (NotNull, JSC::allocateCell<JSExpectAnyPrototype>(vm)) JSExpectAnyPrototype(vm, globalObject, structure);
+ ptr->finishCreation(vm, globalObject);
+ return ptr;
+ }
+
+ DECLARE_INFO;
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ return &vm.plainObjectSpace();
+ }
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+ }
+
+private:
+ JSExpectAnyPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
+};
+
+extern "C" void ExpectAnyClass__finalize(void*);
+extern "C" JSC_DECLARE_HOST_FUNCTION(ExpectAnyClass__call);
+
+STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSExpectAnyPrototype, JSExpectAnyPrototype::Base);
+
+static const HashTableValue JSExpectAnyPrototypeTableValues[] = {};
+
+const ClassInfo JSExpectAnyPrototype::s_info = { "ExpectAny"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSExpectAnyPrototype) };
+
+extern "C" void ExpectAnyPrototype__constructorValueSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value)
+{
+ auto& vm = globalObject->vm();
+ auto* thisObject = jsCast<JSExpectAny*>(JSValue::decode(thisValue));
+ thisObject->m_constructorValue.set(vm, thisObject, JSValue::decode(value));
+}
+
+extern "C" EncodedJSValue ExpectAnyPrototype__constructorValueGetCachedValue(JSC::EncodedJSValue thisValue)
+{
+ auto* thisObject = jsCast<JSExpectAny*>(JSValue::decode(thisValue));
+ return JSValue::encode(thisObject->m_constructorValue.get());
+}
+
+void JSExpectAnyPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ Base::finishCreation(vm);
+
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+JSExpectAny::~JSExpectAny()
+{
+ if (m_ctx) {
+ ExpectAnyClass__finalize(m_ctx);
+ }
+}
+void JSExpectAny::destroy(JSCell* cell)
+{
+ static_cast<JSExpectAny*>(cell)->JSExpectAny::~JSExpectAny();
+}
+
+const ClassInfo JSExpectAny::s_info = { "ExpectAny"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSExpectAny) };
+
+void JSExpectAny::finishCreation(VM& vm)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(info()));
+}
+
+JSExpectAny* JSExpectAny::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx)
+{
+ JSExpectAny* ptr = new (NotNull, JSC::allocateCell<JSExpectAny>(vm)) JSExpectAny(vm, structure, ctx);
+ ptr->finishCreation(vm);
+ return ptr;
+}
+
+extern "C" void* ExpectAny__fromJS(JSC::EncodedJSValue value)
+{
+ JSC::JSValue decodedValue = JSC::JSValue::decode(value);
+ if (decodedValue.isEmpty() || !decodedValue.isCell())
+ return nullptr;
+
+ JSC::JSCell* cell = decodedValue.asCell();
+ JSExpectAny* object = JSC::jsDynamicCast<JSExpectAny*>(cell);
+
+ if (!object)
+ return nullptr;
+
+ return object->wrapped();
+}
+
+extern "C" bool ExpectAny__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr)
+{
+ JSExpectAny* object = JSC::jsDynamicCast<JSExpectAny*>(JSValue::decode(value));
+ if (!object)
+ return false;
+
+ object->m_ctx = ptr;
+ return true;
+}
+
+extern "C" const size_t ExpectAny__ptrOffset = JSExpectAny::offsetOfWrapped();
+
+void JSExpectAny::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
+{
+ auto* thisObject = jsCast<JSExpectAny*>(cell);
+ if (void* wrapped = thisObject->wrapped()) {
+ // if (thisObject->scriptExecutionContext())
+ // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string());
+ }
+ Base::analyzeHeap(cell, analyzer);
+}
+
+JSObject* JSExpectAny::createPrototype(VM& vm, JSDOMGlobalObject* globalObject)
+{
+ return JSExpectAnyPrototype::create(vm, globalObject, JSExpectAnyPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
+}
+
+extern "C" EncodedJSValue ExpectAny__create(Zig::GlobalObject* globalObject, void* ptr)
+{
+ auto& vm = globalObject->vm();
+ JSC::Structure* structure = globalObject->JSExpectAnyStructure();
+ JSExpectAny* instance = JSExpectAny::create(vm, globalObject, structure, ptr);
+
+ return JSValue::encode(instance);
+}
+
+template<typename Visitor>
+void JSExpectAny::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ JSExpectAny* thisObject = jsCast<JSExpectAny*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+ visitor.append(thisObject->m_constructorValue);
+}
+
+DEFINE_VISIT_CHILDREN(JSExpectAny);
+
+template<typename Visitor>
+void JSExpectAny::visitAdditionalChildren(Visitor& visitor)
+{
+ JSExpectAny* thisObject = this;
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ visitor.append(thisObject->m_constructorValue);
+
+ ;
+}
+
+DEFINE_VISIT_ADDITIONAL_CHILDREN(JSExpectAny);
+
+template<typename Visitor>
+void JSExpectAny::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor)
+{
+ JSExpectAny* thisObject = jsCast<JSExpectAny*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ thisObject->visitAdditionalChildren<Visitor>(visitor);
+}
+
+DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSExpectAny);
class JSFileSystemRouterPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h
index acf1dc140..76420193a 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses.h
@@ -236,6 +236,62 @@ public:
mutable JSC::WriteBarrier<JSC::Unknown> m_resultValue;
};
+class JSExpectAny final : public JSC::JSDestructibleObject {
+public:
+ using Base = JSC::JSDestructibleObject;
+ static JSExpectAny* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx);
+
+ DECLARE_EXPORT_INFO;
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<JSExpectAny, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForExpectAny.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForExpectAny = std::forward<decltype(space)>(space); },
+ [](auto& spaces) { return spaces.m_subspaceForExpectAny.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForExpectAny = std::forward<decltype(space)>(space); });
+ }
+
+ static void destroy(JSC::JSCell*);
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(0b11101110), StructureFlags), info());
+ }
+
+ static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject);
+ ;
+
+ ~JSExpectAny();
+
+ void* wrapped() const { return m_ctx; }
+
+ void detach()
+ {
+ m_ctx = nullptr;
+ }
+
+ static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+ static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSExpectAny, m_ctx); }
+
+ void* m_ctx { nullptr };
+
+ JSExpectAny(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
+ : Base(vm, structure)
+ {
+ m_ctx = sinkPtr;
+ }
+
+ void finishCreation(JSC::VM&);
+
+ DECLARE_VISIT_CHILDREN;
+ template<typename Visitor> void visitAdditionalChildren(Visitor&);
+ DECLARE_VISIT_OUTPUT_CONSTRAINTS;
+
+ mutable JSC::WriteBarrier<JSC::Unknown> m_constructorValue;
+};
+
class JSFileSystemRouter final : public JSC::JSDestructibleObject {
public:
using Base = JSC::JSDestructibleObject;
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index 5c9cba7f0..d431a287f 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -3227,13 +3227,21 @@ void JSC__VM__releaseWeakRefs(JSC__VM* arg0)
static auto function_string_view = MAKE_STATIC_STRING_IMPL("Function");
void JSC__JSValue__getClassName(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2)
{
- JSC::JSCell* cell = JSC::JSValue::decode(JSValue0).asCell();
+ JSValue value = JSValue::decode(JSValue0);
+ JSC::JSCell* cell = value.asCell();
if (cell == nullptr) {
arg2->len = 0;
return;
}
- const char* ptr = cell->className();
+ JSObject* obj = value.toObject(arg1);
+ StringView calculated = StringView(JSObject::calculatedClassName(obj));
+ if (calculated.length() > 0) {
+ *arg2 = Zig::toZigString(calculated);
+ return;
+ }
+
+ const char* ptr = cell->classInfo()->className;
auto view = WTF::StringView(ptr, strlen(ptr));
// Fallback to .name if className is empty
@@ -3784,6 +3792,12 @@ void JSC__JSValue__forEachPropertyOrdered(JSC__JSValue JSValue0, JSC__JSGlobalOb
}
}
+bool JSC__JSValue__isConstructor(JSC__JSValue JSValue0)
+{
+ JSValue value = JSValue::decode(JSValue0);
+ return value.isConstructor();
+}
+
extern "C" JSC__JSValue JSC__JSValue__createRopeString(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
{
return JSValue::encode(JSC::jsString(globalObject, JSC::JSValue::decode(JSValue0).toString(globalObject), JSC::JSValue::decode(JSValue1).toString(globalObject)));
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 40fc43091..8a63058a5 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -17,6 +17,8 @@ const JSC = @import("bun").JSC;
const Shimmer = JSC.Shimmer;
const FFI = @import("./FFI.zig");
const NullableAllocator = @import("../../nullable_allocator.zig").NullableAllocator;
+const MutableString = bun.MutableString;
+const JestPrettyFormat = @import("../test/pretty_format.zig").JestPrettyFormat;
pub const JSObject = extern struct {
pub const shim = Shimmer("JSC", "JSObject", @This());
@@ -2442,6 +2444,18 @@ pub const JSGlobalObject = extern struct {
this.vm().throwError(this, this.createErrorInstance(fmt, args));
}
+ pub fn throwPretty(
+ this: *JSGlobalObject,
+ comptime fmt: string,
+ args: anytype,
+ ) void {
+ if (Output.enable_ansi_colors) {
+ this.vm().throwError(this, this.createErrorInstance(Output.prettyFmt(fmt, true), args));
+ } else {
+ this.vm().throwError(this, this.createErrorInstance(Output.prettyFmt(fmt, false), args));
+ }
+ }
+
pub fn queueMicrotask(
this: *JSGlobalObject,
function: JSValue,
@@ -3175,6 +3189,93 @@ pub const JSValue = enum(JSValueReprInt) {
return JSBuffer__bufferFromLength(globalObject, @intCast(i64, len));
}
+ pub fn jestSnapshotPrettyFormat(this: JSValue, out: *MutableString, globalObject: *JSGlobalObject) !void {
+ var buffered_writer = MutableString.BufferedWriter{ .context = out };
+ var writer = buffered_writer.writer();
+ const Writer = @TypeOf(writer);
+
+ const fmt_options = JestPrettyFormat.FormatOptions{
+ .enable_colors = false,
+ .add_newline = false,
+ .flush = false,
+ .quote_strings = true,
+ };
+
+ JestPrettyFormat.format(
+ .Debug,
+ globalObject,
+ @ptrCast([*]const JSValue, &this),
+ 1,
+ Writer,
+ Writer,
+ writer,
+ fmt_options,
+ );
+
+ try buffered_writer.flush();
+
+ const count: usize = brk: {
+ var total: usize = 0;
+ var remain = out.list.items;
+ while (strings.indexOfChar(remain, '`')) |i| {
+ total += 1;
+ remain = remain[i + 1 ..];
+ }
+ break :brk total;
+ };
+
+ if (count > 0) {
+ var result = try out.allocator.alloc(u8, count + out.list.items.len);
+ var input = out.list.items;
+
+ var input_i: usize = 0;
+ var result_i: usize = 0;
+ while (strings.indexOfChar(input[input_i..], '`')) |i| {
+ bun.copy(u8, result[result_i..], input[input_i .. input_i + i]);
+ result_i += i;
+ result[result_i] = '\\';
+ result[result_i + 1] = '`';
+ result_i += 2;
+ input_i += i + 1;
+ }
+
+ if (result_i != result.len) {
+ bun.copy(u8, result[result_i..], input[input_i..]);
+ }
+
+ out.deinit();
+ out.list.items = result;
+ out.list.capacity = result.len;
+ }
+ }
+
+ pub fn jestPrettyFormat(this: JSValue, out: *MutableString, globalObject: *JSGlobalObject) !void {
+ var buffered_writer = MutableString.BufferedWriter{ .context = out };
+ var writer = buffered_writer.writer();
+ const Writer = @TypeOf(writer);
+
+ const fmt_options = JSC.ZigConsoleClient.FormatOptions{
+ .enable_colors = false,
+ .add_newline = false,
+ .flush = false,
+ .ordered_properties = true,
+ .quote_strings = true,
+ };
+
+ JSC.ZigConsoleClient.format(
+ .Debug,
+ globalObject,
+ @ptrCast([*]const JSValue, &this),
+ 1,
+ Writer,
+ Writer,
+ writer,
+ fmt_options,
+ );
+
+ try buffered_writer.flush();
+ }
+
extern fn JSBuffer__bufferFromLength(*JSGlobalObject, i64) JSValue;
/// Must come from globally-allocated memory if allocator is not null
@@ -3496,6 +3597,11 @@ pub const JSValue = enum(JSValueReprInt) {
return cppFn("isClass", .{ this, global });
}
+ pub fn isConstructor(this: JSValue) bool {
+ if (!this.isCell()) return false;
+ return cppFn("isConstructor", .{this});
+ }
+
pub fn getNameProperty(this: JSValue, global: *JSGlobalObject, ret: *ZigString) void {
if (this.isEmptyOrUndefinedOrNull()) {
return;
@@ -4015,6 +4121,7 @@ pub const JSValue = enum(JSValueReprInt) {
"toWTFString",
"toZigException",
"toZigString",
+ "isConstructor",
};
};
diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig
index f9a9a3467..704dd6ae0 100644
--- a/src/bun.js/bindings/exports.zig
+++ b/src/bun.js/bindings/exports.zig
@@ -1426,6 +1426,7 @@ pub const ZigConsoleClient = struct {
JSValue.JSType.Float64Array,
JSValue.JSType.BigInt64Array,
JSValue.JSType.BigUint64Array,
+ .DataView,
=> .TypedArray,
.HeapBigInt => .BigInt,
@@ -1598,11 +1599,11 @@ pub const ZigConsoleClient = struct {
comptime Writer: type,
writer: Writer,
) !void {
- const indent = @min(this.indent, 8);
- var buf = [_]u8{' '} ** 32;
+ const indent = @min(this.indent, 32);
+ var buf = [_]u8{' '} ** 64;
var total_remain: usize = indent;
while (total_remain > 0) {
- const written = @min(16, total_remain);
+ const written = @min(32, total_remain);
try writer.writeAll(buf[0 .. written * 2]);
total_remain -|= written;
}
@@ -1752,14 +1753,14 @@ pub const ZigConsoleClient = struct {
this.addForNewLine(key.len + 1);
writer.print(
- comptime Output.prettyFmt("{}<d>:<r> ", enable_ansi_colors),
+ 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),
+ comptime Output.prettyFmt("\"{}\"<d>:<r> ", enable_ansi_colors),
.{key},
);
} else if (key.is16Bit()) {
@@ -1941,6 +1942,30 @@ pub const ZigConsoleClient = struct {
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)) {
@@ -1981,7 +2006,6 @@ pub const ZigConsoleClient = struct {
value,
null,
null,
-
Writer,
writer_,
enable_ansi_colors,
@@ -2091,13 +2115,13 @@ pub const ZigConsoleClient = struct {
},
.Private => {
if (value.as(JSC.WebCore.Response)) |response| {
- response.writeFormat(this, writer_, enable_ansi_colors) catch {};
+ response.writeFormat(ZigConsoleClient.Formatter, this, writer_, enable_ansi_colors) catch {};
return;
} else if (value.as(JSC.WebCore.Request)) |request| {
- request.writeFormat(this, writer_, enable_ansi_colors) catch {};
+ request.writeFormat(ZigConsoleClient.Formatter, this, writer_, enable_ansi_colors) catch {};
return;
} else if (value.as(JSC.WebCore.Blob)) |blob| {
- blob.writeFormat(this, writer_, enable_ansi_colors) catch {};
+ blob.writeFormat(ZigConsoleClient.Formatter, this, writer_, enable_ansi_colors) catch {};
return;
} else if (value.as(JSC.DOMFormData) != null) {
const toJSONFunction = value.get(this.globalThis, "toJSON").?;
@@ -2162,6 +2186,7 @@ pub const ZigConsoleClient = struct {
writer.writeAll("\n");
this.writeIndent(Writer, writer_) catch {};
}
+
writer.writeAll("Promise { " ++ comptime Output.prettyFmt("<r><cyan>", enable_ansi_colors));
switch (JSPromise.status(@ptrCast(*JSPromise, value.asObjectRef().?), this.globalThis.vm())) {
@@ -2179,16 +2204,36 @@ pub const ZigConsoleClient = struct {
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(5);
+ this.addForNewLine(4);
writer.writeAll(comptime Output.prettyFmt("<r><yellow>true<r>", enable_ansi_colors));
} else {
- this.addForNewLine(4);
+ this.addForNewLine(5);
writer.writeAll(comptime Output.prettyFmt("<r><yellow>false<r>", enable_ansi_colors));
}
},
.GlobalObject => {
- writer.writeAll(comptime Output.prettyFmt("<cyan>[this.globalThis]<r>", enable_ansi_colors));
+ const fmt = "[this.globalThis]";
+ 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);
@@ -2198,11 +2243,13 @@ pub const ZigConsoleClient = struct {
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.writeAll("Map {}");
+ return writer.print("{s} {{}}", .{map_name});
}
- writer.print("Map({d}) {{\n", .{length});
+ writer.print("{s}({d}) {{\n", .{ map_name, length });
{
this.indent += 1;
defer this.indent -|= 1;
@@ -2224,10 +2271,14 @@ pub const ZigConsoleClient = struct {
defer this.quote_strings = prev_quote_strings;
this.writeIndent(Writer, writer_) catch {};
+
+ const set_name = if (value.jsType() == .JSWeakSet) "WeakSet" else "Set";
+
if (length == 0) {
- return writer.writeAll("Set {}");
+ return writer.print("{s} {{}}", .{set_name});
}
- writer.print("Set({d}) {{\n", .{length});
+
+ writer.print("{s}({d}) {{\n", .{ set_name, length });
{
this.indent += 1;
defer this.indent -|= 1;
@@ -2590,9 +2641,6 @@ pub const ZigConsoleClient = struct {
} else {
if (iter.always_newline) {
this.indent -|= 1;
- }
-
- if (iter.always_newline) {
writer.writeAll("\n");
this.writeIndent(Writer, writer_) catch {};
writer.writeAll("}");
@@ -2605,11 +2653,11 @@ pub const ZigConsoleClient = struct {
},
.TypedArray => {
const arrayBuffer = value.asArrayBuffer(this.globalThis).?;
-
const slice = arrayBuffer.byteSlice();
- writer.writeAll(bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)));
+ writer.writeAll(bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)));
writer.print("({d}) [ ", .{arrayBuffer.len});
+
if (slice.len > 0) {
switch (jsType) {
.Int8Array => this.writeTypedArray(
@@ -2668,13 +2716,15 @@ pub const ZigConsoleClient = struct {
@alignCast(std.meta.alignment([]i64), std.mem.bytesAsSlice(i64, slice)),
enable_ansi_colors,
),
- .BigUint64Array => this.writeTypedArray(
- *@TypeOf(writer),
- &writer,
- u64,
- @alignCast(std.meta.alignment([]u64), std.mem.bytesAsSlice(u64, slice)),
- enable_ansi_colors,
- ),
+ .BigUint64Array => {
+ this.writeTypedArray(
+ *@TypeOf(writer),
+ &writer,
+ u64,
+ @alignCast(std.meta.alignment([]u64), std.mem.bytesAsSlice(u64, slice)),
+ enable_ansi_colors,
+ );
+ },
// Uint8Array, Uint8ClampedArray, DataView, ArrayBuffer
else => this.writeTypedArray(*@TypeOf(writer), &writer, u8, slice, enable_ansi_colors),
@@ -2687,7 +2737,7 @@ pub const ZigConsoleClient = struct {
}
}
- fn writeTypedArray(this: *ZigConsoleClient.Formatter, comptime Writer: type, writer: Writer, comptime Number: type, slice: []const Number, comptime enable_ansi_colors: bool) void {
+ fn writeTypedArray(this: *ZigConsoleClient.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
diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig
index 015c740ec..f786e3399 100644
--- a/src/bun.js/bindings/generated_classes.zig
+++ b/src/bun.js/bindings/generated_classes.zig
@@ -616,6 +616,87 @@ pub const JSExpect = struct {
}
}
};
+pub const JSExpectAny = struct {
+ const ExpectAny = Classes.ExpectAny;
+ const GetterType = fn (*ExpectAny, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+ const GetterTypeWithThisValue = fn (*ExpectAny, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+ const SetterType = fn (*ExpectAny, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+ const SetterTypeWithThisValue = fn (*ExpectAny, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+ const CallbackType = fn (*ExpectAny, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue;
+
+ /// Return the pointer to the wrapped object.
+ /// If the object does not match the type, return null.
+ pub fn fromJS(value: JSC.JSValue) ?*ExpectAny {
+ JSC.markBinding(@src());
+ return ExpectAny__fromJS(value);
+ }
+
+ extern fn ExpectAnyPrototype__constructorValueSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void;
+
+ extern fn ExpectAnyPrototype__constructorValueGetCachedValue(JSC.JSValue) JSC.JSValue;
+
+ /// `ExpectAny.constructorValue` setter
+ /// This value will be visited by the garbage collector.
+ pub fn constructorValueSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void {
+ JSC.markBinding(@src());
+ ExpectAnyPrototype__constructorValueSetCachedValue(thisValue, globalObject, value);
+ }
+
+ /// `ExpectAny.constructorValue` getter
+ /// This value will be visited by the garbage collector.
+ pub fn constructorValueGetCached(thisValue: JSC.JSValue) ?JSC.JSValue {
+ JSC.markBinding(@src());
+ const result = ExpectAnyPrototype__constructorValueGetCachedValue(thisValue);
+ if (result == .zero)
+ return null;
+
+ return result;
+ }
+
+ /// Create a new instance of ExpectAny
+ pub fn toJS(this: *ExpectAny, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
+ JSC.markBinding(@src());
+ if (comptime Environment.allow_assert) {
+ const value__ = ExpectAny__create(globalObject, this);
+ std.debug.assert(value__.as(ExpectAny).? == this); // If this fails, likely a C ABI issue.
+ return value__;
+ } else {
+ return ExpectAny__create(globalObject, this);
+ }
+ }
+
+ /// Modify the internal ptr to point to a new instance of ExpectAny.
+ pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*ExpectAny) bool {
+ JSC.markBinding(@src());
+ return ExpectAny__dangerouslySetPtr(value, ptr);
+ }
+
+ /// Detach the ptr from the thisValue
+ pub fn detachPtr(_: *ExpectAny, value: JSC.JSValue) void {
+ JSC.markBinding(@src());
+ std.debug.assert(ExpectAny__dangerouslySetPtr(value, null));
+ }
+
+ extern fn ExpectAny__fromJS(JSC.JSValue) ?*ExpectAny;
+ extern fn ExpectAny__getConstructor(*JSC.JSGlobalObject) JSC.JSValue;
+
+ extern fn ExpectAny__create(globalObject: *JSC.JSGlobalObject, ptr: ?*ExpectAny) JSC.JSValue;
+
+ extern fn ExpectAny__dangerouslySetPtr(JSC.JSValue, ?*ExpectAny) bool;
+
+ comptime {
+ if (@TypeOf(ExpectAny.finalize) != (fn (*ExpectAny) callconv(.C) void)) {
+ @compileLog("ExpectAny.finalize is not a finalizer");
+ }
+
+ if (@TypeOf(ExpectAny.call) != StaticCallbackType)
+ @compileLog("Expected ExpectAny.call to be a static callback");
+ if (!JSC.is_bindgen) {
+ @export(ExpectAny.call, .{ .name = "ExpectAnyClass__call" });
+ @export(ExpectAny.finalize, .{ .name = "ExpectAnyClass__finalize" });
+ }
+ }
+};
pub const JSFileSystemRouter = struct {
const FileSystemRouter = Classes.FileSystemRouter;
const GetterType = fn (*FileSystemRouter, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
@@ -3765,6 +3846,7 @@ comptime {
_ = JSCryptoHasher;
_ = JSDirent;
_ = JSExpect;
+ _ = JSExpectAny;
_ = JSFileSystemRouter;
_ = JSListener;
_ = JSMD4;
diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig
index d779a4ff1..3c895e219 100644
--- a/src/bun.js/bindings/generated_classes_list.zig
+++ b/src/bun.js/bindings/generated_classes_list.zig
@@ -20,6 +20,7 @@ pub const Classes = struct {
pub const TLSSocket = JSC.API.TLSSocket;
pub const Listener = JSC.API.Listener;
pub const Expect = JSC.Jest.Expect;
+ pub const ExpectAny = JSC.Jest.ExpectAny;
pub const FileSystemRouter = JSC.API.FileSystemRouter;
pub const MatchedRoute = JSC.API.MatchedRoute;
pub const Dirent = JSC.Node.Dirent;
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index 20b7fbedb..8312706e1 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -319,6 +319,7 @@ CPP_DECL bool JSC__JSValue__isBigInt32(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isBoolean(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isCallable(JSC__JSValue JSValue0, JSC__VM* arg1);
CPP_DECL bool JSC__JSValue__isClass(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1);
+CPP_DECL bool JSC__JSValue__isConstructor(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isCustomGetterSetter(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isError(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isException(JSC__JSValue JSValue0, JSC__VM* arg1);
diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig
index ce84d1e73..ee0c4fb54 100644
--- a/src/bun.js/bindings/headers.zig
+++ b/src/bun.js/bindings/headers.zig
@@ -232,6 +232,7 @@ pub extern fn JSC__JSValue__isBigInt32(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isBoolean(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isCallable(JSValue0: JSC__JSValue, arg1: *bindings.VM) bool;
pub extern fn JSC__JSValue__isClass(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject) bool;
+pub extern fn JSC__JSValue__isConstructor(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isCustomGetterSetter(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isError(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isException(JSValue0: JSC__JSValue, arg1: *bindings.VM) bool;
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index d56d0bdeb..f54dd5fb8 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -1628,7 +1628,6 @@ pub const VirtualMachine = struct {
// If there were multiple errors, it could be contained in an AggregateError.
// In that case, this function becomes recursive.
// In all other cases, we will convert it to a ZigException.
- const errors_property = ZigString.init("errors");
pub fn printErrorlikeObject(
this: *VirtualMachine,
value: JSValue,
diff --git a/src/bun.js/scripts/generate-classes.ts b/src/bun.js/scripts/generate-classes.ts
index d39ea027b..f13132f8f 100644
--- a/src/bun.js/scripts/generate-classes.ts
+++ b/src/bun.js/scripts/generate-classes.ts
@@ -292,9 +292,7 @@ export function generateHashTable(nameToUse, symbolName, typeName, obj, props =
// { "CLOSED"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, 3 } },
// };
return `
- static const HashTableValue ${nameToUse}TableValues[] = {
-${rows.join(" ,\n")}
- };
+ static const HashTableValue ${nameToUse}TableValues[] = {${rows.length > 0 ? "\n" + rows.join(" ,\n") + "\n" : ""}};
`;
}
@@ -348,7 +346,11 @@ ${renderFieldsImpl(protoSymbolName, typeName, obj, protoFields, obj.values || []
void ${proto}::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
Base::finishCreation(vm);
- reifyStaticProperties(vm, ${className(typeName)}::info(), ${proto}TableValues, *this);${specialSymbols}
+ ${
+ Object.keys(protoFields).length > 0
+ ? `reifyStaticProperties(vm, ${className(typeName)}::info(), ${proto}TableValues, *this);`
+ : ""
+ }${specialSymbols}
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
@@ -1144,9 +1146,9 @@ function generateImpl(typeName, obj) {
return [
generatePrototypeHeader(typeName),
!obj.noConstructor ? generateConstructorHeader(typeName).trim() + "\n" : null,
- Object.keys(proto).length > 0 && generatePrototype(typeName, obj).trim(),
+ generatePrototype(typeName, obj).trim(),
!obj.noConstructor ? generateConstructorImpl(typeName, obj).trim() : null,
- Object.keys(proto).length > 0 && generateClassImpl(typeName, obj).trim(),
+ generateClassImpl(typeName, obj).trim(),
]
.filter(Boolean)
.join("\n\n");
diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts
index 78848b287..9182c8cc6 100644
--- a/src/bun.js/test/jest.classes.ts
+++ b/src/bun.js/test/jest.classes.ts
@@ -2,6 +2,18 @@ import { define } from "../scripts/class-definitions";
export default [
define({
+ name: "ExpectAny",
+ construct: false,
+ noConstructor: true,
+ call: true,
+ finalize: true,
+ JSType: "0b11101110",
+ values: ["constructorValue"],
+ configurable: false,
+ klass: {},
+ proto: {},
+ }),
+ define({
name: "Expect",
construct: true,
call: true,
diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig
index 2cb86c4f7..d8d4fb246 100644
--- a/src/bun.js/test/jest.zig
+++ b/src/bun.js/test/jest.zig
@@ -1,5 +1,7 @@
const std = @import("std");
const bun = @import("bun");
+const js_parser = bun.js_parser;
+const js_ast = bun.JSAst;
const Api = @import("../../api/schema.zig").Api;
const RequestContext = @import("../../http.zig").RequestContext;
const MimeType = @import("../../http.zig").MimeType;
@@ -64,12 +66,75 @@ fn notImplementedProp(
}
pub const DiffFormatter = struct {
- received: JSValue,
- expected: JSValue,
+ received_string: ?string = null,
+ expected_string: ?string = null,
+ received: ?JSValue = null,
+ expected: ?JSValue = null,
globalObject: *JSC.JSGlobalObject,
not: bool = false,
pub fn format(this: DiffFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
+ if (this.expected_string != null and this.received_string != null) {
+ const received = this.received_string.?;
+ const expected = this.expected_string.?;
+
+ var dmp = DiffMatchPatch.default;
+ dmp.diff_timeout = 200;
+ var diffs = try dmp.diff(default_allocator, received, expected, false);
+ defer diffs.deinit(default_allocator);
+
+ const equal_fmt = "<d>{s}<r>";
+ const delete_fmt = "<red>{s}<r>";
+ const insert_fmt = "<green>{s}<r>";
+
+ try writer.writeAll("Expected: ");
+ for (diffs.items) |df| {
+ switch (df.operation) {
+ .delete => continue,
+ .insert => {
+ if (Output.enable_ansi_colors) {
+ try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text});
+ } else {
+ try writer.print(Output.prettyFmt(insert_fmt, false), .{df.text});
+ }
+ },
+ .equal => {
+ if (Output.enable_ansi_colors) {
+ try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text});
+ } else {
+ try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text});
+ }
+ },
+ }
+ }
+
+ try writer.writeAll("\nReceived: ");
+ for (diffs.items) |df| {
+ switch (df.operation) {
+ .insert => continue,
+ .delete => {
+ if (Output.enable_ansi_colors) {
+ try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text});
+ } else {
+ try writer.print(Output.prettyFmt(delete_fmt, false), .{df.text});
+ }
+ },
+ .equal => {
+ if (Output.enable_ansi_colors) {
+ try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text});
+ } else {
+ try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text});
+ }
+ },
+ }
+ }
+ return;
+ }
+
+ if (this.received == null or this.expected == null) return;
+
+ const received = this.received.?;
+ const expected = this.expected.?;
var received_buf = MutableString.init(default_allocator, 0) catch unreachable;
var expected_buf = MutableString.init(default_allocator, 0) catch unreachable;
defer {
@@ -94,7 +159,7 @@ pub const DiffFormatter = struct {
JSC.ZigConsoleClient.format(
.Debug,
this.globalObject,
- @ptrCast([*]const JSValue, &this.received),
+ @ptrCast([*]const JSValue, &received),
1,
Writer,
Writer,
@@ -131,21 +196,21 @@ pub const DiffFormatter = struct {
return;
}
- switch (this.received.determineDiffMethod(this.expected, this.globalObject)) {
+ switch (received.determineDiffMethod(expected, this.globalObject)) {
.none => {
const fmt = "Expected: <green>{any}<r>\nReceived: <red>{any}<r>";
var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = this.globalObject, .quote_strings = true };
if (Output.enable_ansi_colors) {
try writer.print(Output.prettyFmt(fmt, true), .{
- this.expected.toFmt(this.globalObject, &formatter),
- this.received.toFmt(this.globalObject, &formatter),
+ expected.toFmt(this.globalObject, &formatter),
+ received.toFmt(this.globalObject, &formatter),
});
return;
}
try writer.print(Output.prettyFmt(fmt, true), .{
- this.expected.toFmt(this.globalObject, &formatter),
- this.received.toFmt(this.globalObject, &formatter),
+ expected.toFmt(this.globalObject, &formatter),
+ received.toFmt(this.globalObject, &formatter),
});
return;
},
@@ -305,6 +370,8 @@ pub const TestRunner = struct {
/// This silences TestNotRunningError when expect() is used to halt a running test.
did_pending_test_fail: bool = false,
+ snapshots: Snapshots,
+
pub const Drainer = JSC.AnyTask.New(TestRunner, drain);
pub fn enqueue(this: *TestRunner, task: *TestRunnerTask) void {
@@ -427,6 +494,274 @@ pub const TestRunner = struct {
};
};
+pub const Snapshots = struct {
+ const file_header = "// Bun Snapshot v1, https://goo.gl/fbAQLP\n";
+ pub const ValuesHashMap = std.HashMap(usize, string, bun.IdentityContext(usize), std.hash_map.default_max_load_percentage);
+
+ allocator: std.mem.Allocator,
+ update_snapshots: bool,
+ total: usize = 0,
+ added: usize = 0,
+ passed: usize = 0,
+ failed: usize = 0,
+
+ file_buf: *std.ArrayList(u8),
+ values: *ValuesHashMap,
+ counts: *bun.StringHashMap(usize),
+ _current_file: ?File = null,
+ snapshot_dir_path: ?string = null,
+
+ const File = struct {
+ id: TestRunner.File.ID,
+ file: std.fs.File,
+ };
+
+ pub fn getOrPut(this: *Snapshots, expect: *Expect, value: JSValue, hint: string, globalObject: *JSC.JSGlobalObject) !?string {
+ switch (try this.getSnapshotFile(expect.scope.file_id)) {
+ .result => {},
+ .err => |err| {
+ return switch (err.syscall) {
+ .mkdir => error.FailedToMakeSnapshotDirectory,
+ .open => error.FailedToOpenSnapshotFile,
+ else => error.SnapshotFailed,
+ };
+ },
+ }
+
+ const snapshot_name = try expect.getSnapshotName(this.allocator, hint);
+ this.total += 1;
+
+ var count_entry = try this.counts.getOrPut(snapshot_name);
+ const counter = brk: {
+ if (count_entry.found_existing) {
+ this.allocator.free(snapshot_name);
+ count_entry.value_ptr.* += 1;
+ break :brk count_entry.value_ptr.*;
+ }
+ count_entry.value_ptr.* = 1;
+ break :brk count_entry.value_ptr.*;
+ };
+
+ const name = count_entry.key_ptr.*;
+
+ var counter_string_buf = [_]u8{0} ** 32;
+ var counter_string = try std.fmt.bufPrint(&counter_string_buf, "{d}", .{counter});
+
+ var name_with_counter = try this.allocator.alloc(u8, name.len + 1 + counter_string.len);
+ defer this.allocator.free(name_with_counter);
+ bun.copy(u8, name_with_counter[0..name.len], name);
+ name_with_counter[name.len] = ' ';
+ bun.copy(u8, name_with_counter[name.len + 1 ..], counter_string);
+
+ const name_hash = std.hash.Wyhash.hash(0, name_with_counter);
+ if (this.values.get(name_hash)) |expected| {
+ return expected;
+ }
+
+ // doesn't exist. append to file bytes and add to hashmap.
+ var pretty_value = try MutableString.init(this.allocator, 0);
+ try value.jestSnapshotPrettyFormat(&pretty_value, globalObject);
+
+ const serialized_length = "\nexports[`".len + name_with_counter.len + "`] = `".len + pretty_value.list.items.len + "`;\n".len;
+ try this.file_buf.ensureUnusedCapacity(serialized_length);
+ this.file_buf.appendSliceAssumeCapacity("\nexports[`");
+ this.file_buf.appendSliceAssumeCapacity(name_with_counter);
+ this.file_buf.appendSliceAssumeCapacity("`] = `");
+ this.file_buf.appendSliceAssumeCapacity(pretty_value.list.items);
+ this.file_buf.appendSliceAssumeCapacity("`;\n");
+
+ this.added += 1;
+ try this.values.put(name_hash, pretty_value.toOwnedSlice());
+ return null;
+ }
+
+ pub fn parseFile(this: *Snapshots) !void {
+ if (this.file_buf.items.len == 0) return;
+
+ const vm = VirtualMachine.get();
+ var opts = js_parser.Parser.Options.init(vm.bundler.options.jsx, .js);
+ var temp_log = logger.Log.init(this.allocator);
+
+ const test_file = Jest.runner.?.files.get(this._current_file.?.id);
+ const test_filename = test_file.source.path.name.filename;
+ const dir_path = test_file.source.path.name.dirWithTrailingSlash();
+
+ var snapshot_file_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ var remain: []u8 = snapshot_file_path_buf[0..bun.MAX_PATH_BYTES];
+ bun.copy(u8, remain, dir_path);
+ remain = remain[dir_path.len..];
+ bun.copy(u8, remain, "__snapshots__/");
+ remain = remain["__snapshots__/".len..];
+ bun.copy(u8, remain, test_filename);
+ remain = remain[test_filename.len..];
+ bun.copy(u8, remain, ".snap");
+ remain = remain[".snap".len..];
+ remain[0] = 0;
+ const snapshot_file_path = snapshot_file_path_buf[0 .. snapshot_file_path_buf.len - remain.len :0];
+
+ const source = logger.Source.initPathString(snapshot_file_path, this.file_buf.items);
+
+ var parser = try js_parser.Parser.init(
+ opts,
+ &temp_log,
+ &source,
+ vm.bundler.options.define,
+ this.allocator,
+ );
+
+ var parse_result = try parser.parse();
+ var ast = if (parse_result.ok) parse_result.ast else return error.ParseError;
+ defer ast.deinit();
+
+ if (ast.exports_ref == null) return;
+ const exports_ref = ast.exports_ref.?;
+
+ // TODO: when common js transform changes, keep this updated or add flag to support this version
+
+ const export_default = brk: {
+ for (ast.parts) |part| {
+ for (part.stmts) |stmt| {
+ if (stmt.data == .s_export_default and stmt.data.s_export_default.value == .expr) {
+ break :brk stmt.data.s_export_default.value.expr;
+ }
+ }
+ }
+
+ return;
+ };
+
+ if (export_default.data == .e_call) {
+ const function_call = export_default.data.e_call;
+ if (function_call.args.len == 2 and function_call.args.ptr[0].data == .e_function) {
+ const arg_function_stmts = function_call.args.ptr[0].data.e_function.func.body.stmts;
+ for (arg_function_stmts) |stmt| {
+ switch (stmt.data) {
+ .s_expr => |expr| {
+ if (expr.value.data == .e_binary and expr.value.data.e_binary.op == .bin_assign) {
+ const left = expr.value.data.e_binary.left;
+ if (left.data == .e_index and left.data.e_index.index.data == .e_string and left.data.e_index.target.data == .e_identifier) {
+ const target: js_ast.E.Identifier = left.data.e_index.target.data.e_identifier;
+ var index: *js_ast.E.String = left.data.e_index.index.data.e_string;
+ if (target.ref.eql(exports_ref) and expr.value.data.e_binary.right.data == .e_string) {
+ const key = index.slice(this.allocator);
+ var value_string = expr.value.data.e_binary.right.data.e_string;
+ const value = value_string.slice(this.allocator);
+ defer {
+ if (!index.isUTF8()) this.allocator.free(key);
+ if (!value_string.isUTF8()) this.allocator.free(value);
+ }
+ const value_clone = try this.allocator.alloc(u8, value.len);
+ bun.copy(u8, value_clone, value);
+ const name_hash = std.hash.Wyhash.hash(0, key);
+ try this.values.put(name_hash, value_clone);
+ }
+ }
+ }
+ },
+ else => {},
+ }
+ }
+ }
+ }
+ }
+
+ pub fn writeSnapshotFile(this: *Snapshots) !void {
+ if (this._current_file) |_file| {
+ var file = _file;
+ file.file.writeAll(this.file_buf.items) catch {
+ return error.FailedToWriteSnapshotFile;
+ };
+ file.file.close();
+ this.file_buf.clearAndFree();
+
+ var value_itr = this.values.valueIterator();
+ while (value_itr.next()) |value| {
+ this.allocator.free(value.*);
+ }
+ this.values.clearAndFree();
+
+ var count_key_itr = this.counts.keyIterator();
+ while (count_key_itr.next()) |key| {
+ this.allocator.free(key.*);
+ }
+ this.counts.clearAndFree();
+ }
+ }
+
+ fn getSnapshotFile(this: *Snapshots, file_id: TestRunner.File.ID) !JSC.Maybe(void) {
+ if (this._current_file == null or this._current_file.?.id != file_id) {
+ try this.writeSnapshotFile();
+
+ const test_file = Jest.runner.?.files.get(file_id);
+ const test_filename = test_file.source.path.name.filename;
+ const dir_path = test_file.source.path.name.dirWithTrailingSlash();
+
+ var snapshot_file_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ var remain: []u8 = snapshot_file_path_buf[0..bun.MAX_PATH_BYTES];
+ bun.copy(u8, remain, dir_path);
+ remain = remain[dir_path.len..];
+ bun.copy(u8, remain, "__snapshots__/");
+ remain = remain["__snapshots__/".len..];
+
+ if (this.snapshot_dir_path == null or !strings.eqlLong(dir_path, this.snapshot_dir_path.?, true)) {
+ remain[0] = 0;
+ const snapshot_dir_path = snapshot_file_path_buf[0 .. snapshot_file_path_buf.len - remain.len :0];
+ switch (JSC.Node.Syscall.mkdir(snapshot_dir_path, 0o777)) {
+ .result => this.snapshot_dir_path = dir_path,
+ .err => |err| {
+ switch (err.getErrno()) {
+ std.os.E.EXIST => this.snapshot_dir_path = dir_path,
+ else => return JSC.Maybe(void){
+ .err = err,
+ },
+ }
+ },
+ }
+ }
+
+ bun.copy(u8, remain, test_filename);
+ remain = remain[test_filename.len..];
+ bun.copy(u8, remain, ".snap");
+ remain = remain[".snap".len..];
+ remain[0] = 0;
+ const snapshot_file_path = snapshot_file_path_buf[0 .. snapshot_file_path_buf.len - remain.len :0];
+
+ var flags: JSC.Node.Mode = std.os.O.CREAT | std.os.O.RDWR;
+ if (this.update_snapshots) flags |= std.os.O.TRUNC;
+ const fd = switch (JSC.Node.Syscall.open(snapshot_file_path, flags, 0o644)) {
+ .result => |_fd| _fd,
+ .err => |err| return JSC.Maybe(void){
+ .err = err,
+ },
+ };
+
+ var file: File = .{
+ .id = file_id,
+ .file = .{ .handle = fd },
+ };
+
+ if (this.update_snapshots) {
+ try this.file_buf.appendSlice(file_header);
+ } else {
+ const length = try file.file.getEndPos();
+ if (length == 0) {
+ try this.file_buf.appendSlice(file_header);
+ } else {
+ const buf = try this.allocator.alloc(u8, length);
+ _ = try file.file.preadAll(buf, 0);
+ try this.file_buf.appendSlice(buf);
+ this.allocator.free(buf);
+ }
+ }
+
+ this._current_file = file;
+ try this.parseFile();
+ }
+
+ return JSC.Maybe(void).success;
+ }
+};
+
pub const Jest = struct {
pub var runner: ?*TestRunner = null;
@@ -465,6 +800,54 @@ pub const Jest = struct {
}
};
+pub const ExpectAny = struct {
+ pub usingnamespace JSC.Codegen.JSExpectAny;
+
+ pub fn finalize(
+ this: *ExpectAny,
+ ) callconv(.C) void {
+ VirtualMachine.get().allocator.destroy(this);
+ }
+
+ pub fn call(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ const _arguments = callFrame.arguments(1);
+ const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
+
+ if (arguments.len == 0) {
+ globalObject.throw("any() expects to be passed a constructor function.", .{});
+ return .zero;
+ }
+
+ const constructor = arguments[0];
+ constructor.ensureStillAlive();
+ if (!constructor.isConstructor()) {
+ const fmt = "<d>expect.<r>any<d>(<r>constructor<d>)<r>\n\nExpected a constructor\n";
+ globalObject.throwPretty(fmt, .{});
+ return .zero;
+ }
+
+ var any = globalObject.bunVM().allocator.create(ExpectAny) catch unreachable;
+
+ if (Jest.runner.?.pending_test == null) {
+ const err = globalObject.createErrorInstance("expect.any() must be called in a test", .{});
+ err.put(globalObject, ZigString.static("name"), ZigString.init("TestNotRunningError").toValueGC(globalObject));
+ globalObject.throwValue(err);
+ return .zero;
+ }
+
+ any.* = .{};
+ const any_js_value = any.toJS(globalObject);
+ any_js_value.ensureStillAlive();
+ JSC.Jest.ExpectAny.constructorValueSetCached(any_js_value, globalObject, constructor);
+ any_js_value.ensureStillAlive();
+
+ var vm = globalObject.bunVM();
+ vm.autoGarbageCollect();
+
+ return any_js_value;
+ }
+};
+
/// https://jestjs.io/docs/expect
// To support async tests, we need to track the test ID
pub const Expect = struct {
@@ -481,6 +864,49 @@ pub const Expect = struct {
pub const Set = std.EnumSet(Op);
};
+ pub fn getSnapshotName(this: *Expect, allocator: std.mem.Allocator, hint: string) ![]const u8 {
+ const test_name = this.scope.tests.items[this.test_id].label;
+
+ var length: usize = 0;
+ var curr_scope: ?*DescribeScope = this.scope;
+ while (curr_scope) |scope| {
+ if (scope.label.len > 0) {
+ length += scope.label.len + 1;
+ }
+ curr_scope = scope.parent;
+ }
+ length += test_name.len;
+ if (hint.len > 0) {
+ length += hint.len + 2;
+ }
+
+ var buf = try allocator.alloc(u8, length);
+
+ var index = buf.len;
+ if (hint.len > 0) {
+ index -= hint.len;
+ bun.copy(u8, buf[index..], hint);
+ index -= test_name.len + 2;
+ bun.copy(u8, buf[index..], test_name);
+ bun.copy(u8, buf[index + test_name.len ..], ": ");
+ } else {
+ index -= test_name.len;
+ bun.copy(u8, buf[index..], test_name);
+ }
+ // copy describe scopes in reverse order
+ curr_scope = this.scope;
+ while (curr_scope) |scope| {
+ if (scope.label.len > 0) {
+ index -= scope.label.len + 1;
+ bun.copy(u8, buf[index..], scope.label);
+ buf[index + scope.label.len] = ' ';
+ }
+ curr_scope = scope.parent;
+ }
+
+ return buf;
+ }
+
pub fn finalize(
this: *Expect,
) callconv(.C) void {
@@ -2080,6 +2506,215 @@ pub const Expect = struct {
return .zero;
}
+ pub fn toMatchSnapshot(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue {
+ defer this.postMatch(globalObject);
+ const thisValue = callFrame.this();
+ const _arguments = callFrame.arguments(2);
+ const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
+
+ if (this.scope.tests.items.len <= this.test_id) {
+ globalObject.throw("toMatchSnapshot() must be called in a test", .{});
+ return .zero;
+ }
+
+ active_test_expectation_counter.actual += 1;
+
+ const not = this.op.contains(.not);
+ if (not) {
+ const signature = comptime getSignature("toMatchSnapshot", "", true);
+ const fmt = signature ++ "\n\n<b>Matcher error<r>: Snapshot matchers cannot be used with <b>not<r>\n";
+ globalObject.throwPretty(fmt, .{});
+ }
+
+ var hint_string: ZigString = ZigString.Empty;
+ var property_matchers: ?JSValue = null;
+ switch (arguments.len) {
+ 0 => {},
+ 1 => {
+ if (arguments[0].isString()) {
+ arguments[0].toZigString(&hint_string, globalObject);
+ } else if (arguments[0].isObject()) {
+ property_matchers = arguments[0];
+ }
+ },
+ else => {
+ if (!arguments[0].isObject()) {
+ const signature = comptime getSignature("toMatchSnapshot", "<green>properties<r><d>, <r>hint", false);
+ const fmt = signature ++ "\n\nMatcher error: Expected <green>properties<r> must be an object\n";
+ globalObject.throwPretty(fmt, .{});
+ return .zero;
+ }
+
+ property_matchers = arguments[0];
+
+ if (arguments[1].isString()) {
+ arguments[1].toZigString(&hint_string, globalObject);
+ }
+ },
+ }
+
+ var hint = hint_string.toSlice(default_allocator);
+ defer hint.deinit();
+
+ const value: JSValue = Expect.capturedValueGetCached(thisValue) orelse {
+ globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{});
+ return .zero;
+ };
+
+ if (!value.isObject() and property_matchers != null) {
+ const signature = comptime getSignature("toMatchSnapshot", "<green>properties<r><d>, <r>hint", false);
+ const fmt = signature ++ "\n\n<b>Matcher error: <red>received<r> values must be an object when the matcher has <green>properties<r>\n";
+ globalObject.throwPretty(fmt, .{});
+ return .zero;
+ }
+
+ if (property_matchers) |_prop_matchers| {
+ var prop_matchers = _prop_matchers;
+
+ var itr = PropertyMatcherIterator{
+ .received_object = value,
+ .failed = false,
+ };
+
+ prop_matchers.forEachProperty(globalObject, &itr, PropertyMatcherIterator.forEach);
+
+ if (itr.failed) {
+ // TODO: print diff with properties from propertyMatchers
+ const signature = comptime getSignature("toMatchSnapshot", "<green>propertyMatchers<r>", false);
+ const fmt = signature ++ "\n\nExpected <green>propertyMatchers<r> to match properties from received object" ++
+ "\n\nReceived: {any}\n";
+
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject };
+ globalObject.throwPretty(fmt, .{value.toFmt(globalObject, &formatter)});
+ return .zero;
+ }
+ }
+
+ const result = Jest.runner.?.snapshots.getOrPut(this, value, hint.slice(), globalObject) catch |err| {
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject };
+ const test_file_path = Jest.runner.?.files.get(this.scope.file_id).source.path.text;
+ switch (err) {
+ error.FailedToOpenSnapshotFile => globalObject.throw("Failed to open snapshot file for test file: {s}", .{test_file_path}),
+ error.FailedToMakeSnapshotDirectory => globalObject.throw("Failed to make snapshot directory for test file: {s}", .{test_file_path}),
+ error.FailedToWriteSnapshotFile => globalObject.throw("Failed write to snapshot file: {s}", .{test_file_path}),
+ error.ParseError => globalObject.throw("Failed to parse snapshot file for: {s}", .{test_file_path}),
+ else => globalObject.throw("Failed to snapshot value: {any}", .{value.toFmt(globalObject, &formatter)}),
+ }
+ return .zero;
+ };
+
+ if (result) |saved_value| {
+ var pretty_value: MutableString = MutableString.init(default_allocator, 0) catch unreachable;
+ value.jestSnapshotPrettyFormat(&pretty_value, globalObject) catch {
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject };
+ globalObject.throw("Failed to pretty format value: {s}", .{value.toFmt(globalObject, &formatter)});
+ return .zero;
+ };
+ defer pretty_value.deinit();
+
+ if (strings.eqlLong(pretty_value.toOwnedSliceLeaky(), saved_value, true)) {
+ Jest.runner.?.snapshots.passed += 1;
+ return thisValue;
+ }
+
+ Jest.runner.?.snapshots.failed += 1;
+ const signature = comptime getSignature("toMatchSnapshot", "<green>expected<r>", false);
+ const fmt = signature ++ "\n\n{any}\n";
+ const diff_format = DiffFormatter{
+ .received_string = pretty_value.toOwnedSliceLeaky(),
+ .expected_string = saved_value,
+ .globalObject = globalObject,
+ };
+
+ globalObject.throwPretty(fmt, .{diff_format});
+ return .zero;
+ }
+
+ return thisValue;
+ }
+
+ pub const PropertyMatcherIterator = struct {
+ received_object: JSValue,
+ failed: bool,
+ i: usize = 0,
+
+ pub fn forEach(
+ globalObject: *JSGlobalObject,
+ ctx_ptr: ?*anyopaque,
+ key_: [*c]ZigString,
+ value: JSValue,
+ _: bool,
+ ) callconv(.C) void {
+ const key: ZigString = key_.?[0];
+ if (key.eqlComptime("constructor")) return;
+ if (key.eqlComptime("call")) return;
+
+ var ctx: *@This() = bun.cast(*@This(), ctx_ptr orelse return);
+ defer ctx.i += 1;
+ var received_object: JSValue = ctx.received_object;
+
+ if (received_object.get(globalObject, key.slice())) |received_value| {
+ if (JSC.Jest.ExpectAny.fromJS(value)) |_| {
+ var constructor_value = JSC.Jest.ExpectAny.constructorValueGetCached(value) orelse {
+ globalObject.throw("Internal consistency error: the expect.any(constructor value) was garbage collected but it should not have been!", .{});
+ ctx.failed = true;
+ return;
+ };
+
+ if (received_value.isCell() and received_value.isInstanceOf(globalObject, constructor_value)) {
+ received_object.put(globalObject, &key, value);
+ return;
+ }
+
+ // check primitives
+ // TODO: check the constructor for primitives by reading it from JSGlobalObject through a binding.
+ var constructor_name = ZigString.Empty;
+ constructor_value.getNameProperty(globalObject, &constructor_name);
+ if (received_value.isNumber() and constructor_name.eqlComptime("Number")) {
+ received_object.put(globalObject, &key, value);
+ return;
+ }
+ if (received_value.isBoolean() and constructor_name.eqlComptime("Boolean")) {
+ received_object.put(globalObject, &key, value);
+ return;
+ }
+ if (received_value.isString() and constructor_name.eqlComptime("String")) {
+ received_object.put(globalObject, &key, value);
+ return;
+ }
+ if (received_value.isBigInt() and constructor_name.eqlComptime("BigInt")) {
+ received_object.put(globalObject, &key, value);
+ return;
+ }
+
+ ctx.failed = true;
+ return;
+ }
+
+ if (value.isObject()) {
+ if (received_object.get(globalObject, key.slice())) |new_object| {
+ var itr = PropertyMatcherIterator{
+ .received_object = new_object,
+ .failed = false,
+ };
+ value.forEachProperty(globalObject, &itr, PropertyMatcherIterator.forEach);
+ if (itr.failed) {
+ ctx.failed = true;
+ }
+ } else {
+ ctx.failed = true;
+ }
+
+ return;
+ }
+
+ if (value.isSameValue(received_value, globalObject)) return;
+ }
+
+ ctx.failed = true;
+ }
+ };
+
pub fn toBeInstanceOf(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue {
defer this.postMatch(globalObject);
@@ -2098,7 +2733,6 @@ pub const Expect = struct {
}
active_test_expectation_counter.actual += 1;
-
const expected_value = arguments[0];
if (!expected_value.jsType().isFunction()) {
var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true };
@@ -2156,7 +2790,6 @@ pub const Expect = struct {
pub const toContainEqual = notImplementedJSCFn;
pub const toMatch = notImplementedJSCFn;
pub const toMatchObject = notImplementedJSCFn;
- pub const toMatchSnapshot = notImplementedJSCFn;
pub const toMatchInlineSnapshot = notImplementedJSCFn;
pub const toThrowErrorMatchingSnapshot = notImplementedJSCFn;
pub const toThrowErrorMatchingInlineSnapshot = notImplementedJSCFn;
@@ -2179,9 +2812,12 @@ pub const Expect = struct {
pub const getResolves = notImplementedJSCProp;
pub const getRejects = notImplementedJSCProp;
+ pub fn any(globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue {
+ return ExpectAny.call(globalObject, callFrame);
+ }
+
pub const extend = notImplementedStaticFn;
pub const anything = notImplementedStaticFn;
- pub const any = notImplementedStaticFn;
pub const arrayContaining = notImplementedStaticFn;
pub const assertions = notImplementedStaticFn;
pub const hasAssertions = notImplementedStaticFn;
@@ -2225,6 +2861,7 @@ pub const TestScope = struct {
ran: bool = false,
task: ?*TestRunnerTask = null,
skipped: bool = false,
+ snapshot_count: usize = 0,
pub const Class = NewClass(
void,
diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig
new file mode 100644
index 000000000..dd5814aca
--- /dev/null
+++ b/src/bun.js/test/pretty_format.zig
@@ -0,0 +1,1963 @@
+const std = @import("std");
+const bun = @import("bun");
+const Output = bun.Output;
+const JSC = bun.JSC;
+const JSGlobalObject = JSC.JSGlobalObject;
+const JSValue = JSC.JSValue;
+const is_bindgen: bool = std.meta.globalOption("bindgen", bool) orelse false;
+const default_allocator = bun.default_allocator;
+const CAPI = JSC.C;
+const ZigString = JSC.ZigString;
+const strings = bun.strings;
+const string = bun.string;
+const JSLexer = bun.js_lexer;
+const JSPrinter = bun.js_printer;
+const JSPrivateDataPtr = JSC.JSPrivateDataPtr;
+const JS = @import("../javascript.zig");
+const JSPromise = JSC.JSPromise;
+
+pub const EventType = enum(u8) {
+ Event,
+ MessageEvent,
+ CloseEvent,
+ ErrorEvent,
+ OpenEvent,
+ unknown = 254,
+ _,
+
+ pub const map = bun.ComptimeStringMap(EventType, .{
+ .{ EventType.Event.label(), EventType.Event },
+ .{ EventType.MessageEvent.label(), EventType.MessageEvent },
+ .{ EventType.CloseEvent.label(), EventType.CloseEvent },
+ .{ EventType.ErrorEvent.label(), EventType.ErrorEvent },
+ .{ EventType.OpenEvent.label(), EventType.OpenEvent },
+ });
+
+ pub fn label(this: EventType) string {
+ return switch (this) {
+ .Event => "event",
+ .MessageEvent => "message",
+ .CloseEvent => "close",
+ .ErrorEvent => "error",
+ .OpenEvent => "open",
+ else => "event",
+ };
+ }
+};
+
+pub const JestPrettyFormat = struct {
+ pub const Type = *anyopaque;
+ const Counter = std.AutoHashMapUnmanaged(u64, u32);
+
+ counts: Counter = .{},
+
+ 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,
+ _,
+ };
+
+ pub const FormatOptions = struct {
+ enable_colors: bool,
+ add_newline: bool,
+ flush: bool,
+ quote_strings: bool = false,
+ };
+
+ pub fn format(
+ level: MessageLevel,
+ global: *JSGlobalObject,
+ vals: [*]const JSValue,
+ len: usize,
+ comptime RawWriter: type,
+ comptime Writer: type,
+ writer: Writer,
+ options: FormatOptions,
+ ) void {
+ var fmt: JestPrettyFormat.Formatter = undefined;
+ defer {
+ if (fmt.map_node) |node| {
+ node.data = fmt.map;
+ node.data.clearRetainingCapacity();
+ node.release();
+ }
+ }
+
+ if (len == 1) {
+ fmt = JestPrettyFormat.Formatter{
+ .remaining_values = &[_]JSValue{},
+ .globalThis = global,
+ .quote_strings = options.quote_strings,
+ };
+ const tag = JestPrettyFormat.Formatter.Tag.get(vals[0], global);
+
+ var unbuffered_writer = if (comptime Writer != RawWriter)
+ 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 unreachable;
+ }
+ fmt.format(
+ tag,
+ @TypeOf(unbuffered_writer),
+ unbuffered_writer,
+ vals[0],
+ global,
+ true,
+ );
+ if (level == .Error) {
+ unbuffered_writer.writeAll(comptime Output.prettyFmt("<r>", true)) catch unreachable;
+ }
+ } 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 = JestPrettyFormat.Formatter{
+ .remaining_values = vals[0..len][1..],
+ .globalThis = global,
+ .quote_strings = options.quote_strings,
+ };
+ var tag: JestPrettyFormat.Formatter.Tag.Result = undefined;
+
+ var any = false;
+ if (options.enable_colors) {
+ if (level == .Error) {
+ writer.writeAll(comptime Output.prettyFmt("<r><red>", true)) catch unreachable;
+ }
+ while (true) {
+ if (any) {
+ _ = writer.write(" ") catch 0;
+ }
+ any = true;
+
+ tag = JestPrettyFormat.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 unreachable;
+ }
+ } else {
+ while (true) {
+ if (any) {
+ _ = writer.write(" ") catch 0;
+ }
+ any = true;
+ tag = JestPrettyFormat.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;
+ }
+
+ 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,
+ quote_strings: bool = false,
+ failed: bool = false,
+ estimated_line_length: usize = 0,
+ always_newline_scope: bool = false,
+
+ 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: *JestPrettyFormat.Formatter,
+ global: *JSGlobalObject,
+ 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.globalThis = self.global;
+ self.formatter.format(
+ Tag.get(self.value, self.global),
+ @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,
+ Set,
+ Symbol,
+ BigInt,
+
+ GlobalObject,
+ Private,
+ Promise,
+
+ JSON,
+ NativeCode,
+ ArrayBuffer,
+
+ JSX,
+ Event,
+
+ 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: Tag,
+ cell: JSValue.JSType = JSValue.JSType.Cell,
+ };
+
+ pub fn get(value: JSValue, globalThis: *JSGlobalObject) Result {
+ switch (@enumToInt(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,
+ };
+
+ // Cell is the "unknown" type
+ // if we call JSObjectGetPrivate, it can segfault
+ if (js_type == .Cell) {
+ return .{
+ .tag = .NativeCode,
+ .cell = js_type,
+ };
+ }
+
+ if (js_type == .DOMWrapper) {
+ return .{
+ .tag = .Private,
+ .cell = js_type,
+ };
+ }
+
+ if (CAPI.JSObjectGetPrivate(value.asObjectRef()) != null)
+ return .{
+ .tag = .Private,
+ .cell = js_type,
+ };
+
+ // If we check an Object has a method table and it does not
+ // it will crash
+ const callable = js_type != .Object and value.isCallable(globalThis.vm());
+
+ if (value.isClass(globalThis) and !callable) {
+ return .{
+ .tag = .Object,
+ .cell = js_type,
+ };
+ }
+
+ if (callable and js_type == .JSFunction) {
+ return .{
+ .tag = .Function,
+ .cell = js_type,
+ };
+ } else if (callable and js_type == .InternalFunction) {
+ return .{
+ .tag = .Object,
+ .cell = js_type,
+ };
+ }
+
+ if (js_type == .PureForwardingProxy) {
+ return Tag.get(
+ JSC.JSValue.c(JSC.C.JSObjectGetProxyTarget(value.asObjectRef())),
+ globalThis,
+ );
+ }
+
+ // 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) {
+ JSValue.JSType.ErrorInstance => .Error,
+ JSValue.JSType.NumberObject => .Double,
+ JSValue.JSType.DerivedArray, JSValue.JSType.Array => .Array,
+ JSValue.JSType.DerivedStringObject, JSValue.JSType.String, JSValue.JSType.StringObject => .String,
+ JSValue.JSType.RegExpObject => .String,
+ JSValue.JSType.Symbol => .Symbol,
+ JSValue.JSType.BooleanObject => .Boolean,
+ JSValue.JSType.JSFunction => .Function,
+ JSValue.JSType.JSWeakMap, JSValue.JSType.JSMap => .Map,
+ JSValue.JSType.JSWeakSet, JSValue.JSType.JSSet => .Set,
+ JSValue.JSType.JSDate => .JSON,
+ JSValue.JSType.JSPromise => .Promise,
+ JSValue.JSType.Object,
+ JSValue.JSType.FinalObject,
+ .ModuleNamespaceObject,
+ .GlobalObject,
+ => .Object,
+
+ .ArrayBuffer,
+ JSValue.JSType.Int8Array,
+ JSValue.JSType.Uint8Array,
+ JSValue.JSType.Uint8ClampedArray,
+ JSValue.JSType.Int16Array,
+ JSValue.JSType.Uint16Array,
+ JSValue.JSType.Int32Array,
+ JSValue.JSType.Uint32Array,
+ JSValue.JSType.Float32Array,
+ JSValue.JSType.Float64Array,
+ JSValue.JSType.BigInt64Array,
+ JSValue.JSType.BigUint64Array,
+ .DataView,
+ => .TypedArray,
+
+ .HeapBigInt => .BigInt,
+
+ // None of these should ever exist here
+ // But we're going to check anyway
+ .GetterSetter,
+ .CustomGetterSetter,
+ .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,
+
+ else => .JSON,
+ },
+ .cell = js_type,
+ };
+ }
+ };
+
+ const CellType = CAPI.CellType;
+ threadlocal var name_buf: [512]u8 = undefined;
+
+ fn writeWithFormatting(
+ this: *JestPrettyFormat.Formatter,
+ comptime Writer: type,
+ writer_: Writer,
+ comptime Slice: type,
+ slice_: Slice,
+ globalThis: *JSGlobalObject,
+ comptime enable_ansi_colors: bool,
+ ) void {
+ var writer = WrappedWriter(Writer){ .ctx = writer_ };
+ var slice = slice_;
+ var i: u32 = 0;
+ var len: u32 = @truncate(u32, slice.len);
+ var any_non_ascii = false;
+ while (i < len) : (i += 1) {
+ switch (slice[i]) {
+ '%' => {
+ i += 1;
+ if (i >= len)
+ break;
+
+ const token = switch (slice[i]) {
+ 's' => Tag.String,
+ 'f' => Tag.Double,
+ 'o' => Tag.Undefined,
+ 'O' => Tag.Object,
+ 'd', 'i' => Tag.Integer,
+ else => continue,
+ };
+
+ // Flush everything up to the %
+ const end = slice[0 .. i - 1];
+ if (!any_non_ascii)
+ writer.writeAll(end)
+ else
+ writer.writeAll(end);
+ any_non_ascii = false;
+ slice = slice[@min(slice.len, i + 1)..];
+ i = 0;
+ len = @truncate(u32, slice.len);
+ const next_value = this.remaining_values[0];
+ this.remaining_values = this.remaining_values[1..];
+ switch (token) {
+ Tag.String => this.printAs(Tag.String, Writer, writer_, next_value, next_value.jsType(), enable_ansi_colors),
+ Tag.Double => this.printAs(Tag.Double, Writer, writer_, next_value, next_value.jsType(), enable_ansi_colors),
+ Tag.Object => this.printAs(Tag.Object, Writer, writer_, next_value, next_value.jsType(), enable_ansi_colors),
+ Tag.Integer => this.printAs(Tag.Integer, Writer, writer_, next_value, next_value.jsType(), enable_ansi_colors),
+
+ // undefined is overloaded to mean the '%o" field
+ Tag.Undefined => this.format(Tag.get(next_value, globalThis), Writer, writer_, next_value, globalThis, enable_ansi_colors),
+
+ else => unreachable,
+ }
+ if (this.remaining_values.len == 0) break;
+ },
+ '\\' => {
+ i += 1;
+ if (i >= len)
+ break;
+ if (slice[i] == '%') i += 2;
+ },
+ 128...255 => {
+ any_non_ascii = true;
+ },
+ 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 = undefined,
+
+ pub fn print(self: *@This(), comptime fmt: string, args: anytype) void {
+ self.ctx.print(fmt, 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 {
+ strings.formatUTF16Type([]const u16, input, self.ctx) catch {
+ self.failed = true;
+ };
+ }
+ };
+ }
+
+ pub fn writeIndent(
+ this: *JestPrettyFormat.Formatter,
+ comptime Writer: type,
+ writer: Writer,
+ ) !void {
+ const indent = @min(this.indent, 32);
+ var buf = [_]u8{' '} ** 64;
+ var total_remain: usize = indent;
+ while (total_remain > 0) {
+ const written = @min(32, total_remain);
+ try writer.writeAll(buf[0 .. written * 2]);
+ total_remain -|= written;
+ }
+ }
+
+ pub fn printComma(this: *JestPrettyFormat.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) type {
+ return struct {
+ formatter: *JestPrettyFormat.Formatter,
+ writer: Writer,
+ 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);
+ const key = JSC.JSObject.getIndex(nextValue, globalObject, 0);
+ const value = JSC.JSObject.getIndex(nextValue, globalObject, 1);
+ this.formatter.writeIndent(Writer, this.writer) catch unreachable;
+ const key_tag = Tag.get(key, globalObject);
+
+ this.formatter.format(
+ key_tag,
+ Writer,
+ this.writer,
+ key,
+ this.formatter.globalThis,
+ enable_ansi_colors,
+ );
+ this.writer.writeAll(" => ") catch unreachable;
+ const value_tag = Tag.get(value, globalObject);
+ this.formatter.format(
+ value_tag,
+ Writer,
+ this.writer,
+ value,
+ this.formatter.globalThis,
+ enable_ansi_colors,
+ );
+ this.formatter.printComma(Writer, this.writer, enable_ansi_colors) catch unreachable;
+ this.writer.writeAll("\n") catch unreachable;
+ }
+ };
+ }
+
+ pub fn SetIterator(comptime Writer: type, comptime enable_ansi_colors: bool) type {
+ return struct {
+ formatter: *JestPrettyFormat.Formatter,
+ writer: Writer,
+ 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);
+ this.formatter.writeIndent(Writer, this.writer) catch {};
+ const key_tag = Tag.get(nextValue, globalObject);
+ this.formatter.format(
+ key_tag,
+ Writer,
+ this.writer,
+ nextValue,
+ this.formatter.globalThis,
+ enable_ansi_colors,
+ );
+
+ 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: *JestPrettyFormat.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 writer = WrappedWriter(Writer){
+ .ctx = this.writer,
+ .failed = false,
+ };
+ var name_str = ZigString.init("");
+
+ value.getNameProperty(globalThis, &name_str);
+ if (name_str.len > 0 and !strings.eqlComptime(name_str.slice(), "Object")) {
+ writer.print("{} ", .{name_str});
+ } else {
+ value.getPrototype(globalThis).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;
+
+ if (this.formatter.indent == 0) this.writer.writeAll("\n") catch {};
+ var classname = ZigString.Empty;
+ value.getClassName(globalThis, &classname);
+ if (!strings.eqlComptime(classname.slice(), "Object")) {
+ this.writer.print("{} ", .{classname}) catch {};
+ }
+
+ this.writer.writeAll("{\n") catch {};
+ this.formatter.indent += 1;
+ this.formatter.writeIndent(Writer, this.writer) catch {};
+ }
+
+ pub fn forEach(
+ globalThis: *JSGlobalObject,
+ ctx_ptr: ?*anyopaque,
+ key_: [*c]ZigString,
+ value: JSValue,
+ is_symbol: bool,
+ ) callconv(.C) void {
+ const key = key_.?[0];
+ if (key.eqlComptime("constructor")) return;
+ if (key.eqlComptime("call")) return;
+
+ 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: *JestPrettyFormat.Formatter,
+ comptime Format: JestPrettyFormat.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;
+ }
+
+ var entry = this.map.getOrPut(@enumToInt(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(@enumToInt(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 => {
+ var str = ZigString.init("");
+ value.toZigString(&str, this.globalThis);
+ this.addForNewLine(str.len);
+
+ if (value.jsType() == .StringObject or value.jsType() == .DerivedStringObject) {
+ if (str.len == 0) {
+ writer.writeAll("String {}");
+ return;
+ }
+ if (this.indent == 0 and str.len > 0) {
+ writer.writeAll("\n");
+ }
+ writer.writeAll("String {\n");
+ this.indent += 1;
+ defer this.indent -|= 1;
+ this.resetLine();
+ this.writeIndent(Writer, writer_) catch unreachable;
+ const length = str.len;
+ for (str.slice(), 0..) |c, i| {
+ writer.print("\"{d}\": \"{c}\",\n", .{ i, c });
+ if (i != length - 1) this.writeIndent(Writer, writer_) catch unreachable;
+ }
+ this.resetLine();
+ writer.writeAll("}\n");
+ return;
+ }
+
+ if (this.quote_strings and jsType != .RegExpObject) {
+ if (str.len == 0) {
+ 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.is16Bit()) {
+ this.printAs(.JSON, Writer, writer_, value, .StringObject, enable_ansi_colors);
+ return;
+ }
+
+ var has_newline = false;
+ if (strings.indexOfAny(str.slice(), "\n\r")) |_| {
+ has_newline = true;
+ writer.writeAll("\n");
+ }
+
+ writer.writeAll("\"");
+ var remaining = str.slice();
+ while (strings.indexOfAny(remaining, "\\\r")) |i| {
+ switch (remaining[i]) {
+ '\\' => {
+ writer.print("{s}\\", .{remaining[0 .. i + 1]});
+ remaining = remaining[i + 1 ..];
+ },
+ '\r' => {
+ if (i + 1 < remaining.len and remaining[i + 1] == '\n') {
+ writer.print("{s}", .{remaining[0..i]});
+ } else {
+ writer.print("{s}\n", .{remaining[0..i]});
+ }
+ remaining = remaining[i + 1 ..];
+ },
+ else => unreachable,
+ }
+ }
+
+ writer.writeAll(remaining);
+ writer.writeAll("\"");
+ if (has_newline) writer.writeAll("\n");
+ return;
+ }
+
+ if (jsType == .RegExpObject and enable_ansi_colors) {
+ writer.print(comptime Output.prettyFmt("<r><red>", enable_ansi_colors), .{});
+ }
+
+ if (str.is16Bit()) {
+ // streaming print
+ writer.print("{s}", .{str});
+ } else if (strings.isAllASCII(str.slice())) {
+ // fast path
+ writer.writeAll(str.slice());
+ } else if (str.len > 0) {
+ // slow path
+ var buf = strings.allocateLatin1IntoUTF8(bun.default_allocator, []const u8, str.slice()) 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.toInt64();
+ 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(@intCast(usize, i)) + @as(usize, @boolToInt(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 => {
+ var 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()) {
+ this.printAs(.Object, Writer, writer_, value, .Object, enable_ansi_colors);
+ 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 {
+ this.addForNewLine(std.fmt.count("{d}", .{num}));
+ writer.print(comptime Output.prettyFmt("<r><yellow>{d}<r>", enable_ansi_colors), .{num});
+ }
+ },
+ .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), .{});
+ },
+ .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 => {
+ var classname = ZigString.Empty;
+ value.getClassName(this.globalThis, &classname);
+ var message_string = ZigString.Empty;
+ if (value.get(this.globalThis, "message")) |message_prop| {
+ message_prop.toZigString(&message_string, this.globalThis);
+ }
+ if (message_string.len == 0) {
+ writer.print("[{s}]", .{classname});
+ return;
+ }
+ writer.print("[{s}: {s}]", .{ classname, message_string });
+ return;
+ },
+ .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("[class]", enable_ansi_colors), .{});
+ } else {
+ writer.print(comptime Output.prettyFmt("[class <cyan>{}<r>]", enable_ansi_colors), .{printable});
+ }
+ },
+ .Function => {
+ writer.writeAll("[Function]");
+ },
+ .Array => {
+ const len = @truncate(u32, value.getLengthOfArray(this.globalThis));
+ if (len == 0) {
+ writer.writeAll("[]");
+ this.addForNewLine(2);
+ return;
+ }
+
+ if (this.indent == 0) {
+ writer.writeAll("\n");
+ }
+
+ var was_good_time = this.always_newline_scope;
+ {
+ this.indent += 1;
+ defer this.indent -|= 1;
+
+ this.addForNewLine(2);
+
+ var ref = value.asObjectRef();
+
+ var prev_quote_strings = this.quote_strings;
+ this.quote_strings = true;
+ defer this.quote_strings = prev_quote_strings;
+
+ {
+ const element = JSValue.fromRef(CAPI.JSObjectGetPropertyAtIndex(this.globalThis, ref, 0, null));
+ const tag = Tag.get(element, this.globalThis);
+
+ was_good_time = was_good_time or !tag.tag.isPrimitive() or this.goodTimeForANewLine();
+
+ this.resetLine();
+ writer.writeAll("[");
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch unreachable;
+ this.addForNewLine(1);
+
+ 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 (len == 1) {
+ this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
+ }
+ }
+
+ var i: u32 = 1;
+ while (i < len) : (i += 1) {
+ this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
+
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch unreachable;
+
+ const element = JSValue.fromRef(CAPI.JSObjectGetPropertyAtIndex(this.globalThis, ref, i, null));
+ const tag = Tag.get(element, this.globalThis);
+
+ 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 (i == len - 1) {
+ this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
+ }
+ }
+ }
+
+ this.resetLine();
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("]");
+ if (this.indent == 0) {
+ writer.writeAll("\n");
+ }
+ this.resetLine();
+ this.addForNewLine(1);
+ },
+ .Private => {
+ if (value.as(JSC.WebCore.Response)) |response| {
+ response.writeFormat(Formatter, this, writer_, enable_ansi_colors) catch {};
+ return;
+ } else if (value.as(JSC.WebCore.Request)) |request| {
+ request.writeFormat(Formatter, this, writer_, enable_ansi_colors) catch {};
+ return;
+ } else if (value.as(JSC.WebCore.Blob)) |blob| {
+ blob.writeFormat(Formatter, this, writer_, enable_ansi_colors) catch {};
+ return;
+ } else if (value.as(JSC.DOMFormData) != null) {
+ const toJSONFunction = value.get(this.globalThis, "toJSON").?;
+
+ this.addForNewLine("FormData (entries) ".len);
+ writer.writeAll(comptime Output.prettyFmt("<r><blue>FormData<r> <d>(entries)<r> ", enable_ansi_colors));
+
+ return this.printAs(
+ .Object,
+ Writer,
+ writer_,
+ toJSONFunction.callWithThis(this.globalThis, value, &.{}),
+ .Object,
+ enable_ansi_colors,
+ );
+ } else if (value.as(JSC.API.Bun.Timer.TimerObject)) |timer| {
+ this.addForNewLine("Timeout(# ) ".len + bun.fmt.fastDigitCount(@intCast(u64, @max(timer.id, 0))));
+ if (timer.kind == .setInterval) {
+ this.addForNewLine("repeats ".len + bun.fmt.fastDigitCount(@intCast(u64, @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 (jsType != .DOMWrapper) {
+ if (CAPI.JSObjectGetPrivate(value.asRef())) |private_data_ptr| {
+ const priv_data = JSPrivateDataPtr.from(private_data_ptr);
+ switch (priv_data.tag()) {
+ .BuildError => {
+ const build_error = priv_data.as(JS.BuildError);
+ build_error.msg.writeFormat(writer_, enable_ansi_colors) catch {};
+ return;
+ },
+ .ResolveError => {
+ const resolve_error = priv_data.as(JS.ResolveError);
+ resolve_error.msg.writeFormat(writer_, enable_ansi_colors) catch {};
+ return;
+ },
+ 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);
+ },
+ .NativeCode => {
+ this.addForNewLine("[native code]".len);
+ writer.writeAll("[native code]");
+ },
+ .Promise => {
+ if (this.goodTimeForANewLine()) {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ }
+ writer.writeAll("Promise {}");
+ },
+ .Boolean => {
+ if (value.isCell()) {
+ this.printAs(.Object, Writer, writer_, value, .Object, enable_ansi_colors);
+ 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 = "[this.globalThis]";
+ 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});
+ }
+
+ writer.print("\n{s} {{\n", .{map_name});
+ {
+ this.indent += 1;
+ defer this.indent -|= 1;
+ var iter = MapIterator(Writer, enable_ansi_colors){
+ .formatter = this,
+ .writer = writer_,
+ };
+ value.forEach(this.globalThis, &iter, @TypeOf(iter).forEach);
+ }
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("}");
+ writer.writeAll("\n");
+ },
+ .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;
+
+ this.writeIndent(Writer, writer_) catch {};
+
+ const set_name = if (value.jsType() == .JSWeakSet) "WeakSet" else "Set";
+
+ if (length == 0) {
+ return writer.print("{s} {{}}", .{set_name});
+ }
+
+ writer.print("\n{s} {{\n", .{set_name});
+ {
+ this.indent += 1;
+ defer this.indent -|= 1;
+ var iter = SetIterator(Writer, enable_ansi_colors){
+ .formatter = this,
+ .writer = writer_,
+ };
+ value.forEach(this.globalThis, &iter, @TypeOf(iter).forEach);
+ }
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("}");
+ writer.writeAll("\n");
+ },
+ .JSON => {
+ var str = ZigString.init("");
+ value.jsonStringify(this.globalThis, this.indent, &str);
+ this.addForNewLine(str.len);
+ 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 (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 = EventType.map.getWithEql(value.get(this.globalThis, "type").?.getZigString(this.globalThis), ZigString.eqlComptime) orelse EventType.unknown;
+ if (event_type != .MessageEvent and event_type != .ErrorEvent) {
+ 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;
+ defer this.indent -|= 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;
+
+ writer.print(
+ comptime Output.prettyFmt("<r>type: <green>\"{s}\"<r><d>,<r>\n", enable_ansi_colors),
+ .{
+ event_type.label(),
+ },
+ );
+ this.writeIndent(Writer, writer_) catch unreachable;
+
+ switch (event_type) {
+ .MessageEvent => {
+ writer.print(
+ comptime Output.prettyFmt("<r><blue>data<d>:<r> ", enable_ansi_colors),
+ .{},
+ );
+ const data = value.get(this.globalThis, "data").?;
+ const tag = Tag.get(data, this.globalThis);
+ if (tag.cell.isStringLike()) {
+ this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
+ } else {
+ this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
+ }
+ },
+ .ErrorEvent => {
+ writer.print(
+ comptime Output.prettyFmt("<r><blue>error<d>:<r>\n", enable_ansi_colors),
+ .{},
+ );
+
+ const data = value.get(this.globalThis, "error").?;
+ const tag = Tag.get(data, this.globalThis);
+ this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
+ },
+ else => unreachable,
+ }
+ writer.writeAll("\n");
+ }
+
+ 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.get(type_value, this.globalThis);
+
+ 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.get(key_value, this.globalThis), 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.asObjectRef());
+ defer props_iter.deinit();
+
+ var 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, @boolToInt(children_prop != null));
+
+ while (props_iter.next()) |prop| {
+ if (prop.eqlComptime("children"))
+ continue;
+
+ var property_value = props_iter.value;
+ const tag = Tag.get(property_value, this.globalThis);
+
+ if (tag.cell.isHidden()) continue;
+
+ if (needs_space) writer.writeAll(" ");
+ 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 (
+ // 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.writeAll(" ");
+ }
+ }
+ }
+
+ 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) {
+ print_children: {
+ switch (tag.tag) {
+ .String => {
+ var 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.getLengthOfArray(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, @intCast(u32, j));
+ this.format(Tag.get(child, this.globalThis), 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 => {
+ 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.always_newline_scope or this.goodTimeForANewLine(),
+ .parent = value,
+ };
+
+ value.forEachPropertyOrdered(this.globalThis, &iter, Iterator.forEach);
+
+ if (iter.i == 0) {
+ var object_name = ZigString.Empty;
+ value.getClassName(this.globalThis, &object_name);
+
+ if (!strings.eqlComptime(object_name.slice(), "Object")) {
+ if (value.as(JSC.Jest.ExpectAny)) |_| {
+ var constructor = JSC.Jest.ExpectAny.constructorValueGetCached(value) orelse unreachable;
+ var constructor_name = ZigString.Empty;
+ constructor.getNameProperty(this.globalThis, &constructor_name);
+ writer.print("Any<{s}>", .{constructor_name});
+ } else {
+ writer.print("{s} {{}}", .{object_name});
+ }
+ } else {
+ // don't write "Object"
+ writer.writeAll("{}");
+ }
+ } else {
+ this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
+
+ if (iter.always_newline) {
+ this.indent -|= 1;
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("}");
+ this.estimated_line_length += 1;
+ } else {
+ this.estimated_line_length += 2;
+ writer.writeAll(" }");
+ }
+
+ if (this.indent == 0) {
+ writer.writeAll("\n");
+ }
+ }
+ },
+ .TypedArray => {
+ const arrayBuffer = value.asArrayBuffer(this.globalThis).?;
+ const slice = arrayBuffer.byteSlice();
+
+ if (this.indent == 0 and slice.len > 0) {
+ writer.writeAll("\n");
+ }
+
+ if (jsType == .Uint8Array) {
+ var buffer_name = ZigString.Empty;
+ value.getClassName(this.globalThis, &buffer_name);
+ if (strings.eqlComptime(buffer_name.slice(), "Buffer")) {
+ // special formatting for 'Buffer' snapshots only
+ if (slice.len == 0 and this.indent == 0) writer.writeAll("\n");
+ writer.writeAll("{\n");
+ this.indent += 1;
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("\"data\": [");
+
+ this.indent += 1;
+ for (slice) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ this.indent -|= 1;
+
+ if (slice.len > 0) {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("],\n");
+ } else {
+ writer.writeAll("],\n");
+ }
+
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("\"type\": \"Buffer\",\n");
+
+ this.indent -|= 1;
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("}");
+
+ if (this.indent == 0) {
+ writer.writeAll("\n");
+ }
+
+ return;
+ }
+ writer.writeAll(bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)));
+ } else {
+ writer.writeAll(bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)));
+ }
+
+ writer.writeAll(" [");
+
+ if (slice.len > 0) {
+ switch (jsType) {
+ .Int8Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]i8), std.mem.bytesAsSlice(i8, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Int16Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]i16), std.mem.bytesAsSlice(i16, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Uint16Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]u16), std.mem.bytesAsSlice(u16, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Int32Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]i32), std.mem.bytesAsSlice(i32, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Uint32Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]u32), std.mem.bytesAsSlice(u32, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Float32Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]f32), std.mem.bytesAsSlice(f32, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Float64Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]f64), std.mem.bytesAsSlice(f64, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .BigInt64Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]i64), std.mem.bytesAsSlice(i64, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .BigUint64Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]u64), std.mem.bytesAsSlice(u64, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+
+ // Uint8Array, Uint8ClampedArray, DataView, ArrayBuffer
+ else => {
+ var slice_with_type = @alignCast(std.meta.alignment([]u8), std.mem.bytesAsSlice(u8, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ }
+ }
+
+ if (slice.len > 0) {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("]");
+ if (this.indent == 0) {
+ writer.writeAll("\n");
+ }
+ } else {
+ writer.writeAll("]");
+ }
+ },
+ else => {},
+ }
+ }
+
+ pub fn format(this: *JestPrettyFormat.Formatter, result: Tag.Result, comptime Writer: type, writer: Writer, value: JSValue, globalThis: *JSGlobalObject, comptime enable_ansi_colors: bool) void {
+ if (comptime is_bindgen) {
+ return;
+ }
+ var prevGlobalThis = this.globalThis;
+ defer this.globalThis = prevGlobalThis;
+ this.globalThis = globalThis;
+
+ // This looks incredibly redundant. We make the JestPrettyFormat.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
+ return 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),
+ .Double => this.printAs(.Double, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Integer => this.printAs(.Integer, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Null => this.printAs(.Null, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Boolean => this.printAs(.Boolean, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Array => this.printAs(.Array, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Object => this.printAs(.Object, Writer, writer, value, result.cell, enable_ansi_colors),
+ .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),
+ .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),
+ .BigInt => this.printAs(.BigInt, Writer, writer, value, result.cell, enable_ansi_colors),
+ .GlobalObject => this.printAs(.GlobalObject, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Private => this.printAs(.Private, Writer, writer, value, result.cell, enable_ansi_colors),
+ .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),
+ .JSX => this.printAs(.JSX, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Event => this.printAs(.Event, Writer, writer, value, result.cell, enable_ansi_colors),
+ };
+ }
+ };
+};
diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig
index 5a41f5f8c..b670a3aaa 100644
--- a/src/bun.js/webcore/blob.zig
+++ b/src/bun.js/webcore/blob.zig
@@ -307,7 +307,7 @@ pub const Blob = struct {
},
);
}
- pub fn writeFormat(this: *const Blob, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
+ pub fn writeFormat(this: *const Blob, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);
if (this.isDetached()) {
diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig
index d4bd490c1..509d02989 100644
--- a/src/bun.js/webcore/body.zig
+++ b/src/bun.js/webcore/body.zig
@@ -75,7 +75,7 @@ pub const Body = struct {
};
}
- pub fn writeFormat(this: *const Body, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
+ pub fn writeFormat(this: *const Body, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);
try formatter.writeIndent(Writer, writer);
@@ -98,7 +98,7 @@ pub const Body = struct {
try formatter.printComma(Writer, writer, enable_ansi_colors);
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
- try this.value.Blob.writeFormat(formatter, writer, enable_ansi_colors);
+ try this.value.Blob.writeFormat(Formatter, formatter, writer, enable_ansi_colors);
} else if (this.value == .InternalBlob) {
try formatter.printComma(Writer, writer, enable_ansi_colors);
try writer.writeAll("\n");
diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig
index e1fa4a9bb..6581649fc 100644
--- a/src/bun.js/webcore/request.zig
+++ b/src/bun.js/webcore/request.zig
@@ -116,7 +116,7 @@ pub const Request = struct {
};
}
- pub fn writeFormat(this: *Request, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
+ pub fn writeFormat(this: *Request, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);
try writer.print("Request ({}) {{\n", .{bun.fmt.size(this.body.slice().len)});
{
@@ -139,12 +139,12 @@ pub const Request = struct {
if (this.body == .Blob) {
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
- try this.body.Blob.writeFormat(formatter, writer, enable_ansi_colors);
+ try this.body.Blob.writeFormat(Formatter, formatter, writer, enable_ansi_colors);
} else if (this.body == .InternalBlob) {
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
if (this.body.size() == 0) {
- try Blob.initEmpty(undefined).writeFormat(formatter, writer, enable_ansi_colors);
+ try Blob.initEmpty(undefined).writeFormat(Formatter, formatter, writer, enable_ansi_colors);
} else {
try Blob.writeFormatForSize(this.body.size(), writer, enable_ansi_colors);
}
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index 06323d790..f36af88cf 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -113,7 +113,7 @@ pub const Response = struct {
pub const Props = struct {};
- pub fn writeFormat(this: *const Response, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
+ pub fn writeFormat(this: *const Response, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);
try writer.print("Response ({}) {{\n", .{bun.fmt.size(this.body.len())});
{
@@ -145,7 +145,7 @@ pub const Response = struct {
formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable;
try writer.writeAll("\n");
formatter.resetLine();
- try this.body.writeFormat(formatter, writer, enable_ansi_colors);
+ try this.body.writeFormat(Formatter, formatter, writer, enable_ansi_colors);
}
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);