UNPKG

38.8 kBJavaScriptView Raw
1/**
2 * @fileoverview Disallow parenthesising higher precedence subexpressions.
3 * @author Michael Ficarra
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11const astUtils = require("./utils/ast-utils.js");
12
13module.exports = {
14 meta: {
15 type: "layout",
16
17 docs: {
18 description: "disallow unnecessary parentheses",
19 category: "Possible Errors",
20 recommended: false,
21 url: "https://eslint.org/docs/rules/no-extra-parens"
22 },
23
24 fixable: "code",
25
26 schema: {
27 anyOf: [
28 {
29 type: "array",
30 items: [
31 {
32 enum: ["functions"]
33 }
34 ],
35 minItems: 0,
36 maxItems: 1
37 },
38 {
39 type: "array",
40 items: [
41 {
42 enum: ["all"]
43 },
44 {
45 type: "object",
46 properties: {
47 conditionalAssign: { type: "boolean" },
48 nestedBinaryExpressions: { type: "boolean" },
49 returnAssign: { type: "boolean" },
50 ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },
51 enforceForArrowConditionals: { type: "boolean" }
52 },
53 additionalProperties: false
54 }
55 ],
56 minItems: 0,
57 maxItems: 2
58 }
59 ]
60 },
61
62 messages: {
63 unexpected: "Unnecessary parentheses around expression."
64 }
65 },
66
67 create(context) {
68 const sourceCode = context.getSourceCode();
69
70 const tokensToIgnore = new WeakSet();
71 const isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode);
72 const precedence = astUtils.getPrecedence;
73 const ALL_NODES = context.options[0] !== "functions";
74 const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
75 const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
76 const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
77 const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
78 const IGNORE_ARROW_CONDITIONALS = ALL_NODES && context.options[1] &&
79 context.options[1].enforceForArrowConditionals === false;
80
81 const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
82 const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
83
84 let reportsBuffer;
85
86 /**
87 * Determines if this rule should be enforced for a node given the current configuration.
88 * @param {ASTNode} node - The node to be checked.
89 * @returns {boolean} True if the rule should be enforced for this node.
90 * @private
91 */
92 function ruleApplies(node) {
93 if (node.type === "JSXElement" || node.type === "JSXFragment") {
94 const isSingleLine = node.loc.start.line === node.loc.end.line;
95
96 switch (IGNORE_JSX) {
97
98 // Exclude this JSX element from linting
99 case "all":
100 return false;
101
102 // Exclude this JSX element if it is multi-line element
103 case "multi-line":
104 return isSingleLine;
105
106 // Exclude this JSX element if it is single-line element
107 case "single-line":
108 return !isSingleLine;
109
110 // Nothing special to be done for JSX elements
111 case "none":
112 break;
113
114 // no default
115 }
116 }
117
118 return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
119 }
120
121 /**
122 * Determines if a node is surrounded by parentheses twice.
123 * @param {ASTNode} node - The node to be checked.
124 * @returns {boolean} True if the node is doubly parenthesised.
125 * @private
126 */
127 function isParenthesisedTwice(node) {
128 const previousToken = sourceCode.getTokenBefore(node, 1),
129 nextToken = sourceCode.getTokenAfter(node, 1);
130
131 return isParenthesised(node) && previousToken && nextToken &&
132 astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
133 astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
134 }
135
136 /**
137 * Determines if a node is surrounded by (potentially) invalid parentheses.
138 * @param {ASTNode} node - The node to be checked.
139 * @returns {boolean} True if the node is incorrectly parenthesised.
140 * @private
141 */
142 function hasExcessParens(node) {
143 return ruleApplies(node) && isParenthesised(node);
144 }
145
146 /**
147 * Determines if a node that is expected to be parenthesised is surrounded by
148 * (potentially) invalid extra parentheses.
149 * @param {ASTNode} node - The node to be checked.
150 * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
151 * @private
152 */
153 function hasDoubleExcessParens(node) {
154 return ruleApplies(node) && isParenthesisedTwice(node);
155 }
156
157 /**
158 * Determines if a node test expression is allowed to have a parenthesised assignment
159 * @param {ASTNode} node - The node to be checked.
160 * @returns {boolean} True if the assignment can be parenthesised.
161 * @private
162 */
163 function isCondAssignException(node) {
164 return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression";
165 }
166
167 /**
168 * Determines if a node is in a return statement
169 * @param {ASTNode} node - The node to be checked.
170 * @returns {boolean} True if the node is in a return statement.
171 * @private
172 */
173 function isInReturnStatement(node) {
174 for (let currentNode = node; currentNode; currentNode = currentNode.parent) {
175 if (
176 currentNode.type === "ReturnStatement" ||
177 (currentNode.type === "ArrowFunctionExpression" && currentNode.body.type !== "BlockStatement")
178 ) {
179 return true;
180 }
181 }
182
183 return false;
184 }
185
186 /**
187 * Determines if a constructor function is newed-up with parens
188 * @param {ASTNode} newExpression - The NewExpression node to be checked.
189 * @returns {boolean} True if the constructor is called with parens.
190 * @private
191 */
192 function isNewExpressionWithParens(newExpression) {
193 const lastToken = sourceCode.getLastToken(newExpression);
194 const penultimateToken = sourceCode.getTokenBefore(lastToken);
195
196 return newExpression.arguments.length > 0 || astUtils.isOpeningParenToken(penultimateToken) && astUtils.isClosingParenToken(lastToken);
197 }
198
199 /**
200 * Determines if a node is or contains an assignment expression
201 * @param {ASTNode} node - The node to be checked.
202 * @returns {boolean} True if the node is or contains an assignment expression.
203 * @private
204 */
205 function containsAssignment(node) {
206 if (node.type === "AssignmentExpression") {
207 return true;
208 }
209 if (node.type === "ConditionalExpression" &&
210 (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) {
211 return true;
212 }
213 if ((node.left && node.left.type === "AssignmentExpression") ||
214 (node.right && node.right.type === "AssignmentExpression")) {
215 return true;
216 }
217
218 return false;
219 }
220
221 /**
222 * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment
223 * @param {ASTNode} node - The node to be checked.
224 * @returns {boolean} True if the assignment can be parenthesised.
225 * @private
226 */
227 function isReturnAssignException(node) {
228 if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) {
229 return false;
230 }
231
232 if (node.type === "ReturnStatement") {
233 return node.argument && containsAssignment(node.argument);
234 }
235 if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
236 return containsAssignment(node.body);
237 }
238 return containsAssignment(node);
239
240 }
241
242 /**
243 * Determines if a node following a [no LineTerminator here] restriction is
244 * surrounded by (potentially) invalid extra parentheses.
245 * @param {Token} token - The token preceding the [no LineTerminator here] restriction.
246 * @param {ASTNode} node - The node to be checked.
247 * @returns {boolean} True if the node is incorrectly parenthesised.
248 * @private
249 */
250 function hasExcessParensNoLineTerminator(token, node) {
251 if (token.loc.end.line === node.loc.start.line) {
252 return hasExcessParens(node);
253 }
254
255 return hasDoubleExcessParens(node);
256 }
257
258 /**
259 * Determines whether a node should be preceded by an additional space when removing parens
260 * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
261 * @returns {boolean} `true` if a space should be inserted before the node
262 * @private
263 */
264 function requiresLeadingSpace(node) {
265 const leftParenToken = sourceCode.getTokenBefore(node);
266 const tokenBeforeLeftParen = sourceCode.getTokenBefore(node, 1);
267 const firstToken = sourceCode.getFirstToken(node);
268
269 return tokenBeforeLeftParen &&
270 tokenBeforeLeftParen.range[1] === leftParenToken.range[0] &&
271 leftParenToken.range[1] === firstToken.range[0] &&
272 !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, firstToken);
273 }
274
275 /**
276 * Determines whether a node should be followed by an additional space when removing parens
277 * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
278 * @returns {boolean} `true` if a space should be inserted after the node
279 * @private
280 */
281 function requiresTrailingSpace(node) {
282 const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 });
283 const rightParenToken = nextTwoTokens[0];
284 const tokenAfterRightParen = nextTwoTokens[1];
285 const tokenBeforeRightParen = sourceCode.getLastToken(node);
286
287 return rightParenToken && tokenAfterRightParen &&
288 !sourceCode.isSpaceBetweenTokens(rightParenToken, tokenAfterRightParen) &&
289 !astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen);
290 }
291
292 /**
293 * Determines if a given expression node is an IIFE
294 * @param {ASTNode} node The node to check
295 * @returns {boolean} `true` if the given node is an IIFE
296 */
297 function isIIFE(node) {
298 return node.type === "CallExpression" && node.callee.type === "FunctionExpression";
299 }
300
301 /**
302 * Report the node
303 * @param {ASTNode} node node to evaluate
304 * @returns {void}
305 * @private
306 */
307 function report(node) {
308 const leftParenToken = sourceCode.getTokenBefore(node);
309 const rightParenToken = sourceCode.getTokenAfter(node);
310
311 if (!isParenthesisedTwice(node)) {
312 if (tokensToIgnore.has(sourceCode.getFirstToken(node))) {
313 return;
314 }
315
316 if (isIIFE(node) && !isParenthesised(node.callee)) {
317 return;
318 }
319 }
320
321 /**
322 * Finishes reporting
323 * @returns {void}
324 * @private
325 */
326 function finishReport() {
327 context.report({
328 node,
329 loc: leftParenToken.loc.start,
330 messageId: "unexpected",
331 fix(fixer) {
332 const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);
333
334 return fixer.replaceTextRange([
335 leftParenToken.range[0],
336 rightParenToken.range[1]
337 ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
338 }
339 });
340 }
341
342 if (reportsBuffer) {
343 reportsBuffer.reports.push({ node, finishReport });
344 return;
345 }
346
347 finishReport();
348 }
349
350 /**
351 * Evaluate Unary update
352 * @param {ASTNode} node node to evaluate
353 * @returns {void}
354 * @private
355 */
356 function checkUnaryUpdate(node) {
357 if (node.type === "UnaryExpression" && node.argument.type === "BinaryExpression" && node.argument.operator === "**") {
358 return;
359 }
360
361 if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) {
362 report(node.argument);
363 }
364 }
365
366 /**
367 * Check if a member expression contains a call expression
368 * @param {ASTNode} node MemberExpression node to evaluate
369 * @returns {boolean} true if found, false if not
370 */
371 function doesMemberExpressionContainCallExpression(node) {
372 let currentNode = node.object;
373 let currentNodeType = node.object.type;
374
375 while (currentNodeType === "MemberExpression") {
376 currentNode = currentNode.object;
377 currentNodeType = currentNode.type;
378 }
379
380 return currentNodeType === "CallExpression";
381 }
382
383 /**
384 * Evaluate a new call
385 * @param {ASTNode} node node to evaluate
386 * @returns {void}
387 * @private
388 */
389 function checkCallNew(node) {
390 const callee = node.callee;
391
392 if (hasExcessParens(callee) && precedence(callee) >= precedence(node)) {
393 const hasNewParensException = callee.type === "NewExpression" && !isNewExpressionWithParens(callee);
394
395 if (
396 hasDoubleExcessParens(callee) ||
397 !isIIFE(node) && !hasNewParensException && !(
398
399 /*
400 * Allow extra parens around a new expression if
401 * there are intervening parentheses.
402 */
403 (callee.type === "MemberExpression" && doesMemberExpressionContainCallExpression(callee))
404 )
405 ) {
406 report(node.callee);
407 }
408 }
409 if (node.arguments.length === 1) {
410 if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
411 report(node.arguments[0]);
412 }
413 } else {
414 node.arguments
415 .filter(arg => hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)
416 .forEach(report);
417 }
418 }
419
420 /**
421 * Evaluate binary logicals
422 * @param {ASTNode} node node to evaluate
423 * @returns {void}
424 * @private
425 */
426 function checkBinaryLogical(node) {
427 const prec = precedence(node);
428 const leftPrecedence = precedence(node.left);
429 const rightPrecedence = precedence(node.right);
430 const isExponentiation = node.operator === "**";
431 const shouldSkipLeft = (NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression")) ||
432 node.left.type === "UnaryExpression" && isExponentiation;
433 const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");
434
435 if (!shouldSkipLeft && hasExcessParens(node.left) && (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation))) {
436 report(node.left);
437 }
438 if (!shouldSkipRight && hasExcessParens(node.right) && (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation))) {
439 report(node.right);
440 }
441 }
442
443 /**
444 * Check the parentheses around the super class of the given class definition.
445 * @param {ASTNode} node The node of class declarations to check.
446 * @returns {void}
447 */
448 function checkClass(node) {
449 if (!node.superClass) {
450 return;
451 }
452
453 /*
454 * If `node.superClass` is a LeftHandSideExpression, parentheses are extra.
455 * Otherwise, parentheses are needed.
456 */
457 const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR
458 ? hasExcessParens(node.superClass)
459 : hasDoubleExcessParens(node.superClass);
460
461 if (hasExtraParens) {
462 report(node.superClass);
463 }
464 }
465
466 /**
467 * Check the parentheses around the argument of the given spread operator.
468 * @param {ASTNode} node The node of spread elements/properties to check.
469 * @returns {void}
470 */
471 function checkSpreadOperator(node) {
472 const hasExtraParens = precedence(node.argument) >= PRECEDENCE_OF_ASSIGNMENT_EXPR
473 ? hasExcessParens(node.argument)
474 : hasDoubleExcessParens(node.argument);
475
476 if (hasExtraParens) {
477 report(node.argument);
478 }
479 }
480
481 /**
482 * Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration
483 * @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node
484 * @returns {void}
485 */
486 function checkExpressionOrExportStatement(node) {
487 const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);
488 const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken);
489 const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null;
490 const tokenAfterClosingParens = secondToken ? sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken) : null;
491
492 if (
493 astUtils.isOpeningParenToken(firstToken) &&
494 (
495 astUtils.isOpeningBraceToken(secondToken) ||
496 secondToken.type === "Keyword" && (
497 secondToken.value === "function" ||
498 secondToken.value === "class" ||
499 secondToken.value === "let" &&
500 tokenAfterClosingParens &&
501 (
502 astUtils.isOpeningBracketToken(tokenAfterClosingParens) ||
503 tokenAfterClosingParens.type === "Identifier"
504 )
505 ) ||
506 secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && thirdToken && thirdToken.type === "Keyword" && thirdToken.value === "function"
507 )
508 ) {
509 tokensToIgnore.add(secondToken);
510 }
511
512 if (hasExcessParens(node)) {
513 report(node);
514 }
515 }
516
517 /**
518 * Finds the path from the given node to the specified ancestor.
519 * @param {ASTNode} node First node in the path.
520 * @param {ASTNode} ancestor Last node in the path.
521 * @returns {ASTNode[]} Path, including both nodes.
522 * @throws {Error} If the given node does not have the specified ancestor.
523 */
524 function pathToAncestor(node, ancestor) {
525 const path = [node];
526 let currentNode = node;
527
528 while (currentNode !== ancestor) {
529
530 currentNode = currentNode.parent;
531
532 /* istanbul ignore if */
533 if (currentNode === null) {
534 throw new Error("Nodes are not in the ancestor-descendant relationship.");
535 }
536
537 path.push(currentNode);
538 }
539
540 return path;
541 }
542
543 /**
544 * Finds the path from the given node to the specified descendant.
545 * @param {ASTNode} node First node in the path.
546 * @param {ASTNode} descendant Last node in the path.
547 * @returns {ASTNode[]} Path, including both nodes.
548 * @throws {Error} If the given node does not have the specified descendant.
549 */
550 function pathToDescendant(node, descendant) {
551 return pathToAncestor(descendant, node).reverse();
552 }
553
554 /**
555 * Checks whether the syntax of the given ancestor of an 'in' expression inside a for-loop initializer
556 * is preventing the 'in' keyword from being interpreted as a part of an ill-formed for-in loop.
557 *
558 * @param {ASTNode} node Ancestor of an 'in' expression.
559 * @param {ASTNode} child Child of the node, ancestor of the same 'in' expression or the 'in' expression itself.
560 * @returns {boolean} True if the keyword 'in' would be interpreted as the 'in' operator, without any parenthesis.
561 */
562 function isSafelyEnclosingInExpression(node, child) {
563 switch (node.type) {
564 case "ArrayExpression":
565 case "ArrayPattern":
566 case "BlockStatement":
567 case "ObjectExpression":
568 case "ObjectPattern":
569 case "TemplateLiteral":
570 return true;
571 case "ArrowFunctionExpression":
572 case "FunctionExpression":
573 return node.params.includes(child);
574 case "CallExpression":
575 case "NewExpression":
576 return node.arguments.includes(child);
577 case "MemberExpression":
578 return node.computed && node.property === child;
579 case "ConditionalExpression":
580 return node.consequent === child;
581 default:
582 return false;
583 }
584 }
585
586 /**
587 * Starts a new reports buffering. Warnings will be stored in a buffer instead of being reported immediately.
588 * An additional logic that requires multiple nodes (e.g. a whole subtree) may dismiss some of the stored warnings.
589 *
590 * @returns {void}
591 */
592 function startNewReportsBuffering() {
593 reportsBuffer = {
594 upper: reportsBuffer,
595 inExpressionNodes: [],
596 reports: []
597 };
598 }
599
600 /**
601 * Ends the current reports buffering.
602 * @returns {void}
603 */
604 function endCurrentReportsBuffering() {
605 const { upper, inExpressionNodes, reports } = reportsBuffer;
606
607 if (upper) {
608 upper.inExpressionNodes.push(...inExpressionNodes);
609 upper.reports.push(...reports);
610 } else {
611
612 // flush remaining reports
613 reports.forEach(({ finishReport }) => finishReport());
614 }
615
616 reportsBuffer = upper;
617 }
618
619 /**
620 * Checks whether the given node is in the current reports buffer.
621 * @param {ASTNode} node Node to check.
622 * @returns {boolean} True if the node is in the current buffer, false otherwise.
623 */
624 function isInCurrentReportsBuffer(node) {
625 return reportsBuffer.reports.some(r => r.node === node);
626 }
627
628 /**
629 * Removes the given node from the current reports buffer.
630 * @param {ASTNode} node Node to remove.
631 * @returns {void}
632 */
633 function removeFromCurrentReportsBuffer(node) {
634 reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node);
635 }
636
637 return {
638 ArrayExpression(node) {
639 node.elements
640 .filter(e => e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)
641 .forEach(report);
642 },
643
644 ArrowFunctionExpression(node) {
645 if (isReturnAssignException(node)) {
646 return;
647 }
648
649 if (node.body.type === "ConditionalExpression" &&
650 IGNORE_ARROW_CONDITIONALS &&
651 !isParenthesisedTwice(node.body)
652 ) {
653 return;
654 }
655
656 if (node.body.type !== "BlockStatement") {
657 const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken);
658 const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);
659
660 if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) {
661 tokensToIgnore.add(firstBodyToken);
662 }
663 if (hasExcessParens(node.body) && precedence(node.body) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
664 report(node.body);
665 }
666 }
667 },
668
669 AssignmentExpression(node) {
670 if (isReturnAssignException(node)) {
671 return;
672 }
673
674 if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) {
675 report(node.right);
676 }
677 },
678
679 BinaryExpression(node) {
680 if (reportsBuffer && node.operator === "in") {
681 reportsBuffer.inExpressionNodes.push(node);
682 }
683
684 checkBinaryLogical(node);
685 },
686
687 CallExpression: checkCallNew,
688
689 ConditionalExpression(node) {
690 if (isReturnAssignException(node)) {
691 return;
692 }
693
694 if (hasExcessParens(node.test) && precedence(node.test) >= precedence({ type: "LogicalExpression", operator: "||" })) {
695 report(node.test);
696 }
697
698 if (hasExcessParens(node.consequent) && precedence(node.consequent) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
699 report(node.consequent);
700 }
701
702 if (hasExcessParens(node.alternate) && precedence(node.alternate) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
703 report(node.alternate);
704 }
705 },
706
707 DoWhileStatement(node) {
708 if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
709 report(node.test);
710 }
711 },
712
713 ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
714 ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
715
716 "ForInStatement, ForOfStatement"(node) {
717 if (node.left.type !== "VariableDeclarator") {
718 const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
719
720 if (
721 firstLeftToken.value === "let" && (
722
723 /*
724 * If `let` is the only thing on the left side of the loop, it's the loop variable: `for ((let) of foo);`
725 * Removing it will cause a syntax error, because it will be parsed as the start of a VariableDeclarator.
726 */
727 (firstLeftToken.range[1] === node.left.range[1] || /*
728 * If `let` is followed by a `[` token, it's a property access on the `let` value: `for ((let[foo]) of bar);`
729 * Removing it will cause the property access to be parsed as a destructuring declaration of `foo` instead.
730 */
731 astUtils.isOpeningBracketToken(
732 sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)
733 ))
734 )
735 ) {
736 tokensToIgnore.add(firstLeftToken);
737 }
738 }
739 if (!(node.type === "ForOfStatement" && node.right.type === "SequenceExpression") && hasExcessParens(node.right)) {
740 report(node.right);
741 }
742 if (hasExcessParens(node.left)) {
743 report(node.left);
744 }
745 },
746
747 ForStatement(node) {
748 if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) {
749 report(node.test);
750 }
751
752 if (node.update && hasExcessParens(node.update)) {
753 report(node.update);
754 }
755
756 if (node.init) {
757 startNewReportsBuffering();
758
759 if (hasExcessParens(node.init)) {
760 report(node.init);
761 }
762 }
763 },
764
765 "ForStatement > *.init:exit"(node) {
766
767 /*
768 * Removing parentheses around `in` expressions might change semantics and cause errors.
769 *
770 * For example, this valid for loop:
771 * for (let a = (b in c); ;);
772 * after removing parentheses would be treated as an invalid for-in loop:
773 * for (let a = b in c; ;);
774 */
775
776 if (reportsBuffer.reports.length) {
777 reportsBuffer.inExpressionNodes.forEach(inExpressionNode => {
778 const path = pathToDescendant(node, inExpressionNode);
779 let nodeToExclude;
780
781 for (let i = 0; i < path.length; i++) {
782 const pathNode = path[i];
783
784 if (i < path.length - 1) {
785 const nextPathNode = path[i + 1];
786
787 if (isSafelyEnclosingInExpression(pathNode, nextPathNode)) {
788
789 // The 'in' expression in safely enclosed by the syntax of its ancestor nodes (e.g. by '{}' or '[]').
790 return;
791 }
792 }
793
794 if (isParenthesised(pathNode)) {
795 if (isInCurrentReportsBuffer(pathNode)) {
796
797 // This node was supposed to be reported, but parentheses might be necessary.
798
799 if (isParenthesisedTwice(pathNode)) {
800
801 /*
802 * This node is parenthesised twice, it certainly has at least one pair of `extra` parentheses.
803 * If the --fix option is on, the current fixing iteration will remove only one pair of parentheses.
804 * The remaining pair is safely enclosing the 'in' expression.
805 */
806 return;
807 }
808
809 // Exclude the outermost node only.
810 if (!nodeToExclude) {
811 nodeToExclude = pathNode;
812 }
813
814 // Don't break the loop here, there might be some safe nodes or parentheses that will stay inside.
815
816 } else {
817
818 // This node will stay parenthesised, the 'in' expression in safely enclosed by '()'.
819 return;
820 }
821 }
822 }
823
824 // Exclude the node from the list (i.e. treat parentheses as necessary)
825 removeFromCurrentReportsBuffer(nodeToExclude);
826 });
827 }
828
829 endCurrentReportsBuffering();
830 },
831
832 IfStatement(node) {
833 if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
834 report(node.test);
835 }
836 },
837
838 LogicalExpression: checkBinaryLogical,
839
840 MemberExpression(node) {
841 const nodeObjHasExcessParens = hasExcessParens(node.object);
842
843 if (
844 nodeObjHasExcessParens &&
845 precedence(node.object) >= precedence(node) &&
846 (
847 node.computed ||
848 !(
849 astUtils.isDecimalInteger(node.object) ||
850
851 // RegExp literal is allowed to have parens (#1589)
852 (node.object.type === "Literal" && node.object.regex)
853 )
854 )
855 ) {
856 report(node.object);
857 }
858
859 if (nodeObjHasExcessParens &&
860 node.object.type === "CallExpression" &&
861 node.parent.type !== "NewExpression") {
862 report(node.object);
863 }
864
865 if (node.computed && hasExcessParens(node.property)) {
866 report(node.property);
867 }
868 },
869
870 NewExpression: checkCallNew,
871
872 ObjectExpression(node) {
873 node.properties
874 .filter(property => {
875 const value = property.value;
876
877 return value && hasExcessParens(value) && precedence(value) >= PRECEDENCE_OF_ASSIGNMENT_EXPR;
878 }).forEach(property => report(property.value));
879 },
880
881 Property(node) {
882 if (node.computed) {
883 const { key } = node;
884
885 if (key && hasExcessParens(key) && precedence(key) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
886 report(key);
887 }
888 }
889 },
890
891 ReturnStatement(node) {
892 const returnToken = sourceCode.getFirstToken(node);
893
894 if (isReturnAssignException(node)) {
895 return;
896 }
897
898 if (node.argument &&
899 hasExcessParensNoLineTerminator(returnToken, node.argument) &&
900
901 // RegExp literal is allowed to have parens (#1589)
902 !(node.argument.type === "Literal" && node.argument.regex)) {
903 report(node.argument);
904 }
905 },
906
907 SequenceExpression(node) {
908 node.expressions
909 .filter(e => hasExcessParens(e) && precedence(e) >= precedence(node))
910 .forEach(report);
911 },
912
913 SwitchCase(node) {
914 if (node.test && hasExcessParens(node.test)) {
915 report(node.test);
916 }
917 },
918
919 SwitchStatement(node) {
920 if (hasDoubleExcessParens(node.discriminant)) {
921 report(node.discriminant);
922 }
923 },
924
925 ThrowStatement(node) {
926 const throwToken = sourceCode.getFirstToken(node);
927
928 if (hasExcessParensNoLineTerminator(throwToken, node.argument)) {
929 report(node.argument);
930 }
931 },
932
933 UnaryExpression: checkUnaryUpdate,
934 UpdateExpression: checkUnaryUpdate,
935 AwaitExpression: checkUnaryUpdate,
936
937 VariableDeclarator(node) {
938 if (node.init && hasExcessParens(node.init) &&
939 precedence(node.init) >= PRECEDENCE_OF_ASSIGNMENT_EXPR &&
940
941 // RegExp literal is allowed to have parens (#1589)
942 !(node.init.type === "Literal" && node.init.regex)) {
943 report(node.init);
944 }
945 },
946
947 WhileStatement(node) {
948 if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
949 report(node.test);
950 }
951 },
952
953 WithStatement(node) {
954 if (hasDoubleExcessParens(node.object)) {
955 report(node.object);
956 }
957 },
958
959 YieldExpression(node) {
960 if (node.argument) {
961 const yieldToken = sourceCode.getFirstToken(node);
962
963 if ((precedence(node.argument) >= precedence(node) &&
964 hasExcessParensNoLineTerminator(yieldToken, node.argument)) ||
965 hasDoubleExcessParens(node.argument)) {
966 report(node.argument);
967 }
968 }
969 },
970
971 ClassDeclaration: checkClass,
972 ClassExpression: checkClass,
973
974 SpreadElement: checkSpreadOperator,
975 SpreadProperty: checkSpreadOperator,
976 ExperimentalSpreadProperty: checkSpreadOperator
977 };
978
979 }
980};