forked from typescript-eslint/typescript-eslint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
block-spacing.ts
166 lines (150 loc) · 5.25 KB
/
block-spacing.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import type { TSESTree } from '@typescript-eslint/utils';
import { AST_TOKEN_TYPES } from '@typescript-eslint/utils';
import * as util from '../util';
import { getESLintCoreRule } from '../util/getESLintCoreRule';
const baseRule = getESLintCoreRule('block-spacing');
export type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
export type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;
export default util.createRule<Options, MessageIds>({
name: 'block-spacing',
meta: {
type: 'layout',
docs: {
description:
'Disallow or enforce spaces inside of blocks after opening block and before closing block',
recommended: false,
extendsBaseRule: true,
},
fixable: 'whitespace',
hasSuggestions: baseRule.meta.hasSuggestions,
schema: baseRule.meta.schema,
messages: baseRule.meta.messages,
},
defaultOptions: ['always'],
create(context, [whenToApplyOption]) {
const sourceCode = context.getSourceCode();
const baseRules = baseRule.create(context);
const always = whenToApplyOption !== 'never';
const messageId = always ? 'missing' : 'extra';
/**
* Gets the open brace token from a given node.
* @returns The token of the open brace.
*/
function getOpenBrace(
node: TSESTree.TSEnumDeclaration,
): TSESTree.PunctuatorToken {
// guaranteed for enums
// This is the only change made here from the base rule
return sourceCode.getFirstToken(node, {
filter: token =>
token.type === AST_TOKEN_TYPES.Punctuator && token.value == '{',
}) as TSESTree.PunctuatorToken;
}
/**
* Checks whether or not:
* - given tokens are on same line.
* - there is/isn't a space between given tokens.
* @param left A token to check.
* @param right The token which is next to `left`.
* @returns
* When the option is `"always"`, `true` if there are one or more spaces between given tokens.
* When the option is `"never"`, `true` if there are not any spaces between given tokens.
* If given tokens are not on same line, it's always `true`.
*/
function isValid(left: TSESTree.Token, right: TSESTree.Token): boolean {
return (
!util.isTokenOnSameLine(left, right) ||
sourceCode.isSpaceBetween!(left, right) === always
);
}
/**
* Checks and reports invalid spacing style inside braces.
* @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to check.
* @returns
*/
function checkSpacingInsideBraces(node: TSESTree.TSEnumDeclaration): void {
// Gets braces and the first/last token of content.
const openBrace = getOpenBrace(node);
const closeBrace = sourceCode.getLastToken(node)!;
const firstToken = sourceCode.getTokenAfter(openBrace, {
includeComments: true,
})!;
const lastToken = sourceCode.getTokenBefore(closeBrace, {
includeComments: true,
})!;
// Skip if the node is invalid or empty.
if (
openBrace.type !== AST_TOKEN_TYPES.Punctuator ||
openBrace.value !== '{' ||
closeBrace.type !== AST_TOKEN_TYPES.Punctuator ||
closeBrace.value !== '}' ||
firstToken === closeBrace
) {
return;
}
// Skip line comments for option never
if (!always && firstToken.type === AST_TOKEN_TYPES.Line) {
return;
}
// Check.
if (!isValid(openBrace, firstToken)) {
let loc = openBrace.loc;
if (messageId === 'extra') {
loc = {
start: openBrace.loc.end,
end: firstToken.loc.start,
};
}
context.report({
node,
loc,
messageId,
data: {
location: 'after',
token: openBrace.value,
},
fix(fixer) {
if (always) {
return fixer.insertTextBefore(firstToken, ' ');
}
return fixer.removeRange([openBrace.range[1], firstToken.range[0]]);
},
});
}
if (!isValid(lastToken, closeBrace)) {
let loc = closeBrace.loc;
if (messageId === 'extra') {
loc = {
start: lastToken.loc.end,
end: closeBrace.loc.start,
};
}
context.report({
node,
loc,
messageId,
data: {
location: 'before',
token: closeBrace.value,
},
fix(fixer) {
if (always) {
return fixer.insertTextAfter(lastToken, ' ');
}
return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]);
},
});
}
}
return {
...baseRules,
// This code worked "out of the box" for interface and type literal
// Enums were very close to match as well, the only reason they it not is that was that enums don't have a body node in the parser
// So the opening brace punctuator starts in the middle of the node
// `getFirstToken` in the base rule did not filter for the first opening brace punctuator
TSInterfaceBody: baseRules.BlockStatement as never,
TSTypeLiteral: baseRules.BlockStatement as never,
TSEnumDeclaration: checkSpacingInsideBraces,
};
},
});