@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.
Files changed (41) hide show
  1. package/dist/src/index.d.ts +1 -6
  2. package/dist/src/index.d.ts.map +1 -1
  3. package/dist/src/index.js +1 -6
  4. package/dist/src/plugin.js +2 -12
  5. package/dist/src/rules/blank-line-utils.d.ts +30 -0
  6. package/dist/src/rules/blank-line-utils.d.ts.map +1 -0
  7. package/dist/src/rules/blank-line-utils.js +103 -0
  8. package/dist/src/rules/blank-line.d.ts +5 -14
  9. package/dist/src/rules/blank-line.d.ts.map +1 -1
  10. package/dist/src/rules/blank-line.js +426 -66
  11. package/dist/src/rules/overload-signatures-adjacent.js +1 -1
  12. package/package.json +1 -1
  13. package/src/index.ts +1 -6
  14. package/src/plugin.ts +2 -12
  15. package/src/rules/blank-line-utils.ts +165 -0
  16. package/src/rules/blank-line.ts +555 -97
  17. package/src/rules/overload-signatures-adjacent.ts +1 -1
  18. package/dist/src/rules/blank-line-after-control-block.d.ts +0 -7
  19. package/dist/src/rules/blank-line-after-control-block.d.ts.map +0 -1
  20. package/dist/src/rules/blank-line-after-control-block.js +0 -54
  21. package/dist/src/rules/blank-line-after-declaration.d.ts +0 -7
  22. package/dist/src/rules/blank-line-after-declaration.d.ts.map +0 -1
  23. package/dist/src/rules/blank-line-after-declaration.js +0 -79
  24. package/dist/src/rules/blank-line-after-variable-declaration.d.ts +0 -9
  25. package/dist/src/rules/blank-line-after-variable-declaration.d.ts.map +0 -1
  26. package/dist/src/rules/blank-line-after-variable-declaration.js +0 -118
  27. package/dist/src/rules/blank-line-before-return.d.ts +0 -7
  28. package/dist/src/rules/blank-line-before-return.d.ts.map +0 -1
  29. package/dist/src/rules/blank-line-before-return.js +0 -81
  30. package/dist/src/rules/blank-line-between-class-members.d.ts +0 -8
  31. package/dist/src/rules/blank-line-between-class-members.d.ts.map +0 -1
  32. package/dist/src/rules/blank-line-between-class-members.js +0 -85
  33. package/dist/src/rules/blank-line-between-switch-cases.d.ts +0 -8
  34. package/dist/src/rules/blank-line-between-switch-cases.d.ts.map +0 -1
  35. package/dist/src/rules/blank-line-between-switch-cases.js +0 -45
  36. package/src/rules/blank-line-after-control-block.ts +0 -82
  37. package/src/rules/blank-line-after-declaration.ts +0 -106
  38. package/src/rules/blank-line-after-variable-declaration.ts +0 -165
  39. package/src/rules/blank-line-before-return.ts +0 -101
  40. package/src/rules/blank-line-between-class-members.ts +0 -112
  41. package/src/rules/blank-line-between-switch-cases.ts +0 -53
@@ -1,143 +1,601 @@
1
- import type { Fix, Fixer, ESTree, Comment, SourceCode } from "vite-plus/lint/plugins";
1
+ import { defineRule } from "vite-plus/lint/plugins";
2
+ import type { ESTree, Variable } from "vite-plus/lint/plugins";
2
3
 
