diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md
index 2f6425bf9028..642efc190680 100644
--- a/packages/eslint-plugin/README.md
+++ b/packages/eslint-plugin/README.md
@@ -219,6 +219,7 @@ In these cases, we create what we call an extension rule; a rule within our plug
| [`@typescript-eslint/return-await`](./docs/rules/return-await.md) | Enforces consistent returning of awaited values | | :wrench: | :thought_balloon: |
| [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | |
| [`@typescript-eslint/space-before-function-paren`](./docs/rules/space-before-function-paren.md) | Enforces consistent spacing before function parenthesis | | :wrench: | |
+| [`@typescript-eslint/space-infix-ops`](./docs/rules/space-infix-ops.md) | This rule is aimed at ensuring there are spaces around infix operators. | | :wrench: | |
diff --git a/packages/eslint-plugin/docs/rules/space-infix-ops.md b/packages/eslint-plugin/docs/rules/space-infix-ops.md
new file mode 100644
index 000000000000..da36f25726e2
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/space-infix-ops.md
@@ -0,0 +1,26 @@
+# This rule is aimed at ensuring there are spaces around infix operators. (`space-infix-ops`)
+
+This rule extends the base [`eslint/space-infix-ops`](https://eslint.org/docs/rules/space-infix-ops) rule.
+
+It also add support for enum members
+
+```ts
+enum MyEnum {
+ KEY = 'value',
+}
+```
+
+## How to use
+
+```jsonc
+{
+ "space-infix-ops": "off",
+ "@typescript-eslint/space-infix-ops": ["error", { "int32Hint": false }]
+}
+```
+
+## Options
+
+See [`eslint/space-infix-ops` options](https://eslint.org/docs/rules/space-infix-ops#options).
+
+Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/space-infix-ops.md)
diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts
index ec2984064982..7806a034eb4c 100644
--- a/packages/eslint-plugin/src/configs/all.ts
+++ b/packages/eslint-plugin/src/configs/all.ts
@@ -137,6 +137,8 @@ export = {
'@typescript-eslint/semi': 'error',
'space-before-function-paren': 'off',
'@typescript-eslint/space-before-function-paren': 'error',
+ 'space-infix-ops': 'off',
+ '@typescript-eslint/space-infix-ops': 'error',
'@typescript-eslint/strict-boolean-expressions': 'error',
'@typescript-eslint/switch-exhaustiveness-check': 'error',
'@typescript-eslint/triple-slash-reference': 'error',
diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts
index 587b79d30178..059596e4d86a 100644
--- a/packages/eslint-plugin/src/rules/index.ts
+++ b/packages/eslint-plugin/src/rules/index.ts
@@ -15,6 +15,7 @@ import consistentTypeDefinitions from './consistent-type-definitions';
import consistentTypeImports from './consistent-type-imports';
import defaultParamLast from './default-param-last';
import dotNotation from './dot-notation';
+import enumMembersSpacing from './space-infix-ops';
import explicitFunctionReturnType from './explicit-function-return-type';
import explicitMemberAccessibility from './explicit-member-accessibility';
import explicitModuleBoundaryTypes from './explicit-module-boundary-types';
@@ -125,6 +126,7 @@ export default {
'consistent-type-imports': consistentTypeImports,
'default-param-last': defaultParamLast,
'dot-notation': dotNotation,
+ 'space-infix-ops': enumMembersSpacing,
'explicit-function-return-type': explicitFunctionReturnType,
'explicit-member-accessibility': explicitMemberAccessibility,
'explicit-module-boundary-types': explicitModuleBoundaryTypes,
diff --git a/packages/eslint-plugin/src/rules/space-infix-ops.ts b/packages/eslint-plugin/src/rules/space-infix-ops.ts
new file mode 100644
index 000000000000..57c56ad0b476
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/space-infix-ops.ts
@@ -0,0 +1,102 @@
+import {
+ AST_TOKEN_TYPES,
+ TSESTree,
+} from '@typescript-eslint/experimental-utils';
+import baseRule from 'eslint/lib/rules/space-infix-ops';
+import * as util from '../util';
+
+export type Options = util.InferOptionsTypeFromRule;
+export type MessageIds = util.InferMessageIdsTypeFromRule;
+
+export default util.createRule({
+ name: 'space-infix-ops',
+ meta: {
+ type: 'layout',
+ docs: {
+ description:
+ 'This rule is aimed at ensuring there are spaces around infix operators.',
+ category: 'Stylistic Issues',
+ recommended: false,
+ extendsBaseRule: true,
+ },
+ fixable: baseRule.meta.fixable,
+ schema: baseRule.meta.schema,
+ messages: baseRule.meta.messages ?? {
+ missingSpace: "Operator '{{operator}}' must be spaced.",
+ },
+ },
+ defaultOptions: [
+ {
+ int32Hint: false,
+ },
+ ],
+ create(context) {
+ const rules = baseRule.create(context);
+ const sourceCode = context.getSourceCode();
+
+ /**
+ * Check if it has an assignment char and report if it's faulty
+ * @param node The node to report
+ */
+ function checkForAssignmentSpace(node: TSESTree.TSEnumMember): void {
+ if (!node.initializer) {
+ return;
+ }
+
+ const leftNode = sourceCode.getTokenByRangeStart(node.id.range[0])!;
+ const rightNode = sourceCode.getTokenByRangeStart(
+ node.initializer.range[0],
+ )!;
+
+ if (!rightNode) {
+ return;
+ }
+
+ const operator = sourceCode.getFirstTokenBetween(
+ leftNode,
+ rightNode,
+ token =>
+ token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=',
+ );
+ const prev = sourceCode.getTokenBefore(operator!);
+ const next = sourceCode.getTokenAfter(operator!);
+
+ if (
+ operator &&
+ (!sourceCode.isSpaceBetweenTokens(prev!, operator) ||
+ !sourceCode.isSpaceBetweenTokens(operator, next!))
+ ) {
+ context.report({
+ node: node,
+ loc: operator.loc,
+ messageId: 'missingSpace',
+ data: {
+ operator: operator.value,
+ },
+ fix(fixer) {
+ const previousToken = sourceCode.getTokenBefore(operator);
+ const afterToken = sourceCode.getTokenAfter(operator);
+ let fixString = '';
+
+ if (operator.range[0] - previousToken!.range[1] === 0) {
+ fixString = ' ';
+ }
+
+ fixString += operator.value;
+
+ if (afterToken!.range[0] - operator.range[1] === 0) {
+ fixString += ' ';
+ }
+
+ return fixer.replaceText(operator, fixString);
+ },
+ });
+ }
+ }
+
+ return {
+ ...rules,
+ TSEnumMember: checkForAssignmentSpace,
+ };
+ },
+});
diff --git a/packages/eslint-plugin/tests/rules/space-infix-ops.test.ts b/packages/eslint-plugin/tests/rules/space-infix-ops.test.ts
new file mode 100644
index 000000000000..0c90443ca4fc
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/space-infix-ops.test.ts
@@ -0,0 +1,102 @@
+/* eslint-disable eslint-comments/no-use */
+// this rule tests spacing, which prettier will want to fix and break the tests
+/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */
+/* eslint-enable eslint-comments/no-use */
+
+import rule from '../../src/rules/space-infix-ops';
+import { RuleTester } from '../RuleTester';
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+});
+
+ruleTester.run('space-infix-ops', rule, {
+ valid: [
+ {
+ code: `
+ enum Test {
+ KEY1 = 2,
+ }
+ `,
+ },
+ {
+ code: `
+ enum Test {
+ KEY1 = "value",
+ }
+ `,
+ },
+ {
+ code: `
+ enum Test {
+ KEY1,
+ }
+ `,
+ },
+ ],
+ invalid: [
+ {
+ code: `
+ enum Test {
+ A= 2,
+ B = 1,
+ }
+ `,
+ output: `
+ enum Test {
+ A = 2,
+ B = 1,
+ }
+ `,
+ errors: [
+ {
+ messageId: 'missingSpace',
+ column: 12,
+ line: 3,
+ },
+ ],
+ },
+ {
+ code: `
+ enum Test {
+ KEY1= "value1",
+ KEY2 = "value2",
+ }
+ `,
+ output: `
+ enum Test {
+ KEY1 = "value1",
+ KEY2 = "value2",
+ }
+ `,
+ errors: [
+ {
+ messageId: 'missingSpace',
+ column: 15,
+ line: 3,
+ },
+ ],
+ },
+ {
+ code: `
+ enum Test {
+ A =2,
+ B = 1,
+ }
+ `,
+ output: `
+ enum Test {
+ A = 2,
+ B = 1,
+ }
+ `,
+ errors: [
+ {
+ messageId: 'missingSpace',
+ column: 13,
+ line: 3,
+ },
+ ],
+ },
+ ],
+});
diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts
index 8a6c89d529b3..4e10fe235786 100644
--- a/packages/eslint-plugin/typings/eslint-rules.d.ts
+++ b/packages/eslint-plugin/typings/eslint-rules.d.ts
@@ -789,3 +789,25 @@ declare module 'eslint/lib/rules/no-duplicate-imports' {
>;
export = rule;
}
+
+declare module 'eslint/lib/rules/space-infix-ops' {
+ import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
+
+ const rule: TSESLint.RuleModule<
+ 'missingSpace',
+ [
+ {
+ int32Hint: boolean;
+ },
+ ],
+ {
+ AssignmentExpression(node: TSESTree.AssignmentExpression): void;
+ AssignmentPattern(node: TSESTree.AssignmentPattern): void;
+ BinaryExpression(node: TSESTree.BinaryExpression): void;
+ LogicalExpression(node: TSESTree.LogicalExpression): void;
+ ConditionalExpression(node: TSESTree.ConditionalExpression): void;
+ VariableDeclarator(node: TSESTree.VariableDeclarator): void;
+ }
+ >;
+ export = rule;
+}