[d565449] | 1 | /**
|
---|
| 2 | * @fileoverview Disallow parenthesising higher precedence subexpressions.
|
---|
| 3 | * @author Michael Ficarra
|
---|
| 4 | * @deprecated in ESLint v8.53.0
|
---|
| 5 | */
|
---|
| 6 | "use strict";
|
---|
| 7 |
|
---|
| 8 | //------------------------------------------------------------------------------
|
---|
| 9 | // Rule Definition
|
---|
| 10 | //------------------------------------------------------------------------------
|
---|
| 11 |
|
---|
| 12 | const { isParenthesized: isParenthesizedRaw } = require("@eslint-community/eslint-utils");
|
---|
| 13 | const astUtils = require("./utils/ast-utils.js");
|
---|
| 14 |
|
---|
| 15 | /** @type {import('../shared/types').Rule} */
|
---|
| 16 | module.exports = {
|
---|
| 17 | meta: {
|
---|
| 18 | deprecated: true,
|
---|
| 19 | replacedBy: [],
|
---|
| 20 | type: "layout",
|
---|
| 21 |
|
---|
| 22 | docs: {
|
---|
| 23 | description: "Disallow unnecessary parentheses",
|
---|
| 24 | recommended: false,
|
---|
| 25 | url: "https://eslint.org/docs/latest/rules/no-extra-parens"
|
---|
| 26 | },
|
---|
| 27 |
|
---|
| 28 | fixable: "code",
|
---|
| 29 |
|
---|
| 30 | schema: {
|
---|
| 31 | anyOf: [
|
---|
| 32 | {
|
---|
| 33 | type: "array",
|
---|
| 34 | items: [
|
---|
| 35 | {
|
---|
| 36 | enum: ["functions"]
|
---|
| 37 | }
|
---|
| 38 | ],
|
---|
| 39 | minItems: 0,
|
---|
| 40 | maxItems: 1
|
---|
| 41 | },
|
---|
| 42 | {
|
---|
| 43 | type: "array",
|
---|
| 44 | items: [
|
---|
| 45 | {
|
---|
| 46 | enum: ["all"]
|
---|
| 47 | },
|
---|
| 48 | {
|
---|
| 49 | type: "object",
|
---|
| 50 | properties: {
|
---|
| 51 | conditionalAssign: { type: "boolean" },
|
---|
| 52 | ternaryOperandBinaryExpressions: { type: "boolean" },
|
---|
| 53 | nestedBinaryExpressions: { type: "boolean" },
|
---|
| 54 | returnAssign: { type: "boolean" },
|
---|
| 55 | ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },
|
---|
| 56 | enforceForArrowConditionals: { type: "boolean" },
|
---|
| 57 | enforceForSequenceExpressions: { type: "boolean" },
|
---|
| 58 | enforceForNewInMemberExpressions: { type: "boolean" },
|
---|
| 59 | enforceForFunctionPrototypeMethods: { type: "boolean" },
|
---|
| 60 | allowParensAfterCommentPattern: { type: "string" }
|
---|
| 61 | },
|
---|
| 62 | additionalProperties: false
|
---|
| 63 | }
|
---|
| 64 | ],
|
---|
| 65 | minItems: 0,
|
---|
| 66 | maxItems: 2
|
---|
| 67 | }
|
---|
| 68 | ]
|
---|
| 69 | },
|
---|
| 70 |
|
---|
| 71 | messages: {
|
---|
| 72 | unexpected: "Unnecessary parentheses around expression."
|
---|
| 73 | }
|
---|
| 74 | },
|
---|
| 75 |
|
---|
| 76 | create(context) {
|
---|
| 77 | const sourceCode = context.sourceCode;
|
---|
| 78 |
|
---|
| 79 | const tokensToIgnore = new WeakSet();
|
---|
| 80 | const precedence = astUtils.getPrecedence;
|
---|
| 81 | const ALL_NODES = context.options[0] !== "functions";
|
---|
| 82 | const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
|
---|
| 83 | const EXCEPT_COND_TERNARY = ALL_NODES && context.options[1] && context.options[1].ternaryOperandBinaryExpressions === false;
|
---|
| 84 | const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
|
---|
| 85 | const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
|
---|
| 86 | const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
|
---|
| 87 | const IGNORE_ARROW_CONDITIONALS = ALL_NODES && context.options[1] &&
|
---|
| 88 | context.options[1].enforceForArrowConditionals === false;
|
---|
| 89 | const IGNORE_SEQUENCE_EXPRESSIONS = ALL_NODES && context.options[1] &&
|
---|
| 90 | context.options[1].enforceForSequenceExpressions === false;
|
---|
| 91 | const IGNORE_NEW_IN_MEMBER_EXPR = ALL_NODES && context.options[1] &&
|
---|
| 92 | context.options[1].enforceForNewInMemberExpressions === false;
|
---|
| 93 | const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] &&
|
---|
| 94 | context.options[1].enforceForFunctionPrototypeMethods === false;
|
---|
| 95 | const ALLOW_PARENS_AFTER_COMMENT_PATTERN = ALL_NODES && context.options[1] && context.options[1].allowParensAfterCommentPattern;
|
---|
| 96 |
|
---|
| 97 | const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
|
---|
| 98 | const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
|
---|
| 99 |
|
---|
| 100 | let reportsBuffer;
|
---|
| 101 |
|
---|
| 102 | /**
|
---|
| 103 | * Determines whether the given node is a `call` or `apply` method call, invoked directly on a `FunctionExpression` node.
|
---|
| 104 | * Example: function(){}.call()
|
---|
| 105 | * @param {ASTNode} node The node to be checked.
|
---|
| 106 | * @returns {boolean} True if the node is an immediate `call` or `apply` method call.
|
---|
| 107 | * @private
|
---|
| 108 | */
|
---|
| 109 | function isImmediateFunctionPrototypeMethodCall(node) {
|
---|
| 110 | const callNode = astUtils.skipChainExpression(node);
|
---|
| 111 |
|
---|
| 112 | if (callNode.type !== "CallExpression") {
|
---|
| 113 | return false;
|
---|
| 114 | }
|
---|
| 115 | const callee = astUtils.skipChainExpression(callNode.callee);
|
---|
| 116 |
|
---|
| 117 | return (
|
---|
| 118 | callee.type === "MemberExpression" &&
|
---|
| 119 | callee.object.type === "FunctionExpression" &&
|
---|
| 120 | ["call", "apply"].includes(astUtils.getStaticPropertyName(callee))
|
---|
| 121 | );
|
---|
| 122 | }
|
---|
| 123 |
|
---|
| 124 | /**
|
---|
| 125 | * Determines if this rule should be enforced for a node given the current configuration.
|
---|
| 126 | * @param {ASTNode} node The node to be checked.
|
---|
| 127 | * @returns {boolean} True if the rule should be enforced for this node.
|
---|
| 128 | * @private
|
---|
| 129 | */
|
---|
| 130 | function ruleApplies(node) {
|
---|
| 131 | if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
---|
| 132 | const isSingleLine = node.loc.start.line === node.loc.end.line;
|
---|
| 133 |
|
---|
| 134 | switch (IGNORE_JSX) {
|
---|
| 135 |
|
---|
| 136 | // Exclude this JSX element from linting
|
---|
| 137 | case "all":
|
---|
| 138 | return false;
|
---|
| 139 |
|
---|
| 140 | // Exclude this JSX element if it is multi-line element
|
---|
| 141 | case "multi-line":
|
---|
| 142 | return isSingleLine;
|
---|
| 143 |
|
---|
| 144 | // Exclude this JSX element if it is single-line element
|
---|
| 145 | case "single-line":
|
---|
| 146 | return !isSingleLine;
|
---|
| 147 |
|
---|
| 148 | // Nothing special to be done for JSX elements
|
---|
| 149 | case "none":
|
---|
| 150 | break;
|
---|
| 151 |
|
---|
| 152 | // no default
|
---|
| 153 | }
|
---|
| 154 | }
|
---|
| 155 |
|
---|
| 156 | if (node.type === "SequenceExpression" && IGNORE_SEQUENCE_EXPRESSIONS) {
|
---|
| 157 | return false;
|
---|
| 158 | }
|
---|
| 159 |
|
---|
| 160 | if (isImmediateFunctionPrototypeMethodCall(node) && IGNORE_FUNCTION_PROTOTYPE_METHODS) {
|
---|
| 161 | return false;
|
---|
| 162 | }
|
---|
| 163 |
|
---|
| 164 | return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
---|
| 165 | }
|
---|
| 166 |
|
---|
| 167 | /**
|
---|
| 168 | * Determines if a node is surrounded by parentheses.
|
---|
| 169 | * @param {ASTNode} node The node to be checked.
|
---|
| 170 | * @returns {boolean} True if the node is parenthesised.
|
---|
| 171 | * @private
|
---|
| 172 | */
|
---|
| 173 | function isParenthesised(node) {
|
---|
| 174 | return isParenthesizedRaw(1, node, sourceCode);
|
---|
| 175 | }
|
---|
| 176 |
|
---|
| 177 | /**
|
---|
| 178 | * Determines if a node is surrounded by parentheses twice.
|
---|
| 179 | * @param {ASTNode} node The node to be checked.
|
---|
| 180 | * @returns {boolean} True if the node is doubly parenthesised.
|
---|
| 181 | * @private
|
---|
| 182 | */
|
---|
| 183 | function isParenthesisedTwice(node) {
|
---|
| 184 | return isParenthesizedRaw(2, node, sourceCode);
|
---|
| 185 | }
|
---|
| 186 |
|
---|
| 187 | /**
|
---|
| 188 | * Determines if a node is surrounded by (potentially) invalid parentheses.
|
---|
| 189 | * @param {ASTNode} node The node to be checked.
|
---|
| 190 | * @returns {boolean} True if the node is incorrectly parenthesised.
|
---|
| 191 | * @private
|
---|
| 192 | */
|
---|
| 193 | function hasExcessParens(node) {
|
---|
| 194 | return ruleApplies(node) && isParenthesised(node);
|
---|
| 195 | }
|
---|
| 196 |
|
---|
| 197 | /**
|
---|
| 198 | * Determines if a node that is expected to be parenthesised is surrounded by
|
---|
| 199 | * (potentially) invalid extra parentheses.
|
---|
| 200 | * @param {ASTNode} node The node to be checked.
|
---|
| 201 | * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
|
---|
| 202 | * @private
|
---|
| 203 | */
|
---|
| 204 | function hasDoubleExcessParens(node) {
|
---|
| 205 | return ruleApplies(node) && isParenthesisedTwice(node);
|
---|
| 206 | }
|
---|
| 207 |
|
---|
| 208 | /**
|
---|
| 209 | * Determines if a node that is expected to be parenthesised is surrounded by
|
---|
| 210 | * (potentially) invalid extra parentheses with considering precedence level of the node.
|
---|
| 211 | * If the preference level of the node is not higher or equal to precedence lower limit, it also checks
|
---|
| 212 | * whether the node is surrounded by parentheses twice or not.
|
---|
| 213 | * @param {ASTNode} node The node to be checked.
|
---|
| 214 | * @param {number} precedenceLowerLimit The lower limit of precedence.
|
---|
| 215 | * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
|
---|
| 216 | * @private
|
---|
| 217 | */
|
---|
| 218 | function hasExcessParensWithPrecedence(node, precedenceLowerLimit) {
|
---|
| 219 | if (ruleApplies(node) && isParenthesised(node)) {
|
---|
| 220 | if (
|
---|
| 221 | precedence(node) >= precedenceLowerLimit ||
|
---|
| 222 | isParenthesisedTwice(node)
|
---|
| 223 | ) {
|
---|
| 224 | return true;
|
---|
| 225 | }
|
---|
| 226 | }
|
---|
| 227 | return false;
|
---|
| 228 | }
|
---|
| 229 |
|
---|
| 230 | /**
|
---|
| 231 | * Determines if a node test expression is allowed to have a parenthesised assignment
|
---|
| 232 | * @param {ASTNode} node The node to be checked.
|
---|
| 233 | * @returns {boolean} True if the assignment can be parenthesised.
|
---|
| 234 | * @private
|
---|
| 235 | */
|
---|
| 236 | function isCondAssignException(node) {
|
---|
| 237 | return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression";
|
---|
| 238 | }
|
---|
| 239 |
|
---|
| 240 | /**
|
---|
| 241 | * Determines if a node is in a return statement
|
---|
| 242 | * @param {ASTNode} node The node to be checked.
|
---|
| 243 | * @returns {boolean} True if the node is in a return statement.
|
---|
| 244 | * @private
|
---|
| 245 | */
|
---|
| 246 | function isInReturnStatement(node) {
|
---|
| 247 | for (let currentNode = node; currentNode; currentNode = currentNode.parent) {
|
---|
| 248 | if (
|
---|
| 249 | currentNode.type === "ReturnStatement" ||
|
---|
| 250 | (currentNode.type === "ArrowFunctionExpression" && currentNode.body.type !== "BlockStatement")
|
---|
| 251 | ) {
|
---|
| 252 | return true;
|
---|
| 253 | }
|
---|
| 254 | }
|
---|
| 255 |
|
---|
| 256 | return false;
|
---|
| 257 | }
|
---|
| 258 |
|
---|
| 259 | /**
|
---|
| 260 | * Determines if a constructor function is newed-up with parens
|
---|
| 261 | * @param {ASTNode} newExpression The NewExpression node to be checked.
|
---|
| 262 | * @returns {boolean} True if the constructor is called with parens.
|
---|
| 263 | * @private
|
---|
| 264 | */
|
---|
| 265 | function isNewExpressionWithParens(newExpression) {
|
---|
| 266 | const lastToken = sourceCode.getLastToken(newExpression);
|
---|
| 267 | const penultimateToken = sourceCode.getTokenBefore(lastToken);
|
---|
| 268 |
|
---|
| 269 | return newExpression.arguments.length > 0 ||
|
---|
| 270 | (
|
---|
| 271 |
|
---|
| 272 | // The expression should end with its own parens, e.g., new new foo() is not a new expression with parens
|
---|
| 273 | astUtils.isOpeningParenToken(penultimateToken) &&
|
---|
| 274 | astUtils.isClosingParenToken(lastToken) &&
|
---|
| 275 | newExpression.callee.range[1] < newExpression.range[1]
|
---|
| 276 | );
|
---|
| 277 | }
|
---|
| 278 |
|
---|
| 279 | /**
|
---|
| 280 | * Determines if a node is or contains an assignment expression
|
---|
| 281 | * @param {ASTNode} node The node to be checked.
|
---|
| 282 | * @returns {boolean} True if the node is or contains an assignment expression.
|
---|
| 283 | * @private
|
---|
| 284 | */
|
---|
| 285 | function containsAssignment(node) {
|
---|
| 286 | if (node.type === "AssignmentExpression") {
|
---|
| 287 | return true;
|
---|
| 288 | }
|
---|
| 289 | if (node.type === "ConditionalExpression" &&
|
---|
| 290 | (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) {
|
---|
| 291 | return true;
|
---|
| 292 | }
|
---|
| 293 | if ((node.left && node.left.type === "AssignmentExpression") ||
|
---|
| 294 | (node.right && node.right.type === "AssignmentExpression")) {
|
---|
| 295 | return true;
|
---|
| 296 | }
|
---|
| 297 |
|
---|
| 298 | return false;
|
---|
| 299 | }
|
---|
| 300 |
|
---|
| 301 | /**
|
---|
| 302 | * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment
|
---|
| 303 | * @param {ASTNode} node The node to be checked.
|
---|
| 304 | * @returns {boolean} True if the assignment can be parenthesised.
|
---|
| 305 | * @private
|
---|
| 306 | */
|
---|
| 307 | function isReturnAssignException(node) {
|
---|
| 308 | if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) {
|
---|
| 309 | return false;
|
---|
| 310 | }
|
---|
| 311 |
|
---|
| 312 | if (node.type === "ReturnStatement") {
|
---|
| 313 | return node.argument && containsAssignment(node.argument);
|
---|
| 314 | }
|
---|
| 315 | if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
---|
| 316 | return containsAssignment(node.body);
|
---|
| 317 | }
|
---|
| 318 | return containsAssignment(node);
|
---|
| 319 |
|
---|
| 320 | }
|
---|
| 321 |
|
---|
| 322 | /**
|
---|
| 323 | * Determines if a node following a [no LineTerminator here] restriction is
|
---|
| 324 | * surrounded by (potentially) invalid extra parentheses.
|
---|
| 325 | * @param {Token} token The token preceding the [no LineTerminator here] restriction.
|
---|
| 326 | * @param {ASTNode} node The node to be checked.
|
---|
| 327 | * @returns {boolean} True if the node is incorrectly parenthesised.
|
---|
| 328 | * @private
|
---|
| 329 | */
|
---|
| 330 | function hasExcessParensNoLineTerminator(token, node) {
|
---|
| 331 | if (token.loc.end.line === node.loc.start.line) {
|
---|
| 332 | return hasExcessParens(node);
|
---|
| 333 | }
|
---|
| 334 |
|
---|
| 335 | return hasDoubleExcessParens(node);
|
---|
| 336 | }
|
---|
| 337 |
|
---|
| 338 | /**
|
---|
| 339 | * Determines whether a node should be preceded by an additional space when removing parens
|
---|
| 340 | * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
|
---|
| 341 | * @returns {boolean} `true` if a space should be inserted before the node
|
---|
| 342 | * @private
|
---|
| 343 | */
|
---|
| 344 | function requiresLeadingSpace(node) {
|
---|
| 345 | const leftParenToken = sourceCode.getTokenBefore(node);
|
---|
| 346 | const tokenBeforeLeftParen = sourceCode.getTokenBefore(leftParenToken, { includeComments: true });
|
---|
| 347 | const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParenToken, { includeComments: true });
|
---|
| 348 |
|
---|
| 349 | return tokenBeforeLeftParen &&
|
---|
| 350 | tokenBeforeLeftParen.range[1] === leftParenToken.range[0] &&
|
---|
| 351 | leftParenToken.range[1] === tokenAfterLeftParen.range[0] &&
|
---|
| 352 | !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, tokenAfterLeftParen);
|
---|
| 353 | }
|
---|
| 354 |
|
---|
| 355 | /**
|
---|
| 356 | * Determines whether a node should be followed by an additional space when removing parens
|
---|
| 357 | * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
|
---|
| 358 | * @returns {boolean} `true` if a space should be inserted after the node
|
---|
| 359 | * @private
|
---|
| 360 | */
|
---|
| 361 | function requiresTrailingSpace(node) {
|
---|
| 362 | const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 });
|
---|
| 363 | const rightParenToken = nextTwoTokens[0];
|
---|
| 364 | const tokenAfterRightParen = nextTwoTokens[1];
|
---|
| 365 | const tokenBeforeRightParen = sourceCode.getLastToken(node);
|
---|
| 366 |
|
---|
| 367 | return rightParenToken && tokenAfterRightParen &&
|
---|
| 368 | !sourceCode.isSpaceBetweenTokens(rightParenToken, tokenAfterRightParen) &&
|
---|
| 369 | !astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen);
|
---|
| 370 | }
|
---|
| 371 |
|
---|
| 372 | /**
|
---|
| 373 | * Determines if a given expression node is an IIFE
|
---|
| 374 | * @param {ASTNode} node The node to check
|
---|
| 375 | * @returns {boolean} `true` if the given node is an IIFE
|
---|
| 376 | */
|
---|
| 377 | function isIIFE(node) {
|
---|
| 378 | const maybeCallNode = astUtils.skipChainExpression(node);
|
---|
| 379 |
|
---|
| 380 | return maybeCallNode.type === "CallExpression" && maybeCallNode.callee.type === "FunctionExpression";
|
---|
| 381 | }
|
---|
| 382 |
|
---|
| 383 | /**
|
---|
| 384 | * Determines if the given node can be the assignment target in destructuring or the LHS of an assignment.
|
---|
| 385 | * This is to avoid an autofix that could change behavior because parsers mistakenly allow invalid syntax,
|
---|
| 386 | * such as `(a = b) = c` and `[(a = b) = c] = []`. Ideally, this function shouldn't be necessary.
|
---|
| 387 | * @param {ASTNode} [node] The node to check
|
---|
| 388 | * @returns {boolean} `true` if the given node can be a valid assignment target
|
---|
| 389 | */
|
---|
| 390 | function canBeAssignmentTarget(node) {
|
---|
| 391 | return node && (node.type === "Identifier" || node.type === "MemberExpression");
|
---|
| 392 | }
|
---|
| 393 |
|
---|
| 394 | /**
|
---|
| 395 | * Checks if a node is fixable.
|
---|
| 396 | * A node is fixable if removing a single pair of surrounding parentheses does not turn it
|
---|
| 397 | * into a directive after fixing other nodes.
|
---|
| 398 | * Almost all nodes are fixable, except if all of the following conditions are met:
|
---|
| 399 | * The node is a string Literal
|
---|
| 400 | * It has a single pair of parentheses
|
---|
| 401 | * It is the only child of an ExpressionStatement
|
---|
| 402 | * @param {ASTNode} node The node to evaluate.
|
---|
| 403 | * @returns {boolean} Whether or not the node is fixable.
|
---|
| 404 | * @private
|
---|
| 405 | */
|
---|
| 406 | function isFixable(node) {
|
---|
| 407 |
|
---|
| 408 | // if it's not a string literal it can be autofixed
|
---|
| 409 | if (node.type !== "Literal" || typeof node.value !== "string") {
|
---|
| 410 | return true;
|
---|
| 411 | }
|
---|
| 412 | if (isParenthesisedTwice(node)) {
|
---|
| 413 | return true;
|
---|
| 414 | }
|
---|
| 415 | return !astUtils.isTopLevelExpressionStatement(node.parent);
|
---|
| 416 | }
|
---|
| 417 |
|
---|
| 418 | /**
|
---|
| 419 | * Report the node
|
---|
| 420 | * @param {ASTNode} node node to evaluate
|
---|
| 421 | * @returns {void}
|
---|
| 422 | * @private
|
---|
| 423 | */
|
---|
| 424 | function report(node) {
|
---|
| 425 | const leftParenToken = sourceCode.getTokenBefore(node);
|
---|
| 426 | const rightParenToken = sourceCode.getTokenAfter(node);
|
---|
| 427 |
|
---|
| 428 | if (!isParenthesisedTwice(node)) {
|
---|
| 429 | if (tokensToIgnore.has(sourceCode.getFirstToken(node))) {
|
---|
| 430 | return;
|
---|
| 431 | }
|
---|
| 432 |
|
---|
| 433 | if (isIIFE(node) && !isParenthesised(node.callee)) {
|
---|
| 434 | return;
|
---|
| 435 | }
|
---|
| 436 |
|
---|
| 437 | if (ALLOW_PARENS_AFTER_COMMENT_PATTERN) {
|
---|
| 438 | const commentsBeforeLeftParenToken = sourceCode.getCommentsBefore(leftParenToken);
|
---|
| 439 | const totalCommentsBeforeLeftParenTokenCount = commentsBeforeLeftParenToken.length;
|
---|
| 440 | const ignorePattern = new RegExp(ALLOW_PARENS_AFTER_COMMENT_PATTERN, "u");
|
---|
| 441 |
|
---|
| 442 | if (
|
---|
| 443 | totalCommentsBeforeLeftParenTokenCount > 0 &&
|
---|
| 444 | ignorePattern.test(commentsBeforeLeftParenToken[totalCommentsBeforeLeftParenTokenCount - 1].value)
|
---|
| 445 | ) {
|
---|
| 446 | return;
|
---|
| 447 | }
|
---|
| 448 | }
|
---|
| 449 | }
|
---|
| 450 |
|
---|
| 451 | /**
|
---|
| 452 | * Finishes reporting
|
---|
| 453 | * @returns {void}
|
---|
| 454 | * @private
|
---|
| 455 | */
|
---|
| 456 | function finishReport() {
|
---|
| 457 | context.report({
|
---|
| 458 | node,
|
---|
| 459 | loc: leftParenToken.loc,
|
---|
| 460 | messageId: "unexpected",
|
---|
| 461 | fix: isFixable(node)
|
---|
| 462 | ? fixer => {
|
---|
| 463 | const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);
|
---|
| 464 |
|
---|
| 465 | return fixer.replaceTextRange([
|
---|
| 466 | leftParenToken.range[0],
|
---|
| 467 | rightParenToken.range[1]
|
---|
| 468 | ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
|
---|
| 469 | }
|
---|
| 470 | : null
|
---|
| 471 | });
|
---|
| 472 | }
|
---|
| 473 |
|
---|
| 474 | if (reportsBuffer) {
|
---|
| 475 | reportsBuffer.reports.push({ node, finishReport });
|
---|
| 476 | return;
|
---|
| 477 | }
|
---|
| 478 |
|
---|
| 479 | finishReport();
|
---|
| 480 | }
|
---|
| 481 |
|
---|
| 482 | /**
|
---|
| 483 | * Evaluate a argument of the node.
|
---|
| 484 | * @param {ASTNode} node node to evaluate
|
---|
| 485 | * @returns {void}
|
---|
| 486 | * @private
|
---|
| 487 | */
|
---|
| 488 | function checkArgumentWithPrecedence(node) {
|
---|
| 489 | if (hasExcessParensWithPrecedence(node.argument, precedence(node))) {
|
---|
| 490 | report(node.argument);
|
---|
| 491 | }
|
---|
| 492 | }
|
---|
| 493 |
|
---|
| 494 | /**
|
---|
| 495 | * Check if a member expression contains a call expression
|
---|
| 496 | * @param {ASTNode} node MemberExpression node to evaluate
|
---|
| 497 | * @returns {boolean} true if found, false if not
|
---|
| 498 | */
|
---|
| 499 | function doesMemberExpressionContainCallExpression(node) {
|
---|
| 500 | let currentNode = node.object;
|
---|
| 501 | let currentNodeType = node.object.type;
|
---|
| 502 |
|
---|
| 503 | while (currentNodeType === "MemberExpression") {
|
---|
| 504 | currentNode = currentNode.object;
|
---|
| 505 | currentNodeType = currentNode.type;
|
---|
| 506 | }
|
---|
| 507 |
|
---|
| 508 | return currentNodeType === "CallExpression";
|
---|
| 509 | }
|
---|
| 510 |
|
---|
| 511 | /**
|
---|
| 512 | * Evaluate a new call
|
---|
| 513 | * @param {ASTNode} node node to evaluate
|
---|
| 514 | * @returns {void}
|
---|
| 515 | * @private
|
---|
| 516 | */
|
---|
| 517 | function checkCallNew(node) {
|
---|
| 518 | const callee = node.callee;
|
---|
| 519 |
|
---|
| 520 | if (hasExcessParensWithPrecedence(callee, precedence(node))) {
|
---|
| 521 | if (
|
---|
| 522 | hasDoubleExcessParens(callee) ||
|
---|
| 523 | !(
|
---|
| 524 | isIIFE(node) ||
|
---|
| 525 |
|
---|
| 526 | // (new A)(); new (new A)();
|
---|
| 527 | (
|
---|
| 528 | callee.type === "NewExpression" &&
|
---|
| 529 | !isNewExpressionWithParens(callee) &&
|
---|
| 530 | !(
|
---|
| 531 | node.type === "NewExpression" &&
|
---|
| 532 | !isNewExpressionWithParens(node)
|
---|
| 533 | )
|
---|
| 534 | ) ||
|
---|
| 535 |
|
---|
| 536 | // new (a().b)(); new (a.b().c);
|
---|
| 537 | (
|
---|
| 538 | node.type === "NewExpression" &&
|
---|
| 539 | callee.type === "MemberExpression" &&
|
---|
| 540 | doesMemberExpressionContainCallExpression(callee)
|
---|
| 541 | ) ||
|
---|
| 542 |
|
---|
| 543 | // (a?.b)(); (a?.())();
|
---|
| 544 | (
|
---|
| 545 | !node.optional &&
|
---|
| 546 | callee.type === "ChainExpression"
|
---|
| 547 | )
|
---|
| 548 | )
|
---|
| 549 | ) {
|
---|
| 550 | report(node.callee);
|
---|
| 551 | }
|
---|
| 552 | }
|
---|
| 553 | node.arguments
|
---|
| 554 | .filter(arg => hasExcessParensWithPrecedence(arg, PRECEDENCE_OF_ASSIGNMENT_EXPR))
|
---|
| 555 | .forEach(report);
|
---|
| 556 | }
|
---|
| 557 |
|
---|
| 558 | /**
|
---|
| 559 | * Evaluate binary logicals
|
---|
| 560 | * @param {ASTNode} node node to evaluate
|
---|
| 561 | * @returns {void}
|
---|
| 562 | * @private
|
---|
| 563 | */
|
---|
| 564 | function checkBinaryLogical(node) {
|
---|
| 565 | const prec = precedence(node);
|
---|
| 566 | const leftPrecedence = precedence(node.left);
|
---|
| 567 | const rightPrecedence = precedence(node.right);
|
---|
| 568 | const isExponentiation = node.operator === "**";
|
---|
| 569 | const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression");
|
---|
| 570 | const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");
|
---|
| 571 |
|
---|
| 572 | if (!shouldSkipLeft && hasExcessParens(node.left)) {
|
---|
| 573 | if (
|
---|
| 574 | !(["AwaitExpression", "UnaryExpression"].includes(node.left.type) && isExponentiation) &&
|
---|
| 575 | !astUtils.isMixedLogicalAndCoalesceExpressions(node.left, node) &&
|
---|
| 576 | (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation)) ||
|
---|
| 577 | isParenthesisedTwice(node.left)
|
---|
| 578 | ) {
|
---|
| 579 | report(node.left);
|
---|
| 580 | }
|
---|
| 581 | }
|
---|
| 582 |
|
---|
| 583 | if (!shouldSkipRight && hasExcessParens(node.right)) {
|
---|
| 584 | if (
|
---|
| 585 | !astUtils.isMixedLogicalAndCoalesceExpressions(node.right, node) &&
|
---|
| 586 | (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation)) ||
|
---|
| 587 | isParenthesisedTwice(node.right)
|
---|
| 588 | ) {
|
---|
| 589 | report(node.right);
|
---|
| 590 | }
|
---|
| 591 | }
|
---|
| 592 | }
|
---|
| 593 |
|
---|
| 594 | /**
|
---|
| 595 | * Check the parentheses around the super class of the given class definition.
|
---|
| 596 | * @param {ASTNode} node The node of class declarations to check.
|
---|
| 597 | * @returns {void}
|
---|
| 598 | */
|
---|
| 599 | function checkClass(node) {
|
---|
| 600 | if (!node.superClass) {
|
---|
| 601 | return;
|
---|
| 602 | }
|
---|
| 603 |
|
---|
| 604 | /*
|
---|
| 605 | * If `node.superClass` is a LeftHandSideExpression, parentheses are extra.
|
---|
| 606 | * Otherwise, parentheses are needed.
|
---|
| 607 | */
|
---|
| 608 | const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR
|
---|
| 609 | ? hasExcessParens(node.superClass)
|
---|
| 610 | : hasDoubleExcessParens(node.superClass);
|
---|
| 611 |
|
---|
| 612 | if (hasExtraParens) {
|
---|
| 613 | report(node.superClass);
|
---|
| 614 | }
|
---|
| 615 | }
|
---|
| 616 |
|
---|
| 617 | /**
|
---|
| 618 | * Check the parentheses around the argument of the given spread operator.
|
---|
| 619 | * @param {ASTNode} node The node of spread elements/properties to check.
|
---|
| 620 | * @returns {void}
|
---|
| 621 | */
|
---|
| 622 | function checkSpreadOperator(node) {
|
---|
| 623 | if (hasExcessParensWithPrecedence(node.argument, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
|
---|
| 624 | report(node.argument);
|
---|
| 625 | }
|
---|
| 626 | }
|
---|
| 627 |
|
---|
| 628 | /**
|
---|
| 629 | * Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration
|
---|
| 630 | * @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node
|
---|
| 631 | * @returns {void}
|
---|
| 632 | */
|
---|
| 633 | function checkExpressionOrExportStatement(node) {
|
---|
| 634 | const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);
|
---|
| 635 | const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken);
|
---|
| 636 | const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null;
|
---|
| 637 | const tokenAfterClosingParens = secondToken ? sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken) : null;
|
---|
| 638 |
|
---|
| 639 | if (
|
---|
| 640 | astUtils.isOpeningParenToken(firstToken) &&
|
---|
| 641 | (
|
---|
| 642 | astUtils.isOpeningBraceToken(secondToken) ||
|
---|
| 643 | secondToken.type === "Keyword" && (
|
---|
| 644 | secondToken.value === "function" ||
|
---|
| 645 | secondToken.value === "class" ||
|
---|
| 646 | secondToken.value === "let" &&
|
---|
| 647 | tokenAfterClosingParens &&
|
---|
| 648 | (
|
---|
| 649 | astUtils.isOpeningBracketToken(tokenAfterClosingParens) ||
|
---|
| 650 | tokenAfterClosingParens.type === "Identifier"
|
---|
| 651 | )
|
---|
| 652 | ) ||
|
---|
| 653 | secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && thirdToken && thirdToken.type === "Keyword" && thirdToken.value === "function"
|
---|
| 654 | )
|
---|
| 655 | ) {
|
---|
| 656 | tokensToIgnore.add(secondToken);
|
---|
| 657 | }
|
---|
| 658 |
|
---|
| 659 | const hasExtraParens = node.parent.type === "ExportDefaultDeclaration"
|
---|
| 660 | ? hasExcessParensWithPrecedence(node, PRECEDENCE_OF_ASSIGNMENT_EXPR)
|
---|
| 661 | : hasExcessParens(node);
|
---|
| 662 |
|
---|
| 663 | if (hasExtraParens) {
|
---|
| 664 | report(node);
|
---|
| 665 | }
|
---|
| 666 | }
|
---|
| 667 |
|
---|
| 668 | /**
|
---|
| 669 | * Finds the path from the given node to the specified ancestor.
|
---|
| 670 | * @param {ASTNode} node First node in the path.
|
---|
| 671 | * @param {ASTNode} ancestor Last node in the path.
|
---|
| 672 | * @returns {ASTNode[]} Path, including both nodes.
|
---|
| 673 | * @throws {Error} If the given node does not have the specified ancestor.
|
---|
| 674 | */
|
---|
| 675 | function pathToAncestor(node, ancestor) {
|
---|
| 676 | const path = [node];
|
---|
| 677 | let currentNode = node;
|
---|
| 678 |
|
---|
| 679 | while (currentNode !== ancestor) {
|
---|
| 680 |
|
---|
| 681 | currentNode = currentNode.parent;
|
---|
| 682 |
|
---|
| 683 | /* c8 ignore start */
|
---|
| 684 | if (currentNode === null) {
|
---|
| 685 | throw new Error("Nodes are not in the ancestor-descendant relationship.");
|
---|
| 686 | }/* c8 ignore stop */
|
---|
| 687 |
|
---|
| 688 | path.push(currentNode);
|
---|
| 689 | }
|
---|
| 690 |
|
---|
| 691 | return path;
|
---|
| 692 | }
|
---|
| 693 |
|
---|
| 694 | /**
|
---|
| 695 | * Finds the path from the given node to the specified descendant.
|
---|
| 696 | * @param {ASTNode} node First node in the path.
|
---|
| 697 | * @param {ASTNode} descendant Last node in the path.
|
---|
| 698 | * @returns {ASTNode[]} Path, including both nodes.
|
---|
| 699 | * @throws {Error} If the given node does not have the specified descendant.
|
---|
| 700 | */
|
---|
| 701 | function pathToDescendant(node, descendant) {
|
---|
| 702 | return pathToAncestor(descendant, node).reverse();
|
---|
| 703 | }
|
---|
| 704 |
|
---|
| 705 | /**
|
---|
| 706 | * Checks whether the syntax of the given ancestor of an 'in' expression inside a for-loop initializer
|
---|
| 707 | * is preventing the 'in' keyword from being interpreted as a part of an ill-formed for-in loop.
|
---|
| 708 | * @param {ASTNode} node Ancestor of an 'in' expression.
|
---|
| 709 | * @param {ASTNode} child Child of the node, ancestor of the same 'in' expression or the 'in' expression itself.
|
---|
| 710 | * @returns {boolean} True if the keyword 'in' would be interpreted as the 'in' operator, without any parenthesis.
|
---|
| 711 | */
|
---|
| 712 | function isSafelyEnclosingInExpression(node, child) {
|
---|
| 713 | switch (node.type) {
|
---|
| 714 | case "ArrayExpression":
|
---|
| 715 | case "ArrayPattern":
|
---|
| 716 | case "BlockStatement":
|
---|
| 717 | case "ObjectExpression":
|
---|
| 718 | case "ObjectPattern":
|
---|
| 719 | case "TemplateLiteral":
|
---|
| 720 | return true;
|
---|
| 721 | case "ArrowFunctionExpression":
|
---|
| 722 | case "FunctionExpression":
|
---|
| 723 | return node.params.includes(child);
|
---|
| 724 | case "CallExpression":
|
---|
| 725 | case "NewExpression":
|
---|
| 726 | return node.arguments.includes(child);
|
---|
| 727 | case "MemberExpression":
|
---|
| 728 | return node.computed && node.property === child;
|
---|
| 729 | case "ConditionalExpression":
|
---|
| 730 | return node.consequent === child;
|
---|
| 731 | default:
|
---|
| 732 | return false;
|
---|
| 733 | }
|
---|
| 734 | }
|
---|
| 735 |
|
---|
| 736 | /**
|
---|
| 737 | * Starts a new reports buffering. Warnings will be stored in a buffer instead of being reported immediately.
|
---|
| 738 | * An additional logic that requires multiple nodes (e.g. a whole subtree) may dismiss some of the stored warnings.
|
---|
| 739 | * @returns {void}
|
---|
| 740 | */
|
---|
| 741 | function startNewReportsBuffering() {
|
---|
| 742 | reportsBuffer = {
|
---|
| 743 | upper: reportsBuffer,
|
---|
| 744 | inExpressionNodes: [],
|
---|
| 745 | reports: []
|
---|
| 746 | };
|
---|
| 747 | }
|
---|
| 748 |
|
---|
| 749 | /**
|
---|
| 750 | * Ends the current reports buffering.
|
---|
| 751 | * @returns {void}
|
---|
| 752 | */
|
---|
| 753 | function endCurrentReportsBuffering() {
|
---|
| 754 | const { upper, inExpressionNodes, reports } = reportsBuffer;
|
---|
| 755 |
|
---|
| 756 | if (upper) {
|
---|
| 757 | upper.inExpressionNodes.push(...inExpressionNodes);
|
---|
| 758 | upper.reports.push(...reports);
|
---|
| 759 | } else {
|
---|
| 760 |
|
---|
| 761 | // flush remaining reports
|
---|
| 762 | reports.forEach(({ finishReport }) => finishReport());
|
---|
| 763 | }
|
---|
| 764 |
|
---|
| 765 | reportsBuffer = upper;
|
---|
| 766 | }
|
---|
| 767 |
|
---|
| 768 | /**
|
---|
| 769 | * Checks whether the given node is in the current reports buffer.
|
---|
| 770 | * @param {ASTNode} node Node to check.
|
---|
| 771 | * @returns {boolean} True if the node is in the current buffer, false otherwise.
|
---|
| 772 | */
|
---|
| 773 | function isInCurrentReportsBuffer(node) {
|
---|
| 774 | return reportsBuffer.reports.some(r => r.node === node);
|
---|
| 775 | }
|
---|
| 776 |
|
---|
| 777 | /**
|
---|
| 778 | * Removes the given node from the current reports buffer.
|
---|
| 779 | * @param {ASTNode} node Node to remove.
|
---|
| 780 | * @returns {void}
|
---|
| 781 | */
|
---|
| 782 | function removeFromCurrentReportsBuffer(node) {
|
---|
| 783 | reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node);
|
---|
| 784 | }
|
---|
| 785 |
|
---|
| 786 | /**
|
---|
| 787 | * Checks whether a node is a MemberExpression at NewExpression's callee.
|
---|
| 788 | * @param {ASTNode} node node to check.
|
---|
| 789 | * @returns {boolean} True if the node is a MemberExpression at NewExpression's callee. false otherwise.
|
---|
| 790 | */
|
---|
| 791 | function isMemberExpInNewCallee(node) {
|
---|
| 792 | if (node.type === "MemberExpression") {
|
---|
| 793 | return node.parent.type === "NewExpression" && node.parent.callee === node
|
---|
| 794 | ? true
|
---|
| 795 | : node.parent.object === node && isMemberExpInNewCallee(node.parent);
|
---|
| 796 | }
|
---|
| 797 | return false;
|
---|
| 798 | }
|
---|
| 799 |
|
---|
| 800 | /**
|
---|
| 801 | * Checks if the left-hand side of an assignment is an identifier, the operator is one of
|
---|
| 802 | * `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous class or function.
|
---|
| 803 | *
|
---|
| 804 | * As per https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation, an
|
---|
| 805 | * assignment involving one of the operators `=`, `&&=`, `||=` or `??=` where the right-hand
|
---|
| 806 | * side is an anonymous class or function and the left-hand side is an *unparenthesized*
|
---|
| 807 | * identifier has different semantics than other assignments.
|
---|
| 808 | * Specifically, when an expression like `foo = function () {}` is evaluated, `foo.name`
|
---|
| 809 | * will be set to the string "foo", i.e. the identifier name. The same thing does not happen
|
---|
| 810 | * when evaluating `(foo) = function () {}`.
|
---|
| 811 | * Since the parenthesizing of the identifier in the left-hand side is significant in this
|
---|
| 812 | * special case, the parentheses, if present, should not be flagged as unnecessary.
|
---|
| 813 | * @param {ASTNode} node an AssignmentExpression node.
|
---|
| 814 | * @returns {boolean} `true` if the left-hand side of the assignment is an identifier, the
|
---|
| 815 | * operator is one of `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous
|
---|
| 816 | * class or function; otherwise, `false`.
|
---|
| 817 | */
|
---|
| 818 | function isAnonymousFunctionAssignmentException({ left, operator, right }) {
|
---|
| 819 | if (left.type === "Identifier" && ["=", "&&=", "||=", "??="].includes(operator)) {
|
---|
| 820 | const rhsType = right.type;
|
---|
| 821 |
|
---|
| 822 | if (rhsType === "ArrowFunctionExpression") {
|
---|
| 823 | return true;
|
---|
| 824 | }
|
---|
| 825 | if ((rhsType === "FunctionExpression" || rhsType === "ClassExpression") && !right.id) {
|
---|
| 826 | return true;
|
---|
| 827 | }
|
---|
| 828 | }
|
---|
| 829 | return false;
|
---|
| 830 | }
|
---|
| 831 |
|
---|
| 832 | return {
|
---|
| 833 | ArrayExpression(node) {
|
---|
| 834 | node.elements
|
---|
| 835 | .filter(e => e && hasExcessParensWithPrecedence(e, PRECEDENCE_OF_ASSIGNMENT_EXPR))
|
---|
| 836 | .forEach(report);
|
---|
| 837 | },
|
---|
| 838 |
|
---|
| 839 | ArrayPattern(node) {
|
---|
| 840 | node.elements
|
---|
| 841 | .filter(e => canBeAssignmentTarget(e) && hasExcessParens(e))
|
---|
| 842 | .forEach(report);
|
---|
| 843 | },
|
---|
| 844 |
|
---|
| 845 | ArrowFunctionExpression(node) {
|
---|
| 846 | if (isReturnAssignException(node)) {
|
---|
| 847 | return;
|
---|
| 848 | }
|
---|
| 849 |
|
---|
| 850 | if (node.body.type === "ConditionalExpression" &&
|
---|
| 851 | IGNORE_ARROW_CONDITIONALS
|
---|
| 852 | ) {
|
---|
| 853 | return;
|
---|
| 854 | }
|
---|
| 855 |
|
---|
| 856 | if (node.body.type !== "BlockStatement") {
|
---|
| 857 | const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken);
|
---|
| 858 | const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);
|
---|
| 859 |
|
---|
| 860 | if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) {
|
---|
| 861 | tokensToIgnore.add(firstBodyToken);
|
---|
| 862 | }
|
---|
| 863 | if (hasExcessParensWithPrecedence(node.body, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
|
---|
| 864 | report(node.body);
|
---|
| 865 | }
|
---|
| 866 | }
|
---|
| 867 | },
|
---|
| 868 |
|
---|
| 869 | AssignmentExpression(node) {
|
---|
| 870 | if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left) &&
|
---|
| 871 | (!isAnonymousFunctionAssignmentException(node) || isParenthesisedTwice(node.left))) {
|
---|
| 872 | report(node.left);
|
---|
| 873 | }
|
---|
| 874 |
|
---|
| 875 | if (!isReturnAssignException(node) && hasExcessParensWithPrecedence(node.right, precedence(node))) {
|
---|
| 876 | report(node.right);
|
---|
| 877 | }
|
---|
| 878 | },
|
---|
| 879 |
|
---|
| 880 | BinaryExpression(node) {
|
---|
| 881 | if (reportsBuffer && node.operator === "in") {
|
---|
| 882 | reportsBuffer.inExpressionNodes.push(node);
|
---|
| 883 | }
|
---|
| 884 |
|
---|
| 885 | checkBinaryLogical(node);
|
---|
| 886 | },
|
---|
| 887 |
|
---|
| 888 | CallExpression: checkCallNew,
|
---|
| 889 |
|
---|
| 890 | ConditionalExpression(node) {
|
---|
| 891 | if (isReturnAssignException(node)) {
|
---|
| 892 | return;
|
---|
| 893 | }
|
---|
| 894 |
|
---|
| 895 | const availableTypes = new Set(["BinaryExpression", "LogicalExpression"]);
|
---|
| 896 |
|
---|
| 897 | if (
|
---|
| 898 | !(EXCEPT_COND_TERNARY && availableTypes.has(node.test.type)) &&
|
---|
| 899 | !isCondAssignException(node) &&
|
---|
| 900 | hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" }))
|
---|
| 901 | ) {
|
---|
| 902 | report(node.test);
|
---|
| 903 | }
|
---|
| 904 |
|
---|
| 905 | if (
|
---|
| 906 | !(EXCEPT_COND_TERNARY && availableTypes.has(node.consequent.type)) &&
|
---|
| 907 | hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
|
---|
| 908 | report(node.consequent);
|
---|
| 909 | }
|
---|
| 910 |
|
---|
| 911 | if (
|
---|
| 912 | !(EXCEPT_COND_TERNARY && availableTypes.has(node.alternate.type)) &&
|
---|
| 913 | hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
|
---|
| 914 | report(node.alternate);
|
---|
| 915 | }
|
---|
| 916 | },
|
---|
| 917 |
|
---|
| 918 | DoWhileStatement(node) {
|
---|
| 919 | if (hasExcessParens(node.test) && !isCondAssignException(node)) {
|
---|
| 920 | report(node.test);
|
---|
| 921 | }
|
---|
| 922 | },
|
---|
| 923 |
|
---|
| 924 | ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
|
---|
| 925 | ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
|
---|
| 926 |
|
---|
| 927 | ForInStatement(node) {
|
---|
| 928 | if (node.left.type !== "VariableDeclaration") {
|
---|
| 929 | const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
|
---|
| 930 |
|
---|
| 931 | if (
|
---|
| 932 | firstLeftToken.value === "let" &&
|
---|
| 933 | astUtils.isOpeningBracketToken(
|
---|
| 934 | sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)
|
---|
| 935 | )
|
---|
| 936 | ) {
|
---|
| 937 |
|
---|
| 938 | // ForInStatement#left expression cannot start with `let[`.
|
---|
| 939 | tokensToIgnore.add(firstLeftToken);
|
---|
| 940 | }
|
---|
| 941 | }
|
---|
| 942 |
|
---|
| 943 | if (hasExcessParens(node.left)) {
|
---|
| 944 | report(node.left);
|
---|
| 945 | }
|
---|
| 946 |
|
---|
| 947 | if (hasExcessParens(node.right)) {
|
---|
| 948 | report(node.right);
|
---|
| 949 | }
|
---|
| 950 | },
|
---|
| 951 |
|
---|
| 952 | ForOfStatement(node) {
|
---|
| 953 | if (node.left.type !== "VariableDeclaration") {
|
---|
| 954 | const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
|
---|
| 955 |
|
---|
| 956 | if (firstLeftToken.value === "let") {
|
---|
| 957 |
|
---|
| 958 | // ForOfStatement#left expression cannot start with `let`.
|
---|
| 959 | tokensToIgnore.add(firstLeftToken);
|
---|
| 960 | }
|
---|
| 961 | }
|
---|
| 962 |
|
---|
| 963 | if (hasExcessParens(node.left)) {
|
---|
| 964 | report(node.left);
|
---|
| 965 | }
|
---|
| 966 |
|
---|
| 967 | if (hasExcessParensWithPrecedence(node.right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
|
---|
| 968 | report(node.right);
|
---|
| 969 | }
|
---|
| 970 | },
|
---|
| 971 |
|
---|
| 972 | ForStatement(node) {
|
---|
| 973 | if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) {
|
---|
| 974 | report(node.test);
|
---|
| 975 | }
|
---|
| 976 |
|
---|
| 977 | if (node.update && hasExcessParens(node.update)) {
|
---|
| 978 | report(node.update);
|
---|
| 979 | }
|
---|
| 980 |
|
---|
| 981 | if (node.init) {
|
---|
| 982 |
|
---|
| 983 | if (node.init.type !== "VariableDeclaration") {
|
---|
| 984 | const firstToken = sourceCode.getFirstToken(node.init, astUtils.isNotOpeningParenToken);
|
---|
| 985 |
|
---|
| 986 | if (
|
---|
| 987 | firstToken.value === "let" &&
|
---|
| 988 | astUtils.isOpeningBracketToken(
|
---|
| 989 | sourceCode.getTokenAfter(firstToken, astUtils.isNotClosingParenToken)
|
---|
| 990 | )
|
---|
| 991 | ) {
|
---|
| 992 |
|
---|
| 993 | // ForStatement#init expression cannot start with `let[`.
|
---|
| 994 | tokensToIgnore.add(firstToken);
|
---|
| 995 | }
|
---|
| 996 | }
|
---|
| 997 |
|
---|
| 998 | startNewReportsBuffering();
|
---|
| 999 |
|
---|
| 1000 | if (hasExcessParens(node.init)) {
|
---|
| 1001 | report(node.init);
|
---|
| 1002 | }
|
---|
| 1003 | }
|
---|
| 1004 | },
|
---|
| 1005 |
|
---|
| 1006 | "ForStatement > *.init:exit"(node) {
|
---|
| 1007 |
|
---|
| 1008 | /*
|
---|
| 1009 | * Removing parentheses around `in` expressions might change semantics and cause errors.
|
---|
| 1010 | *
|
---|
| 1011 | * For example, this valid for loop:
|
---|
| 1012 | * for (let a = (b in c); ;);
|
---|
| 1013 | * after removing parentheses would be treated as an invalid for-in loop:
|
---|
| 1014 | * for (let a = b in c; ;);
|
---|
| 1015 | */
|
---|
| 1016 |
|
---|
| 1017 | if (reportsBuffer.reports.length) {
|
---|
| 1018 | reportsBuffer.inExpressionNodes.forEach(inExpressionNode => {
|
---|
| 1019 | const path = pathToDescendant(node, inExpressionNode);
|
---|
| 1020 | let nodeToExclude;
|
---|
| 1021 |
|
---|
| 1022 | for (let i = 0; i < path.length; i++) {
|
---|
| 1023 | const pathNode = path[i];
|
---|
| 1024 |
|
---|
| 1025 | if (i < path.length - 1) {
|
---|
| 1026 | const nextPathNode = path[i + 1];
|
---|
| 1027 |
|
---|
| 1028 | if (isSafelyEnclosingInExpression(pathNode, nextPathNode)) {
|
---|
| 1029 |
|
---|
| 1030 | // The 'in' expression in safely enclosed by the syntax of its ancestor nodes (e.g. by '{}' or '[]').
|
---|
| 1031 | return;
|
---|
| 1032 | }
|
---|
| 1033 | }
|
---|
| 1034 |
|
---|
| 1035 | if (isParenthesised(pathNode)) {
|
---|
| 1036 | if (isInCurrentReportsBuffer(pathNode)) {
|
---|
| 1037 |
|
---|
| 1038 | // This node was supposed to be reported, but parentheses might be necessary.
|
---|
| 1039 |
|
---|
| 1040 | if (isParenthesisedTwice(pathNode)) {
|
---|
| 1041 |
|
---|
| 1042 | /*
|
---|
| 1043 | * This node is parenthesised twice, it certainly has at least one pair of `extra` parentheses.
|
---|
| 1044 | * If the --fix option is on, the current fixing iteration will remove only one pair of parentheses.
|
---|
| 1045 | * The remaining pair is safely enclosing the 'in' expression.
|
---|
| 1046 | */
|
---|
| 1047 | return;
|
---|
| 1048 | }
|
---|
| 1049 |
|
---|
| 1050 | // Exclude the outermost node only.
|
---|
| 1051 | if (!nodeToExclude) {
|
---|
| 1052 | nodeToExclude = pathNode;
|
---|
| 1053 | }
|
---|
| 1054 |
|
---|
| 1055 | // Don't break the loop here, there might be some safe nodes or parentheses that will stay inside.
|
---|
| 1056 |
|
---|
| 1057 | } else {
|
---|
| 1058 |
|
---|
| 1059 | // This node will stay parenthesised, the 'in' expression in safely enclosed by '()'.
|
---|
| 1060 | return;
|
---|
| 1061 | }
|
---|
| 1062 | }
|
---|
| 1063 | }
|
---|
| 1064 |
|
---|
| 1065 | // Exclude the node from the list (i.e. treat parentheses as necessary)
|
---|
| 1066 | removeFromCurrentReportsBuffer(nodeToExclude);
|
---|
| 1067 | });
|
---|
| 1068 | }
|
---|
| 1069 |
|
---|
| 1070 | endCurrentReportsBuffering();
|
---|
| 1071 | },
|
---|
| 1072 |
|
---|
| 1073 | IfStatement(node) {
|
---|
| 1074 | if (hasExcessParens(node.test) && !isCondAssignException(node)) {
|
---|
| 1075 | report(node.test);
|
---|
| 1076 | }
|
---|
| 1077 | },
|
---|
| 1078 |
|
---|
| 1079 | ImportExpression(node) {
|
---|
| 1080 | const { source } = node;
|
---|
| 1081 |
|
---|
| 1082 | if (source.type === "SequenceExpression") {
|
---|
| 1083 | if (hasDoubleExcessParens(source)) {
|
---|
| 1084 | report(source);
|
---|
| 1085 | }
|
---|
| 1086 | } else if (hasExcessParens(source)) {
|
---|
| 1087 | report(source);
|
---|
| 1088 | }
|
---|
| 1089 | },
|
---|
| 1090 |
|
---|
| 1091 | LogicalExpression: checkBinaryLogical,
|
---|
| 1092 |
|
---|
| 1093 | MemberExpression(node) {
|
---|
| 1094 | const shouldAllowWrapOnce = isMemberExpInNewCallee(node) &&
|
---|
| 1095 | doesMemberExpressionContainCallExpression(node);
|
---|
| 1096 | const nodeObjHasExcessParens = shouldAllowWrapOnce
|
---|
| 1097 | ? hasDoubleExcessParens(node.object)
|
---|
| 1098 | : hasExcessParens(node.object) &&
|
---|
| 1099 | !(
|
---|
| 1100 | isImmediateFunctionPrototypeMethodCall(node.parent) &&
|
---|
| 1101 | node.parent.callee === node &&
|
---|
| 1102 | IGNORE_FUNCTION_PROTOTYPE_METHODS
|
---|
| 1103 | );
|
---|
| 1104 |
|
---|
| 1105 | if (
|
---|
| 1106 | nodeObjHasExcessParens &&
|
---|
| 1107 | precedence(node.object) >= precedence(node) &&
|
---|
| 1108 | (
|
---|
| 1109 | node.computed ||
|
---|
| 1110 | !(
|
---|
| 1111 | astUtils.isDecimalInteger(node.object) ||
|
---|
| 1112 |
|
---|
| 1113 | // RegExp literal is allowed to have parens (#1589)
|
---|
| 1114 | (node.object.type === "Literal" && node.object.regex)
|
---|
| 1115 | )
|
---|
| 1116 | )
|
---|
| 1117 | ) {
|
---|
| 1118 | report(node.object);
|
---|
| 1119 | }
|
---|
| 1120 |
|
---|
| 1121 | if (nodeObjHasExcessParens &&
|
---|
| 1122 | node.object.type === "CallExpression"
|
---|
| 1123 | ) {
|
---|
| 1124 | report(node.object);
|
---|
| 1125 | }
|
---|
| 1126 |
|
---|
| 1127 | if (nodeObjHasExcessParens &&
|
---|
| 1128 | !IGNORE_NEW_IN_MEMBER_EXPR &&
|
---|
| 1129 | node.object.type === "NewExpression" &&
|
---|
| 1130 | isNewExpressionWithParens(node.object)) {
|
---|
| 1131 | report(node.object);
|
---|
| 1132 | }
|
---|
| 1133 |
|
---|
| 1134 | if (nodeObjHasExcessParens &&
|
---|
| 1135 | node.optional &&
|
---|
| 1136 | node.object.type === "ChainExpression"
|
---|
| 1137 | ) {
|
---|
| 1138 | report(node.object);
|
---|
| 1139 | }
|
---|
| 1140 |
|
---|
| 1141 | if (node.computed && hasExcessParens(node.property)) {
|
---|
| 1142 | report(node.property);
|
---|
| 1143 | }
|
---|
| 1144 | },
|
---|
| 1145 |
|
---|
| 1146 | "MethodDefinition[computed=true]"(node) {
|
---|
| 1147 | if (hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
|
---|
| 1148 | report(node.key);
|
---|
| 1149 | }
|
---|
| 1150 | },
|
---|
| 1151 |
|
---|
| 1152 | NewExpression: checkCallNew,
|
---|
| 1153 |
|
---|
| 1154 | ObjectExpression(node) {
|
---|
| 1155 | node.properties
|
---|
| 1156 | .filter(property => property.value && hasExcessParensWithPrecedence(property.value, PRECEDENCE_OF_ASSIGNMENT_EXPR))
|
---|
| 1157 | .forEach(property => report(property.value));
|
---|
| 1158 | },
|
---|
| 1159 |
|
---|
| 1160 | ObjectPattern(node) {
|
---|
| 1161 | node.properties
|
---|
| 1162 | .filter(property => {
|
---|
| 1163 | const value = property.value;
|
---|
| 1164 |
|
---|
| 1165 | return canBeAssignmentTarget(value) && hasExcessParens(value);
|
---|
| 1166 | }).forEach(property => report(property.value));
|
---|
| 1167 | },
|
---|
| 1168 |
|
---|
| 1169 | Property(node) {
|
---|
| 1170 | if (node.computed) {
|
---|
| 1171 | const { key } = node;
|
---|
| 1172 |
|
---|
| 1173 | if (key && hasExcessParensWithPrecedence(key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
|
---|
| 1174 | report(key);
|
---|
| 1175 | }
|
---|
| 1176 | }
|
---|
| 1177 | },
|
---|
| 1178 |
|
---|
| 1179 | PropertyDefinition(node) {
|
---|
| 1180 | if (node.computed && hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
|
---|
| 1181 | report(node.key);
|
---|
| 1182 | }
|
---|
| 1183 |
|
---|
| 1184 | if (node.value && hasExcessParensWithPrecedence(node.value, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
|
---|
| 1185 | report(node.value);
|
---|
| 1186 | }
|
---|
| 1187 | },
|
---|
| 1188 |
|
---|
| 1189 | RestElement(node) {
|
---|
| 1190 | const argument = node.argument;
|
---|
| 1191 |
|
---|
| 1192 | if (canBeAssignmentTarget(argument) && hasExcessParens(argument)) {
|
---|
| 1193 | report(argument);
|
---|
| 1194 | }
|
---|
| 1195 | },
|
---|
| 1196 |
|
---|
| 1197 | ReturnStatement(node) {
|
---|
| 1198 | const returnToken = sourceCode.getFirstToken(node);
|
---|
| 1199 |
|
---|
| 1200 | if (isReturnAssignException(node)) {
|
---|
| 1201 | return;
|
---|
| 1202 | }
|
---|
| 1203 |
|
---|
| 1204 | if (node.argument &&
|
---|
| 1205 | hasExcessParensNoLineTerminator(returnToken, node.argument) &&
|
---|
| 1206 |
|
---|
| 1207 | // RegExp literal is allowed to have parens (#1589)
|
---|
| 1208 | !(node.argument.type === "Literal" && node.argument.regex)) {
|
---|
| 1209 | report(node.argument);
|
---|
| 1210 | }
|
---|
| 1211 | },
|
---|
| 1212 |
|
---|
| 1213 | SequenceExpression(node) {
|
---|
| 1214 | const precedenceOfNode = precedence(node);
|
---|
| 1215 |
|
---|
| 1216 | node.expressions
|
---|
| 1217 | .filter(e => hasExcessParensWithPrecedence(e, precedenceOfNode))
|
---|
| 1218 | .forEach(report);
|
---|
| 1219 | },
|
---|
| 1220 |
|
---|
| 1221 | SwitchCase(node) {
|
---|
| 1222 | if (node.test && hasExcessParens(node.test)) {
|
---|
| 1223 | report(node.test);
|
---|
| 1224 | }
|
---|
| 1225 | },
|
---|
| 1226 |
|
---|
| 1227 | SwitchStatement(node) {
|
---|
| 1228 | if (hasExcessParens(node.discriminant)) {
|
---|
| 1229 | report(node.discriminant);
|
---|
| 1230 | }
|
---|
| 1231 | },
|
---|
| 1232 |
|
---|
| 1233 | ThrowStatement(node) {
|
---|
| 1234 | const throwToken = sourceCode.getFirstToken(node);
|
---|
| 1235 |
|
---|
| 1236 | if (hasExcessParensNoLineTerminator(throwToken, node.argument)) {
|
---|
| 1237 | report(node.argument);
|
---|
| 1238 | }
|
---|
| 1239 | },
|
---|
| 1240 |
|
---|
| 1241 | UnaryExpression: checkArgumentWithPrecedence,
|
---|
| 1242 | UpdateExpression(node) {
|
---|
| 1243 | if (node.prefix) {
|
---|
| 1244 | checkArgumentWithPrecedence(node);
|
---|
| 1245 | } else {
|
---|
| 1246 | const { argument } = node;
|
---|
| 1247 | const operatorToken = sourceCode.getLastToken(node);
|
---|
| 1248 |
|
---|
| 1249 | if (argument.loc.end.line === operatorToken.loc.start.line) {
|
---|
| 1250 | checkArgumentWithPrecedence(node);
|
---|
| 1251 | } else {
|
---|
| 1252 | if (hasDoubleExcessParens(argument)) {
|
---|
| 1253 | report(argument);
|
---|
| 1254 | }
|
---|
| 1255 | }
|
---|
| 1256 | }
|
---|
| 1257 | },
|
---|
| 1258 | AwaitExpression: checkArgumentWithPrecedence,
|
---|
| 1259 |
|
---|
| 1260 | VariableDeclarator(node) {
|
---|
| 1261 | if (
|
---|
| 1262 | node.init && hasExcessParensWithPrecedence(node.init, PRECEDENCE_OF_ASSIGNMENT_EXPR) &&
|
---|
| 1263 |
|
---|
| 1264 | // RegExp literal is allowed to have parens (#1589)
|
---|
| 1265 | !(node.init.type === "Literal" && node.init.regex)
|
---|
| 1266 | ) {
|
---|
| 1267 | report(node.init);
|
---|
| 1268 | }
|
---|
| 1269 | },
|
---|
| 1270 |
|
---|
| 1271 | WhileStatement(node) {
|
---|
| 1272 | if (hasExcessParens(node.test) && !isCondAssignException(node)) {
|
---|
| 1273 | report(node.test);
|
---|
| 1274 | }
|
---|
| 1275 | },
|
---|
| 1276 |
|
---|
| 1277 | WithStatement(node) {
|
---|
| 1278 | if (hasExcessParens(node.object)) {
|
---|
| 1279 | report(node.object);
|
---|
| 1280 | }
|
---|
| 1281 | },
|
---|
| 1282 |
|
---|
| 1283 | YieldExpression(node) {
|
---|
| 1284 | if (node.argument) {
|
---|
| 1285 | const yieldToken = sourceCode.getFirstToken(node);
|
---|
| 1286 |
|
---|
| 1287 | if ((precedence(node.argument) >= precedence(node) &&
|
---|
| 1288 | hasExcessParensNoLineTerminator(yieldToken, node.argument)) ||
|
---|
| 1289 | hasDoubleExcessParens(node.argument)) {
|
---|
| 1290 | report(node.argument);
|
---|
| 1291 | }
|
---|
| 1292 | }
|
---|
| 1293 | },
|
---|
| 1294 |
|
---|
| 1295 | ClassDeclaration: checkClass,
|
---|
| 1296 | ClassExpression: checkClass,
|
---|
| 1297 |
|
---|
| 1298 | SpreadElement: checkSpreadOperator,
|
---|
| 1299 | SpreadProperty: checkSpreadOperator,
|
---|
| 1300 | ExperimentalSpreadProperty: checkSpreadOperator,
|
---|
| 1301 |
|
---|
| 1302 | TemplateLiteral(node) {
|
---|
| 1303 | node.expressions
|
---|
| 1304 | .filter(e => e && hasExcessParens(e))
|
---|
| 1305 | .forEach(report);
|
---|
| 1306 | },
|
---|
| 1307 |
|
---|
| 1308 | AssignmentPattern(node) {
|
---|
| 1309 | const { left, right } = node;
|
---|
| 1310 |
|
---|
| 1311 | if (canBeAssignmentTarget(left) && hasExcessParens(left)) {
|
---|
| 1312 | report(left);
|
---|
| 1313 | }
|
---|
| 1314 |
|
---|
| 1315 | if (right && hasExcessParensWithPrecedence(right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
|
---|
| 1316 | report(right);
|
---|
| 1317 | }
|
---|
| 1318 | }
|
---|
| 1319 | };
|
---|
| 1320 |
|
---|
| 1321 | }
|
---|
| 1322 | };
|
---|