<commit_before>/**
 * @fileoverview Rule to check paired unsubscribe
 * @author Gvozd
 */
'use strict';

const defaultOptions = [
    {
        open: 'addEventListener',
        close: 'removeEventListener'
    },
    {
        open: 'on',
        close: 'off'
    },
    {
        open: 'subscribe',
        close: 'unsubscribe'
    }
];

module.exports = {
    meta: {
        docs: {
            description: 'check paired unsubscribe',
            category: 'Possible Errors',
            recommended: false
        },
        schema: [
            {
                type: 'array',
                items: {
                    type: 'object',
                    properties: {
                        open: {
                            type: 'string'
                        },
                        close: {
                            type: 'string'
                        }
                    },
                    required: ['open', 'close'],
                    additionalProperties: false
                }
            }
        ]
    },
    create(context) {
        const pairs = processOptions(context.options[0] || defaultOptions);
        const sourceCode = context.getSourceCode();
        const callExpressions = [];
        return {
            'CallExpression'(node) {
                const callee = node.callee;
                let functionName;
                switch (callee.type) {
                    case 'MemberExpression':
                        functionName = callee.property.name;
                        break;
                    case 'Identifier':
                        functionName = callee.name;
                        break;
                    case 'FunctionExpression':
                    case 'CallExpression':
                        break;
                    default:
                        throw new Error('unknown callee type: ' + callee.type);
                }
                callExpressions.push({
                    node,
                    functionName,
                    source: sourceCode.getTokens(node)
                        .map((x) => x.value).join('')
                });
            },
            'Program:exit'() {
                for (const data of callExpressions) {
                    if (pairs.hasOwnProperty(data.functionName)) {
                        const closeName = pairs[data.functionName];
                        const hasCloseCall = callExpressions
                            .some(
                                (x) => x.functionName === closeName &&
                                x.source.split(closeName).join(data.functionName) === data.source
                            );
                        if (!hasCloseCall) {
                            context.report({
                                node: data.node,
                                message: `unpaired "${data.functionName}"`
                            });
                        }
                    }
                }
            }
        };
    }
};

function processOptions(options) {
    const pairs = {};
    for (const pair of options) {
        pairs[pair.open] = pair.close;
    }
    return pairs;
}
<commit_msg>refactor: prepare for additional option<commit_after>/**
 * @fileoverview Rule to check paired unsubscribe
 * @author Gvozd
 */
'use strict';

const defaultOptions = [
    {
        open: 'addEventListener',
        close: 'removeEventListener'
    },
    {
        open: 'on',
        close: 'off'
    },
    {
        open: 'subscribe',
        close: 'unsubscribe'
    }
];

module.exports = {
    meta: {
        docs: {
            description: 'check paired unsubscribe',
            category: 'Possible Errors',
            recommended: false
        },
        schema: [
            {
                type: 'array',
                items: {
                    type: 'object',
                    properties: {
                        open: {
                            type: 'string'
                        },
                        close: {
                            type: 'string'
                        }
                    },
                    required: ['open', 'close'],
                    additionalProperties: false
                }
            }
        ]
    },
    create(context) {
        const options = processOptions(context.options[0] || defaultOptions);
        const sourceCode = context.getSourceCode();
        const callExpressions = [];
        return {
            'CallExpression'(node) {
                const callee = node.callee;
                let methodName;
                switch (callee.type) {
                    case 'MemberExpression':
                        methodName = callee.property.name;
                        break;
                    case 'Identifier':
                        methodName = callee.name;
                        break;
                    case 'FunctionExpression':
                    case 'CallExpression':
                        break;
                    default:
                        throw new Error('unknown callee type: ' + callee.type);
                }
                callExpressions.push({
                    node,
                    methodName,
                    fullSource: sourceCode.getTokens(node)
                        .map((x) => x.value).join('')
                });
            },
            'Program:exit'() {
                for (const callExpression of callExpressions) {
                    if (!options.hasOwnProperty(callExpression.methodName)) {
                        continue;
                    }
                    if (!hasPair(callExpression, callExpressions, options)) {
                        context.report({
                            node: callExpression.node,
                            message: `unpaired "${callExpression.methodName}"`
                        });
                    }
                }
            }
        };
    }
};

function processOptions(options) {
    const pairs = {};
    for (const option of options) {
        pairs[option.open] = {
            open: option.open,
            close: option.close
        };
    }
    return pairs;
}

function hasPair(callExpression, callExpressions, options) {
    const closeName = options[callExpression.methodName].close;
    return callExpressions
        .some(
            (x) => x.methodName === closeName &&
                x.fullSource.split(closeName).join(callExpression.methodName) === callExpression.fullSource
        );
}

{'fraction_non_alphanumeric': 0.09562423500611995, 'fraction_numerical': 0.0003059975520195838, 'mean_word_length': 0.9845173041894353, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1851169', 'n_tokens_mistral': 1527, 'n_tokens_neox': 1434, 'n_words': 301}