massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-mixed-operators.js
1 /**
2  * @fileoverview Rule to disallow mixed binary operators.
3  * @author Toru Nagashima
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils.js");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 const ARITHMETIC_OPERATORS = ["+", "-", "*", "/", "%", "**"];
19 const BITWISE_OPERATORS = ["&", "|", "^", "~", "<<", ">>", ">>>"];
20 const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="];
21 const LOGICAL_OPERATORS = ["&&", "||"];
22 const RELATIONAL_OPERATORS = ["in", "instanceof"];
23 const TERNARY_OPERATOR = ["?:"];
24 const COALESCE_OPERATOR = ["??"];
25 const ALL_OPERATORS = [].concat(
26     ARITHMETIC_OPERATORS,
27     BITWISE_OPERATORS,
28     COMPARISON_OPERATORS,
29     LOGICAL_OPERATORS,
30     RELATIONAL_OPERATORS,
31     TERNARY_OPERATOR,
32     COALESCE_OPERATOR
33 );
34 const DEFAULT_GROUPS = [
35     ARITHMETIC_OPERATORS,
36     BITWISE_OPERATORS,
37     COMPARISON_OPERATORS,
38     LOGICAL_OPERATORS,
39     RELATIONAL_OPERATORS
40 ];
41 const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u;
42
43 /**
44  * Normalizes options.
45  * @param {Object|undefined} options A options object to normalize.
46  * @returns {Object} Normalized option object.
47  */
48 function normalizeOptions(options = {}) {
49     const hasGroups = options.groups && options.groups.length > 0;
50     const groups = hasGroups ? options.groups : DEFAULT_GROUPS;
51     const allowSamePrecedence = options.allowSamePrecedence !== false;
52
53     return {
54         groups,
55         allowSamePrecedence
56     };
57 }
58
59 /**
60  * Checks whether any group which includes both given operator exists or not.
61  * @param {Array.<string[]>} groups A list of groups to check.
62  * @param {string} left An operator.
63  * @param {string} right Another operator.
64  * @returns {boolean} `true` if such group existed.
65  */
66 function includesBothInAGroup(groups, left, right) {
67     return groups.some(group => group.indexOf(left) !== -1 && group.indexOf(right) !== -1);
68 }
69
70 /**
71  * Checks whether the given node is a conditional expression and returns the test node else the left node.
72  * @param {ASTNode} node A node which can be a BinaryExpression or a LogicalExpression node.
73  * This parent node can be BinaryExpression, LogicalExpression
74  *      , or a ConditionalExpression node
75  * @returns {ASTNode} node the appropriate node(left or test).
76  */
77 function getChildNode(node) {
78     return node.type === "ConditionalExpression" ? node.test : node.left;
79 }
80
81 //------------------------------------------------------------------------------
82 // Rule Definition
83 //------------------------------------------------------------------------------
84
85 module.exports = {
86     meta: {
87         type: "suggestion",
88
89         docs: {
90             description: "disallow mixed binary operators",
91             category: "Stylistic Issues",
92             recommended: false,
93             url: "https://eslint.org/docs/rules/no-mixed-operators"
94         },
95
96         schema: [
97             {
98                 type: "object",
99                 properties: {
100                     groups: {
101                         type: "array",
102                         items: {
103                             type: "array",
104                             items: { enum: ALL_OPERATORS },
105                             minItems: 2,
106                             uniqueItems: true
107                         },
108                         uniqueItems: true
109                     },
110                     allowSamePrecedence: {
111                         type: "boolean",
112                         default: true
113                     }
114                 },
115                 additionalProperties: false
116             }
117         ],
118
119         messages: {
120             unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'. Use parentheses to clarify the intended order of operations."
121         }
122     },
123
124     create(context) {
125         const sourceCode = context.getSourceCode();
126         const options = normalizeOptions(context.options[0]);
127
128         /**
129          * Checks whether a given node should be ignored by options or not.
130          * @param {ASTNode} node A node to check. This is a BinaryExpression
131          *      node or a LogicalExpression node. This parent node is one of
132          *      them, too.
133          * @returns {boolean} `true` if the node should be ignored.
134          */
135         function shouldIgnore(node) {
136             const a = node;
137             const b = node.parent;
138
139             return (
140                 !includesBothInAGroup(options.groups, a.operator, b.type === "ConditionalExpression" ? "?:" : b.operator) ||
141                 (
142                     options.allowSamePrecedence &&
143                     astUtils.getPrecedence(a) === astUtils.getPrecedence(b)
144                 )
145             );
146         }
147
148         /**
149          * Checks whether the operator of a given node is mixed with parent
150          * node's operator or not.
151          * @param {ASTNode} node A node to check. This is a BinaryExpression
152          *      node or a LogicalExpression node. This parent node is one of
153          *      them, too.
154          * @returns {boolean} `true` if the node was mixed.
155          */
156         function isMixedWithParent(node) {
157
158             return (
159                 node.operator !== node.parent.operator &&
160                 !astUtils.isParenthesised(sourceCode, node)
161             );
162         }
163
164         /**
165          * Gets the operator token of a given node.
166          * @param {ASTNode} node A node to check. This is a BinaryExpression
167          *      node or a LogicalExpression node.
168          * @returns {Token} The operator token of the node.
169          */
170         function getOperatorToken(node) {
171             return sourceCode.getTokenAfter(getChildNode(node), astUtils.isNotClosingParenToken);
172         }
173
174         /**
175          * Reports both the operator of a given node and the operator of the
176          * parent node.
177          * @param {ASTNode} node A node to check. This is a BinaryExpression
178          *      node or a LogicalExpression node. This parent node is one of
179          *      them, too.
180          * @returns {void}
181          */
182         function reportBothOperators(node) {
183             const parent = node.parent;
184             const left = (getChildNode(parent) === node) ? node : parent;
185             const right = (getChildNode(parent) !== node) ? node : parent;
186             const data = {
187                 leftOperator: left.operator || "?:",
188                 rightOperator: right.operator || "?:"
189             };
190
191             context.report({
192                 node: left,
193                 loc: getOperatorToken(left).loc,
194                 messageId: "unexpectedMixedOperator",
195                 data
196             });
197             context.report({
198                 node: right,
199                 loc: getOperatorToken(right).loc,
200                 messageId: "unexpectedMixedOperator",
201                 data
202             });
203         }
204
205         /**
206          * Checks between the operator of this node and the operator of the
207          * parent node.
208          * @param {ASTNode} node A node to check.
209          * @returns {void}
210          */
211         function check(node) {
212             if (
213                 TARGET_NODE_TYPE.test(node.parent.type) &&
214                 isMixedWithParent(node) &&
215                 !shouldIgnore(node)
216             ) {
217                 reportBothOperators(node);
218             }
219         }
220
221         return {
222             BinaryExpression: check,
223             LogicalExpression: check
224         };
225     }
226 };