Skip to content

Commit

Permalink
feat(eslint-plugin): add extension rule space-before-blocks (#1606) (
Browse files Browse the repository at this point in the history
…#4184)

Co-authored-by: Josh Goldberg <me@joshuakgoldberg.com>
  • Loading branch information
FDIM and JoshuaKGoldberg committed Feb 24, 2022
1 parent 66501d6 commit 208b6d0
Show file tree
Hide file tree
Showing 9 changed files with 439 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Expand Up @@ -231,6 +231,7 @@ In these cases, we create what we call an extension rule; a rule within our plug
| [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :white_check_mark: | | :thought_balloon: |
| [`@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-blocks`](./docs/rules/space-before-blocks.md) | Enforces consistent spacing before blocks | | :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: | |

Expand Down
60 changes: 60 additions & 0 deletions packages/eslint-plugin/docs/rules/space-before-blocks.md
@@ -0,0 +1,60 @@
# `space-before-blocks`

Enforces consistent spacing before blocks.

## Rule Details

This rule extends the base [`eslint/space-before-blocks`](https://eslint.org/docs/rules/space-before-blocks) rule.
It adds support for interfaces and enums:

### ❌ Incorrect

```ts
enum Breakpoint{
Large, Medium;
}

interface State{
currentBreakpoint: Breakpoint;
}
```

### ✅ Correct

```ts
enum Breakpoint {
Large, Medium;
}

interface State {
currentBreakpoint: Breakpoint;
}
```

In case a more specific options object is passed these blocks will follow `classes` configuration option.

## How to Use

```jsonc
{
// note you must disable the base rule as it can report incorrect errors
"space-before-blocks": "off",
"@typescript-eslint/space-before-blocks": ["error"]
}
```

## Options

See [`eslint/space-before-blocks` options](https://eslint.org/docs/rules/space-before-blocks#options).

<sup>

Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/space-before-blocks.md)

</sup>

## Attributes

- [ ] ✅ Recommended
- [x] 🔧 Fixable
- [ ] 💭 Requires type information
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/configs/all.ts
Expand Up @@ -157,6 +157,8 @@ export = {
'@typescript-eslint/space-before-function-paren': 'error',
'space-infix-ops': 'off',
'@typescript-eslint/space-infix-ops': 'error',
'space-before-blocks': 'off',
'@typescript-eslint/space-before-blocks': '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 @@ -112,6 +112,7 @@ import restrictTemplateExpressions from './restrict-template-expressions';
import returnAwait from './return-await';
import semi from './semi';
import sortTypeUnionIntersectionMembers from './sort-type-union-intersection-members';
import spaceBeforeBlocks from './space-before-blocks';
import spaceBeforeFunctionParen from './space-before-function-paren';
import spaceInfixOps from './space-infix-ops';
import strictBooleanExpressions from './strict-boolean-expressions';
Expand Down Expand Up @@ -237,6 +238,7 @@ export default {
'return-await': returnAwait,
semi: semi,
'sort-type-union-intersection-members': sortTypeUnionIntersectionMembers,
'space-before-blocks': spaceBeforeBlocks,
'space-before-function-paren': spaceBeforeFunctionParen,
'space-infix-ops': spaceInfixOps,
'strict-boolean-expressions': strictBooleanExpressions,
Expand Down
90 changes: 90 additions & 0 deletions packages/eslint-plugin/src/rules/space-before-blocks.ts
@@ -0,0 +1,90 @@
import { TSESTree } from '@typescript-eslint/utils';
import { getESLintCoreRule } from '../util/getESLintCoreRule';
import * as util from '../util';

const baseRule = getESLintCoreRule('space-before-blocks');

export type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
export type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;

export default util.createRule<Options, MessageIds>({
name: 'space-before-blocks',
meta: {
type: 'layout',
docs: {
description: 'Enforces consistent spacing before blocks',
recommended: false,
extendsBaseRule: true,
},
fixable: baseRule.meta.fixable,
hasSuggestions: baseRule.meta.hasSuggestions,
schema: baseRule.meta.schema,
messages: {
// @ts-expect-error -- we report on this messageId so we need to ensure it's there in case ESLint changes in future
unexpectedSpace: 'Unexpected space before opening brace.',
// @ts-expect-error -- we report on this messageId so we need to ensure it's there in case ESLint changes in future
missingSpace: 'Missing space before opening brace.',
...baseRule.meta.messages,
},
},
defaultOptions: ['always'],
create(context) {
const rules = baseRule.create(context);
const config = context.options[0];
const sourceCode = context.getSourceCode();

let requireSpace = true;

if (typeof config === 'object') {
requireSpace = config.classes === 'always';
} else if (config === 'never') {
requireSpace = false;
}

function checkPrecedingSpace(
node: TSESTree.Token | TSESTree.TSInterfaceBody,
): void {
const precedingToken = sourceCode.getTokenBefore(node);
if (precedingToken && util.isTokenOnSameLine(precedingToken, node)) {
const hasSpace = sourceCode.isSpaceBetweenTokens(
precedingToken,
node as TSESTree.Token,
);

if (requireSpace && !hasSpace) {
context.report({
node,
messageId: 'missingSpace',
fix(fixer) {
return fixer.insertTextBefore(node, ' ');
},
});
} else if (!requireSpace && hasSpace) {
context.report({
node,
messageId: 'unexpectedSpace',
fix(fixer) {
return fixer.removeRange([
precedingToken.range[1],
node.range[0],
]);
},
});
}
}
}

function checkSpaceAfterEnum(node: TSESTree.TSEnumDeclaration): void {
const punctuator = sourceCode.getTokenAfter(node.id);
if (punctuator) {
checkPrecedingSpace(punctuator);
}
}

return {
...rules,
TSEnumDeclaration: checkSpaceAfterEnum,
TSInterfaceBody: checkPrecedingSpace,
};
},
});
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/util/getESLintCoreRule.ts
Expand Up @@ -33,6 +33,7 @@ interface RuleMap {
'prefer-const': typeof import('eslint/lib/rules/prefer-const');
quotes: typeof import('eslint/lib/rules/quotes');
semi: typeof import('eslint/lib/rules/semi');
'space-before-blocks': typeof import('eslint/lib/rules/space-before-blocks');
'space-infix-ops': typeof import('eslint/lib/rules/space-infix-ops');
strict: typeof import('eslint/lib/rules/strict');
}
Expand Down

0 comments on commit 208b6d0

Please sign in to comment.