Skip to content

Commit

Permalink
feat(eslint-plugin): [space-infix-ops] extention rule
Browse files Browse the repository at this point in the history
  • Loading branch information
drichard-nexapp committed Oct 8, 2020
1 parent 6bc9325 commit df7934c
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Expand Up @@ -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: | |

<!-- end extension rule list -->

Expand Down
26 changes: 26 additions & 0 deletions 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).

<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/space-infix-ops.md)</sup>
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/configs/all.ts
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
100 changes: 100 additions & 0 deletions packages/eslint-plugin/src/rules/space-infix-ops.ts
@@ -0,0 +1,100 @@
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<typeof baseRule>;
export type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;

export default util.createRule<Options, MessageIds>({
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,
},
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,
};
},
});
102 changes: 102 additions & 0 deletions 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,
},
],
},
],
});
22 changes: 22 additions & 0 deletions packages/eslint-plugin/typings/eslint-rules.d.ts
Expand Up @@ -788,3 +788,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;
}

0 comments on commit df7934c

Please sign in to comment.