UNPKG

4.9 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 },
27
28 schema: [
29 {
30 enum: ["except-parens", "always"]
31 }
32 ]
33 },
34
35 create(context) {
36
37 const prohibitAssign = (context.options[0] || "except-parens");
38
39 const sourceCode = context.getSourceCode();
40
41 /**
42 * Check whether an AST node is the test expression for a conditional statement.
43 * @param {!Object} node The node to test.
44 * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`.
45 */
46 function isConditionalTestExpression(node) {
47 return node.parent &&
48 node.parent.test &&
49 node === node.parent.test;
50 }
51
52 /**
53 * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement.
54 * @param {!Object} node The node to use at the start of the search.
55 * @returns {?Object} The closest ancestor node that represents a conditional statement.
56 */
57 function findConditionalAncestor(node) {
58 let currentAncestor = node;
59
60 do {
61 if (isConditionalTestExpression(currentAncestor)) {
62 return currentAncestor.parent;
63 }
64 } while ((currentAncestor = currentAncestor.parent) && !astUtils.isFunction(currentAncestor));
65
66 return null;
67 }
68
69 /**
70 * Check whether the code represented by an AST node is enclosed in two sets of parentheses.
71 * @param {!Object} node The node to test.
72 * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
73 */
74 function isParenthesisedTwice(node) {
75 const previousToken = sourceCode.getTokenBefore(node, 1),
76 nextToken = sourceCode.getTokenAfter(node, 1);
77
78 return astUtils.isParenthesised(sourceCode, node) &&
79 astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
80 astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
81 }
82
83 /**
84 * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses.
85 * @param {!Object} node The node for the conditional statement.
86 * @returns {void}
87 */
88 function testForAssign(node) {
89 if (node.test &&
90 (node.test.type === "AssignmentExpression") &&
91 (node.type === "ForStatement"
92 ? !astUtils.isParenthesised(sourceCode, node.test)
93 : !isParenthesisedTwice(node.test)
94 )
95 ) {
96
97 // must match JSHint's error message
98 context.report({
99 node,
100 loc: node.test.loc.start,
101 message: "Expected a conditional expression and instead saw an assignment."
102 });
103 }
104 }
105
106 /**
107 * Check whether an assignment expression is descended from a conditional statement's test expression.
108 * @param {!Object} node The node for the assignment expression.
109 * @returns {void}
110 */
111 function testForConditionalAncestor(node) {
112 const ancestor = findConditionalAncestor(node);
113
114 if (ancestor) {
115 context.report({
116 node: ancestor,
117 message: "Unexpected assignment within {{type}}.",
118 data: {
119 type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
120 }
121 });
122 }
123 }
124
125 if (prohibitAssign === "always") {
126 return {
127 AssignmentExpression: testForConditionalAncestor
128 };
129 }
130
131 return {
132 DoWhileStatement: testForAssign,
133 ForStatement: testForAssign,
134 IfStatement: testForAssign,
135 WhileStatement: testForAssign
136 };
137
138 }
139};