diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index bae1a498cf0..1ece81eee81 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -710,6 +710,20 @@ module.exports = { reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node); } + /** + * Checks whether a node is a MemberExpression at NewExpression's callee. + * @param {ASTNode} node node to check. + * @returns {boolean} True if the node is a MemberExpression at NewExpression's callee. false otherwise. + */ + function isMemberExpInNewCallee(node) { + if (node.type === "MemberExpression") { + return node.parent.type === "NewExpression" && node.parent.callee === node + ? true + : node.parent.object === node && isMemberExpInNewCallee(node.parent); + } + return false; + } + return { ArrayExpression(node) { node.elements @@ -950,7 +964,11 @@ module.exports = { LogicalExpression: checkBinaryLogical, MemberExpression(node) { - const nodeObjHasExcessParens = hasExcessParens(node.object) && + const shouldAllowWrapOnce = isMemberExpInNewCallee(node) && + doesMemberExpressionContainCallExpression(node); + const nodeObjHasExcessParens = shouldAllowWrapOnce + ? hasDoubleExcessParens(node.object) + : hasExcessParens(node.object) && !( isImmediateFunctionPrototypeMethodCall(node.parent) && node.parent.callee === node && @@ -974,8 +992,8 @@ module.exports = { } if (nodeObjHasExcessParens && - node.object.type === "CallExpression" && - node.parent.type !== "NewExpression") { + node.object.type === "CallExpression" + ) { report(node.object); } diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js index 91099f8cba0..e2501bb45a0 100644 --- a/tests/lib/rules/no-extra-parens.js +++ b/tests/lib/rules/no-extra-parens.js @@ -612,6 +612,14 @@ ruleTester.run("no-extra-parens", rule, { "for (; a; a); a; a;", "for (let a = (b && c) === d; ;);", + "new (a()).b.c;", + "new (a().b).c;", + "new (a().b.c);", + "new (a().b().d);", + "new a().b().d;", + "new (a(b()).c)", + "new (a.b()).c", + // Nullish coalescing { code: "var v = (a ?? b) || c", parserOptions: { ecmaVersion: 2020 } }, { code: "var v = a ?? (b || c)", parserOptions: { ecmaVersion: 2020 } }, @@ -759,6 +767,7 @@ ruleTester.run("no-extra-parens", rule, { invalid("(new foo(bar)).baz", "new foo(bar).baz", "NewExpression"), invalid("(new foo.bar()).baz", "new foo.bar().baz", "NewExpression"), invalid("(new foo.bar()).baz()", "new foo.bar().baz()", "NewExpression"), + invalid("new a[(b()).c]", "new a[b().c]", "CallExpression"), invalid("(a)()", "a()", "Identifier"), invalid("(a.b)()", "a.b()", "MemberExpression"), @@ -772,6 +781,14 @@ ruleTester.run("no-extra-parens", rule, { invalid("((new A))()", "(new A)()", "NewExpression"), invalid("new (foo\n.baz\n.bar\n.foo.baz)", "new foo\n.baz\n.bar\n.foo.baz", "MemberExpression"), invalid("new (foo.baz.bar.baz)", "new foo.baz.bar.baz", "MemberExpression"), + invalid("new ((a.b())).c", "new (a.b()).c", "CallExpression"), + invalid("new ((a().b)).c", "new (a().b).c", "MemberExpression"), + invalid("new ((a().b().d))", "new (a().b().d)", "MemberExpression"), + invalid("new ((a())).b.d", "new (a()).b.d", "CallExpression"), + invalid("new (a.b).d;", "new a.b.d;", "MemberExpression"), + invalid("(a().b).d;", "a().b.d;", "MemberExpression"), + invalid("(a.b()).d;", "a.b().d;", "CallExpression"), + invalid("(a.b).d;", "a.b.d;", "MemberExpression"), invalid("0, (_ => 0)", "0, _ => 0", "ArrowFunctionExpression", 1), invalid("(_ => 0), 0", "_ => 0, 0", "ArrowFunctionExpression", 1),