<commit_before>import {
    createCallUtil,
    chainContainsUtil,
    getNodeBeforeMemberExpressionUtil,
    updateExpectUtil,
    createCallChainUtil,
} from '../utils/chai-chain-utils';
import logger from '../utils/logger';
import { removeRequireAndImport } from '../utils/imports';
import { findParentOfType, traverseMemberExpressionUtil } from '../utils/recast-helpers';
import finale from '../utils/finale';

const fns = [
    'keys',
    'a',
    'an',
    'instanceof',
    'lengthof',
    'length',
    'equal',
    'throw',
    'include',
    'contain',
    'eql',
    'above',
    'least',
    'below',
    'lessthan',
    'most',
    'match',
    'string',
    'members',
    'property',
    'ownproperty',
    'ownpropertydescriptor',
    'gte',
    'lte',
    'within',
];

const members = [
    'ok',
    'true',
    'false',
    'extensible',
    'finite',
    'frozen',
    'sealed',
    'null',
    'undefined',
    'exist',
    'empty',
    'nan',
    'defined',
];

const unsupportedProperties = new Set([
    'arguments',
    'respondTo',
    'satisfy',
    'closeTo',
    'oneOf',
    'change',
    'increase',
    'decrease',
]);

const mapValueNameToObjectMethod = {
    extensible: 'isExtensible',
    frozen: 'isFrozen',
    sealed: 'isSealed',
};

export const assertPrefixes = new Set(['to', 'with', 'that']);

module.exports = function transformer(fileInfo, api, options) {
    const j = api.jscodeshift;
    const root = j(fileInfo.source);
    let mutations = 0;

    const createCall = createCallUtil(j);
    const chainContains = chainContainsUtil(j);
    const getAllBefore = getNodeBeforeMemberExpressionUtil(j);
    const updateExpect = updateExpectUtil(j);
    const createCallChain = createCallChainUtil(j);

    const chaiExpectRemoved = removeRequireAndImport(j, root, 'chai', 'expect');
    const chaiShouldRemoved = removeRequireAndImport(j, root, 'chai', 'should');
    if (chaiExpectRemoved || chaiShouldRemoved) {
        mutations += 1;
    }

    const isExpectCall = node =>
        node.name === 'expect' ||
        (node.type === j.MemberExpression.name && isExpectCall(node.object)) ||
        (node.type === j.CallExpression.name && isExpectCall(node.callee));

    const isShouldMemberExpression = traverseMemberExpressionUtil(
        j,
        node => node.type === 'Identifier' && node.name === 'should'
    );

    const isExpectMemberExpression = traverseMemberExpressionUtil(
        j,
        node => node.type === j.CallExpression.name && node.callee.name === 'expect'
    );

    const logWarning = (msg, node) => logger(fileInfo, msg, node);

    const typeOf = (value, args, containsNot) => {
        switch (args[0].value) {
            case 'null':
                return createCall(
                    'toBeNull',
                    [],
                    updateExpect(value, node => node, containsNot)
                );
            case 'undefined':
                return createCall(
                    'toBeUndefined',
                    [],
                    updateExpect(value, node => node, containsNot)
                );
            case 'array':
                return createCall(
                    'toBe',
                    [j.booleanLiteral(containsNot ? false : true)],
                    updateExpect(value, node =>
                        j.callExpression(
                            j.memberExpression(
                                j.identifier('Array'),
                                j.identifier('isArray')
                            ),
                            [node]
                        )
                    )
                );
            case 'error':
                return createCall(
                    'toBeInstanceOf',
                    [j.identifier('Error')],
                    updateExpect(value, node => node),
                    containsNot
                );
            default:
                return createCall(
                    'toBe',
                    args,
                    updateExpect(value, node => j.unaryExpression('typeof', node)),
                    containsNot
                );
        }
    };

    // TODO: not sure if this is even required for chai...
    // E.g. is should(x).true valid?
    const isPrefix = name => assertPrefixes.has(name);

    function parseArgs(args) {
        if (args.length === 1 && args[0].type === j.ObjectExpression.name) {
            return [createCallChain(['Object', 'keys'], args)];
        } else if (args.length > 1) {
            return [j.arrayExpression(args)];
        }

        return args;
    }

    function containing(node) {
        switch (node.type) {
            case j.ArrayExpression.name:
                return createCallChain(['expect', 'arrayContaining'], [node]);
            case j.ObjectExpression.name:
                return createCallChain(['expect', 'objectContaining'], [node]);
            default:
                return node;
        }
    }

    function getRestWithLengthHandled(p, rest) {
        const containsLength = chainContains('length', p.value.callee, isPrefix);
        return containsLength
            ? updateExpect(rest, node => j.memberExpression(node, j.identifier('length')))
            : rest;
    }

    function withIn(p, rest, args, containsNot) {
        if (args.length < 2) {
            logWarning(`.within needs at least two arguments`, p);
            return p.value;
        }

        j(p)
            .closest(j.ExpressionStatement)
            .insertBefore(
                j.expressionStatement(
                    createCall('toBeGreaterThanOrEqual', [args[0]], rest, containsNot)
                )
            );

        return createCall('toBeLessThanOrEqual', [args[1]], rest, containsNot);
    }

    const shouldChainedToExpect = () =>
        root
            .find(j.MemberExpression, {
                property: {
                    type: j.Identifier.name,
                    name: 'should',
                },
            })
            .replaceWith(p => j.callExpression(j.identifier('expect'), [p.node.object]))<commit_msg>Add initial fix for #106<commit_after>import {
    createCallUtil,
    chainContainsUtil,
    getNodeBeforeMemberExpressionUtil,
    updateExpectUtil,
    createCallChainUtil,
} from '../utils/chai-chain-utils';
import logger from '../utils/logger';
import { removeRequireAndImport } from '../utils/imports';
import { findParentOfType, traverseMemberExpressionUtil } from '../utils/recast-helpers';
import finale from '../utils/finale';

