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