diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts index e3ab2395279..653d2fa74d1 100644 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ b/packages/eslint-plugin/src/rules/no-extra-parens.ts @@ -62,11 +62,32 @@ export default util.createRule({ return rule(node); } + function callExp( node: TSESTree.CallExpression | TSESTree.NewExpression, ): void { const rule = rules.CallExpression as (n: typeof node) => void; + // ESLint core cannot check parens well when there is function type in type parameters with only one argument. + // e.g. foo<() => void>(a); + if ( + node.typeParameters?.params.some(util.isTSFunctionType) && + node.arguments.length === 1 + ) { + const sourceCode = context.getSourceCode(); + const tokenAfterTypeParam = sourceCode.getTokenAfter( + node.typeParameters, + ); + const tokenBeforeArg = sourceCode.getTokenBefore(node.arguments[0]); + + if (tokenAfterTypeParam === tokenBeforeArg) { + return rule({ + ...node, + arguments: [], + }); + } + } + if (util.isTypeAssertion(node.callee)) { // reduces the precedence of the node so the rule thinks it needs to be wrapped return rule({ diff --git a/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts b/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts index 29afab88ec5..4b47df5deda 100644 --- a/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts +++ b/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts @@ -14,6 +14,17 @@ ruleTester.run('no-extra-parens', rule, { valid: [ ...batchedSingleLineTests({ code: ` +foo<() => void>(a); +foo<() => void>(a = b); +foo<() => void>(a, b); +foo<() => void>((a, b)); +foo<() => () => void>(a); +foo<() => void>(()=> undefined); +foo void>(a); + `, + }), + ...batchedSingleLineTests({ + code: ` (0).toString(); (function(){}) ? a() : b(); (/^a$/).test(x); @@ -52,6 +63,10 @@ for (;(a = b);); code: `b => (b = 1);`, options: ['all', { returnAssign: false }], }, + { + code: `foo<() => void>(b => (b = 1));`, + options: ['all', { returnAssign: false }], + }, { code: `b => b ? (c = d) : (c = e);`, options: ['all', { returnAssign: false }], @@ -61,6 +76,7 @@ for (;(a = b);); x = a || (b && c); x = a + (b * c); x = (a * b) / c; +foo<() => void>(x = (a * b) / c); `, options: ['all', { nestedBinaryExpressions: false }], }), @@ -222,6 +238,60 @@ switch (foo) { case 1: case (<2>2): break; default: break; } invalid: [ ...batchedSingleLineTests({ code: ` +foo<() => void>((a)); +foo<() => void>((a = b)); +foo<() => void>((a), b); +foo<() => () => void>((a)); +foo<() => void>((()=> undefined)); +foo void>((a)); +foo void>((a),); +foo void>(((a))); + `, + errors: [ + { + messageId: 'unexpected', + line: 2, + column: 17, + }, + { + messageId: 'unexpected', + line: 3, + column: 17, + }, + { + messageId: 'unexpected', + line: 4, + column: 17, + }, + { + messageId: 'unexpected', + line: 5, + column: 23, + }, + { + messageId: 'unexpected', + line: 6, + column: 17, + }, + { + messageId: 'unexpected', + line: 7, + column: 20, + }, + { + messageId: 'unexpected', + line: 8, + column: 20, + }, + { + messageId: 'unexpected', + line: 9, + column: 21, + }, + ], + }), + ...batchedSingleLineTests({ + code: ` a = (b * c); (a * b) + c; for (a in (b, c));