You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							240 lines
						
					
					
						
							8.1 KiB
						
					
					
				
			
		
		
	
	
							240 lines
						
					
					
						
							8.1 KiB
						
					
					
				/** | 
						|
 * @fileoverview Rule to disallow mixed binary operators. | 
						|
 * @author Toru Nagashima | 
						|
 */ | 
						|
 | 
						|
"use strict"; | 
						|
 | 
						|
//------------------------------------------------------------------------------ | 
						|
// Requirements | 
						|
//------------------------------------------------------------------------------ | 
						|
 | 
						|
const astUtils = require("./utils/ast-utils.js"); | 
						|
 | 
						|
//------------------------------------------------------------------------------ | 
						|
// Helpers | 
						|
//------------------------------------------------------------------------------ | 
						|
 | 
						|
const ARITHMETIC_OPERATORS = ["+", "-", "*", "/", "%", "**"]; | 
						|
const BITWISE_OPERATORS = ["&", "|", "^", "~", "<<", ">>", ">>>"]; | 
						|
const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="]; | 
						|
const LOGICAL_OPERATORS = ["&&", "||"]; | 
						|
const RELATIONAL_OPERATORS = ["in", "instanceof"]; | 
						|
const TERNARY_OPERATOR = ["?:"]; | 
						|
const ALL_OPERATORS = [].concat( | 
						|
    ARITHMETIC_OPERATORS, | 
						|
    BITWISE_OPERATORS, | 
						|
    COMPARISON_OPERATORS, | 
						|
    LOGICAL_OPERATORS, | 
						|
    RELATIONAL_OPERATORS, | 
						|
    TERNARY_OPERATOR | 
						|
); | 
						|
const DEFAULT_GROUPS = [ | 
						|
    ARITHMETIC_OPERATORS, | 
						|
    BITWISE_OPERATORS, | 
						|
    COMPARISON_OPERATORS, | 
						|
    LOGICAL_OPERATORS, | 
						|
    RELATIONAL_OPERATORS | 
						|
]; | 
						|
const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u; | 
						|
 | 
						|
/** | 
						|
 * Normalizes options. | 
						|
 * @param {Object|undefined} options A options object to normalize. | 
						|
 * @returns {Object} Normalized option object. | 
						|
 */ | 
						|
