e4903b366ac534d29496e687948e5c5c1e288fde
[platform/framework/web/crosswalk-tizen.git] /
1 /**
2  * @fileoverview Disallow parenthesesisng higher precedence subexpressions.
3  * @author Michael Ficarra
4  * @copyright 2014 Michael Ficarra. All rights reserved.
5  */
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Rule Definition
10 //------------------------------------------------------------------------------
11
12 module.exports = function(context) {
13
14     function isParenthesised(node) {
15         var previousToken = context.getTokenBefore(node),
16             nextToken = context.getTokenAfter(node);
17
18         return previousToken && nextToken &&
19             previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
20             nextToken.value === ")" && nextToken.range[0] >= node.range[1];
21     }
22
23     function isParenthesisedTwice(node) {
24         var previousToken = context.getTokenBefore(node, 1),
25             nextToken = context.getTokenAfter(node, 1);
26
27         return isParenthesised(node) && previousToken && nextToken &&
28             previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
29             nextToken.value === ")" && nextToken.range[0] >= node.range[1];
30     }
31
32     function precedence(node) {
33
34         switch (node.type) {
35             case "SequenceExpression":
36                 return 0;
37
38             case "AssignmentExpression":
39             case "YieldExpression":
40                 return 1;
41
42             case "ConditionalExpression":
43                 return 3;
44
45             case "LogicalExpression":
46                 switch (node.operator) {
47                     case "||":
48                         return 4;
49                     case "&&":
50                         return 5;
51                     // no default
52                 }
53
54                 /* falls through */
55             case "BinaryExpression":
56                 switch (node.operator) {
57                     case "|":
58                         return 6;
59                     case "^":
60                         return 7;
61                     case "&":
62                         return 8;
63                     case "==":
64                     case "!=":
65                     case "===":
66                     case "!==":
67                         return 9;
68                     case "<":
69                     case "<=":
70                     case ">":
71                     case ">=":
72                     case "in":
73                     case "instanceof":
74                         return 10;
75                     case "<<":
76                     case ">>":
77                     case ">>>":
78                         return 11;
79                     case "+":
80                     case "-":
81                         return 12;
82                     case "*":
83                     case "/":
84                     case "%":
85                         return 13;
86                     // no default
87                 }
88                 /* falls through */
89             case "UnaryExpression":
90                 return 14;
91             case "UpdateExpression":
92                 return 15;
93             case "CallExpression":
94                 // IIFE is allowed to have parens in any position (#655)
95                 if (node.callee.type === "FunctionExpression") {
96                     return -1;
97                 }
98                 return 16;
99             case "NewExpression":
100                 return 17;
101             // no default
102         }
103         return 18;
104     }
105
106     function report(node) {
107         var previousToken = context.getTokenBefore(node);
108         context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression.");
109     }
110
111     function dryUnaryUpdate(node) {
112         if (isParenthesised(node.argument) && precedence(node.argument) >= precedence(node)) {
113             report(node.argument);
114         }
115     }
116
117     function dryCallNew(node) {
118         if (isParenthesised(node.callee) && precedence(node.callee) >= precedence(node) &&
119         !(node.type === "CallExpression" && node.callee.type === "FunctionExpression")) {
120             report(node.callee);
121         }
122         if (node.arguments.length === 1) {
123             if (isParenthesisedTwice(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) {
124                 report(node.arguments[0]);
125             }
126         } else {
127             [].forEach.call(node.arguments, function(arg) {
128                 if (isParenthesised(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) {
129                     report(arg);
130                 }
131             });
132         }
133     }
134
135     function dryBinaryLogical(node) {
136         var prec = precedence(node);
137         if (isParenthesised(node.left) && precedence(node.left) >= prec) {
138             report(node.left);
139         }
140         if (isParenthesised(node.right) && precedence(node.right) > prec) {
141             report(node.right);
142         }
143     }
144
145     return {
146         "ArrayExpression": function(node) {
147             [].forEach.call(node.elements, function(e) {
148                 if (e && isParenthesised(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) {
149                     report(e);
150                 }
151             });
152         },
153         "AssignmentExpression": function(node) {
154             if (isParenthesised(node.right) && precedence(node.right) >= precedence(node)) {
155                 report(node.right);
156             }
157         },
158         "BinaryExpression": dryBinaryLogical,
159         "CallExpression": dryCallNew,
160         "ConditionalExpression": function(node) {
161             if (isParenthesised(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) {
162                 report(node.test);
163             }
164             if (isParenthesised(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) {
165                 report(node.consequent);
166             }
167             if (isParenthesised(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) {
168                 report(node.alternate);
169             }
170         },
171         "DoWhileStatement": function(node) {
172             if (isParenthesisedTwice(node.test)) {
173                 report(node.test);
174             }
175         },
176         "ExpressionStatement": function(node) {
177             var firstToken;
178             if (isParenthesised(node.expression)) {
179                 firstToken = context.getFirstToken(node.expression);
180                 if (firstToken.value !== "function" && firstToken.value !== "{") {
181                     report(node.expression);
182                 }
183             }
184         },
185         "ForInStatement": function(node) {
186             if (isParenthesised(node.right)) {
187                 report(node.right);
188             }
189         },
190         "ForOfStatement": function(node) {
191             if (isParenthesised(node.right)) {
192                 report(node.right);
193             }
194         },
195         "ForStatement": function(node) {
196             if (node.init && isParenthesised(node.init)) {
197                 report(node.init);
198             }
199
200             if (node.test && isParenthesised(node.test)) {
201                 report(node.test);
202             }
203
204             if (node.update && isParenthesised(node.update)) {
205                 report(node.update);
206             }
207         },
208         "IfStatement": function(node) {
209             if (isParenthesisedTwice(node.test)) {
210                 report(node.test);
211             }
212         },
213         "LogicalExpression": dryBinaryLogical,
214         "MemberExpression": function(node) {
215             if (
216                 isParenthesised(node.object) &&
217                 precedence(node.object) >= precedence(node) &&
218                 (
219                     node.computed ||
220                     !(
221                         (node.object.type === "Literal" &&
222                         typeof node.object.value === "number" &&
223                         /^[0-9]+$/.test(context.getFirstToken(node.object).value))
224                         ||
225                         // RegExp literal is allowed to have parens (#1589)
226                         (node.object.type === "Literal" && node.object.regex)
227                     )
228                 )
229             ) {
230                 report(node.object);
231             }
232         },
233         "NewExpression": dryCallNew,
234         "ObjectExpression": function(node) {
235             [].forEach.call(node.properties, function(e) {
236                 var v = e.value;
237                 if (v && isParenthesised(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) {
238                     report(v);
239                 }
240             });
241         },
242         "ReturnStatement": function(node) {
243             if (node.argument && isParenthesised(node.argument) &&
244                     // RegExp literal is allowed to have parens (#1589)
245                     !(node.argument.type === "Literal" && node.argument.regex)) {
246                 report(node.argument);
247             }
248         },
249         "SequenceExpression": function(node) {
250             [].forEach.call(node.expressions, function(e) {
251                 if (isParenthesised(e) && precedence(e) >= precedence(node)) {
252                     report(e);
253                 }
254             });
255         },
256         "SwitchCase": function(node) {
257             if (node.test && isParenthesised(node.test)) {
258                 report(node.test);
259             }
260         },
261         "SwitchStatement": function(node) {
262             if (isParenthesisedTwice(node.discriminant)) {
263                 report(node.discriminant);
264             }
265         },
266         "ThrowStatement": function(node) {
267             if (isParenthesised(node.argument)) {
268                 report(node.argument);
269             }
270         },
271         "UnaryExpression": dryUnaryUpdate,
272         "UpdateExpression": dryUnaryUpdate,
273         "VariableDeclarator": function(node) {
274             if (node.init && isParenthesised(node.init) &&
275                     precedence(node.init) >= precedence({type: "AssignmentExpression"}) &&
276                     // RegExp literal is allowed to have parens (#1589)
277                     !(node.init.type === "Literal" && node.init.regex)) {
278                 report(node.init);
279             }
280         },
281         "WhileStatement": function(node) {
282             if (isParenthesisedTwice(node.test)) {
283                 report(node.test);
284             }
285         },
286         "WithStatement": function(node) {
287             if (isParenthesisedTwice(node.object)) {
288                 report(node.object);
289             }
290         }
291     };
292
293 };