diff --git a/packages/eslint-plugin/docs/rules/restrict-plus-operands.md b/packages/eslint-plugin/docs/rules/restrict-plus-operands.md index 1efffa83e69..7b5af55a84f 100644 --- a/packages/eslint-plugin/docs/rules/restrict-plus-operands.md +++ b/packages/eslint-plugin/docs/rules/restrict-plus-operands.md @@ -16,6 +16,37 @@ var foo = 1n + 1; ## Options +This rule has an object option: + +- `"checkCompoundAssignments": false`: (default) does not check compound assignments (`+=`) +- `"checkCompoundAssignments": true` + +### checkCompoundAssignments + +Examples of **incorrect** code for the `{ "checkCompoundAssignments": true }` option: + +```ts +/*eslint @typescript-eslint/restrict-plus-operands: ["error", { "checkCompoundAssignments": true }]*/ + +let foo: string | undefined; +foo += 'some data'; + +let bar: string = ''; +bar += 0; +``` + +Examples of **correct** code for the `{ "checkCompoundAssignments": true }` option: + +```ts +/*eslint @typescript-eslint/restrict-plus-operands: ["error", { "checkCompoundAssignments": true }]*/ + +let foo: number = 0; +foo += 1; + +let bar = ''; +bar += 'test'; +``` + ```json { "@typescript-eslint/restrict-plus-operands": "error" diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index 1f715f1f902..c41587a169a 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -2,7 +2,14 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; import ts from 'typescript'; import * as util from '../util'; -export default util.createRule({ +type Options = [ + { + checkCompoundAssignments?: boolean; + }, +]; +type MessageIds = 'notNumbers' | 'notStrings' | 'notBigInts'; + +export default util.createRule({ name: 'restrict-plus-operands', meta: { type: 'problem', @@ -20,12 +27,25 @@ export default util.createRule({ "Operands of '+' operation must either be both strings or both numbers. Consider using a template literal.", notBigInts: "Operands of '+' operation must be both bigints.", }, - schema: [], + schema: [ + { + type: 'object', + additionalProperties: false, + properties: { + checkCompoundAssignments: { + type: 'boolean', + }, + }, + }, + ], }, - defaultOptions: [], - create(context) { + defaultOptions: [ + { + checkCompoundAssignments: false, + }, + ], + create(context, [{ checkCompoundAssignments }]) { const service = util.getParserServices(context); - const typeChecker = service.program.getTypeChecker(); type BaseLiteral = 'string' | 'number' | 'bigint' | 'invalid'; @@ -83,32 +103,41 @@ export default util.createRule({ return getBaseTypeOfLiteralType(type); } - return { - "BinaryExpression[operator='+']"(node: TSESTree.BinaryExpression): void { - const leftType = getNodeType(node.left); - const rightType = getNodeType(node.right); + function checkPlusOperands( + node: TSESTree.BinaryExpression | TSESTree.AssignmentExpression, + ): void { + const leftType = getNodeType(node.left); + const rightType = getNodeType(node.right); - if ( - leftType === 'invalid' || - rightType === 'invalid' || - leftType !== rightType - ) { - if (leftType === 'string' || rightType === 'string') { - context.report({ - node, - messageId: 'notStrings', - }); - } else if (leftType === 'bigint' || rightType === 'bigint') { - context.report({ - node, - messageId: 'notBigInts', - }); - } else { - context.report({ - node, - messageId: 'notNumbers', - }); - } + if ( + leftType === 'invalid' || + rightType === 'invalid' || + leftType !== rightType + ) { + if (leftType === 'string' || rightType === 'string') { + context.report({ + node, + messageId: 'notStrings', + }); + } else if (leftType === 'bigint' || rightType === 'bigint') { + context.report({ + node, + messageId: 'notBigInts', + }); + } else { + context.report({ + node, + messageId: 'notNumbers', + }); + } + } + } + + return { + "BinaryExpression[operator='+']": checkPlusOperands, + "AssignmentExpression[operator='+=']"(node): void { + if (checkCompoundAssignments) { + checkPlusOperands(node); } }, }; 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 44583a202a1..5d979706f84 100644 --- a/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts @@ -81,6 +81,28 @@ function foo(a: T) { return a + 1; } `, + { + code: ` +let foo: number = 0; +foo += 1; + `, + options: [ + { + checkCompoundAssignments: false, + }, + ], + }, + { + code: ` +let foo: number = 0; +foo += "string"; + `, + options: [ + { + checkCompoundAssignments: false, + }, + ], + }, ], invalid: [ { @@ -383,5 +405,41 @@ function foo(a: T) { }, ], }, + { + code: ` +let foo: string | undefined; +foo += "some data"; + `, + options: [ + { + checkCompoundAssignments: true, + }, + ], + errors: [ + { + messageId: 'notStrings', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +let foo = ''; +foo += 0; + `, + options: [ + { + checkCompoundAssignments: true, + }, + ], + errors: [ + { + messageId: 'notStrings', + line: 3, + column: 1, + }, + ], + }, ], });