3
- /**
4
- * 文の配列を持つ親から、その文が属する配列を取得します。
5
- */
6
- function getStatementBody(node: ESTree.Node): readonly ESTree.Node[] | null {
7
- const { parent } = node;
4
+ import {
5
+ isComment,
6
+ hasBlankLine,
7
+ isDocComment,
8
+ getBlankLineFix,
9
+ getInsertionTarget,
10
+ getRemoveBlankLineFix,
11
+ } from "./blank-line-utils.js";
8
12
 
9
- if (parent === null) {
10
- return null;
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
- if (
14
- parent.type === "Program" ||
15
- parent.type === "BlockStatement" ||
16
- parent.type === "StaticBlock" ||
17
- parent.type === "TSModuleBlock"
18
- ) {
19
- return parent.body;
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 (parent.type === "SwitchCase") {
23
- return parent.consequent;
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 getNextStatement(node: ESTree.Node): ESTree.Node | null {
30
- const body = getStatementBody(node);
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 (body === null) {
33
- return null;
158
+ if (node.type === "ExportNamedDeclaration" && node.declaration?.type === "VariableDeclaration") {
159
+ return node.declaration;
34
160
  }
35
161
 
36
- const index = body.indexOf(node);
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
- if (index === -1 || index === body.length - 1) {
39
- return null;
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 body[index + 1] ?? null;
184
+ return false;
43
185
  }
44
186
 
45
- function getPreviousStatement(node: ESTree.Node): ESTree.Node | null {
46
- const body = getStatementBody(node);
187
+ function isDirective(node: ESTree.Node): boolean {
188
+ return node.type === "ExpressionStatement" && typeof node.directive === "string";
189
+ }
47
190
 
48
- if (body === null) {
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
- const index = body.indexOf(node);
53
-
54
- if (index <= 0) {
196
+ if (member.computed) {
55
197
  return null;
56
198
  }
57
199
 
58
- return body[index - 1] ?? null;
59
- }
200
+ const key = member.key;
60
201
 
61
- /**
62
- * 空行の挿入位置を返します。
63
- *
64
- * 次の文に付くコメントがある場合は、そのコメントの前を挿入位置にします。
65
- * 同じ行の末尾コメントは対象外です。
66
- */
67
- function getInsertionTarget(
68
- node: ESTree.Node,
69
- nextStatement: ESTree.Node,
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 hasBlankLine(node: ESTree.Node, target: ESTree.Node | Comment): boolean {
85
- return target.loc.start.line > node.loc.end.line + 1;
213
+ function isOverloadMember(member: ESTree.ClassElement): boolean {
214
+ return member.type === "MethodDefinition" && member.value.body === null;
86
215
  }
87
216
 
88
- function getBlankLineFix(
89
- fixer: Fixer,
90
- sourceCode: SourceCode,
91
- node: ESTree.Node,
92
- target: ESTree.Node | Comment,
93
- ): Fix | null {
94
- if (target.loc.start.line === node.loc.end.line) {
95
- return fixer.insertTextAfterRange([node.range[1], node.range[1]], "\n\n");
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 lineEnd = sourceCode.text.indexOf("\n", node.range[1]);
231
+ const previousName = getKeyName(previous);
232
+ const nextName = getKeyName(next);
99
233
 
100
- if (lineEnd === -1) {
101
- return null;
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 fixer.insertTextBeforeRange([lineEnd, lineEnd + 1], "\n");
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
- function getRemoveBlankLineFix(
111
- fixer: Fixer,
112
- sourceCode: SourceCode,
113
- node: ESTree.Node,
114
- target: ESTree.Node | Comment,
115
- ): Fix | null {
116
- if (target.loc.start.line <= node.loc.end.line + 1) {
117
- return null;
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
- const fromLineEnd = sourceCode.text.indexOf("\n", node.range[1]);
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
- if (fromLineEnd === -1) {
123
- return null;
124
- }
291
+ // JSDoc 形式のブロックコメントの 1 行上は空行にします。
292
+ // それ以外のコメントを挟む場合、前後の空行は任意とします。
293
+ if (isComment(target) && !isDocComment(target)) {
294
+ return;
295
+ }
125
296
 
126
- const targetLineStart = sourceCode.text.lastIndexOf("\n", target.range[0]) + 1;
127
- const indent = sourceCode.text.slice(targetLineStart, target.range[0]);
297
+ if (hasBlankLine(previous, target)) {
298
+ return;
299
+ }
128
300
 
129
- if (indent.trim() !== "") {
130
- return null;
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
- return fixer.replaceTextRange([fromLineEnd, target.range[0]], `\n${indent}`);
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
- export {
137
- hasBlankLine,
138
- getBlankLineFix,
139
- getNextStatement,
140
- getInsertionTarget,
141
- getPreviousStatement,
142
- getRemoveBlankLineFix,
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;