diff --git a/packages/eslint-plugin/docs/rules/restrict-plus-operands.md b/packages/eslint-plugin/docs/rules/restrict-plus-operands.md index 7ad23b60763..35d7a984f90 100644 --- a/packages/eslint-plugin/docs/rules/restrict-plus-operands.md +++ b/packages/eslint-plugin/docs/rules/restrict-plus-operands.md @@ -22,14 +22,25 @@ var foo = 1n + 1n; ## Options -This rule has an object option: +The rule accepts an options object with the following properties: -- `"checkCompoundAssignments": false`: (default) does not check compound assignments (`+=`) -- `"checkCompoundAssignments": true` +```ts +type Options = { + // if true, check compound assignments (`+=`) + checkCompoundAssignments?: boolean; + // if true, 'any' itself and `string`,`bigint`, `number` is allowed. + allowAny?: boolean; +}; + +const defaults = { + checkCompoundAssignments: false, + allowAny: false, +}; +``` ### `checkCompoundAssignments` -Examples of code for the `{ "checkCompoundAssignments": true }` option: +Examples of code for this rule with `{ checkCompoundAssignments: true }`: @@ -57,6 +68,29 @@ let bar = ''; bar += 'test'; ``` +### `allowAny` + +Examples of code for this rule with `{ allowAny: true }`: + + + +#### ❌ Incorrect + +```ts +var fn = (a: any, b: boolean) => a + b; +var fn = (a: any, b: []) => a + b; +var fn = (a: any, b: {}) => a + b; +``` + +#### ✅ Correct + +```ts +var fn = (a: any, b: any) => a + b; +var fn = (a: any, b: string) => a + b; +var fn = (a: any, b: bigint) => a + b; +var fn = (a: any, b: number) => a + b; +``` + ## How to Use ```json diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index 30c155e3dcd..afcbf9abf2f 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -5,6 +5,7 @@ import * as util from '../util'; type Options = [ { checkCompoundAssignments?: boolean; + allowAny?: boolean; }, ]; type MessageIds = 'notNumbers' | 'notStrings' | 'notBigInts'; @@ -34,6 +35,9 @@ export default util.createRule({ checkCompoundAssignments: { type: 'boolean', }, + allowAny: { + type: 'boolean', + }, }, }, ], @@ -41,13 +45,14 @@ export default util.createRule({ defaultOptions: [ { checkCompoundAssignments: false, + allowAny: false, }, ], - create(context, [{ checkCompoundAssignments }]) { + create(context, [{ checkCompoundAssignments, allowAny }]) { const service = util.getParserServices(context); const typeChecker = service.program.getTypeChecker(); - type BaseLiteral = 'string' | 'number' | 'bigint' | 'invalid'; + type BaseLiteral = 'string' | 'number' | 'bigint' | 'invalid' | 'any'; /** * Helper function to get base type of node @@ -82,7 +87,8 @@ export default util.createRule({ if ( stringType === 'number' || stringType === 'string' || - stringType === 'bigint' + stringType === 'bigint' || + stringType === 'any' ) { return stringType; } @@ -108,28 +114,53 @@ export default util.createRule({ const leftType = getNodeType(node.left); const rightType = getNodeType(node.right); - if ( - leftType === 'invalid' || - rightType === 'invalid' || - leftType !== rightType - ) { - if (leftType === 'string' || rightType === 'string') { + if (leftType === rightType) { + if (leftType === 'invalid') { context.report({ node, - messageId: 'notStrings', + messageId: 'notNumbers', }); - } else if (leftType === 'bigint' || rightType === 'bigint') { + } + + if (!allowAny && leftType === 'any') { context.report({ node, - messageId: 'notBigInts', + messageId: 'notNumbers', }); - } else { + } + + return; + } + + if (leftType === 'any' || rightType === 'any') { + if (!allowAny || leftType === 'invalid' || rightType === 'invalid') { context.report({ node, messageId: 'notNumbers', }); } + + return; } + + if (leftType === 'string' || rightType === 'string') { + return context.report({ + node, + messageId: 'notStrings', + }); + } + + if (leftType === 'bigint' || rightType === 'bigint') { + return context.report({ + node, + messageId: 'notBigInts', + }); + } + + context.report({ + node, + messageId: 'notNumbers', + }); } return { diff --git a/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts b/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts index af33e3ad3db..12b7e44657e 100644 --- a/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts @@ -139,6 +139,46 @@ foo += 'string'; }, ], }, + { + code: ` +export const f = (a: any, b: any) => a + b; + `, + options: [ + { + allowAny: true, + }, + ], + }, + { + code: ` +export const f = (a: any, b: string) => a + b; + `, + options: [ + { + allowAny: true, + }, + ], + }, + { + code: ` +export const f = (a: any, b: bigint) => a + b; + `, + options: [ + { + allowAny: true, + }, + ], + }, + { + code: ` +export const f = (a: any, b: number) => a + b; + `, + options: [ + { + allowAny: true, + }, + ], + }, ], invalid: [ { @@ -515,7 +555,7 @@ function foo(a: T) { `, errors: [ { - messageId: 'notStrings', + messageId: 'notNumbers', line: 4, column: 19, }, @@ -571,5 +611,125 @@ foo += 0; }, ], }, + { + code: ` +const f = (a: any, b: boolean) => a + b; + `, + options: [ + { + allowAny: true, + }, + ], + errors: [ + { + messageId: 'notNumbers', + line: 2, + column: 35, + }, + ], + }, + { + code: ` +const f = (a: any, b: []) => a + b; + `, + options: [ + { + allowAny: true, + }, + ], + errors: [ + { + messageId: 'notNumbers', + line: 2, + column: 30, + }, + ], + }, + + { + code: ` +const f = (a: any, b: any) => a + b; + `, + options: [ + { + allowAny: false, + }, + ], + errors: [ + { + messageId: 'notNumbers', + line: 2, + column: 31, + }, + ], + }, + { + code: ` +const f = (a: any, b: string) => a + b; + `, + options: [ + { + allowAny: false, + }, + ], + errors: [ + { + messageId: 'notNumbers', + line: 2, + column: 34, + }, + ], + }, + { + code: ` +const f = (a: any, b: bigint) => a + b; + `, + options: [ + { + allowAny: false, + }, + ], + errors: [ + { + messageId: 'notNumbers', + line: 2, + column: 34, + }, + ], + }, + { + code: ` +const f = (a: any, b: number) => a + b; + `, + options: [ + { + allowAny: false, + }, + ], + errors: [ + { + messageId: 'notNumbers', + line: 2, + column: 34, + }, + ], + }, + { + code: ` +const f = (a: any, b: boolean) => a + b; + `, + options: [ + { + allowAny: false, + }, + ], + errors: [ + { + messageId: 'notNumbers', + line: 2, + column: 35, + }, + ], + }, ], });