aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js')
-rw-r--r--src/bun.js/bindings/OnigurumaRegExp.cpp64
-rw-r--r--src/bun.js/bindings/OnigurumaRegExp.h64
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp31
-rw-r--r--src/bun.js/bindings/bindings.cpp540
-rw-r--r--src/bun.js/bindings/bindings.zig6
-rw-r--r--src/bun.js/bindings/headers-handwritten.h2
-rw-r--r--src/bun.js/bindings/headers.h3
-rw-r--r--src/bun.js/bindings/headers.zig1
-rw-r--r--src/bun.js/test/jest.zig41
9 files changed, 685 insertions, 67 deletions
diff --git a/src/bun.js/bindings/OnigurumaRegExp.cpp b/src/bun.js/bindings/OnigurumaRegExp.cpp
index 496a3de67..c9dedb09c 100644
--- a/src/bun.js/bindings/OnigurumaRegExp.cpp
+++ b/src/bun.js/bindings/OnigurumaRegExp.cpp
@@ -376,70 +376,6 @@ private:
void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
};
-class OnigurumaRegEx final : public JSC::JSDestructibleObject {
-public:
- using Base = JSC::JSDestructibleObject;
-
- static OnigurumaRegEx* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
- {
- OnigurumaRegEx* ptr = new (NotNull, JSC::allocateCell<OnigurumaRegEx>(vm)) OnigurumaRegEx(vm, globalObject, structure);
- ptr->finishCreation(vm);
- return ptr;
- }
-
- static OnigurumaRegEx* create(JSC::JSGlobalObject* globalObject, WTF::String&& pattern, WTF::String&& flags)
- {
- auto* structure = reinterpret_cast<Zig::GlobalObject*>(globalObject)->OnigurumaRegExpStructure();
- auto* object = create(globalObject->vm(), globalObject, structure);
- object->m_flagsString = WTFMove(flags);
- object->m_patternString = WTFMove(pattern);
-
- return object;
- }
-
- 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<OnigurumaRegEx, UseCustomHeapCellType::No>(
- vm,
- [](auto& spaces) { return spaces.m_clientSubspaceForOnigurumaRegExp.get(); },
- [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForOnigurumaRegExp = WTFMove(space); },
- [](auto& spaces) { return spaces.m_subspaceForOnigurumaRegExp.get(); },
- [](auto& spaces, auto&& space) { spaces.m_subspaceForOnigurumaRegExp = WTFMove(space); });
- }
-
- static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
- {
- return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(RegExpObjectType, StructureFlags), info());
- }
-
- // static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
-
- const WTF::String& flagsString() const { return m_flagsString; }
- void setFlagsString(const WTF::String& flagsString) { m_flagsString = flagsString; }
- const WTF::String& patternString() const { return m_patternString; }
- void setPatternString(const WTF::String& patternString) { m_patternString = patternString; }
-
- int32_t m_lastIndex = 0;
-
-private:
- OnigurumaRegEx(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
- : Base(vm, structure)
- {
- }
-
- void finishCreation(JSC::VM&)
- {
- Base::finishCreation(vm());
- }
-
- WTF::String m_patternString = {};
- WTF::String m_flagsString = {};
-};
-
const ClassInfo OnigurumaRegExpConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(OnigurumaRegExpConstructor) };
const ClassInfo OnigurumaRegExpPrototype::s_info = { "Object"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(OnigurumaRegExpPrototype) };
const ClassInfo OnigurumaRegEx::s_info = { "RegExp"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(OnigurumaRegEx) };
diff --git a/src/bun.js/bindings/OnigurumaRegExp.h b/src/bun.js/bindings/OnigurumaRegExp.h
index 058492844..8ddb65a0c 100644
--- a/src/bun.js/bindings/OnigurumaRegExp.h
+++ b/src/bun.js/bindings/OnigurumaRegExp.h
@@ -12,6 +12,70 @@ namespace Zig {
using namespace JSC;
using namespace WebCore;
+class OnigurumaRegEx final : public JSC::JSDestructibleObject {
+public:
+ using Base = JSC::JSDestructibleObject;
+
+ static OnigurumaRegEx* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
+ {
+ OnigurumaRegEx* ptr = new (NotNull, JSC::allocateCell<OnigurumaRegEx>(vm)) OnigurumaRegEx(vm, globalObject, structure);
+ ptr->finishCreation(vm);
+ return ptr;
+ }
+
+ static OnigurumaRegEx* create(JSC::JSGlobalObject* globalObject, WTF::String&& pattern, WTF::String&& flags)
+ {
+ auto* structure = reinterpret_cast<Zig::GlobalObject*>(globalObject)->OnigurumaRegExpStructure();
+ auto* object = create(globalObject->vm(), globalObject, structure);
+ object->m_flagsString = WTFMove(flags);
+ object->m_patternString = WTFMove(pattern);
+
+ return object;
+ }
+
+ 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<OnigurumaRegEx, UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForOnigurumaRegExp.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForOnigurumaRegExp = WTFMove(space); },
+ [](auto& spaces) { return spaces.m_subspaceForOnigurumaRegExp.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForOnigurumaRegExp = WTFMove(space); });
+ }
+
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(RegExpObjectType, StructureFlags), info());
+ }
+
+ // static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+
+ const WTF::String& flagsString() const { return m_flagsString; }
+ void setFlagsString(const WTF::String& flagsString) { m_flagsString = flagsString; }
+ const WTF::String& patternString() const { return m_patternString; }
+ void setPatternString(const WTF::String& patternString) { m_patternString = patternString; }
+
+ int32_t m_lastIndex = 0;
+
+private:
+ OnigurumaRegEx(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM&)
+ {
+ Base::finishCreation(vm());
+ }
+
+ WTF::String m_patternString = {};
+ WTF::String m_flagsString = {};
+};
+
class OnigurumaRegExpConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 5d7c49292..c1abb8291 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -1782,6 +1782,31 @@ JSC_DEFINE_HOST_FUNCTION(functionBunEscapeHTML, (JSC::JSGlobalObject * lexicalGl
}
}
+JSC_DECLARE_HOST_FUNCTION(functionBunDeepEquals);
+
+JSC_DEFINE_HOST_FUNCTION(functionBunDeepEquals, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ auto* global = reinterpret_cast<GlobalObject*>(globalObject);
+ JSC::VM& vm = global->vm();
+
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ if (callFrame->argumentCount() < 2) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ throwTypeError(globalObject, throwScope, "Expected 2 values to compare"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ JSC::JSValue arg1 = callFrame->argument(0);
+ JSC::JSValue arg2 = callFrame->argument(1);
+
+ Vector<std::pair<JSValue, JSValue>, 16> stack;
+
+ bool isEqual = Bun__deepEquals(globalObject, arg1, arg2, stack, &scope, true);
+ RETURN_IF_EXCEPTION(scope, {});
+ return JSValue::encode(jsBoolean(isEqual));
+}
+
JSC_DECLARE_HOST_FUNCTION(functionBunNanoseconds);
JSC_DEFINE_HOST_FUNCTION(functionBunNanoseconds, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
@@ -3130,6 +3155,12 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm
}
{
+ JSC::Identifier identifier = JSC::Identifier::fromString(vm, "deepEquals"_s);
+ object->putDirectNativeFunction(vm, this, identifier, 2, functionBunDeepEquals, ImplementationVisibility::Public, NoIntrinsic,
+ JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
+ }
+
+ {
JSC::Identifier identifier = JSC::Identifier::fromString(vm, "version"_s);
object->putDirect(vm, PropertyName(identifier), JSC::jsOwnedString(vm, makeString(Bun__version + 1)),
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index 8c0df075e..4cb163f88 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -77,6 +77,12 @@
#include "HTTPHeaderNames.h"
#include "JSDOMPromiseDeferred.h"
#include "JavaScriptCore/TestRunnerUtils.h"
+#include "JavaScriptCore/DateInstance.h"
+#include "JavaScriptCore/RegExpObject.h"
+#include "JavaScriptCore/PropertyNameArray.h"
+#include "JavaScriptCore/HashMapImpl.h"
+#include "JavaScriptCore/HashMapImplInlines.h"
+#include "OnigurumaRegExp.h"
template<typename UWSResponse>
static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res)
@@ -122,6 +128,530 @@ static void handlePromise(PromiseType* promise, JSC__JSGlobalObject* globalObjec
}
}
+static bool canPerformFastPropertyEnumerationForObjectAssignBun(Structure* s)
+{
+ if (s->typeInfo().overridesGetOwnPropertySlot())
+ return false;
+ if (s->typeInfo().overridesAnyFormOfGetOwnPropertyNames())
+ return false;
+ // FIXME: Indexed properties can be handled.
+ // https://bugs.webkit.org/show_bug.cgi?id=185358
+ if (hasIndexedProperties(s->indexingType()))
+ return false;
+ if (s->hasGetterSetterProperties())
+ return false;
+ if (s->hasReadOnlyOrGetterSetterPropertiesExcludingProto())
+ return false;
+ if (s->hasCustomGetterSetterProperties())
+ return false;
+ if (s->isUncacheableDictionary())
+ return false;
+ // Cannot perform fast [[Put]] to |target| if the property names of the |source| contain "__proto__".
+ if (s->hasUnderscoreProtoPropertyExcludingOriginalProto())
+ return false;
+ return true;
+}
+
+// adapted from underscorejs [https://underscorejs.org/docs/modules/isEqual.html]
+bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2, Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16>& stack, ThrowScope* scope, bool addToStack)
+{
+ VM& vm = globalObject->vm();
+ if (!v1.isEmpty() && !v2.isEmpty() && JSC::sameValue(globalObject, v1, v2)) {
+ return true;
+ }
+
+ if (v1.isEmpty() || v2.isEmpty())
+ return v1.isEmpty() == v2.isEmpty();
+
+ if (v1.isPrimitive() || v2.isPrimitive())
+ return false;
+
+ RELEASE_ASSERT(v1.isCell());
+ RELEASE_ASSERT(v2.isCell());
+
+ size_t length = stack.size();
+ size_t originalLength = length;
+ for (size_t i = 0; i < length; i++) {
+ auto values = stack.at(i);
+ if (JSC::JSValue::strictEqual(globalObject, values.first, v1)) {
+ return JSC::JSValue::strictEqual(globalObject, values.second, v2);
+ } else if (JSC::JSValue::strictEqual(globalObject, values.second, v2))
+ return false;
+ }
+
+ if (addToStack) {
+ stack.append({ v1, v2 });
+ }
+
+ JSCell* c1 = v1.asCell();
+ JSCell* c2 = v2.asCell();
+ JSC::JSType c1Type = c1->type();
+ JSC::JSType c2Type = c2->type();
+
+ switch (c1Type) {
+ case JSSetType: {
+ if (c2Type != JSSetType) {
+ return false;
+ }
+
+ JSSet* set1 = jsCast<JSSet*>(c1);
+ JSSet* set2 = jsCast<JSSet*>(c2);
+
+ if (set1->size() != set2->size()) {
+ return false;
+ }
+
+ IterationRecord iterationRecord1 = iteratorForIterable(globalObject, v1);
+ bool isEqual = true;
+ while (true) {
+ JSValue next1 = iteratorStep(globalObject, iterationRecord1);
+ if (next1.isFalse()) {
+ break;
+ }
+
+ JSValue nextValue1 = iteratorValue(globalObject, next1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ bool found = false;
+ IterationRecord iterationRecord2 = iteratorForIterable(globalObject, v2);
+ while (true) {
+ JSValue next2 = iteratorStep(globalObject, iterationRecord2);
+ if (UNLIKELY(next2.isFalse())) {
+ break;
+ }
+
+ JSValue nextValue2 = iteratorValue(globalObject, next2);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ // set has unique values, no need to count
+ if (Bun__deepEquals(globalObject, nextValue1, nextValue2, stack, scope, false)) {
+ found = true;
+ if (!nextValue1.isPrimitive()) {
+ stack.append({ nextValue1, nextValue2 });
+ }
+ break;
+ }
+ }
+
+ if (!found) {
+ isEqual = false;
+ break;
+ }
+ }
+
+ if (!isEqual) {
+ return false;
+ }
+
+ break;
+ }
+ case JSMapType: {
+ if (c2Type != JSMapType) {
+ return false;
+ }
+
+ JSMap* map1 = jsCast<JSMap*>(c1);
+ JSMap* map2 = jsCast<JSMap*>(c2);
+ size_t leftSize = map1->size();
+
+ if (leftSize != map2->size()) {
+ return false;
+ }
+
+ IterationRecord iterationRecord1 = iteratorForIterable(globalObject, v1);
+ bool isEqual = true;
+ while (true) {
+ JSValue next1 = iteratorStep(globalObject, iterationRecord1);
+ if (next1.isFalse()) {
+ break;
+ }
+
+ JSValue nextValue1 = iteratorValue(globalObject, next1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (UNLIKELY(!nextValue1.isObject())) {
+ return false;
+ }
+
+ JSObject* nextValueObject1 = asObject(nextValue1);
+
+ JSValue key1 = nextValueObject1->getIndex(globalObject, static_cast<unsigned>(0));
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ JSValue value1 = nextValueObject1->getIndex(globalObject, static_cast<unsigned>(1));
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ bool found = false;
+ IterationRecord iterationRecord2 = iteratorForIterable(globalObject, v2);
+ while (true) {
+ JSValue next2 = iteratorStep(globalObject, iterationRecord2);
+ if (UNLIKELY(next2.isFalse())) {
+ break;
+ }
+
+ JSValue nextValue2 = iteratorValue(globalObject, next2);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (UNLIKELY(!nextValue2.isObject())) {
+ return false;
+ }
+
+ JSObject* nextValueObject2 = asObject(nextValue2);
+
+ JSValue key2 = nextValueObject2->getIndex(globalObject, static_cast<unsigned>(0));
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ JSValue value2 = nextValueObject2->getIndex(globalObject, static_cast<unsigned>(1));
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (Bun__deepEquals(globalObject, key1, key2, stack, scope, false)) {
+ if (Bun__deepEquals(globalObject, nextValue1, nextValue2, stack, scope, false)) {
+ found = true;
+ if (!nextValue1.isPrimitive()) {
+ stack.append({ nextValue1, nextValue2 });
+ }
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ isEqual = false;
+ break;
+ }
+ }
+
+ if (!isEqual) {
+ return false;
+ }
+
+ break;
+ }
+ case ArrayBufferType: {
+ if (c2Type != ArrayBufferType) {
+ return false;
+ }
+
+ JSC::ArrayBuffer* left = jsCast<JSArrayBuffer*>(v1)->impl();
+ JSC::ArrayBuffer* right = jsCast<JSArrayBuffer*>(v2)->impl();
+ size_t byteLength = left->byteLength();
+
+ if (right->byteLength() != byteLength) {
+ return false;
+ }
+
+ if (byteLength == 0)
+ return true;
+
+ if (UNLIKELY(right->isDetached() || left->isDetached())) {
+ return false;
+ }
+
+ const void* vector = left->data();
+ const void* rightVector = right->data();
+ if (UNLIKELY(!vector || !rightVector)) {
+ return false;
+ }
+
+ if (UNLIKELY(vector == rightVector))
+ return true;
+
+ return (memcmp(vector, rightVector, byteLength) == 0);
+ }
+ case JSDateType: {
+ if (c2Type != JSDateType) {
+ return false;
+ }
+
+ JSC::DateInstance* left = jsCast<DateInstance*>(v1);
+ JSC::DateInstance* right = jsCast<DateInstance*>(v2);
+
+ if (left->structureID() == right->structureID()) {
+ return left->internalNumber() == right->internalNumber();
+ }
+ break;
+ }
+ case RegExpObjectType: {
+ if (c2Type != RegExpObjectType) {
+ return false;
+ }
+
+ if (OnigurumaRegEx* left = jsDynamicCast<OnigurumaRegEx*>(v1)) {
+ OnigurumaRegEx* right = jsDynamicCast<OnigurumaRegEx*>(v2);
+ if (UNLIKELY(!right)) {
+ return false;
+ }
+
+ if (!equal(left->patternString(), right->patternString())) {
+ return false;
+ }
+
+ if (!equal(left->flagsString(), right->flagsString())) {
+ return false;
+ }
+
+ return true;
+ } else if (JSC::RegExpObject* left = jsDynamicCast<JSC::RegExpObject*>(v1)) {
+ JSC::RegExpObject* right = jsDynamicCast<JSC::RegExpObject*>(v2);
+
+ if (UNLIKELY(!right)) {
+ return false;
+ }
+
+ return left->regExp()->key() == right->regExp()->key();
+ }
+
+ return false;
+ }
+ case Int8ArrayType:
+ case Uint8ArrayType:
+ case Uint8ClampedArrayType:
+ case Int16ArrayType:
+ case Uint16ArrayType:
+ case Int32ArrayType:
+ case Uint32ArrayType:
+ case Float32ArrayType:
+ case Float64ArrayType:
+ case BigInt64ArrayType:
+ case BigUint64ArrayType: {
+ if (!isTypedArrayType(c2Type)) {
+ return false;
+ }
+
+ JSC::JSArrayBufferView* left = jsCast<JSArrayBufferView*>(v1);
+ JSC::JSArrayBufferView* right = jsCast<JSArrayBufferView*>(v2);
+ size_t byteLength = left->byteLength();
+
+ if (right->byteLength() != byteLength) {
+ return false;
+ }
+
+ if (byteLength == 0)
+ return true;
+
+ if (UNLIKELY(right->isDetached() || left->isDetached())) {
+ return false;
+ }
+
+ const void* vector = left->vector();
+ const void* rightVector = right->vector();
+ if (UNLIKELY(!vector || !rightVector)) {
+ return false;
+ }
+
+ if (UNLIKELY(vector == rightVector))
+ return true;
+
+ return (memcmp(vector, rightVector, byteLength) == 0);
+ }
+
+ case JSFunctionType: {
+ return false;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ bool v1Array = isArray(globalObject, v1);
+ RETURN_IF_EXCEPTION(*scope, false);
+ bool v2Array = isArray(globalObject, v2);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ JSObject* o1 = v1.getObject();
+ JSObject* o2 = v2.getObject();
+
+ if (v1Array != v2Array)
+ return false;
+
+ if (v1Array && v2Array) {
+ JSC::JSArray* array1 = JSC::jsCast<JSC::JSArray*>(v1);
+ JSC::JSArray* array2 = JSC::jsCast<JSC::JSArray*>(v2);
+ size_t length = array1->length();
+ if (length != array2->length()) {
+ return false;
+ }
+
+ if (array1->canDoFastIndexedAccess() && array2->canDoFastIndexedAccess()) {
+ for (size_t i = 0; i < length; i++) {
+ JSValue left = o1->getIndexQuickly(i);
+ RETURN_IF_EXCEPTION(*scope, false);
+ JSValue right = o2->getIndexQuickly(i);
+ RETURN_IF_EXCEPTION(*scope, false);
+ if (!Bun__deepEquals(globalObject, left, right, stack, scope, true)) {
+ return false;
+ }
+
+ RETURN_IF_EXCEPTION(*scope, false);
+ }
+
+ } else {
+ for (size_t i = 0; i < length; i++) {
+ JSValue left = o1->getIndex(globalObject, i);
+ RETURN_IF_EXCEPTION(*scope, false);
+ JSValue right = o2->getIndex(globalObject, i);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (!Bun__deepEquals(globalObject, left, right, stack, scope, true)) {
+ return false;
+ }
+
+ RETURN_IF_EXCEPTION(*scope, false);
+ }
+ }
+
+ JSC::PropertyNameArray a1(vm, PropertyNameMode::Symbols, PrivateSymbolMode::Include);
+ JSC::PropertyNameArray a2(vm, PropertyNameMode::Symbols, PrivateSymbolMode::Include);
+ JSObject::getOwnPropertyNames(o1, globalObject, a1, DontEnumPropertiesMode::Exclude);
+ JSObject::getOwnPropertyNames(o2, globalObject, a2, DontEnumPropertiesMode::Exclude);
+
+ size_t propertyLength = a1.size();
+ if (propertyLength != a2.size()) {
+ return false;
+ }
+
+ // take a property name from one, try to get it from both
+ for (size_t i = 0; i < propertyLength; i++) {
+ Identifier i1 = a1[i];
+ PropertyName propertyName1 = PropertyName(i1);
+
+ JSValue prop1 = o1->get(globalObject, propertyName1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (UNLIKELY(!prop1)) {
+ return false;
+ }
+
+ JSValue prop2 = o2->getIfPropertyExists(globalObject, propertyName1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (!prop2) {
+ return false;
+ }
+
+ if (!Bun__deepEquals(globalObject, prop1, prop2, stack, scope, true)) {
+ return false;
+ }
+
+ RETURN_IF_EXCEPTION(*scope, false);
+ }
+
+ if (addToStack) {
+ stack.remove(originalLength);
+ }
+
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ return true;
+ }
+
+ JSC::Structure* o1Structure = o1->structure();
+ if (canPerformFastPropertyEnumerationForObjectAssignBun(o1Structure)) {
+ JSC::Structure* o2Structure = o2->structure();
+ if (canPerformFastPropertyEnumerationForObjectAssignBun(o2Structure)) {
+
+ size_t count1 = 0;
+
+ bool result = true;
+ if (o2Structure->maxOffset() != o1Structure->maxOffset()) {
+ return false;
+ }
+
+ o1Structure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool {
+ if (entry.attributes() & PropertyAttribute::DontEnum) {
+ return true;
+ }
+ count1++;
+
+ JSValue right = o2->getDirect(vm, JSC::PropertyName(entry.key()));
+
+ if (!right) {
+ result = false;
+ return false;
+ }
+
+ JSValue left = o1->getDirect(entry.offset());
+ if (left == right || JSC::sameValue(globalObject, left, right)) {
+ return true;
+ }
+
+ if (!Bun__deepEquals(globalObject, left, right, stack, scope, true)) {
+ result = false;
+ return false;
+ }
+
+ return true;
+ });
+
+ if (result && o2Structure->id() != o1Structure->id()) {
+ size_t remain = count1;
+ o2Structure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool {
+ if (entry.attributes() & PropertyAttribute::DontEnum) {
+ return true;
+ }
+
+ if (remain == 0) {
+ result = false;
+ return false;
+ }
+
+ remain--;
+ return true;
+ });
+ }
+
+ if (addToStack) {
+ stack.remove(originalLength);
+ }
+
+ return result;
+ }
+ }
+
+ JSC::PropertyNameArray a1(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include);
+ JSC::PropertyNameArray a2(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include);
+ o1->getPropertyNames(globalObject, a1, DontEnumPropertiesMode::Exclude);
+ o2->getPropertyNames(globalObject, a2, DontEnumPropertiesMode::Exclude);
+
+ size_t propertyLength = a1.size();
+ if (propertyLength != a2.size()) {
+ return false;
+ }
+
+ // take a property name from one, try to get it from both
+ for (size_t i = 0; i < propertyLength; i++) {
+ Identifier i1 = a1[i];
+ PropertyName propertyName1 = PropertyName(i1);
+
+ JSValue prop1 = o1->get(globalObject, propertyName1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (UNLIKELY(!prop1)) {
+ return false;
+ }
+
+ JSValue prop2 = o2->getIfPropertyExists(globalObject, propertyName1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (!prop2) {
+ return false;
+ }
+
+ if (!Bun__deepEquals(globalObject, prop1, prop2, stack, scope, true)) {
+ return false;
+ }
+
+ RETURN_IF_EXCEPTION(*scope, false);
+ }
+
+ if (addToStack) {
+ stack.remove(originalLength);
+ }
+
+ return true;
+}
+
extern "C" {
void WebCore__FetchHeaders__toUWSResponse(WebCore__FetchHeaders* arg0, bool is_ssl, void* arg2)
@@ -706,6 +1236,16 @@ bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1,
return JSC::sameValue(globalObject, left, right);
}
+bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
+{
+ JSC::JSValue v1 = JSC::JSValue::decode(JSValue0);
+ JSC::JSValue v2 = JSC::JSValue::decode(JSValue1);
+
+ ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
+ Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16> stack;
+ return Bun__deepEquals(globalObject, v1, v2, stack, &scope, true);
+}
+
// This is the same as the C API version, except it returns a JSValue which may be a *Exception
// We want that so we can return stack traces.
JSC__JSValue JSObjectCallAsFunctionReturnValue(JSContextRef ctx, JSObjectRef object,
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index b769625ec..7b9fb74c1 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -3423,6 +3423,10 @@ pub const JSValue = enum(JSValueReprInt) {
return @enumToInt(this) == @enumToInt(other) or cppFn("isSameValue", .{ this, other, global });
}
+ pub fn deepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool {
+ return cppFn("deepEquals", .{ this, other, global });
+ }
+
pub fn asString(this: JSValue) *JSString {
return cppFn("asString", .{
this,
@@ -3611,7 +3615,7 @@ pub const JSValue = enum(JSValueReprInt) {
return this.asNullableVoid().?;
}
- pub const Extern = [_][]const u8{ "coerceToInt32", "fastGet_", "getStaticProperty", "createUninitializedUint8Array", "fromInt64NoTruncate", "fromUInt64NoTruncate", "toUInt64NoTruncate", "asPromise", "toInt64", "_then", "put", "makeWithNameAndPrototype", "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt64", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable", "toBooleanSlow" };
+ pub const Extern = [_][]const u8{ "coerceToInt32", "fastGet_", "getStaticProperty", "createUninitializedUint8Array", "fromInt64NoTruncate", "fromUInt64NoTruncate", "toUInt64NoTruncate", "asPromise", "toInt64", "_then", "put", "makeWithNameAndPrototype", "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt64", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable", "toBooleanSlow", "deepEquals" };
};
extern "c" fn Microtask__run(*Microtask, *JSGlobalObject) void;
diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h
index 553259ec0..b8d071785 100644
--- a/src/bun.js/bindings/headers-handwritten.h
+++ b/src/bun.js/bindings/headers-handwritten.h
@@ -271,4 +271,6 @@ extern "C" size_t Bun__encoding__byteLengthUTF16(const UChar* ptr, size_t len, E
extern "C" int64_t Bun__encoding__constructFromLatin1(void*, const unsigned char* ptr, size_t len, Encoding encoding);
extern "C" int64_t Bun__encoding__constructFromUTF16(void*, const UChar* ptr, size_t len, Encoding encoding);
+bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSC::JSValue v1, JSC::JSValue v2, Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16>& stack, JSC::ThrowScope* scope, bool addToStack);
+
#endif
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index a9d106029..67dc9e393 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -1,5 +1,5 @@
// clang-format off
-//-- AUTOGENERATED FILE -- 1669191472
+//-- AUTOGENERATED FILE -- 1669267948
#pragma once
#include <stddef.h>
@@ -486,6 +486,7 @@ CPP_DECL JSC__JSValue JSC__JSValue__createRangeError(const ZigString* arg0, cons
CPP_DECL JSC__JSValue JSC__JSValue__createStringArray(JSC__JSGlobalObject* arg0, ZigString* arg1, size_t arg2, bool arg3);
CPP_DECL JSC__JSValue JSC__JSValue__createTypeError(const ZigString* arg0, const ZigString* arg1, JSC__JSGlobalObject* arg2);
CPP_DECL JSC__JSValue JSC__JSValue__createUninitializedUint8Array(JSC__JSGlobalObject* arg0, size_t arg1);
+CPP_DECL bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* arg2);
CPP_DECL bool JSC__JSValue__eqlCell(JSC__JSValue JSValue0, JSC__JSCell* arg1);
CPP_DECL bool JSC__JSValue__eqlValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1);
CPP_DECL JSC__JSValue JSC__JSValue__fastGet_(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, unsigned char arg2);
diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig
index 41f5f7178..7b649f26f 100644
--- a/src/bun.js/bindings/headers.zig
+++ b/src/bun.js/bindings/headers.zig
@@ -279,6 +279,7 @@ pub extern fn JSC__JSValue__createRangeError(arg0: [*c]const ZigString, arg1: [*
pub extern fn JSC__JSValue__createStringArray(arg0: ?*JSC__JSGlobalObject, arg1: [*c]ZigString, arg2: usize, arg3: bool) JSC__JSValue;
pub extern fn JSC__JSValue__createTypeError(arg0: [*c]const ZigString, arg1: [*c]const ZigString, arg2: ?*JSC__JSGlobalObject) JSC__JSValue;
pub extern fn JSC__JSValue__createUninitializedUint8Array(arg0: ?*JSC__JSGlobalObject, arg1: usize) JSC__JSValue;
+pub extern fn JSC__JSValue__deepEquals(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: ?*JSC__JSGlobalObject) bool;
pub extern fn JSC__JSValue__eqlCell(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSCell) bool;
pub extern fn JSC__JSValue__eqlValue(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue) bool;
pub extern fn JSC__JSValue__fastGet_(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject, arg2: u8) JSC__JSValue;
diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig
index c7372b573..dafb7dc0c 100644
--- a/src/bun.js/test/jest.zig
+++ b/src/bun.js/test/jest.zig
@@ -675,6 +675,46 @@ pub const Expect = struct {
return .zero;
}
+ pub fn toEqual(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ const thisValue = callFrame.this();
+ const _arguments = callFrame.arguments(1);
+ const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
+
+ if (arguments.len < 1) {
+ globalObject.throwInvalidArguments("toEqual() requires 1 argument", .{});
+ return .zero;
+ }
+
+ if (this.scope.tests.items.len <= this.test_id) {
+ globalObject.throw("toEqual() must be called in a test", .{});
+ return .zero;
+ }
+
+ active_test_expectation_counter.actual += 1;
+
+ const expected = arguments[0];
+ const value = Expect.capturedValueGetCached(thisValue) orelse {
+ globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{});
+ return .zero;
+ };
+ value.ensureStillAlive();
+
+ const not = this.op.contains(.not);
+ var pass = value.deepEquals(expected, globalObject);
+
+ if (not) pass = !pass;
+ if (pass) return thisValue;
+
+ // handle failure
+ var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject };
+ if (not) {
+ globalObject.throw("Expected values to not be equal:\n\tExpected: {any}\n\tReceived: {any}", .{ expected.toFmt(globalObject, &fmt), value.toFmt(globalObject, &fmt) });
+ } else {
+ globalObject.throw("Expected values to be equal:\n\tExpected: {any}\n\tReceived: {any}", .{ expected.toFmt(globalObject, &fmt), value.toFmt(globalObject, &fmt) });
+ }
+ return .zero;
+ }
+
pub const toHaveProperty = notImplementedJSCFn;
pub const toHaveBeenCalledTimes = notImplementedJSCFn;
pub const toHaveBeenCalledWith = notImplementedJSCFn;
@@ -691,7 +731,6 @@ pub const Expect = struct {
pub const toBeLessThanOrEqual = notImplementedJSCFn;
pub const toBeInstanceOf = notImplementedJSCFn;
pub const toContainEqual = notImplementedJSCFn;
- pub const toEqual = notImplementedJSCFn;
pub const toMatch = notImplementedJSCFn;
pub const toMatchObject = notImplementedJSCFn;
pub const toMatchSnapshot = notImplementedJSCFn;