UNPKG

29.7 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("../ast-utils.js");
12
13module.exports = {
14 meta: {
15 docs: {
16 description: "disallow unnecessary parentheses",
17 category: "Possible Errors",
18 recommended: false,
19 url: "https://eslint.org/docs/rules/no-extra-parens"
20 },
21
22 fixable: "code",
23
24 schema: {
25 anyOf: [
26 {
27 type: "array",
28 items: [
29 {
30 enum: ["functions"]
31 }
32 ],
33 minItems: 0,
34 maxItems: 1
35 },
36 {
37 type: "array",
38 items: [
39 {
40 enum: ["all"]
41 },
42 {
43 type: "object",
44 properties: {
45 conditionalAssign: { type: "boolean" },
46 nestedBinaryExpressions: { type: "boolean" },
47 returnAssign: { type: "boolean" },
48 ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },
49 enforceForArrowConditionals: { type: "boolean" }
50 },
51 additionalProperties: false
52 }
53 ],
54 minItems: 0,
55 maxItems: 2
56 }
57 ]
58 },
59
60 messages: {
61 unexpected: "Gratuitous parentheses around expression."
62 }
63 },
64
65 create(context) {
66 const sourceCode = context.getSourceCode();
67
68 const tokensToIgnore = new WeakSet();
69 const isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode);
70 const precedence = astUtils.getPrecedence;
71 const ALL_NODES = context.options[0] !== "functions";
72 const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
73 const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
74 const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
75 const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
76 const IGNORE_ARROW_CONDITIONALS = ALL_NODES && context.options[1] &&
77 context.options[1].enforceForArrowConditionals === false;
78
79 const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
80 const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
81
82 /**
83 * Determines if this rule should be enforced for a node given the current configuration.
84 * @param {ASTNode} node - The node to be checked.
85 * @returns {boolean} True if the rule should be enforced for this node.
86 * @private
87 */
88 function ruleApplies(node) {
89 if (node.type === "JSXElement") {
90 const isSingleLine = node.loc.start.line === node.loc.end.line;
91
92 switch (IGNORE_JSX) {
93
94 // Exclude this JSX element from linting
95 case "all":
96 return false;
97
98 // Exclude this JSX element if it is multi-line element
99 case "multi-line":
100 return isSingleLine;
101
102 // Exclude this JSX element if it is single-line element
103 case "single-line":
104 return !isSingleLine;
105
106 // Nothing special to be done for JSX elements
107 case "none":
108 break;
109
110 // no default
111 }
112 }
113
114 return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
115 }
116
117 /**
118 * Determines if a node is surrounded by parentheses twice.
119 * @param {ASTNode} node - The node to be checked.
120 * @returns {boolean} True if the node is doubly parenthesised.
121 * @private
122 */
123 function isParenthesisedTwice(node) {
124 const previousToken = sourceCode.getTokenBefore(node, 1),
125 nextToken = sourceCode.getTokenAfter(node, 1);
126
127 return isParenthesised(node) && previousToken && nextToken &&
128 astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
129 astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
130 }
131
132 /**
133 * Determines if a node is surrounded by (potentially) invalid parentheses.
134 * @param {ASTNode} node - The node to be checked.
135 * @returns {boolean} True if the node is incorrectly parenthesised.
136 * @private
137 */
138 function hasExcessParens(node) {
139 return ruleApplies(node) && isParenthesised(node);
140 }
141
142 /**
143 * Determines if a node that is expected to be parenthesised is surrounded by
144 * (potentially) invalid extra parentheses.
145 * @param {ASTNode} node - The node to be checked.
146 * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
147 * @private
148 */
149 function hasDoubleExcessParens(node) {
150 return ruleApplies(node) && isParenthesisedTwice(node);
151 }
152
153 /**
154 * Determines if a node test expression is allowed to have a parenthesised assignment
155 * @param {ASTNode} node - The node to be checked.
156 * @returns {boolean} True if the assignment can be parenthesised.
157 * @private
158 */
159 function isCondAssignException(node) {
160 return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression";
161 }
162
163 /**
164 * Determines if a node is in a return statement
165 * @param {ASTNode} node - The node to be checked.
166 * @returns {boolean} True if the node is in a return statement.
167 * @private
168 */
169 function isInReturnStatement(node) {
170 while (node) {
171 if (node.type === "ReturnStatement" ||
172 (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement")) {
173 return true;
174 }
175 node = node.parent;
176 }
177
178 return false;
179 }
180
181 /**
182 * Determines if a constructor function is newed-up with parens
183 * @param {ASTNode} newExpression - The NewExpression node to be checked.
184 * @returns {boolean} True if the constructor is called with parens.
185 * @private
186 */
187 function isNewExpressionWithParens(newExpression) {
188 const lastToken = sourceCode.getLastToken(newExpression);
189 const penultimateToken = sourceCode.getTokenBefore(lastToken);
190
191 return newExpression.arguments.length > 0 || astUtils.isOpeningParenToken(penultimateToken) && astUtils.isClosingParenToken(lastToken);
192 }
193
194 /**
195 * Determines if a node is or contains an assignment expression
196 * @param {ASTNode} node - The node to be checked.
197 * @returns {boolean} True if the node is or contains an assignment expression.
198 * @private
199 */
200 function containsAssignment(node) {
201 if (node.type === "AssignmentExpression") {
202 return true;
203 }
204 if (node.type === "ConditionalExpression" &&
205 (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) {
206 return true;
207 }
208 if ((node.left && node.left.type === "AssignmentExpression") ||
209 (node.right && node.right.type === "AssignmentExpression")) {
210 return true;
211 }
212
213 return false;
214 }
215
216 /**
217 * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment
218 * @param {ASTNode} node - The node to be checked.
219 * @returns {boolean} True if the assignment can be parenthesised.
220 * @private
221 */
222 function isReturnAssignException(node) {
223 if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) {
224 return false;
225 }
226
227 if (node.type === "ReturnStatement") {
228 return node.argument && containsAssignment(node.argument);
229 }
230 if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
231 return containsAssignment(node.body);
232 }
233 return containsAssignment(node);
234
235 }
236
237 /**
238 * Determines if a node following a [no LineTerminator here] restriction is
239 * surrounded by (potentially) invalid extra parentheses.
240 * @param {Token} token - The token preceding the [no LineTerminator here] restriction.
241 * @param {ASTNode} node - The node to be checked.
242 * @returns {boolean} True if the node is incorrectly parenthesised.
243 * @private
244 */
245 function hasExcessParensNoLineTerminator(token, node) {
246 if (token.loc.end.line === node.loc.start.line) {
247 return hasExcessParens(node);
248 }
249
250 return hasDoubleExcessParens(node);
251 }
252
253 /**
254 * Determines whether a node should be preceded by an additional space when removing parens
255 * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
256 * @returns {boolean} `true` if a space should be inserted before the node
257 * @private
258 */
259 function requiresLeadingSpace(node) {
260 const leftParenToken = sourceCode.getTokenBefore(node);
261 const tokenBeforeLeftParen = sourceCode.getTokenBefore(node, 1);
262 const firstToken = sourceCode.getFirstToken(node);
263
264 return tokenBeforeLeftParen &&
265 tokenBeforeLeftParen.range[1] === leftParenToken.range[0] &&
266 leftParenToken.range[1] === firstToken.range[0] &&
267 !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, firstToken);
268 }
269
270 /**
271 * Determines whether a node should be followed by an additional space when removing parens
272 * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
273 * @returns {boolean} `true` if a space should be inserted after the node
274 * @private
275 */
276 function requiresTrailingSpace(node) {
277 const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 });
278 const rightParenToken = nextTwoTokens[0];
279 const tokenAfterRightParen = nextTwoTokens[1];
280 const tokenBeforeRightParen = sourceCode.getLastToken(node);
281
282 return rightParenToken && tokenAfterRightParen &&
283 !sourceCode.isSpaceBetweenTokens(rightParenToken, tokenAfterRightParen) &&
284 !astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen);
285 }
286
287 /**
288 * Determines if a given expression node is an IIFE
289 * @param {ASTNode} node The node to check
290 * @returns {boolean} `true` if the given node is an IIFE
291 */
292 function isIIFE(node) {
293 return node.type === "CallExpression" && node.callee.type === "FunctionExpression";
294 }
295
296 /**
297 * Report the node
298 * @param {ASTNode} node node to evaluate
299 * @returns {void}
300 * @private
301 */
302 function report(node) {
303 const leftParenToken = sourceCode.getTokenBefore(node);
304 const rightParenToken = sourceCode.getTokenAfter(node);
305
306 if (!isParenthesisedTwice(node)) {
307 if (tokensToIgnore.has(sourceCode.getFirstToken(node))) {
308 return;
309 }
310
311 if (isIIFE(node) && !isParenthesised(node.callee)) {
312 return;
313 }
314 }
315
316 context.report({
317 node,
318 loc: leftParenToken.loc.start,
319 messageId: "unexpected",
320 fix(fixer) {
321 const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);
322
323 return fixer.replaceTextRange([
324 leftParenToken.range[0],
325 rightParenToken.range[1]
326 ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
327 }
328 });
329 }
330
331 /**
332 * Evaluate Unary update
333 * @param {ASTNode} node node to evaluate
334 * @returns {void}
335 * @private
336 */
337 function checkUnaryUpdate(node) {
338 if (node.type === "UnaryExpression" && node.argument.type === "BinaryExpression" && node.argument.operator === "**") {
339 return;
340 }
341
342 if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) {
343 report(node.argument);
344 }
345 }
346
347 /**
348 * Check if a member expression contains a call expression
349 * @param {ASTNode} node MemberExpression node to evaluate
350 * @returns {boolean} true if found, false if not
351 */
352 function doesMemberExpressionContainCallExpression(node) {
353 let currentNode = node.object;
354 let currentNodeType = node.object.type;
355
356 while (currentNodeType === "MemberExpression") {
357 currentNode = currentNode.object;
358 currentNodeType = currentNode.type;
359 }
360
361 return currentNodeType === "CallExpression";
362 }
363
364 /**
365 * Evaluate a new call
366 * @param {ASTNode} node node to evaluate
367 * @returns {void}
368 * @private
369 */
370 function checkCallNew(node) {
371 const callee = node.callee;
372
373 if (hasExcessParens(callee) && precedence(callee) >= precedence(node)) {
374 const hasNewParensException = callee.type === "NewExpression" && !isNewExpressionWithParens(callee);
375
376 if (
377 hasDoubleExcessParens(callee) ||
378 !isIIFE(node) && !hasNewParensException && !(
379
380 /*
381 * Allow extra parens around a new expression if
382 * there are intervening parentheses.
383 */
384 callee.type === "MemberExpression" &&
385 doesMemberExpressionContainCallExpression(callee)
386 )
387 ) {
388 report(node.callee);
389 }
390 }
391 if (node.arguments.length === 1) {
392 if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
393 report(node.arguments[0]);
394 }
395 } else {
396 node.arguments
397 .filter(arg => hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)
398 .forEach(report);
399 }
400 }
401
402 /**
403 * Evaluate binary logicals
404 * @param {ASTNode} node node to evaluate
405 * @returns {void}
406 * @private
407 */
408 function checkBinaryLogical(node) {
409 const prec = precedence(node);
410 const leftPrecedence = precedence(node.left);
411 const rightPrecedence = precedence(node.right);
412 const isExponentiation = node.operator === "**";
413 const shouldSkipLeft = (NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression")) ||
414 node.left.type === "UnaryExpression" && isExponentiation;
415 const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");
416
417 if (!shouldSkipLeft && hasExcessParens(node.left) && (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation))) {
418 report(node.left);
419 }
420 if (!shouldSkipRight && hasExcessParens(node.right) && (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation))) {
421 report(node.right);
422 }
423 }
424
425 /**
426 * Check the parentheses around the super class of the given class definition.
427 * @param {ASTNode} node The node of class declarations to check.
428 * @returns {void}
429 */
430 function checkClass(node) {
431 if (!node.superClass) {
432 return;
433 }
434
435 /*
436 * If `node.superClass` is a LeftHandSideExpression, parentheses are extra.
437 * Otherwise, parentheses are needed.
438 */
439 const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR
440 ? hasExcessParens(node.superClass)
441 : hasDoubleExcessParens(node.superClass);
442
443 if (hasExtraParens) {
444 report(node.superClass);
445 }
446 }
447
448 /**
449 * Check the parentheses around the argument of the given spread operator.
450 * @param {ASTNode} node The node of spread elements/properties to check.
451 * @returns {void}
452 */
453 function checkSpreadOperator(node) {
454 const hasExtraParens = precedence(node.argument) >= PRECEDENCE_OF_ASSIGNMENT_EXPR
455 ? hasExcessParens(node.argument)
456 : hasDoubleExcessParens(node.argument);
457
458 if (hasExtraParens) {
459 report(node.argument);
460 }
461 }
462
463 /**
464 * Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration
465 * @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node
466 * @returns {void}
467 */
468 function checkExpressionOrExportStatement(node) {
469 const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);
470 const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken);
471 const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null;
472
473 if (
474 astUtils.isOpeningParenToken(firstToken) &&
475 (
476 astUtils.isOpeningBraceToken(secondToken) ||
477 secondToken.type === "Keyword" && (
478 secondToken.value === "function" ||
479 secondToken.value === "class" ||
480 secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken))
481 ) ||
482 secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && thirdToken && thirdToken.type === "Keyword" && thirdToken.value === "function"
483 )
484 ) {
485 tokensToIgnore.add(secondToken);
486 }
487
488 if (hasExcessParens(node)) {
489 report(node);
490 }
491 }
492
493 return {
494 ArrayExpression(node) {
495 node.elements
496 .filter(e => e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)
497 .forEach(report);
498 },
499
500 ArrowFunctionExpression(node) {
501 if (isReturnAssignException(node)) {
502 return;
503 }
504
505 if (node.body.type === "ConditionalExpression" &&
506 IGNORE_ARROW_CONDITIONALS &&
507 !isParenthesisedTwice(node.body)
508 ) {
509 return;
510 }
511
512 if (node.body.type !== "BlockStatement") {
513 const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken);
514 const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);
515
516 if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) {
517 tokensToIgnore.add(firstBodyToken);
518 }
519 if (hasExcessParens(node.body) && precedence(node.body) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
520 report(node.body);
521 }
522 }
523 },
524
525 AssignmentExpression(node) {
526 if (isReturnAssignException(node)) {
527 return;
528 }
529
530 if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) {
531 report(node.right);
532 }
533 },
534
535 BinaryExpression: checkBinaryLogical,
536 CallExpression: checkCallNew,
537
538 ConditionalExpression(node) {
539 if (isReturnAssignException(node)) {
540 return;
541 }
542
543 if (hasExcessParens(node.test) && precedence(node.test) >= precedence({ type: "LogicalExpression", operator: "||" })) {
544 report(node.test);
545 }
546
547 if (hasExcessParens(node.consequent) && precedence(node.consequent) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
548 report(node.consequent);
549 }
550
551 if (hasExcessParens(node.alternate) && precedence(node.alternate) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
552 report(node.alternate);
553 }
554 },
555
556 DoWhileStatement(node) {
557 if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
558 report(node.test);
559 }
560 },
561
562 ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
563 ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
564
565 "ForInStatement, ForOfStatement"(node) {
566 if (node.left.type !== "VariableDeclarator") {
567 const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
568
569 if (
570 firstLeftToken.value === "let" && (
571
572 /*
573 * If `let` is the only thing on the left side of the loop, it's the loop variable: `for ((let) of foo);`
574 * Removing it will cause a syntax error, because it will be parsed as the start of a VariableDeclarator.
575 */
576 firstLeftToken.range[1] === node.left.range[1] ||
577
578 /*
579 * If `let` is followed by a `[` token, it's a property access on the `let` value: `for ((let[foo]) of bar);`
580 * Removing it will cause the property access to be parsed as a destructuring declaration of `foo` instead.
581 */
582 astUtils.isOpeningBracketToken(
583 sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)
584 )
585 )
586 ) {
587 tokensToIgnore.add(firstLeftToken);
588 }
589 }
590 if (!(node.type === "ForOfStatement" && node.right.type === "SequenceExpression") && hasExcessParens(node.right)) {
591 report(node.right);
592 }
593 if (hasExcessParens(node.left)) {
594 report(node.left);
595 }
596 },
597
598 ForStatement(node) {
599 if (node.init && hasExcessParens(node.init)) {
600 report(node.init);
601 }
602
603 if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) {
604 report(node.test);
605 }
606
607 if (node.update && hasExcessParens(node.update)) {
608 report(node.update);
609 }
610 },
611
612 IfStatement(node) {
613 if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
614 report(node.test);
615 }
616 },
617
618 LogicalExpression: checkBinaryLogical,
619
620 MemberExpression(node) {
621 const nodeObjHasExcessParens = hasExcessParens(node.object);
622
623 if (
624 nodeObjHasExcessParens &&
625 precedence(node.object) >= precedence(node) &&
626 (
627 node.computed ||
628 !(
629 astUtils.isDecimalInteger(node.object) ||
630
631 // RegExp literal is allowed to have parens (#1589)
632 (node.object.type === "Literal" && node.object.regex)
633 )
634 )
635 ) {
636 report(node.object);
637 }
638
639 if (nodeObjHasExcessParens &&
640 node.object.type === "CallExpression" &&
641 node.parent.type !== "NewExpression") {
642 report(node.object);
643 }
644
645 if (node.computed && hasExcessParens(node.property)) {
646 report(node.property);
647 }
648 },
649
650 NewExpression: checkCallNew,
651
652 ObjectExpression(node) {
653 node.properties
654 .filter(property => {
655 const value = property.value;
656
657 return value && hasExcessParens(value) && precedence(value) >= PRECEDENCE_OF_ASSIGNMENT_EXPR;
658 }).forEach(property => report(property.value));
659 },
660
661 ReturnStatement(node) {
662 const returnToken = sourceCode.getFirstToken(node);
663
664 if (isReturnAssignException(node)) {
665 return;
666 }
667
668 if (node.argument &&
669 hasExcessParensNoLineTerminator(returnToken, node.argument) &&
670
671 // RegExp literal is allowed to have parens (#1589)
672 !(node.argument.type === "Literal" && node.argument.regex)) {
673 report(node.argument);
674 }
675 },
676
677 SequenceExpression(node) {
678 node.expressions
679 .filter(e => hasExcessParens(e) && precedence(e) >= precedence(node))
680 .forEach(report);
681 },
682
683 SwitchCase(node) {
684 if (node.test && hasExcessParens(node.test)) {
685 report(node.test);
686 }
687 },
688
689 SwitchStatement(node) {
690 if (hasDoubleExcessParens(node.discriminant)) {
691 report(node.discriminant);
692 }
693 },
694
695 ThrowStatement(node) {
696 const throwToken = sourceCode.getFirstToken(node);
697
698 if (hasExcessParensNoLineTerminator(throwToken, node.argument)) {
699 report(node.argument);
700 }
701 },
702
703 UnaryExpression: checkUnaryUpdate,
704 UpdateExpression: checkUnaryUpdate,
705 AwaitExpression: checkUnaryUpdate,
706
707 VariableDeclarator(node) {
708 if (node.init && hasExcessParens(node.init) &&
709 precedence(node.init) >= PRECEDENCE_OF_ASSIGNMENT_EXPR &&
710
711 // RegExp literal is allowed to have parens (#1589)
712 !(node.init.type === "Literal" && node.init.regex)) {
713 report(node.init);
714 }
715 },
716
717 WhileStatement(node) {
718 if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
719 report(node.test);
720 }
721 },
722
723 WithStatement(node) {
724 if (hasDoubleExcessParens(node.object)) {
725 report(node.object);
726 }
727 },
728
729 YieldExpression(node) {
730 if (node.argument) {
731 const yieldToken = sourceCode.getFirstToken(node);
732
733 if ((precedence(node.argument) >= precedence(node) &&
734 hasExcessParensNoLineTerminator(yieldToken, node.argument)) ||
735 hasDoubleExcessParens(node.argument)) {
736 report(node.argument);
737 }
738 }
739 },
740
741 ClassDeclaration: checkClass,
742 ClassExpression: checkClass,
743
744 SpreadElement: checkSpreadOperator,
745 SpreadProperty: checkSpreadOperator,
746 ExperimentalSpreadProperty: checkSpreadOperator
747 };
748
749 }
750};