]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-cond-assign.js
42f75af7d0c7d2857cddfb5e45e331f4694326ff
[pve-eslint.git] / eslint / lib / rules / no-cond-assign.js
1 /**
2 * @fileoverview Rule to flag assignment in a conditional statement's test expression
3 * @author Stephen Murray <spmurrayzzz>
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 const TEST_CONDITION_PARENT_TYPES = new Set(["IfStatement", "WhileStatement", "DoWhileStatement", "ForStatement", "ConditionalExpression"]);
19
20 const NODE_DESCRIPTIONS = {
21 DoWhileStatement: "a 'do...while' statement",
22 ForStatement: "a 'for' statement",
23 IfStatement: "an 'if' statement",
24 WhileStatement: "a 'while' statement"
25 };
26
27 //------------------------------------------------------------------------------
28 // Rule Definition
29 //------------------------------------------------------------------------------
30
31 module.exports = {
32 meta: {
33 type: "problem",
34
35 docs: {
36 description: "disallow assignment operators in conditional expressions",
37 recommended: true,
38 url: "https://eslint.org/docs/rules/no-cond-assign"
39 },
40
41 schema: [
42 {
43 enum: ["except-parens", "always"]
44 }
45 ],
46
47 messages: {
48 unexpected: "Unexpected assignment within {{type}}.",
49
50 // must match JSHint's error message
51 missing: "Expected a conditional expression and instead saw an assignment."
52 }
53 },
54
55 create(context) {
56
57 const prohibitAssign = (context.options[0] || "except-parens");
58
59 const sourceCode = context.getSourceCode();
60
61 /**
62 * Check whether an AST node is the test expression for a conditional statement.
63 * @param {!Object} node The node to test.
64 * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`.
65 */
66 function isConditionalTestExpression(node) {
67 return node.parent &&
68 TEST_CONDITION_PARENT_TYPES.has(node.parent.type) &&
69 node === node.parent.test;
70 }
71
72 /**
73 * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement.
74 * @param {!Object} node The node to use at the start of the search.
75 * @returns {?Object} The closest ancestor node that represents a conditional statement.
76 */
77 function findConditionalAncestor(node) {
78 let currentAncestor = node;
79
80 do {
81 if (isConditionalTestExpression(currentAncestor)) {
82 return currentAncestor.parent;
83 }
84 } while ((currentAncestor = currentAncestor.parent) && !astUtils.isFunction(currentAncestor));
85
86 return null;
87 }
88
89 /**
90 * Check whether the code represented by an AST node is enclosed in two sets of parentheses.
91 * @param {!Object} node The node to test.
92 * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
93 */
94 function isParenthesisedTwice(node) {
95 const previousToken = sourceCode.getTokenBefore(node, 1),
96 nextToken = sourceCode.getTokenAfter(node, 1);
97
98 return astUtils.isParenthesised(sourceCode, node) &&
99 previousToken && astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
100 astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
101 }
102
103 /**
104 * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses.
105 * @param {!Object} node The node for the conditional statement.
106 * @returns {void}
107 */
108 function testForAssign(node) {
109 if (node.test &&
110 (node.test.type === "AssignmentExpression") &&
111 (node.type === "ForStatement"
112 ? !astUtils.isParenthesised(sourceCode, node.test)
113 : !isParenthesisedTwice(node.test)
114 )
115 ) {
116
117 context.report({
118 node: node.test,
119 messageId: "missing"
120 });
121 }
122 }
123
124 /**
125 * Check whether an assignment expression is descended from a conditional statement's test expression.
126 * @param {!Object} node The node for the assignment expression.
127 * @returns {void}
128 */
129 function testForConditionalAncestor(node) {
130 const ancestor = findConditionalAncestor(node);
131
132 if (ancestor) {
133 context.report({
134 node,
135 messageId: "unexpected",
136 data: {
137 type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
138 }
139 });
140 }
141 }
142
143 if (prohibitAssign === "always") {
144 return {
145 AssignmentExpression: testForConditionalAncestor
146 };
147 }
148
149 return {
150 DoWhileStatement: testForAssign,
151 ForStatement: testForAssign,
152 IfStatement: testForAssign,
153 WhileStatement: testForAssign,
154 ConditionalExpression: testForAssign
155 };
156
157 }
158 };