From a61eecc1d988cec312ddda3bfacf6c077978dd65 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Mon, 11 Mar 2024 19:13:05 +0900 Subject: [PATCH] feat(eslint-plugin): [prefer-reduce-type-parameter] supports tuple, union, intersection --- .../src/rules/prefer-reduce-type-parameter.ts | 14 +++- .../prefer-reduce-type-parameter.test.ts | 69 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts index 9a196e7be0d..7eb781ed0d1 100644 --- a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts +++ b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts @@ -1,5 +1,7 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import * as tsutils from 'ts-api-utils'; +import type * as ts from 'typescript'; import { createRule, @@ -51,6 +53,16 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); + function isArrayType(type: ts.Type): boolean { + return tsutils + .unionTypeParts(type) + .every(unionPart => + tsutils + .intersectionTypeParts(unionPart) + .every(t => checker.isArrayType(t) || checker.isTupleType(t)), + ); + } + return { 'CallExpression > MemberExpression.callee'( callee: MemberExpressionWithCallExpressionParent, @@ -72,7 +84,7 @@ export default createRule({ ); // Check the owner type of the `reduce` method. - if (checker.isArrayType(calleeObjType)) { + if (isArrayType(calleeObjType)) { context.report({ messageId: 'preferTypeParameter', node: secondArg, diff --git a/packages/eslint-plugin/tests/rules/prefer-reduce-type-parameter.test.ts b/packages/eslint-plugin/tests/rules/prefer-reduce-type-parameter.test.ts index 43aadcec074..d93212ae43f 100644 --- a/packages/eslint-plugin/tests/rules/prefer-reduce-type-parameter.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-reduce-type-parameter.test.ts @@ -39,6 +39,24 @@ ruleTester.run('prefer-reduce-type-parameter', rule, { '[1, 2, 3].reduce((sum, num) => sum + num, 0);', '[1, 2, 3].reduce((a, s) => a.concat(s * 2), []);', '[1, 2, 3]?.reduce((a, s) => a.concat(s * 2), []);', + ` + declare const tuple: [number, number, number]; + tuple.reduce((a, s) => a.concat(s * 2), []); + `, + ` + type Reducer = { reduce: (callback: (arg: any) => any, arg: any) => any }; + declare const tuple: [number, number, number] | Reducer; + tuple.reduce(a => { + return a.concat(1); + }, [] as number[]); + `, + ` + type Reducer = { reduce: (callback: (arg: any) => any, arg: any) => any }; + declare const arrayOrReducer: number[] & Reducer; + arrayOrReducer.reduce(a => { + return a.concat(1); + }, [] as number[]); + `, ], invalid: [ { @@ -206,5 +224,56 @@ function f(a: U) { }, ], }, + { + code: ` +declare const tuple: [number, number, number]; +tuple.reduce((a, s) => a.concat(s * 2), [] as number[]); + `, + output: ` +declare const tuple: [number, number, number]; +tuple.reduce((a, s) => a.concat(s * 2), []); + `, + errors: [ + { + messageId: 'preferTypeParameter', + column: 41, + line: 3, + }, + ], + }, + { + code: ` +declare const tupleOrArray: [number, number, number] | number[]; +tupleOrArray.reduce((a, s) => a.concat(s * 2), [] as number[]); + `, + output: ` +declare const tupleOrArray: [number, number, number] | number[]; +tupleOrArray.reduce((a, s) => a.concat(s * 2), []); + `, + errors: [ + { + messageId: 'preferTypeParameter', + column: 48, + line: 3, + }, + ], + }, + { + code: ` +declare const tuple: [number, number, number] & number[]; +tuple.reduce((a, s) => a.concat(s * 2), [] as number[]); + `, + output: ` +declare const tuple: [number, number, number] & number[]; +tuple.reduce((a, s) => a.concat(s * 2), []); + `, + errors: [ + { + messageId: 'preferTypeParameter', + column: 41, + line: 3, + }, + ], + }, ], });