in src/bun.js/bindings/bindings.cpp [316:575]
AsymmetricMatcherResult matchAsymmetricMatcherAndGetFlags(JSGlobalObject* globalObject, JSValue matcherProp, JSValue otherProp, ThrowScope* throwScope, ExpectFlags& flags)
{
JSCell* matcherPropCell = matcherProp.asCell();
AsymmetricMatcherConstructorType constructorType = AsymmetricMatcherConstructorType::none;
if (auto* expectAnything = jsDynamicCast<JSExpectAnything*>(matcherPropCell)) {
if (!readFlagsAndProcessPromise(matcherProp, flags, globalObject, otherProp, constructorType))
return AsymmetricMatcherResult::FAIL;
if (otherProp.isUndefinedOrNull()) {
return AsymmetricMatcherResult::FAIL;
}
return AsymmetricMatcherResult::PASS;
} else if (auto* expectAny = jsDynamicCast<JSExpectAny*>(matcherPropCell)) {
if (!readFlagsAndProcessPromise(matcherProp, flags, globalObject, otherProp, constructorType))
return AsymmetricMatcherResult::FAIL;
JSValue constructorValue = expectAny->m_constructorValue.get();
JSObject* constructorObject = constructorValue.getObject();
switch (constructorType) {
case AsymmetricMatcherConstructorType::Symbol: {
if (otherProp.isSymbol()) {
return AsymmetricMatcherResult::PASS;
}
break;
}
case AsymmetricMatcherConstructorType::String: {
if (otherProp.isCell()) {
JSCell* cell = otherProp.asCell();
switch (cell->type()) {
case JSC::StringType:
case JSC::StringObjectType:
case JSC::DerivedStringObjectType: {
return AsymmetricMatcherResult::PASS;
}
default: {
break;
}
}
}
break;
}
case AsymmetricMatcherConstructorType::BigInt: {
if (otherProp.isBigInt()) {
return AsymmetricMatcherResult::PASS;
}
break;
}
case AsymmetricMatcherConstructorType::Boolean: {
if (otherProp.isBoolean()) {
return AsymmetricMatcherResult::PASS;
}
if (auto* booleanObject = jsDynamicCast<BooleanObject*>(otherProp)) {
return AsymmetricMatcherResult::PASS;
}
break;
}
case AsymmetricMatcherConstructorType::Number: {
if (otherProp.isNumber()) {
return AsymmetricMatcherResult::PASS;
}
if (auto* numberObject = jsDynamicCast<NumberObject*>(otherProp)) {
return AsymmetricMatcherResult::PASS;
}
break;
}
case AsymmetricMatcherConstructorType::Promise: {
if (otherProp.isCell() && otherProp.asCell()->type() == JSPromiseType) {
return AsymmetricMatcherResult::PASS;
}
break;
}
case AsymmetricMatcherConstructorType::Array: {
if (JSC::isArray(globalObject, otherProp)) {
return AsymmetricMatcherResult::PASS;
}
break;
}
case AsymmetricMatcherConstructorType::Object: {
if (otherProp.isObject()) {
return AsymmetricMatcherResult::PASS;
}
break;
}
case AsymmetricMatcherConstructorType::InstanceOf: {
break;
}
case AsymmetricMatcherConstructorType::none: {
ASSERT_NOT_REACHED_WITH_MESSAGE("Invalid constructor type");
break;
}
}
if (constructorObject->hasInstance(globalObject, otherProp)) {
return AsymmetricMatcherResult::PASS;
}
return AsymmetricMatcherResult::FAIL;
} else if (auto* expectStringContaining = jsDynamicCast<JSExpectStringContaining*>(matcherPropCell)) {
if (!readFlagsAndProcessPromise(matcherProp, flags, globalObject, otherProp, constructorType))
return AsymmetricMatcherResult::FAIL;
JSValue expectedSubstring = expectStringContaining->m_stringValue.get();
if (otherProp.isString()) {
String otherString = otherProp.toWTFString(globalObject);
RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
String substring = expectedSubstring.toWTFString(globalObject);
RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
if (otherString.find(substring) != WTF::notFound) {
return AsymmetricMatcherResult::PASS;
}
}
return AsymmetricMatcherResult::FAIL;
} else if (auto* expectStringMatching = jsDynamicCast<JSExpectStringMatching*>(matcherPropCell)) {
if (!readFlagsAndProcessPromise(matcherProp, flags, globalObject, otherProp, constructorType))
return AsymmetricMatcherResult::FAIL;
JSValue expectedTestValue = expectStringMatching->m_testValue.get();
if (otherProp.isString()) {
if (expectedTestValue.isString()) {
String otherString = otherProp.toWTFString(globalObject);
RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
String substring = expectedTestValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
if (otherString.find(substring) != WTF::notFound) {
return AsymmetricMatcherResult::PASS;
}
} else if (expectedTestValue.isCell() and expectedTestValue.asCell()->type() == RegExpObjectType) {
if (auto* regex = jsDynamicCast<RegExpObject*>(expectedTestValue)) {
JSString* otherString = otherProp.toString(globalObject);
if (regex->match(globalObject, otherString)) {
return AsymmetricMatcherResult::PASS;
}
}
}
}
return AsymmetricMatcherResult::FAIL;
} else if (auto* expectArrayContaining = jsDynamicCast<JSExpectArrayContaining*>(matcherPropCell)) {
if (!readFlagsAndProcessPromise(matcherProp, flags, globalObject, otherProp, constructorType))
return AsymmetricMatcherResult::FAIL;
JSValue expectedArrayValue = expectArrayContaining->m_arrayValue.get();
if (JSC::isArray(globalObject, otherProp)) {
if (JSC::isArray(globalObject, expectedArrayValue)) {
JSArray* expectedArray = jsDynamicCast<JSArray*>(expectedArrayValue);
JSArray* otherArray = jsDynamicCast<JSArray*>(otherProp);
unsigned expectedLength = expectedArray->length();
unsigned otherLength = otherArray->length();
// A empty array is all array's subset
if (expectedLength == 0) {
return AsymmetricMatcherResult::PASS;
}
// O(m*n) but works for now
for (unsigned m = 0; m < expectedLength; m++) {
JSValue expectedValue = expectedArray->getIndex(globalObject, m);
bool found = false;
for (unsigned n = 0; n < otherLength; n++) {
JSValue otherValue = otherArray->getIndex(globalObject, n);
ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
Vector<std::pair<JSValue, JSValue>, 16> stack;
MarkedArgumentBuffer gcBuffer;
if (Bun__deepEquals<false, true>(globalObject, expectedValue, otherValue, gcBuffer, stack, &scope, true)) {
found = true;
break;
}
}
if (!found) {
return AsymmetricMatcherResult::FAIL;
}
}
return AsymmetricMatcherResult::PASS;
}
}
return AsymmetricMatcherResult::FAIL;
} else if (auto* expectObjectContaining = jsDynamicCast<JSExpectObjectContaining*>(matcherPropCell)) {
if (!readFlagsAndProcessPromise(matcherProp, flags, globalObject, otherProp, constructorType))
return AsymmetricMatcherResult::FAIL;
JSValue patternObject = expectObjectContaining->m_objectValue.get();
if (patternObject.isObject()) {
if (otherProp.isObject()) {
ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
if (Bun__deepMatch<true>(otherProp, patternObject, globalObject, &scope, false, true)) {
return AsymmetricMatcherResult::PASS;
}
}
}
return AsymmetricMatcherResult::FAIL;
} else if (auto* expectCloseTo = jsDynamicCast<JSExpectCloseTo*>(matcherPropCell)) {
if (!readFlagsAndProcessPromise(matcherProp, flags, globalObject, otherProp, constructorType))
return AsymmetricMatcherResult::FAIL;
if (!otherProp.isNumber()) {
// disable the "not" flag here, because if not a number it should still return FAIL when negated
flags = flags & ~FLAG_NOT;
return AsymmetricMatcherResult::FAIL;
}
JSValue expectedValue = expectCloseTo->m_numberValue.get();
JSValue digitsValue = expectCloseTo->m_digitsValue.get();
double received = otherProp.toNumber(globalObject);
double expected = expectedValue.toNumber(globalObject);
constexpr double infinity = std::numeric_limits<double>::infinity();
// special handing because (Infinity - Infinity) or (-Infinity - -Infinity) is NaN
if ((received == infinity && expected == infinity) || (received == -infinity && expected == -infinity)) {
return AsymmetricMatcherResult::PASS;
} else {
int32_t digits = digitsValue.toInt32(globalObject);
double threshold = 0.5 * std::pow(10.0, -digits);
bool isClose = std::abs(expected - received) < threshold;
return isClose ? AsymmetricMatcherResult::PASS : AsymmetricMatcherResult::FAIL;
}
} else if (auto* customMatcher = jsDynamicCast<JSExpectCustomAsymmetricMatcher*>(matcherPropCell)) {
if (!readFlagsAndProcessPromise(matcherProp, flags, globalObject, otherProp, constructorType))
return AsymmetricMatcherResult::FAIL;
// ignore the "not" flag here, because the custom matchers handle it themselves (accessing this.isNot)
// and it would result in a double negation
flags = flags & ~FLAG_NOT;
bool passed = ExpectCustomAsymmetricMatcher__execute(customMatcher->wrapped(), JSValue::encode(matcherProp), globalObject, JSValue::encode(otherProp));
return passed ? AsymmetricMatcherResult::PASS : AsymmetricMatcherResult::FAIL;
}
return AsymmetricMatcherResult::NOT_MATCHER;
}