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 Sep 23, 2020
1 parent 39c45f3 commit 953f14a
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Expand Up @@ -217,6 +217,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
18 changes: 18 additions & 0 deletions packages/eslint-plugin/docs/rules/space-infix-ops.md
@@ -0,0 +1,18 @@
# 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.

## 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/semi.md)</sup>
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/configs/all.ts
Expand Up @@ -132,6 +132,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 @@ -14,6 +14,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 @@ -122,6 +123,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
153 changes: 153 additions & 0 deletions packages/eslint-plugin/src/rules/space-infix-ops.ts
@@ -0,0 +1,153 @@
import {
AST_NODE_TYPES,
TSESLint,
TSESTree,
} from '@typescript-eslint/experimental-utils';
import baseRule from 'eslint/lib/rules/space-infix-ops';
import { TSEnumMember } from '../../../parser/node_modules/@typescript-eslint/types/dist/ts-estree';
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();

/**
* Find the first non space specified char
* @param {TSESTree.token} left The token to the left
* @param {TSESTree.Token} right The token to the right
* @param {string} op The token to find
* @returns {TSESTree.Token|null}
* @private
*/
function getFirstNonSpacedToken(
left: TSESTree.Token,
right: TSESTree.Token,
op: string,
): TSESTree.Token | null {
const operator = sourceCode.getFirstTokenBetween(
left,
right,
token => token.value === op,
);
const prev = sourceCode.getTokenBefore(operator!);
const next = sourceCode.getTokenAfter(operator!);

if (
!sourceCode.isSpaceBetweenTokens(prev!, operator!) ||
!sourceCode.isSpaceBetweenTokens(operator!, next!)
) {
return operator;
}

return null;
}

/**
* Reports an AST node as a rule violation
* @param {TSESTree.Node} mainNode The node to report
* @param {TSESTree.Token} culpritToken The token which has a problem
* @returns {void}
* @private
*/
function report(
mainNode: TSESTree.Node,
culpritToken: TSESTree.Token,
): void {
context.report({
node: mainNode,
loc: culpritToken.loc,
messageId: 'missingSpace',
data: {
operator: culpritToken.value,
},
fix(fixer) {
const previousToken = sourceCode.getTokenBefore(culpritToken);
const afterToken = sourceCode.getTokenAfter(culpritToken);
let fixString = '';

if (culpritToken.range[0] - previousToken!.range[1] === 0) {
fixString = ' ';
}

fixString += culpritToken.value;

if (afterToken!.range[0] - culpritToken.range[1] === 0) {
fixString += ' ';
}

return fixer.replaceText(culpritToken, fixString);
},
});
}

/**
* Check if it has an assignment char and report if it's faulty
* @param {TSESTree.Node} node The node to report
* @returns {void}
* @private
*/
function checkForAssignmentSpace(node: TSEnumMember): void {
if (!node.initializer) {
return;
}

const leftNode = sourceCode.getTokenByRangeStart(
sourceCode.getIndexFromLoc(node.id.loc.start),
)!;
const rightNode = sourceCode.getTokenByRangeStart(
sourceCode.getIndexFromLoc(node.initializer.loc.start),
)!;

if (!rightNode) {
return;
}

const operator = '=';

const nonSpacedNode = getFirstNonSpacedToken(
leftNode,
rightNode,
operator,
);

if (nonSpacedNode) {
report(node, nonSpacedNode);
}
}

const nodesToCheck = [AST_NODE_TYPES.TSEnumMember].reduce<
TSESLint.RuleListener
>((acc, node) => {
acc[node as string] = checkForAssignmentSpace;
return acc;
}, {});

return {
...rules,
...nodesToCheck,
};
},
});
98 changes: 98 additions & 0 deletions packages/eslint-plugin/tests/rules/space-infix-ops.test.ts
@@ -0,0 +1,98 @@
/* eslint-disable @typescript-eslint/internal/plugin-test-formatting */
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 @@ -762,3 +762,25 @@ declare module 'eslint/lib/rules/comma-dangle' {
>;
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 953f14a

Please sign in to comment.