@tai-kun/vp-lint-plugins 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +1 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -6
- package/dist/src/plugin.js +2 -12
- package/dist/src/rules/blank-line-utils.d.ts +30 -0
- package/dist/src/rules/blank-line-utils.d.ts.map +1 -0
- package/dist/src/rules/blank-line-utils.js +103 -0
- package/dist/src/rules/blank-line.d.ts +5 -14
- package/dist/src/rules/blank-line.d.ts.map +1 -1
- package/dist/src/rules/blank-line.js +426 -66
- package/dist/src/rules/overload-signatures-adjacent.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -6
- package/src/plugin.ts +2 -12
- package/src/rules/blank-line-utils.ts +165 -0
- package/src/rules/blank-line.ts +555 -97
- package/src/rules/overload-signatures-adjacent.ts +1 -1
- package/dist/src/rules/blank-line-after-control-block.d.ts +0 -7
- package/dist/src/rules/blank-line-after-control-block.d.ts.map +0 -1
- package/dist/src/rules/blank-line-after-control-block.js +0 -54
- package/dist/src/rules/blank-line-after-declaration.d.ts +0 -7
- package/dist/src/rules/blank-line-after-declaration.d.ts.map +0 -1
- package/dist/src/rules/blank-line-after-declaration.js +0 -79
- package/dist/src/rules/blank-line-after-variable-declaration.d.ts +0 -9
- package/dist/src/rules/blank-line-after-variable-declaration.d.ts.map +0 -1
- package/dist/src/rules/blank-line-after-variable-declaration.js +0 -118
- package/dist/src/rules/blank-line-before-return.d.ts +0 -7
- package/dist/src/rules/blank-line-before-return.d.ts.map +0 -1
- package/dist/src/rules/blank-line-before-return.js +0 -81
- package/dist/src/rules/blank-line-between-class-members.d.ts +0 -8
- package/dist/src/rules/blank-line-between-class-members.d.ts.map +0 -1
- package/dist/src/rules/blank-line-between-class-members.js +0 -85
- package/dist/src/rules/blank-line-between-switch-cases.d.ts +0 -8
- package/dist/src/rules/blank-line-between-switch-cases.d.ts.map +0 -1
- package/dist/src/rules/blank-line-between-switch-cases.js +0 -45
- package/src/rules/blank-line-after-control-block.ts +0 -82
- package/src/rules/blank-line-after-declaration.ts +0 -106
- package/src/rules/blank-line-after-variable-declaration.ts +0 -165
- package/src/rules/blank-line-before-return.ts +0 -101
- package/src/rules/blank-line-between-class-members.ts +0 -112
- package/src/rules/blank-line-between-switch-cases.ts +0 -53
package/src/rules/blank-line.ts
CHANGED
|
@@ -1,143 +1,601 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { defineRule } from "vite-plus/lint/plugins";
|
|
2
|
+
import type { ESTree, Variable } from "vite-plus/lint/plugins";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import {
|
|
5
|
+
isComment,
|
|
6
|
+
hasBlankLine,
|
|
7
|
+
isDocComment,
|
|
8
|
+
getBlankLineFix,
|
|
9
|
+
getInsertionTarget,
|
|
10
|
+
getRemoveBlankLineFix,
|
|
11
|
+
} from "./blank-line-utils.js";
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
type ControlStatement =
|
|
14
|
+
| ESTree.DoWhileStatement
|
|
15
|
+
| ESTree.ForInStatement
|
|
16
|
+
| ESTree.ForOfStatement
|
|
17
|
+
| ESTree.ForStatement
|
|
18
|
+
| ESTree.IfStatement
|
|
19
|
+
| ESTree.SwitchStatement
|
|
20
|
+
| ESTree.TryStatement
|
|
21
|
+
| ESTree.WhileStatement;
|
|
22
|
+
|
|
23
|
+
const CONTROL_STATEMENT_TYPES: ReadonlySet<ControlStatement["type"]> = new Set([
|
|
24
|
+
"DoWhileStatement",
|
|
25
|
+
"ForInStatement",
|
|
26
|
+
"ForOfStatement",
|
|
27
|
+
"ForStatement",
|
|
28
|
+
"IfStatement",
|
|
29
|
+
"SwitchStatement",
|
|
30
|
+
"TryStatement",
|
|
31
|
+
"WhileStatement",
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const DECLARATION_TYPES: ReadonlySet<string> = new Set([
|
|
35
|
+
"ClassDeclaration",
|
|
36
|
+
"FunctionDeclaration",
|
|
37
|
+
"TSDeclareFunction",
|
|
38
|
+
"TSEnumDeclaration",
|
|
39
|
+
"TSInterfaceDeclaration",
|
|
40
|
+
"TSModuleDeclaration",
|
|
41
|
+
"TSTypeAliasDeclaration",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const SKIP_RETURN_PREVIOUS_TYPES: ReadonlySet<string> = new Set([
|
|
45
|
+
// 制御ブロックの後は afterControlBlock が担当するため、ここでは判定しません。
|
|
46
|
+
"DoWhileStatement",
|
|
47
|
+
"ForInStatement",
|
|
48
|
+
"ForOfStatement",
|
|
49
|
+
"ForStatement",
|
|
50
|
+
"IfStatement",
|
|
51
|
+
"SwitchStatement",
|
|
52
|
+
"TryStatement",
|
|
53
|
+
"WhileStatement",
|
|
54
|
+
// 宣言の後は afterDeclaration が担当するため、ここでは判定しません。
|
|
55
|
+
"ClassDeclaration",
|
|
56
|
+
"ExportAllDeclaration",
|
|
57
|
+
"ExportDefaultDeclaration",
|
|
58
|
+
"ExportNamedDeclaration",
|
|
59
|
+
"FunctionDeclaration",
|
|
60
|
+
"ImportDeclaration",
|
|
61
|
+
"TSDeclareFunction",
|
|
62
|
+
"TSEnumDeclaration",
|
|
63
|
+
"TSInterfaceDeclaration",
|
|
64
|
+
"TSModuleDeclaration",
|
|
65
|
+
"TSTypeAliasDeclaration",
|
|
66
|
+
// 途中で終わる文や空文の後は対象外にします。
|
|
67
|
+
"BlockStatement",
|
|
68
|
+
"BreakStatement",
|
|
69
|
+
"ContinueStatement",
|
|
70
|
+
"DebuggerStatement",
|
|
71
|
+
"EmptyStatement",
|
|
72
|
+
"LabeledStatement",
|
|
73
|
+
"ReturnStatement",
|
|
74
|
+
"ThrowStatement",
|
|
75
|
+
"WithStatement",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
interface BlankLineOptions {
|
|
79
|
+
afterControlBlock: boolean;
|
|
80
|
+
afterDeclaration: boolean;
|
|
81
|
+
afterVariableDeclaration: boolean;
|
|
82
|
+
beforeReturn: boolean;
|
|
83
|
+
betweenClassMembers: boolean;
|
|
84
|
+
betweenSwitchCases: boolean;
|
|
85
|
+
betweenTypeMembers: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const DEFAULT_OPTIONS: BlankLineOptions = {
|
|
89
|
+
beforeReturn: true,
|
|
90
|
+
afterDeclaration: true,
|
|
91
|
+
afterControlBlock: true,
|
|
92
|
+
betweenSwitchCases: true,
|
|
93
|
+
betweenTypeMembers: true,
|
|
94
|
+
betweenClassMembers: true,
|
|
95
|
+
afterVariableDeclaration: true,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
99
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getFlag(option: Record<string, unknown>, key: keyof BlankLineOptions): boolean {
|
|
103
|
+
const value = option[key];
|
|
104
|
+
|
|
105
|
+
return value === undefined ? DEFAULT_OPTIONS[key] : value === true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function getOptions(options: readonly unknown[]): BlankLineOptions {
|
|
109
|
+
const option = options[0];
|
|
110
|
+
|
|
111
|
+
if (!isObject(option)) {
|
|
112
|
+
return { ...DEFAULT_OPTIONS };
|
|
11
113
|
}
|
|
12
114
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
115
|
+
return {
|
|
116
|
+
beforeReturn: getFlag(option, "beforeReturn"),
|
|
117
|
+
afterDeclaration: getFlag(option, "afterDeclaration"),
|
|
118
|
+
afterControlBlock: getFlag(option, "afterControlBlock"),
|
|
119
|
+
betweenSwitchCases: getFlag(option, "betweenSwitchCases"),
|
|
120
|
+
betweenTypeMembers: getFlag(option, "betweenTypeMembers"),
|
|
121
|
+
betweenClassMembers: getFlag(option, "betweenClassMembers"),
|
|
122
|
+
afterVariableDeclaration: getFlag(option, "afterVariableDeclaration"),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isControlStatement(node: ESTree.Node): node is ControlStatement {
|
|
127
|
+
return CONTROL_STATEMENT_TYPES.has(node.type as ControlStatement["type"]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getDeclaration(node: ESTree.Node): ESTree.Node | null {
|
|
131
|
+
if (DECLARATION_TYPES.has(node.type)) {
|
|
132
|
+
return node;
|
|
20
133
|
}
|
|
21
134
|
|
|
22
|
-
if (
|
|
23
|
-
|
|
135
|
+
if (
|
|
136
|
+
(node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") &&
|
|
137
|
+
node.declaration !== null &&
|
|
138
|
+
DECLARATION_TYPES.has(node.declaration.type)
|
|
139
|
+
) {
|
|
140
|
+
return node.declaration;
|
|
24
141
|
}
|
|
25
142
|
|
|
26
143
|
return null;
|
|
27
144
|
}
|
|
28
145
|
|
|
29
|
-
function
|
|
30
|
-
|
|
146
|
+
function isOverload(declaration: ESTree.Node): boolean {
|
|
147
|
+
return (
|
|
148
|
+
(declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction") &&
|
|
149
|
+
declaration.body === null
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function getVariableDeclaration(node: ESTree.Node): ESTree.VariableDeclaration | null {
|
|
154
|
+
if (node.type === "VariableDeclaration") {
|
|
155
|
+
return node;
|
|
156
|
+
}
|
|
31
157
|
|
|
32
|
-
if (
|
|
33
|
-
return
|
|
158
|
+
if (node.type === "ExportNamedDeclaration" && node.declaration?.type === "VariableDeclaration") {
|
|
159
|
+
return node.declaration;
|
|
34
160
|
}
|
|
35
161
|
|
|
36
|
-
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isRelatedExpression(
|
|
166
|
+
statement: ESTree.Statement,
|
|
167
|
+
variables: ReadonlySet<Variable>,
|
|
168
|
+
): boolean {
|
|
169
|
+
if (statement.type !== "ExpressionStatement") {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
37
172
|
|
|
38
|
-
|
|
39
|
-
|
|
173
|
+
for (const variable of variables) {
|
|
174
|
+
for (const reference of variable.references) {
|
|
175
|
+
if (
|
|
176
|
+
reference.identifier.range[0] >= statement.range[0] &&
|
|
177
|
+
reference.identifier.range[1] <= statement.range[1]
|
|
178
|
+
) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
40
182
|
}
|
|
41
183
|
|
|
42
|
-
return
|
|
184
|
+
return false;
|
|
43
185
|
}
|
|
44
186
|
|
|
45
|
-
function
|
|
46
|
-
|
|
187
|
+
function isDirective(node: ESTree.Node): boolean {
|
|
188
|
+
return node.type === "ExpressionStatement" && typeof node.directive === "string";
|
|
189
|
+
}
|
|
47
190
|
|
|
48
|
-
|
|
191
|
+
function getKeyName(member: ESTree.ClassElement): string | null {
|
|
192
|
+
if (member.type === "TSIndexSignature" || member.type === "StaticBlock") {
|
|
49
193
|
return null;
|
|
50
194
|
}
|
|
51
195
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (index <= 0) {
|
|
196
|
+
if (member.computed) {
|
|
55
197
|
return null;
|
|
56
198
|
}
|
|
57
199
|
|
|
58
|
-
|
|
59
|
-
}
|
|
200
|
+
const key = member.key;
|
|
60
201
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
sourceCode: SourceCode,
|
|
71
|
-
): ESTree.Node | Comment {
|
|
72
|
-
return (
|
|
73
|
-
sourceCode
|
|
74
|
-
.getAllComments()
|
|
75
|
-
.find(
|
|
76
|
-
(comment) =>
|
|
77
|
-
comment.range[0] >= node.range[1] &&
|
|
78
|
-
comment.range[1] <= nextStatement.range[0] &&
|
|
79
|
-
comment.loc.start.line > node.loc.end.line,
|
|
80
|
-
) ?? nextStatement
|
|
81
|
-
);
|
|
202
|
+
if (key.type === "Identifier") {
|
|
203
|
+
return key.name;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (key.type === "Literal" && (typeof key.value === "string" || typeof key.value === "number")) {
|
|
207
|
+
return String(key.value);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return null;
|
|
82
211
|
}
|
|
83
212
|
|
|
84
|
-
function
|
|
85
|
-
return
|
|
213
|
+
function isOverloadMember(member: ESTree.ClassElement): boolean {
|
|
214
|
+
return member.type === "MethodDefinition" && member.value.body === null;
|
|
86
215
|
}
|
|
87
216
|
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
217
|
+
function isGetterSetterPair(previous: ESTree.ClassElement, next: ESTree.ClassElement): boolean {
|
|
218
|
+
if (previous.type !== "MethodDefinition" || next.type !== "MethodDefinition") {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
!(
|
|
224
|
+
(previous.kind === "get" && next.kind === "set") ||
|
|
225
|
+
(previous.kind === "set" && next.kind === "get")
|
|
226
|
+
)
|
|
227
|
+
) {
|
|
228
|
+
return false;
|
|
96
229
|
}
|
|
97
230
|
|
|
98
|
-
const
|
|
231
|
+
const previousName = getKeyName(previous);
|
|
232
|
+
const nextName = getKeyName(next);
|
|
99
233
|
|
|
100
|
-
|
|
101
|
-
|
|
234
|
+
return previousName !== null && previousName === nextName;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function getMemberStart(member: ESTree.ClassElement): ESTree.ClassElement | ESTree.Decorator {
|
|
238
|
+
if (member.type === "TSIndexSignature" || member.type === "StaticBlock") {
|
|
239
|
+
return member;
|
|
102
240
|
}
|
|
103
241
|
|
|
104
|
-
return
|
|
242
|
+
return member.decorators[0] ?? member;
|
|
105
243
|
}
|
|
106
244
|
|
|
107
245
|
/**
|
|
108
|
-
*
|
|
246
|
+
* 宣言・制御ブロック・変数宣言グループ・return・クラスや型のメンバー・switch の case における空行を、1 つのルールでまとめて扱います。
|
|
247
|
+
*
|
|
248
|
+
* 文の境界ごとに該当する項目を 1 つだけ判定するため、複数の診断が同じ箇所で矛盾しません。
|
|
249
|
+
* 行コメントや通常のブロックコメントを挟む場合、前後の空行は任意とします。
|
|
109
250
|
*/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
251
|
+
const blankLine = defineRule({
|
|
252
|
+
meta: {
|
|
253
|
+
docs: {
|
|
254
|
+
description:
|
|
255
|
+
"Require blank lines between statements, class members, and switch cases, and above JSDoc-style block comments.",
|
|
256
|
+
},
|
|
257
|
+
type: "layout",
|
|
258
|
+
schema: [
|
|
259
|
+
{
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {
|
|
262
|
+
beforeReturn: { type: "boolean" },
|
|
263
|
+
afterDeclaration: { type: "boolean" },
|
|
264
|
+
afterControlBlock: { type: "boolean" },
|
|
265
|
+
betweenSwitchCases: { type: "boolean" },
|
|
266
|
+
betweenTypeMembers: { type: "boolean" },
|
|
267
|
+
betweenClassMembers: { type: "boolean" },
|
|
268
|
+
afterVariableDeclaration: { type: "boolean" },
|
|
269
|
+
},
|
|
270
|
+
additionalProperties: false,
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
fixable: "whitespace",
|
|
274
|
+
messages: {
|
|
275
|
+
blankLine: "Expected a blank line here.",
|
|
276
|
+
unexpectedBlankLine:
|
|
277
|
+
"Expected no blank line between a variable declaration and its related statement.",
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
create(context) {
|
|
281
|
+
const sourceCode = context.sourceCode;
|
|
282
|
+
const options = getOptions(context.options);
|
|
119
283
|
|
|
120
|
-
|
|
284
|
+
function requireBlankLine(
|
|
285
|
+
previous: ESTree.Node,
|
|
286
|
+
next: ESTree.Node,
|
|
287
|
+
reportNode: ESTree.Node,
|
|
288
|
+
): void {
|
|
289
|
+
const target = getInsertionTarget(previous, next, sourceCode);
|
|
121
290
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
291
|
+
// JSDoc 形式のブロックコメントの 1 行上は空行にします。
|
|
292
|
+
// それ以外のコメントを挟む場合、前後の空行は任意とします。
|
|
293
|
+
if (isComment(target) && !isDocComment(target)) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
125
296
|
|
|
126
|
-
|
|
127
|
-
|
|
297
|
+
if (hasBlankLine(previous, target)) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
128
300
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
301
|
+
context.report({
|
|
302
|
+
fix(fixer) {
|
|
303
|
+
return getBlankLineFix(fixer, sourceCode, previous, target);
|
|
304
|
+
},
|
|
305
|
+
node: reportNode,
|
|
306
|
+
messageId: "blankLine",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
132
309
|
|
|
133
|
-
|
|
134
|
-
|
|
310
|
+
/**
|
|
311
|
+
* 変数宣言グループを収集します。
|
|
312
|
+
*
|
|
313
|
+
* 連続する変数宣言と、宣言した変数を使う式文を同じグループとして扱います。
|
|
314
|
+
* グループ開始でない場合は null を返します。
|
|
315
|
+
*/
|
|
316
|
+
function collectVariableGroup(
|
|
317
|
+
body: readonly ESTree.Node[],
|
|
318
|
+
start: number,
|
|
319
|
+
): {
|
|
320
|
+
variables: Set<Variable>;
|
|
321
|
+
statements: ESTree.Node[];
|
|
322
|
+
} | null {
|
|
323
|
+
const first = body[start];
|
|
135
324
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
325
|
+
if (first === undefined) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const firstDeclaration = getVariableDeclaration(first);
|
|
330
|
+
|
|
331
|
+
if (firstDeclaration === null) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const variables = new Set<Variable>(sourceCode.getDeclaredVariables(firstDeclaration));
|
|
336
|
+
const statements: ESTree.Node[] = [first];
|
|
337
|
+
let index = start + 1;
|
|
338
|
+
|
|
339
|
+
while (index < body.length) {
|
|
340
|
+
const next = body[index];
|
|
341
|
+
|
|
342
|
+
if (next === undefined) {
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const nextDeclaration = getVariableDeclaration(next);
|
|
347
|
+
const lastStatement = statements.at(-1);
|
|
348
|
+
|
|
349
|
+
if (
|
|
350
|
+
nextDeclaration !== null &&
|
|
351
|
+
lastStatement !== undefined &&
|
|
352
|
+
getVariableDeclaration(lastStatement) !== null
|
|
353
|
+
) {
|
|
354
|
+
for (const variable of sourceCode.getDeclaredVariables(nextDeclaration)) {
|
|
355
|
+
variables.add(variable);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
statements.push(next);
|
|
359
|
+
index += 1;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (next.type === "ExpressionStatement" && isRelatedExpression(next, variables)) {
|
|
364
|
+
statements.push(next);
|
|
365
|
+
index += 1;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
variables,
|
|
374
|
+
statements,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function checkVariableGroupBoundary(last: ESTree.Node, next: ESTree.Node | undefined): void {
|
|
379
|
+
if (next === undefined || next.type === "EmptyStatement") {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (next.type === "ReturnStatement") {
|
|
384
|
+
if (options.beforeReturn) {
|
|
385
|
+
requireBlankLine(last, next, next);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (options.afterVariableDeclaration) {
|
|
392
|
+
requireBlankLine(last, next, next);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function checkStatementBody(body: readonly ESTree.Node[]): void {
|
|
397
|
+
let index = 0;
|
|
398
|
+
|
|
399
|
+
while (index < body.length) {
|
|
400
|
+
const group =
|
|
401
|
+
options.afterVariableDeclaration || options.beforeReturn
|
|
402
|
+
? collectVariableGroup(body, index)
|
|
403
|
+
: null;
|
|
404
|
+
|
|
405
|
+
if (group !== null) {
|
|
406
|
+
if (options.afterVariableDeclaration) {
|
|
407
|
+
for (let offset = 0; offset < group.statements.length - 1; offset += 1) {
|
|
408
|
+
const previous = group.statements[offset];
|
|
409
|
+
const next = group.statements[offset + 1];
|
|
410
|
+
|
|
411
|
+
if (previous === undefined || next === undefined) {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const target = getInsertionTarget(previous, next, sourceCode);
|
|
416
|
+
|
|
417
|
+
// JSDoc 形式のブロックコメントの 1 行上は空行にします。
|
|
418
|
+
if (isDocComment(target)) {
|
|
419
|
+
requireBlankLine(previous, next, next);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// 変数同士の境界は空行の有無を問いません。
|
|
424
|
+
if (next.type !== "ExpressionStatement") {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (isComment(target) || !hasBlankLine(previous, target)) {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
context.report({
|
|
433
|
+
fix(fixer) {
|
|
434
|
+
return getRemoveBlankLineFix(fixer, sourceCode, previous, target);
|
|
435
|
+
},
|
|
436
|
+
node: next,
|
|
437
|
+
messageId: "unexpectedBlankLine",
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const last = group.statements.at(-1);
|
|
443
|
+
const next = body[index + group.statements.length];
|
|
444
|
+
|
|
445
|
+
if (last === undefined) {
|
|
446
|
+
index += group.statements.length;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
checkVariableGroupBoundary(last, next);
|
|
451
|
+
|
|
452
|
+
index += group.statements.length;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const previous = body[index];
|
|
457
|
+
const next = body[index + 1];
|
|
458
|
+
|
|
459
|
+
if (previous === undefined || next === undefined || next.type === "EmptyStatement") {
|
|
460
|
+
index += 1;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (isControlStatement(previous)) {
|
|
465
|
+
if (options.afterControlBlock) {
|
|
466
|
+
requireBlankLine(previous, next, previous);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
index += 1;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const declaration = getDeclaration(previous);
|
|
474
|
+
|
|
475
|
+
if (declaration !== null) {
|
|
476
|
+
if (!isOverload(declaration) && options.afterDeclaration) {
|
|
477
|
+
requireBlankLine(previous, next, previous);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
index += 1;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (next.type === "ReturnStatement") {
|
|
485
|
+
if (
|
|
486
|
+
options.beforeReturn &&
|
|
487
|
+
!SKIP_RETURN_PREVIOUS_TYPES.has(previous.type) &&
|
|
488
|
+
!isDirective(previous)
|
|
489
|
+
) {
|
|
490
|
+
requireBlankLine(previous, next, next);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
index += 1;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
index += 1;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* 型や interface のメンバー境界を確認します。
|
|
503
|
+
*
|
|
504
|
+
* メンバー間の空行は要求せず、JSDoc 形式のブロックコメントの 1 行上だけ空行を要求します。
|
|
505
|
+
*/
|
|
506
|
+
function checkTypeMembers(members: readonly ESTree.TSSignature[]): void {
|
|
507
|
+
for (let index = 1; index < members.length; index += 1) {
|
|
508
|
+
const previous = members[index - 1];
|
|
509
|
+
const next = members[index];
|
|
510
|
+
|
|
511
|
+
if (previous === undefined || next === undefined) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const target = getInsertionTarget(previous, next, sourceCode);
|
|
516
|
+
|
|
517
|
+
if (!isDocComment(target) || hasBlankLine(previous, target)) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
context.report({
|
|
522
|
+
fix(fixer) {
|
|
523
|
+
return getBlankLineFix(fixer, sourceCode, previous, target);
|
|
524
|
+
},
|
|
525
|
+
node: next,
|
|
526
|
+
messageId: "blankLine",
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
Program(node): void {
|
|
533
|
+
checkStatementBody(node.body);
|
|
534
|
+
},
|
|
535
|
+
ClassBody(node): void {
|
|
536
|
+
if (!options.betweenClassMembers) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
for (let index = 1; index < node.body.length; index += 1) {
|
|
541
|
+
const previous = node.body[index - 1];
|
|
542
|
+
const next = node.body[index];
|
|
543
|
+
|
|
544
|
+
if (previous === undefined || next === undefined) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (isOverloadMember(previous) || isGetterSetterPair(previous, next)) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
requireBlankLine(previous, getMemberStart(next), next);
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
SwitchCase(node): void {
|
|
556
|
+
checkStatementBody(node.consequent);
|
|
557
|
+
},
|
|
558
|
+
StaticBlock(node): void {
|
|
559
|
+
checkStatementBody(node.body);
|
|
560
|
+
},
|
|
561
|
+
TSModuleBlock(node): void {
|
|
562
|
+
checkStatementBody(node.body);
|
|
563
|
+
},
|
|
564
|
+
TSTypeLiteral(node): void {
|
|
565
|
+
if (!options.betweenTypeMembers) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
checkTypeMembers(node.members);
|
|
570
|
+
},
|
|
571
|
+
BlockStatement(node): void {
|
|
572
|
+
checkStatementBody(node.body);
|
|
573
|
+
},
|
|
574
|
+
SwitchStatement(node): void {
|
|
575
|
+
if (!options.betweenSwitchCases) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
for (let index = 1; index < node.cases.length; index += 1) {
|
|
580
|
+
const previous = node.cases[index - 1];
|
|
581
|
+
const next = node.cases[index];
|
|
582
|
+
|
|
583
|
+
if (previous === undefined || next === undefined || previous.consequent.length === 0) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
requireBlankLine(previous, next, next);
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
TSInterfaceBody(node): void {
|
|
591
|
+
if (!options.betweenTypeMembers) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
checkTypeMembers(node.body);
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
export default blankLine;
|