9b00fe4e32bebaf0ab6fca6b13d6d8075fc48c9d
[platform/framework/web/crosswalk-tizen.git] /
1 /**
2  * @fileoverview Rule to flag assignment in a conditional statement's test expression
3  * @author Stephen Murray <spmurrayzzz>
4  */
5 "use strict";
6
7 var NODE_DESCRIPTIONS = {
8     "DoWhileStatement": "a 'do...while' statement",
9     "ForStatement": "a 'for' statement",
10     "IfStatement": "an 'if' statement",
11     "WhileStatement": "a 'while' statement"
12 };
13
14 //------------------------------------------------------------------------------
15 // Rule Definition
16 //------------------------------------------------------------------------------
17
18 module.exports = function(context) {
19
20     var prohibitAssign = (context.options[0] || "except-parens");
21
22     /**
23      * Check whether an AST node is the test expression for a conditional statement.
24      * @param {!Object} node The node to test.
25      * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`.
26      */
27     function isConditionalTestExpression(node) {
28         return node.parent &&
29             node.parent.test &&
30             node === node.parent.test;
31     }
32
33     /**
34      * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement.
35      * @param {!Object} node The node to use at the start of the search.
36      * @returns {?Object} The closest ancestor node that represents a conditional statement.
37      */
38     function findConditionalAncestor(node) {
39         var currentAncestor = node;
40
41         while ((currentAncestor = currentAncestor.parent)) {
42             if (isConditionalTestExpression(currentAncestor)) {
43                 return currentAncestor.parent;
44             }
45         }
46
47         return null;
48     }
49
50     /**
51      * Check whether the code represented by an AST node is enclosed in parentheses.
52      * @param {!Object} node The node to test.
53      * @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`.
54      */
55     function isParenthesised(node) {
56         var previousToken = context.getTokenBefore(node),
57             nextToken = context.getTokenAfter(node);
58
59         return previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
60             nextToken.value === ")" && nextToken.range[0] >= node.range[1];
61     }
62
63     /**
64      * Check whether the code represented by an AST node is enclosed in two sets of parentheses.
65      * @param {!Object} node The node to test.
66      * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
67      */
68     function isParenthesisedTwice(node) {
69         var previousToken = context.getTokenBefore(node, 1),
70             nextToken = context.getTokenAfter(node, 1);
71
72         return isParenthesised(node) &&
73             previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
74             nextToken.value === ")" && nextToken.range[0] >= node.range[1];
75     }
76
77     /**
78      * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses.
79      * @param {!Object} node The node for the conditional statement.
80      * @returns {void}
81      */
82     function testForAssign(node) {
83         if (node.test && (node.test.type === "AssignmentExpression") && !isParenthesisedTwice(node.test)) {
84             // must match JSHint's error message
85             context.report(node, "Expected a conditional expression and instead saw an assignment.");
86         }
87     }
88
89     /**
90      * Check whether an assignment expression is descended from a conditional statement's test expression.
91      * @param {!Object} node The node for the assignment expression.
92      * @returns {void}
93      */
94     function testForConditionalAncestor(node) {
95         var ancestor = findConditionalAncestor(node);
96
97         if (ancestor) {
98             context.report(ancestor, "Unexpected assignment within {{type}}.", {
99                 type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
100             });
101         }
102     }
103
104     if (prohibitAssign === "always") {
105         return {
106             "AssignmentExpression": testForConditionalAncestor
107         };
108     }
109
110     return {
111         "DoWhileStatement": testForAssign,
112         "ForStatement": testForAssign,
113         "IfStatement": testForAssign,
114         "WhileStatement": testForAssign
115     };
116
117 };