const fns = [
    'keys',
    'a',
    'an',
    'instanceof',
    'lengthof',
    'length',
    'equal',
    'throw',
    'include',
    'contain',
    'eql',
    'above',
    'least',
    'below',
    'lessthan',
    'most',
    'match',
    'string',
    'members',
    'property',
    'ownproperty',
    'ownpropertydescriptor',
    'gte',
    'lte',
    'within',
];

const members = [
    'ok',
    'true',
    'false',
    'extensible',
    'finite',
    'frozen',
    'sealed',
    'null',
    'undefined',
    'exist',
    'empty',
    'nan',
    'defined',
];

const unsupportedProperties = new Set([
    'arguments',
    'respondTo',
    'satisfy',
    'closeTo',
    'oneOf',
    'change',
    'increase',
    'decrease',
]);

const mapValueNameToObjectMethod = {
    extensible: 'isExtensible',
    frozen: 'isFrozen',
    sealed: 'isSealed',
};

export const assertPrefixes = new Set(['to', 'with', 'that']);

module.exports = function transformer(fileInfo, api, options) {
    const j = api.jscodeshift;
    const root = j(fileInfo.source);
    let mutations = 0;

    const createCall = createCallUtil(j);
    const chainContains = chainContainsUtil(j);
    const getAllBefore = getNodeBeforeMemberExpressionUtil(j);
    const updateExpect = updateExpectUtil(j);
    const createCallChain = createCallChainUtil(j);

    const chaiExpectRemoved = removeRequireAndImport(j, root, 'chai', 'expect');
    const chaiShouldRemoved = removeRequireAndImport(j, root, 'chai', 'should');
    if (chaiExpectRemoved || chaiShouldRemoved) {
        mutations += 1;
    }

    const isExpectCall = node =>
        node.name === 'expect' ||
        (node.type === j.MemberExpression.name && isExpectCall(node.object)) ||
        (node.type === j.CallExpression.name && isExpectCall(node.callee));

    const isShouldMemberExpression = traverseMemberExpressionUtil(
        j,
        node => node.type === 'Identifier' && node.name === 'should'
    );

    const isExpectMemberExpression = traverseMemberExpressionUtil(
        j,
        node => node.type === j.CallExpression.name && node.callee.name === 'expect'
    );

    const logWarning = (msg, node) => logger(fileInfo, msg, node);

    const typeOf = (value, args, containsNot) => {
        switch (args[0].value) {
            case 'null':
                return createCall(
                    'toBeNull',
                    [],
                    updateExpect(value, node => node, containsNot)
                );
            case 'undefined':
                return createCall(
                    'toBeUndefined',
                    [],
                    updateExpect(value, node => node, containsNot)
                );
            case 'array':
                return createCall(
                    'toBe',
                    [j.booleanLiteral(containsNot ? false : true)],
                    updateExpect(value, node =>
                        j.callExpression(
                            j.memberExpression(
                                j.identifier('Array'),
                                j.identifier('isArray')
                            ),
                            [node]
                        )
                    )
                );
            case 'error':
                return createCall(
                    'toBeInstanceOf',
                    [j.identifier('Error')],
                    updateExpect(value, node => node),
                    containsNot
                );
            default:
                return createCall(
                    'toBe',
                    args,
                    updateExpect(value, node => j.unaryExpression('typeof', node)),
                    containsNot
                );
        }
    };

    // TODO: not sure if this is even required for chai...
    // E.g. is should(x).true valid?
    const isPrefix = name => assertPrefixes.has(name);

    function parseArgs(args) {
        if (args.length === 1 && args[0].type === j.ObjectExpression.name) {
            return [createCallChain(['Object', 'keys'], args)];
        } else if (args.length > 1) {
            return [j.arrayExpression(args)];
        }

        return args;
    }

    function containing(node) {
        switch (node.type) {
            case j.ArrayExpression.name:
                return createCallChain(['expect', 'arrayContaining'], [node]);
            case j.ObjectExpression.name:
                return createCallChain(['expect', 'objectContaining'], [node]);
            default:
                return node;
        }
    }

    function getRestWithLengthHandled(p, rest) {
        const containsLength = chainContains('length', p.value.callee, isPrefix);
        const newRest = containsLength
            ? updateExpect(rest, node => j.memberExpression(node, j.identifier('length')))
            : rest;
        if (newRest.arguments) {
            newRest.arguments = newRest.arguments.slice(0, 1);
        }
        return newRest;
    }

    function withIn(p, rest, args, containsNot) {
        if (args.length < 2) {
            logWarning(`.within needs at least two arguments`, p);
            return p.value;
        }

        j(p)
            .closest(j.ExpressionStatement)
            .insertBefore(
                j.expressionStatement(
                    createCall('toBeGreaterThanOrEqual', [args[0]], rest, containsNot)
                )
            );

        return createCall('toBeLessThanOrEqual', [args[1]], rest, containsNot);
    }

    const shouldChainedToExpect = () =>
        root
            .find(j.MemberExpression, {
                property: {
                    type: j.Identifier.name,
                    name: 'should',
                },
            })
            .replaceWith(p => j.callExpression(j.identifier('expect'), [p.node.object]))
{'fraction_non_alphanumeric': 0.12728448972990458, 'fraction_numerical': 0.0018599385411612486, 'mean_word_length': 1.7294195541822996, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4417356', 'n_tokens_mistral': 3437, 'n_tokens_neox': 3299, 'n_words': 709}