1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | module.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 |
|
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 |
|
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 | };
|