UNPKG

8.72 kBJavaScriptView Raw
1/**
2 * @fileoverview Disallow parenthesesisng higher precedence subexpressions.
3 * @author Michael Ficarra
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11module.exports = function(context) {
12
13 function isParenthesised(node) {
14 var tokens = context.getTokens(node, 1, 1),
15 firstToken = tokens[0],
16 lastToken = tokens[tokens.length - 1];
17 return firstToken.value === "(" && firstToken.range[1] <= node.range[0] &&
18 lastToken.value === ")" && lastToken.range[0] >= node.range[1];
19 }
20
21 function isParenthesisedTwice(node) {
22 var tokens = context.getTokens(node, 2, 2),
23 firstToken = tokens[0],
24 lastToken = tokens[tokens.length - 1];
25 return isParenthesised(node) &&
26 firstToken.value === "(" && firstToken.range[1] <= node.range[0] &&
27 lastToken.value === ")" && lastToken.range[0] >= node.range[1];
28 }
29
30 function precedence(node) {
31
32 switch(node.type) {
33 case "SequenceExpression":
34 return 0;
35
36 case "AssignmentExpression":
37 return 1;
38
39 case "ConditionalExpression":
40 return 3;
41
42 case "LogicalExpression":
43 switch(node.operator) {
44 case "||":
45 return 4;
46 case "&&":
47 return 5;
48 }
49
50 /* falls through */
51 case "BinaryExpression":
52 switch(node.operator) {
53 case "|":
54 return 6;
55 case "^":
56 return 7;
57 case "&":
58 return 8;
59 case "==":
60 case "!=":
61 case "===":
62 case "!==":
63 return 9;
64 case "<":
65 case "<=":
66 case ">":
67 case ">=":
68 case "in":
69 case "instanceof":
70 return 10;
71 case "<<":
72 case ">>":
73 case ">>>":
74 return 11;
75 case "+":
76 case "-":
77 return 12;
78 case "*":
79 case "/":
80 case "%":
81 return 13;
82 }
83 /* falls through */
84 case "UnaryExpression":
85 return 14;
86 case "UpdateExpression":
87 return 15;
88 case "CallExpression":
89 return 16;
90 case "NewExpression":
91 return 17;
92 }
93 return 18;
94 }
95
96 function report(node) {
97 context.report(node, "Gratuitous parentheses around expression.");
98 }
99
100 function dryUnaryUpdate(node) {
101 if(isParenthesised(node.argument) && precedence(node.argument) >= precedence(node)) {
102 report(node.argument);
103 }
104 }
105
106 function dryCallNew(node) {
107 if(isParenthesised(node.callee) && precedence(node.callee) >= precedence(node)) {
108 report(node.callee);
109 }
110 if(node.arguments.length === 1) {
111 if(isParenthesisedTwice(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) {
112 report(node.arguments[0]);
113 }
114 } else {
115 [].forEach.call(node.arguments, function(arg){
116 if(isParenthesised(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) {
117 report(arg);
118 }
119 });
120 }
121 }
122
123 function dryBinaryLogical(node) {
124 var prec = precedence(node);
125 if(isParenthesised(node.left) && precedence(node.left) >= prec) {
126 report(node.left);
127 }
128 if(isParenthesised(node.right) && precedence(node.right) > prec) {
129 report(node.right);
130 }
131 }
132
133 return {
134 "ArrayExpression": function(node) {
135 [].forEach.call(node.elements, function(e) {
136 if(e && isParenthesised(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) {
137 report(e);
138 }
139 });
140 },
141 "AssignmentExpression": function(node) {
142 if(isParenthesised(node.right) && precedence(node.right) >= precedence(node)) {
143 report(node.right);
144 }
145 },
146 "BinaryExpression": dryBinaryLogical,
147 "CallExpression": dryCallNew,
148 "ConditionalExpression": function(node) {
149 if(isParenthesised(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) {
150 report(node.test);
151 }
152 if(isParenthesised(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) {
153 report(node.consequent);
154 }
155 if(isParenthesised(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) {
156 report(node.alternate);
157 }
158 },
159 "DoWhileStatement": function(node) {
160 if(isParenthesisedTwice(node.test)) { report(node.test); }
161 },
162 "ExpressionStatement": function(node) {
163 var tokens;
164 if(isParenthesised(node.expression)) {
165 tokens = context.getTokens(node.expression);
166 if(tokens[0].value !== "function" && tokens[0].value !== "{") {
167 report(node.expression);
168 }
169 }
170 },
171 "ForInStatement": function(node) {
172 if(isParenthesised(node.right)) { report(node.right); }
173 },
174 "ForStatement": function(node) {
175 if(node.init && isParenthesised(node.init)) { report(node.init); }
176 if(node.test && isParenthesised(node.test)) { report(node.test); }
177 if(node.update && isParenthesised(node.update)) { report(node.update); }
178 },
179 "IfStatement": function(node) {
180 if(isParenthesisedTwice(node.test)) { report(node.test); }
181 },
182 "LogicalExpression": dryBinaryLogical,
183 "MemberExpression": function(node) {
184 if(
185 isParenthesised(node.object) &&
186 precedence(node.object) >= precedence(node) &&
187 (
188 node.computed ||
189 !(
190 node.object.type === "Literal" &&
191 typeof node.object.value === "number" &&
192 /^[0-9]+$/.test(context.getTokens(node.object)[0].value)
193 )
194 )
195 ) {
196 report(node.object);
197 }
198 },
199 "NewExpression": dryCallNew,
200 "ObjectExpression": function(node) {
201 [].forEach.call(node.properties, function(e) {
202 var v = e.value;
203 if(v && isParenthesised(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) {
204 report(v);
205 }
206 });
207 },
208 "ReturnStatement": function(node) {
209 if(node.argument && isParenthesised(node.argument)) { report(node.argument); }
210 },
211 "SequenceExpression": function(node) {
212 [].forEach.call(node.expressions, function(e) {
213 if(isParenthesised(e)) { report(e); }
214 });
215 },
216 "SwitchCase": function(node) {
217 if(node.test && isParenthesised(node.test)) { report(node.test); }
218 },
219 "SwitchStatement": function(node) {
220 if(isParenthesisedTwice(node.discriminant)) { report(node.discriminant); }
221 },
222 "ThrowStatement": function(node) {
223 if(isParenthesised(node.argument)) { report(node.argument); }
224 },
225 "UnaryExpression": dryUnaryUpdate,
226 "UpdateExpression": dryUnaryUpdate,
227 "VariableDeclarator": function(node) {
228 if(node.init && isParenthesised(node.init) && precedence(node.init) >= precedence({type: "AssignmentExpression"})) {
229 report(node.init);
230 }
231 },
232 "WhileStatement": function(node) {
233 if(isParenthesisedTwice(node.test)) { report(node.test); }
234 },
235 "WithStatement": function(node) {
236 if(isParenthesisedTwice(node.object)) { report(node.object); }
237 }
238 };
239
240};