UNPKG

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