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 e3ff336f2b70..c6e4eb73ee7f 100644 --- a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts +++ b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts @@ -57,7 +57,29 @@ export default createRule({ const [, secondArg] = callee.parent.arguments; - if (callee.parent.arguments.length < 2 || !isTypeAssertion(secondArg)) { + if (callee.parent.arguments.length < 2) { + return; + } + + if (isTypeAssertion(secondArg)) { + const initializerType = services.getTypeAtLocation( + secondArg.expression, + ); + + const assertedType = services.getTypeAtLocation( + secondArg.typeAnnotation, + ); + + const isAssertionNecessary = !checker.isTypeAssignableTo( + initializerType, + assertedType, + ); + + // don't report this if the resulting fix will be a type error + if (isAssertionNecessary) { + return; + } + } else { return; } 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 170622d6f599..f26ccb53350f 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 @@ -57,23 +57,70 @@ ruleTester.run('prefer-reduce-type-parameter', rule, { return a.concat(1); }, [] as number[]); `, + ` + ['a', 'b'].reduce( + (accum, name) => ({ + ...accum, + [name]: true, + }), + {} as Record<'a' | 'b', boolean>, + ); + `, + // Object literal may only specify known properties, and 'c' does not exist in + // type 'Record<"a" | "b", boolean>'. + ` + ['a', 'b'].reduce( + (accum, name) => ({ + ...accum, + [name]: true, + }), + { a: true, b: false, c: true } as Record<'a' | 'b', boolean>, + ); + `, + // '{}' is assignable to the constraint of type 'T', but 'T' could be + // instantiated with a different subtype of constraint 'Record'. + ` + function f>() { + ['a', 'b'].reduce( + (accum, name) => ({ + ...accum, + [name]: true, + }), + {} as T, + ); + } + `, + ` + function f() { + ['a', 'b'].reduce( + (accum, name) => ({ + ...accum, + [name]: true, + }), + {} as T, + ); + } + `, + ` + ['a', 'b'].reduce((accum, name) => \`\${accum} | hello \${name}!\`); + `, ], invalid: [ { code: ` declare const arr: string[]; -arr.reduce(acc => acc, arr.shift() as string); +arr.reduce(acc => acc, arr.shift() as string | undefined); `, errors: [ { - column: 32, + column: 44, line: 3, messageId: 'preferTypeParameter', }, ], output: ` declare const arr: string[]; -arr.reduce(acc => acc, arr.shift()); +arr.reduce(acc => acc, arr.shift()); `, }, { @@ -275,5 +322,63 @@ declare const tuple: [number, number, number] & number[]; tuple.reduce((a, s) => a.concat(s * 2), []); `, }, + { + code: ` +['a', 'b'].reduce( + (accum, name) => ({ + ...accum, + [name]: true, + }), + {} as Record, +); + `, + errors: [ + { + column: 3, + line: 7, + messageId: 'preferTypeParameter', + }, + ], + output: ` +['a', 'b'].reduce>( + (accum, name) => ({ + ...accum, + [name]: true, + }), + {}, +); + `, + }, + { + code: ` +function f>(t: T) { + ['a', 'b'].reduce( + (accum, name) => ({ + ...accum, + [name]: true, + }), + t as Record, + ); +} + `, + errors: [ + { + column: 5, + line: 8, + messageId: 'preferTypeParameter', + }, + ], + output: ` +function f>(t: T) { + ['a', 'b'].reduce>( + (accum, name) => ({ + ...accum, + [name]: true, + }), + t, + ); +} + `, + }, ], });