Skip to content

Commit

Permalink
feat(eslint-plugin): add extension rule space-before-blocks (typesc…
Browse files Browse the repository at this point in the history
  • Loading branch information
FDIM committed Nov 18, 2021
1 parent 0fe07e2 commit 41d7481
Show file tree
Hide file tree
Showing 6 changed files with 426 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/configs/all.ts
Expand Up @@ -155,6 +155,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 @@ -110,6 +110,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 @@ -233,6 +234,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
146 changes: 146 additions & 0 deletions packages/eslint-plugin/src/rules/space-before-blocks.ts
@@ -0,0 +1,146 @@
import {
AST_NODE_TYPES,
TSESTree,
} from '@typescript-eslint/experimental-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: 'enforce consistent spacing before blocks.',
recommended: false,
extendsBaseRule: true,
},
fixable: baseRule.meta.fixable,
hasSuggestions: baseRule.meta.hasSuggestions,
schema: [
{
oneOf: [
{
enum: ['always', 'never'],
},
{
type: 'object',
properties: {
keywords: {
enum: ['always', 'never', 'off'],
},
functions: {
enum: ['always', 'never', 'off'],
},
classes: {
enum: ['always', 'never', 'off'],
},
enums: {
enum: ['always', 'never', 'off'],
},
interfaces: {
enum: ['always', 'never', 'off'],
},
},
additionalProperties: false,
},
],
},
],
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 alwaysEnums = true,
alwaysInterfaces = true,
neverEnums = false,
neverInterfaces = false;

if (typeof config === 'object') {
alwaysEnums = config.enums === 'always';
alwaysInterfaces = config.interfaces === 'always';
neverEnums = config.enums === 'never';
neverInterfaces = config.interfaces === 'never';
} else if (config === 'never') {
alwaysEnums = false;
alwaysInterfaces = false;
neverEnums = true;
neverInterfaces = true;
}

/**
* Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line.
* @param {ASTNode|Token} node The AST node of a BlockStatement.
* @returns {void} undefined.
*/
function checkPrecedingSpace(
node:
| TSESTree.Token
| TSESTree.TSInterfaceBody
| TSESTree.PunctuatorToken,
) {
const precedingToken = sourceCode.getTokenBefore(node);
if (precedingToken && util.isTokenOnSameLine(precedingToken, node)) {
const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node);
let requireSpace: boolean;
let requireNoSpace: boolean;

if (node.type === AST_NODE_TYPES.TSInterfaceBody) {
requireSpace = alwaysInterfaces;
requireNoSpace = neverInterfaces;
} else {
requireSpace = alwaysEnums;
requireNoSpace = neverEnums;
}

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

function checkSpaceAfterEnum(node: TSESTree.TSEnumDeclaration) {
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 41d7481

Please sign in to comment.