AsymmetricMatcherResult matchAsymmetricMatcherAndGetFlags()

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;
}