2 * @fileoverview Disallow parenthesesisng higher precedence subexpressions.
3 * @author Michael Ficarra
4 * @copyright 2014 Michael Ficarra. All rights reserved.
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 module.exports = function(context) {
14 function isParenthesised(node) {
15 var previousToken = context.getTokenBefore(node),
16 nextToken = context.getTokenAfter(node);
18 return previousToken && nextToken &&
19 previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
20 nextToken.value === ")" && nextToken.range[0] >= node.range[1];
23 function isParenthesisedTwice(node) {
24 var previousToken = context.getTokenBefore(node, 1),
25 nextToken = context.getTokenAfter(node, 1);
27 return isParenthesised(node) && previousToken && nextToken &&
28 previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
29 nextToken.value === ")" && nextToken.range[0] >= node.range[1];
32 function precedence(node) {
35 case "SequenceExpression":
38 case "AssignmentExpression":
39 case "YieldExpression":
42 case "ConditionalExpression":
45 case "LogicalExpression":
46 switch (node.operator) {
55 case "BinaryExpression":
56 switch (node.operator) {
89 case "UnaryExpression":
91 case "UpdateExpression":
93 case "CallExpression":
94 // IIFE is allowed to have parens in any position (#655)
95 if (node.callee.type === "FunctionExpression") {
106 function report(node) {
107 var previousToken = context.getTokenBefore(node);
108 context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression.");
111 function dryUnaryUpdate(node) {
112 if (isParenthesised(node.argument) && precedence(node.argument) >= precedence(node)) {
113 report(node.argument);
117 function dryCallNew(node) {
118 if (isParenthesised(node.callee) && precedence(node.callee) >= precedence(node) &&
119 !(node.type === "CallExpression" && node.callee.type === "FunctionExpression")) {
122 if (node.arguments.length === 1) {
123 if (isParenthesisedTwice(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) {
124 report(node.arguments[0]);
127 [].forEach.call(node.arguments, function(arg) {
128 if (isParenthesised(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) {
135 function dryBinaryLogical(node) {
136 var prec = precedence(node);
137 if (isParenthesised(node.left) && precedence(node.left) >= prec) {
140 if (isParenthesised(node.right) && precedence(node.right) > prec) {
146 "ArrayExpression": function(node) {
147 [].forEach.call(node.elements, function(e) {
148 if (e && isParenthesised(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) {
153 "AssignmentExpression": function(node) {
154 if (isParenthesised(node.right) && precedence(node.right) >= precedence(node)) {
158 "BinaryExpression": dryBinaryLogical,
159 "CallExpression": dryCallNew,
160 "ConditionalExpression": function(node) {
161 if (isParenthesised(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) {
164 if (isParenthesised(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) {
165 report(node.consequent);
167 if (isParenthesised(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) {
168 report(node.alternate);
171 "DoWhileStatement": function(node) {
172 if (isParenthesisedTwice(node.test)) {
176 "ExpressionStatement": function(node) {
178 if (isParenthesised(node.expression)) {
179 firstToken = context.getFirstToken(node.expression);
180 if (firstToken.value !== "function" && firstToken.value !== "{") {
181 report(node.expression);
185 "ForInStatement": function(node) {
186 if (isParenthesised(node.right)) {
190 "ForOfStatement": function(node) {
191 if (isParenthesised(node.right)) {
195 "ForStatement": function(node) {
196 if (node.init && isParenthesised(node.init)) {
200 if (node.test && isParenthesised(node.test)) {
204 if (node.update && isParenthesised(node.update)) {
208 "IfStatement": function(node) {
209 if (isParenthesisedTwice(node.test)) {
213 "LogicalExpression": dryBinaryLogical,
214 "MemberExpression": function(node) {
216 isParenthesised(node.object) &&
217 precedence(node.object) >= precedence(node) &&
221 (node.object.type === "Literal" &&
222 typeof node.object.value === "number" &&
223 /^[0-9]+$/.test(context.getFirstToken(node.object).value))
225 // RegExp literal is allowed to have parens (#1589)
226 (node.object.type === "Literal" && node.object.regex)
233 "NewExpression": dryCallNew,
234 "ObjectExpression": function(node) {
235 [].forEach.call(node.properties, function(e) {
237 if (v && isParenthesised(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) {
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);
249 "SequenceExpression": function(node) {
250 [].forEach.call(node.expressions, function(e) {
251 if (isParenthesised(e) && precedence(e) >= precedence(node)) {
256 "SwitchCase": function(node) {
257 if (node.test && isParenthesised(node.test)) {
261 "SwitchStatement": function(node) {
262 if (isParenthesisedTwice(node.discriminant)) {
263 report(node.discriminant);
266 "ThrowStatement": function(node) {
267 if (isParenthesised(node.argument)) {
268 report(node.argument);
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)) {
281 "WhileStatement": function(node) {
282 if (isParenthesisedTwice(node.test)) {
286 "WithStatement": function(node) {
287 if (isParenthesisedTwice(node.object)) {