| /** |
| * @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 ALL_OPERATORS = [].concat( |
| ARITHMETIC_OPERATORS, |
| BITWISE_OPERATORS, |
| COMPARISON_OPERATORS, |
| LOGICAL_OPERATORS, |
| RELATIONAL_OPERATORS |
| ); |
| const DEFAULT_GROUPS = [ |
| ARITHMETIC_OPERATORS, |
| BITWISE_OPERATORS, |
| COMPARISON_OPERATORS, |
| LOGICAL_OPERATORS, |
| RELATIONAL_OPERATORS |
| ]; |
| const TARGET_NODE_TYPE = /^(?:Binary|Logical)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); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // 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.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) |
| ); |
| } |
| |
| /** |
| * 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(node.left, 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 = (parent.left === node) ? node : parent; |
| const right = (parent.left !== 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.start, |
| message, |
| data |
| }); |
| context.report({ |
| node: right, |
| loc: getOperatorToken(right).loc.start, |
| 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) && |
| isMixedWithParent(node) && |
| !shouldIgnore(node) |
| ) { |
| reportBothOperators(node); |
| } |
| } |
| |
| return { |
| BinaryExpression: check, |
| LogicalExpression: check |
| }; |
| } |
| }; |