function normalizeOptions(options = {}) { | 
						|
    const hasGroups = options.groups && options.groups.length > 0; | 
						|
    const groups = hasGroups ? options.groups : DEFAULT_GROUPS; | 
						|
    const allowSamePrecedence = options.allowSamePrecedence !== false; | 
						|
 | 
						|
    return { | 
						|
        groups, | 
						|
        allowSamePrecedence | 
						|
    }; | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Checks whether any group which includes both given operator exists or not. | 
						|
 * @param {Array.<string[]>} groups A list of groups to check. | 
						|
 * @param {string} left An operator. | 
						|
 * @param {string} right Another operator. | 
						|
 * @returns {boolean} `true` if such group existed. | 
						|
 */ | 
						|
function includesBothInAGroup(groups, left, right) { | 
						|
    return groups.some(group => group.indexOf(left) !== -1 && group.indexOf(right) !== -1); | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Checks whether the given node is a conditional expression and returns the test node else the left node. | 
						|
 * @param {ASTNode} node A node which can be a BinaryExpression or a LogicalExpression node. | 
						|
 * This parent node can be BinaryExpression, LogicalExpression | 
						|
 *      , or a ConditionalExpression node | 
						|
 * @returns {ASTNode} node the appropriate node(left or test). | 
						|
 */ | 
						|
function getChildNode(node) { | 
						|
    return node.type === "ConditionalExpression" ? node.test : node.left; | 
						|
} | 
						|
 | 
						|
//------------------------------------------------------------------------------ | 
						|
// Rule Definition | 
						|
//------------------------------------------------------------------------------ | 
						|
 | 
						|
module.exports = { | 
						|
    meta: { | 
						|
        type: "suggestion", | 
						|
 | 
						|
        docs: { | 
						|
            description: "disallow mixed binary operators", | 
						|
            category: "Stylistic Issues", | 
						|
            recommended: false, | 
						|
            url: "https://eslint.org/docs/rules/no-mixed-operators" | 
						|
        }, | 
						|
 | 
						|
        schema: [ | 
						|
            { | 
						|
                type: "object", | 
						|
                properties: { | 
						|
                    groups: { | 
						|
                        type: "array", | 
						|
                        items: { | 
						|
                            type: "array", | 
						|
                            items: { enum: ALL_OPERATORS }, | 
						|
                            minItems: 2, | 
						|
                            uniqueItems: true | 
						|
                        }, | 
						|
                        uniqueItems: true | 
						|
                    }, | 
						|
                    allowSamePrecedence: { | 
						|
                        type: "boolean", | 
						|
                        default: true | 
						|
                    } | 
						|
                }, | 
						|
                additionalProperties: false | 
						|
            } | 
						|
        ] | 
						|
    }, | 
						|
 | 
						|
    create(context) { | 
						|
        const sourceCode = context.getSourceCode(); | 
						|
        const options = normalizeOptions(context.options[0]); | 
						|
 | 
						|
        /** | 
						|
         * Checks whether a given node should be ignored by options or not. | 
						|
         * @param {ASTNode} node A node to check. This is a BinaryExpression | 
						|
         *      node or a LogicalExpression node. This parent node is one of | 
						|
         *      them, too. | 
						|
         * @returns {boolean} `true` if the node should be ignored. | 
						|
         */ | 
						|
        function shouldIgnore(node) { | 
						|
            const a = node; | 
						|
            const b = node.parent; | 
						|
 | 
						|
            return ( | 
						|
                !includesBothInAGroup(options.groups, a.operator, b.type === "ConditionalExpression" ? "?:" : b.operator) || | 
						|
                ( | 
						|
                    options.allowSamePrecedence && | 
						|
                    astUtils.getPrecedence(a) === astUtils.getPrecedence(b) | 
						|
                ) | 
						|
            ); | 
						|
        } | 
						|
 | 
						|
        /** | 
						|
         * Checks whether the operator of a given node is mixed with parent | 
						|
         * node's operator or not. | 
						|
         * @param {ASTNode} node A node to check. This is a BinaryExpression | 
						|
         *      node or a LogicalExpression node. This parent node is one of | 
						|
         *      them, too. | 
						|
         * @returns {boolean} `true` if the node was mixed. | 
						|
         */ | 
						|
        function isMixedWithParent(node) { | 
						|
 | 
						|
            return ( | 
						|
                node.operator !== node.parent.operator && | 
						|
                !astUtils.isParenthesised(sourceCode, node) | 
						|
            ); | 
						|
        } | 
						|
 | 
						|
        /** | 
						|
         * Checks whether the operator of a given node is mixed with a | 
						|
         * conditional expression. | 
						|
         * @param {ASTNode} node A node to check. This is a conditional | 
						|
         *      expression node | 
						|
         * @returns {boolean} `true` if the node was mixed. | 
						|
         */ | 
						|
        function isMixedWithConditionalParent(node) { | 
						|
            return !astUtils.isParenthesised(sourceCode, node) && !astUtils.isParenthesised(sourceCode, node.test); | 
						|
        } | 
						|
 | 
						|
        /** | 
						|
         * Gets the operator token of a given node. | 
						|
         * @param {ASTNode} node A node to check. This is a BinaryExpression | 
						|
         *      node or a LogicalExpression node. | 
						|
         * @returns {Token} The operator token of the node. | 
						|
         */ | 
						|
        function getOperatorToken(node) { | 
						|
            return sourceCode.getTokenAfter(getChildNode(node), astUtils.isNotClosingParenToken); | 
						|
        } | 
						|
 | 
						|
        /** | 
						|
         * Reports both the operator of a given node and the operator of the | 
						|
         * parent node. | 
						|
         * @param {ASTNode} node A node to check. This is a BinaryExpression | 
						|
         *      node or a LogicalExpression node. This parent node is one of | 
						|
         *      them, too. | 
						|
         * @returns {void} | 
						|
         */ | 
						|
        function reportBothOperators(node) { | 
						|
            const parent = node.parent; | 
						|
            const left = (getChildNode(parent) === node) ? node : parent; | 
						|
            const right = (getChildNode(parent) !== node) ? node : parent; | 
						|
            const message = | 
						|
                "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'."; | 
						|
            const data = { | 
						|
                leftOperator: left.operator || "?:", | 
						|
                rightOperator: right.operator || "?:" | 
						|
            }; | 
						|
 | 
						|
            context.report({ | 
						|
                node: left, | 
						|
                loc: getOperatorToken(left).loc, | 
						|
                message, | 
						|
                data | 
						|
            }); | 
						|
            context.report({ | 
						|
                node: right, | 
						|
                loc: getOperatorToken(right).loc, | 
						|
                message, | 
						|
                data | 
						|
            }); | 
						|
        } | 
						|
 | 
						|
        /** | 
						|
         * Checks between the operator of this node and the operator of the | 
						|
         * parent node. | 
						|
         * @param {ASTNode} node A node to check. | 
						|
         * @returns {void} | 
						|
         */ | 
						|
        function check(node) { | 
						|
            if (TARGET_NODE_TYPE.test(node.parent.type)) { | 
						|
                if (node.parent.type === "ConditionalExpression" && !shouldIgnore(node) && isMixedWithConditionalParent(node.parent)) { | 
						|
                    reportBothOperators(node); | 
						|
                } else { | 
						|
                    if (TARGET_NODE_TYPE.test(node.parent.type) && | 
						|
                        isMixedWithParent(node) && | 
						|
                        !shouldIgnore(node) | 
						|
                    ) { | 
						|
                        reportBothOperators(node); | 
						|
                    } | 
						|
                } | 
						|
            } | 
						|
 | 
						|
        } | 
						|
 | 
						|
        return { | 
						|
            BinaryExpression: check, | 
						|
            LogicalExpression: check | 
						|
 | 
						|
        }; | 
						|
    } | 
						|
};
 | 
						|
 |