Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): add extension rule space-before-blocks (#1606) #4184

Merged
merged 2 commits into from Feb 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
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(
FDIM marked this conversation as resolved.
Show resolved Hide resolved
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