@tai-kun/vp-lint-plugins 0.0.4 → 0.0.6

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 (79) 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/{comment-utils.d.ts → _comment-utils.d.ts} +1 -1
  9. package/dist/src/rules/_comment-utils.d.ts.map +1 -0
  10. package/dist/src/rules/{comment-utils.js → _comment-utils.js} +2 -1
  11. package/dist/src/rules/{jsdoc-utils.d.ts → _jsdoc-utils.d.ts} +1 -1
  12. package/dist/src/rules/_jsdoc-utils.d.ts.map +1 -0
  13. package/dist/src/rules/{jsdoc-utils.js → _jsdoc-utils.js} +1 -1
  14. package/dist/src/rules/{object-sort-utils.d.ts → _object-sort-utils.d.ts} +1 -1
  15. package/dist/src/rules/_object-sort-utils.d.ts.map +1 -0
  16. package/dist/src/rules/blank-line.d.ts +5 -14
  17. package/dist/src/rules/blank-line.d.ts.map +1 -1
  18. package/dist/src/rules/blank-line.js +426 -66
  19. package/dist/src/rules/comment-japanese-spacing.js +1 -1
  20. package/dist/src/rules/comment-one-sentence-per-line.d.ts +1 -1
  21. package/dist/src/rules/comment-one-sentence-per-line.js +2 -2
  22. package/dist/src/rules/jsdoc-param-order.js +1 -1
  23. package/dist/src/rules/jsdoc-tag-lines.js +1 -1
  24. package/dist/src/rules/jsdoc-tag-order.js +1 -1
  25. package/dist/src/rules/object-properties-newline.d.ts +2 -1
  26. package/dist/src/rules/object-properties-newline.d.ts.map +1 -1
  27. package/dist/src/rules/object-properties-newline.js +39 -8
  28. package/dist/src/rules/overload-signatures-adjacent.js +1 -1
  29. package/dist/src/rules/sort-import-export-specifiers.js +1 -1
  30. package/dist/src/rules/sort-jsx-props.js +1 -1
  31. package/dist/src/rules/sort-object-properties.js +1 -1
  32. package/dist/src/rules/sort-type-members.js +1 -1
  33. package/package.json +1 -1
  34. package/src/index.ts +1 -6
  35. package/src/plugin.ts +2 -12
  36. package/src/rules/_blank-line-utils.ts +165 -0
  37. package/src/rules/{comment-utils.ts → _comment-utils.ts} +2 -1
  38. package/src/rules/{jsdoc-utils.ts → _jsdoc-utils.ts} +1 -1
  39. package/src/rules/blank-line.ts +555 -97
  40. package/src/rules/comment-japanese-spacing.ts +2 -2
  41. package/src/rules/comment-one-sentence-per-line.ts +3 -3
  42. package/src/rules/jsdoc-param-order.ts +1 -1
  43. package/src/rules/jsdoc-tag-lines.ts +2 -2
  44. package/src/rules/jsdoc-tag-order.ts +1 -1
  45. package/src/rules/object-properties-newline.ts +93 -10
  46. package/src/rules/overload-signatures-adjacent.ts +1 -1
  47. package/src/rules/sort-import-export-specifiers.ts +1 -1
  48. package/src/rules/sort-jsx-props.ts +1 -1
  49. package/src/rules/sort-object-properties.ts +1 -1
  50. package/src/rules/sort-type-members.ts +1 -1
  51. package/dist/src/rules/blank-line-after-control-block.d.ts +0 -7
  52. package/dist/src/rules/blank-line-after-control-block.d.ts.map +0 -1
  53. package/dist/src/rules/blank-line-after-control-block.js +0 -54
  54. package/dist/src/rules/blank-line-after-declaration.d.ts +0 -7
  55. package/dist/src/rules/blank-line-after-declaration.d.ts.map +0 -1
  56. package/dist/src/rules/blank-line-after-declaration.js +0 -79
  57. package/dist/src/rules/blank-line-after-variable-declaration.d.ts +0 -9
  58. package/dist/src/rules/blank-line-after-variable-declaration.d.ts.map +0 -1
  59. package/dist/src/rules/blank-line-after-variable-declaration.js +0 -118
  60. package/dist/src/rules/blank-line-before-return.d.ts +0 -7
  61. package/dist/src/rules/blank-line-before-return.d.ts.map +0 -1
  62. package/dist/src/rules/blank-line-before-return.js +0 -81
  63. package/dist/src/rules/blank-line-between-class-members.d.ts +0 -8
  64. package/dist/src/rules/blank-line-between-class-members.d.ts.map +0 -1
  65. package/dist/src/rules/blank-line-between-class-members.js +0 -85
  66. package/dist/src/rules/blank-line-between-switch-cases.d.ts +0 -8
  67. package/dist/src/rules/blank-line-between-switch-cases.d.ts.map +0 -1
  68. package/dist/src/rules/blank-line-between-switch-cases.js +0 -45
  69. package/dist/src/rules/comment-utils.d.ts.map +0 -1
  70. package/dist/src/rules/jsdoc-utils.d.ts.map +0 -1
  71. package/dist/src/rules/object-sort-utils.d.ts.map +0 -1
  72. package/src/rules/blank-line-after-control-block.ts +0 -82
  73. package/src/rules/blank-line-after-declaration.ts +0 -106
  74. package/src/rules/blank-line-after-variable-declaration.ts +0 -165
  75. package/src/rules/blank-line-before-return.ts +0 -101
  76. package/src/rules/blank-line-between-class-members.ts +0 -112
  77. package/src/rules/blank-line-between-switch-cases.ts +0 -53
  78. /package/dist/src/rules/{object-sort-utils.js → _object-sort-utils.js} +0 -0
  79. /package/src/rules/{object-sort-utils.ts → _object-sort-utils.ts} +0 -0
@@ -1,86 +1,446 @@
1
- /**
2
- * 文の配列を持つ親から、その文が属する配列を取得します。
3
- */
4
- function getStatementBody(node) {
5
- const { parent } = node;
6
- if (parent === null) {
7
- return null;
1
+ import { defineRule } from "vite-plus/lint/plugins";
2
+ import { isComment, hasBlankLine, isDocComment, getBlankLineFix, getInsertionTarget, getRemoveBlankLineFix, } from "./_blank-line-utils.js";
3
+ const CONTROL_STATEMENT_TYPES = new Set([
4
+ "DoWhileStatement",
5
+ "ForInStatement",
6
+ "ForOfStatement",
7
+ "ForStatement",
8
+ "IfStatement",
9
+ "SwitchStatement",
10
+ "TryStatement",
11
+ "WhileStatement",
12
+ ]);
13
+ const DECLARATION_TYPES = new Set([
14
+ "ClassDeclaration",
15
+ "FunctionDeclaration",
16
+ "TSDeclareFunction",
17
+ "TSEnumDeclaration",
18
+ "TSInterfaceDeclaration",
19
+ "TSModuleDeclaration",
20
+ "TSTypeAliasDeclaration",
21
+ ]);
22
+ const SKIP_RETURN_PREVIOUS_TYPES = new Set([
23
+ // 制御ブロックの後は afterControlBlock が担当するため、ここでは判定しません。
24
+ "DoWhileStatement",
25
+ "ForInStatement",
26
+ "ForOfStatement",
27
+ "ForStatement",
28
+ "IfStatement",
29
+ "SwitchStatement",
30
+ "TryStatement",
31
+ "WhileStatement",
32
+ // 宣言の後は afterDeclaration が担当するため、ここでは判定しません。
33
+ "ClassDeclaration",
34
+ "ExportAllDeclaration",
35
+ "ExportDefaultDeclaration",
36
+ "ExportNamedDeclaration",
37
+ "FunctionDeclaration",
38
+ "ImportDeclaration",
39
+ "TSDeclareFunction",
40
+ "TSEnumDeclaration",
41
+ "TSInterfaceDeclaration",
42
+ "TSModuleDeclaration",
43
+ "TSTypeAliasDeclaration",
44
+ // 途中で終わる文や空文の後は対象外にします。
45
+ "BlockStatement",
46
+ "BreakStatement",
47
+ "ContinueStatement",
48
+ "DebuggerStatement",
49
+ "EmptyStatement",
50
+ "LabeledStatement",
51
+ "ReturnStatement",
52
+ "ThrowStatement",
53
+ "WithStatement",
54
+ ]);
55
+ const DEFAULT_OPTIONS = {
56
+ beforeReturn: true,
57
+ afterDeclaration: true,
58
+ afterControlBlock: true,
59
+ betweenSwitchCases: true,
60
+ betweenTypeMembers: true,
61
+ betweenClassMembers: true,
62
+ afterVariableDeclaration: true,
63
+ };
64
+ function isObject(value) {
65
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66
+ }
67
+ function getFlag(option, key) {
68
+ const value = option[key];
69
+ return value === undefined ? DEFAULT_OPTIONS[key] : value === true;
70
+ }
71
+ function getOptions(options) {
72
+ const option = options[0];
73
+ if (!isObject(option)) {
74
+ return { ...DEFAULT_OPTIONS };
8
75
  }
9
- if (parent.type === "Program" ||
10
- parent.type === "BlockStatement" ||
11
- parent.type === "StaticBlock" ||
12
- parent.type === "TSModuleBlock") {
13
- return parent.body;
76
+ return {
77
+ beforeReturn: getFlag(option, "beforeReturn"),
78
+ afterDeclaration: getFlag(option, "afterDeclaration"),
79
+ afterControlBlock: getFlag(option, "afterControlBlock"),
80
+ betweenSwitchCases: getFlag(option, "betweenSwitchCases"),
81
+ betweenTypeMembers: getFlag(option, "betweenTypeMembers"),
82
+ betweenClassMembers: getFlag(option, "betweenClassMembers"),
83
+ afterVariableDeclaration: getFlag(option, "afterVariableDeclaration"),
84
+ };
85
+ }
86
+ function isControlStatement(node) {
87
+ return CONTROL_STATEMENT_TYPES.has(node.type);
88
+ }
89
+ function getDeclaration(node) {
90
+ if (DECLARATION_TYPES.has(node.type)) {
91
+ return node;
14
92
  }
15
- if (parent.type === "SwitchCase") {
16
- return parent.consequent;
93
+ if ((node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") &&
94
+ node.declaration !== null &&
95
+ DECLARATION_TYPES.has(node.declaration.type)) {
96
+ return node.declaration;
17
97
  }
18
98
  return null;
19
99
  }
20
- function getNextStatement(node) {
21
- const body = getStatementBody(node);
22
- if (body === null) {
23
- return null;
100
+ function isOverload(declaration) {
101
+ return ((declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction") &&
102
+ declaration.body === null);
103
+ }
104
+ function getVariableDeclaration(node) {
105
+ if (node.type === "VariableDeclaration") {
106
+ return node;
24
107
  }
25
- const index = body.indexOf(node);
26
- if (index === -1 || index === body.length - 1) {
27
- return null;
108
+ if (node.type === "ExportNamedDeclaration" && node.declaration?.type === "VariableDeclaration") {
109
+ return node.declaration;
28
110
  }
29
- return body[index + 1] ?? null;
111
+ return null;
30
112
  }
31
- function getPreviousStatement(node) {
32
- const body = getStatementBody(node);
33
- if (body === null) {
34
- return null;
113
+ function isRelatedExpression(statement, variables) {
114
+ if (statement.type !== "ExpressionStatement") {
115
+ return false;
35
116
  }
36
- const index = body.indexOf(node);
37
- if (index <= 0) {
38
- return null;
117
+ for (const variable of variables) {
118
+ for (const reference of variable.references) {
119
+ if (reference.identifier.range[0] >= statement.range[0] &&
120
+ reference.identifier.range[1] <= statement.range[1]) {
121
+ return true;
122
+ }
123
+ }
39
124
  }
40
- return body[index - 1] ?? null;
125
+ return false;
41
126
  }
42
- /**
43
- * 空行の挿入位置を返します。
44
- *
45
- * 次の文に付くコメントがある場合は、そのコメントの前を挿入位置にします。
46
- * 同じ行の末尾コメントは対象外です。
47
- */
48
- function getInsertionTarget(node, nextStatement, sourceCode) {
49
- return (sourceCode
50
- .getAllComments()
51
- .find((comment) => comment.range[0] >= node.range[1] &&
52
- comment.range[1] <= nextStatement.range[0] &&
53
- comment.loc.start.line > node.loc.end.line) ?? nextStatement);
127
+ function isDirective(node) {
128
+ return node.type === "ExpressionStatement" && typeof node.directive === "string";
54
129
  }
55
- function hasBlankLine(node, target) {
56
- return target.loc.start.line > node.loc.end.line + 1;
57
- }
58
- function getBlankLineFix(fixer, sourceCode, node, target) {
59
- if (target.loc.start.line === node.loc.end.line) {
60
- return fixer.insertTextAfterRange([node.range[1], node.range[1]], "\n\n");
130
+ function getKeyName(member) {
131
+ if (member.type === "TSIndexSignature" || member.type === "StaticBlock") {
132
+ return null;
61
133
  }
62
- const lineEnd = sourceCode.text.indexOf("\n", node.range[1]);
63
- if (lineEnd === -1) {
134
+ if (member.computed) {
64
135
  return null;
65
136
  }
66
- return fixer.insertTextBeforeRange([lineEnd, lineEnd + 1], "\n");
137
+ const key = member.key;
138
+ if (key.type === "Identifier") {
139
+ return key.name;
140
+ }
141
+ if (key.type === "Literal" && (typeof key.value === "string" || typeof key.value === "number")) {
142
+ return String(key.value);
143
+ }
144
+ return null;
67
145
  }
68
- /**
69
- * 指定したノードと対象の間の空行を削除します。
70
- */
71
- function getRemoveBlankLineFix(fixer, sourceCode, node, target) {
72
- if (target.loc.start.line <= node.loc.end.line + 1) {
73
- return null;
146
+ function isOverloadMember(member) {
147
+ return member.type === "MethodDefinition" && member.value.body === null;
148
+ }
149
+ function isGetterSetterPair(previous, next) {
150
+ if (previous.type !== "MethodDefinition" || next.type !== "MethodDefinition") {
151
+ return false;
74
152
  }
75
- const fromLineEnd = sourceCode.text.indexOf("\n", node.range[1]);
76
- if (fromLineEnd === -1) {
77
- return null;
153
+ if (!((previous.kind === "get" && next.kind === "set") ||
154
+ (previous.kind === "set" && next.kind === "get"))) {
155
+ return false;
78
156
  }
79
- const targetLineStart = sourceCode.text.lastIndexOf("\n", target.range[0]) + 1;
80
- const indent = sourceCode.text.slice(targetLineStart, target.range[0]);
81
- if (indent.trim() !== "") {
82
- return null;
157
+ const previousName = getKeyName(previous);
158
+ const nextName = getKeyName(next);
159
+ return previousName !== null && previousName === nextName;
160
+ }
161
+ function getMemberStart(member) {
162
+ if (member.type === "TSIndexSignature" || member.type === "StaticBlock") {
163
+ return member;
83
164
  }
84
- return fixer.replaceTextRange([fromLineEnd, target.range[0]], `\n${indent}`);
165
+ return member.decorators[0] ?? member;
85
166
  }
86
- export { hasBlankLine, getBlankLineFix, getNextStatement, getInsertionTarget, getPreviousStatement, getRemoveBlankLineFix, };
167
+ /**
168
+ * 宣言・制御ブロック・変数宣言グループ・return・クラスや型のメンバー・switch の case における空行を、1 つのルールでまとめて扱います。
169
+ *
170
+ * 文の境界ごとに該当する項目を 1 つだけ判定するため、複数の診断が同じ箇所で矛盾しません。
171
+ * 行コメントや通常のブロックコメントを挟む場合、前後の空行は任意とします。
172
+ */
173
+ const blankLine = defineRule({
174
+ meta: {
175
+ docs: {
176
+ description: "Require blank lines between statements, class members, and switch cases, and above JSDoc-style block comments.",
177
+ },
178
+ type: "layout",
179
+ schema: [
180
+ {
181
+ type: "object",
182
+ properties: {
183
+ beforeReturn: { type: "boolean" },
184
+ afterDeclaration: { type: "boolean" },
185
+ afterControlBlock: { type: "boolean" },
186
+ betweenSwitchCases: { type: "boolean" },
187
+ betweenTypeMembers: { type: "boolean" },
188
+ betweenClassMembers: { type: "boolean" },
189
+ afterVariableDeclaration: { type: "boolean" },
190
+ },
191
+ additionalProperties: false,
192
+ },
193
+ ],
194
+ fixable: "whitespace",
195
+ messages: {
196
+ blankLine: "Expected a blank line here.",
197
+ unexpectedBlankLine: "Expected no blank line between a variable declaration and its related statement.",
198
+ },
199
+ },
200
+ create(context) {
201
+ const sourceCode = context.sourceCode;
202
+ const options = getOptions(context.options);
203
+ function requireBlankLine(previous, next, reportNode) {
204
+ const target = getInsertionTarget(previous, next, sourceCode);
205
+ // JSDoc 形式のブロックコメントの 1 行上は空行にします。
206
+ // それ以外のコメントを挟む場合、前後の空行は任意とします。
207
+ if (isComment(target) && !isDocComment(target)) {
208
+ return;
209
+ }
210
+ if (hasBlankLine(previous, target)) {
211
+ return;
212
+ }
213
+ context.report({
214
+ fix(fixer) {
215
+ return getBlankLineFix(fixer, sourceCode, previous, target);
216
+ },
217
+ node: reportNode,
218
+ messageId: "blankLine",
219
+ });
220
+ }
221
+ /**
222
+ * 変数宣言グループを収集します。
223
+ *
224
+ * 連続する変数宣言と、宣言した変数を使う式文を同じグループとして扱います。
225
+ * グループ開始でない場合は null を返します。
226
+ */
227
+ function collectVariableGroup(body, start) {
228
+ const first = body[start];
229
+ if (first === undefined) {
230
+ return null;
231
+ }
232
+ const firstDeclaration = getVariableDeclaration(first);
233
+ if (firstDeclaration === null) {
234
+ return null;
235
+ }
236
+ const variables = new Set(sourceCode.getDeclaredVariables(firstDeclaration));
237
+ const statements = [first];
238
+ let index = start + 1;
239
+ while (index < body.length) {
240
+ const next = body[index];
241
+ if (next === undefined) {
242
+ break;
243
+ }
244
+ const nextDeclaration = getVariableDeclaration(next);
245
+ const lastStatement = statements.at(-1);
246
+ if (nextDeclaration !== null &&
247
+ lastStatement !== undefined &&
248
+ getVariableDeclaration(lastStatement) !== null) {
249
+ for (const variable of sourceCode.getDeclaredVariables(nextDeclaration)) {
250
+ variables.add(variable);
251
+ }
252
+ statements.push(next);
253
+ index += 1;
254
+ continue;
255
+ }
256
+ if (next.type === "ExpressionStatement" && isRelatedExpression(next, variables)) {
257
+ statements.push(next);
258
+ index += 1;
259
+ continue;
260
+ }
261
+ break;
262
+ }
263
+ return {
264
+ variables,
265
+ statements,
266
+ };
267
+ }
268
+ function checkVariableGroupBoundary(last, next) {
269
+ if (next === undefined || next.type === "EmptyStatement") {
270
+ return;
271
+ }
272
+ if (next.type === "ReturnStatement") {
273
+ if (options.beforeReturn) {
274
+ requireBlankLine(last, next, next);
275
+ }
276
+ return;
277
+ }
278
+ if (options.afterVariableDeclaration) {
279
+ requireBlankLine(last, next, next);
280
+ }
281
+ }
282
+ function checkStatementBody(body) {
283
+ let index = 0;
284
+ while (index < body.length) {
285
+ const group = options.afterVariableDeclaration || options.beforeReturn
286
+ ? collectVariableGroup(body, index)
287
+ : null;
288
+ if (group !== null) {
289
+ if (options.afterVariableDeclaration) {
290
+ for (let offset = 0; offset < group.statements.length - 1; offset += 1) {
291
+ const previous = group.statements[offset];
292
+ const next = group.statements[offset + 1];
293
+ if (previous === undefined || next === undefined) {
294
+ continue;
295
+ }
296
+ const target = getInsertionTarget(previous, next, sourceCode);
297
+ // JSDoc 形式のブロックコメントの 1 行上は空行にします。
298
+ if (isDocComment(target)) {
299
+ requireBlankLine(previous, next, next);
300
+ continue;
301
+ }
302
+ // 変数同士の境界は空行の有無を問いません。
303
+ if (next.type !== "ExpressionStatement") {
304
+ continue;
305
+ }
306
+ if (isComment(target) || !hasBlankLine(previous, target)) {
307
+ continue;
308
+ }
309
+ context.report({
310
+ fix(fixer) {
311
+ return getRemoveBlankLineFix(fixer, sourceCode, previous, target);
312
+ },
313
+ node: next,
314
+ messageId: "unexpectedBlankLine",
315
+ });
316
+ }
317
+ }
318
+ const last = group.statements.at(-1);
319
+ const next = body[index + group.statements.length];
320
+ if (last === undefined) {
321
+ index += group.statements.length;
322
+ continue;
323
+ }
324
+ checkVariableGroupBoundary(last, next);
325
+ index += group.statements.length;
326
+ continue;
327
+ }
328
+ const previous = body[index];
329
+ const next = body[index + 1];
330
+ if (previous === undefined || next === undefined || next.type === "EmptyStatement") {
331
+ index += 1;
332
+ continue;
333
+ }
334
+ if (isControlStatement(previous)) {
335
+ if (options.afterControlBlock) {
336
+ requireBlankLine(previous, next, previous);
337
+ }
338
+ index += 1;
339
+ continue;
340
+ }
341
+ const declaration = getDeclaration(previous);
342
+ if (declaration !== null) {
343
+ if (!isOverload(declaration) && options.afterDeclaration) {
344
+ requireBlankLine(previous, next, previous);
345
+ }
346
+ index += 1;
347
+ continue;
348
+ }
349
+ if (next.type === "ReturnStatement") {
350
+ if (options.beforeReturn &&
351
+ !SKIP_RETURN_PREVIOUS_TYPES.has(previous.type) &&
352
+ !isDirective(previous)) {
353
+ requireBlankLine(previous, next, next);
354
+ }
355
+ index += 1;
356
+ continue;
357
+ }
358
+ index += 1;
359
+ }
360
+ }
361
+ /**
362
+ * 型や interface のメンバー境界を確認します。
363
+ *
364
+ * メンバー間の空行は要求せず、JSDoc 形式のブロックコメントの 1 行上だけ空行を要求します。
365
+ */
366
+ function checkTypeMembers(members) {
367
+ for (let index = 1; index < members.length; index += 1) {
368
+ const previous = members[index - 1];
369
+ const next = members[index];
370
+ if (previous === undefined || next === undefined) {
371
+ continue;
372
+ }
373
+ const target = getInsertionTarget(previous, next, sourceCode);
374
+ if (!isDocComment(target) || hasBlankLine(previous, target)) {
375
+ continue;
376
+ }
377
+ context.report({
378
+ fix(fixer) {
379
+ return getBlankLineFix(fixer, sourceCode, previous, target);
380
+ },
381
+ node: next,
382
+ messageId: "blankLine",
383
+ });
384
+ }
385
+ }
386
+ return {
387
+ Program(node) {
388
+ checkStatementBody(node.body);
389
+ },
390
+ ClassBody(node) {
391
+ if (!options.betweenClassMembers) {
392
+ return;
393
+ }
394
+ for (let index = 1; index < node.body.length; index += 1) {
395
+ const previous = node.body[index - 1];
396
+ const next = node.body[index];
397
+ if (previous === undefined || next === undefined) {
398
+ continue;
399
+ }
400
+ if (isOverloadMember(previous) || isGetterSetterPair(previous, next)) {
401
+ continue;
402
+ }
403
+ requireBlankLine(previous, getMemberStart(next), next);
404
+ }
405
+ },
406
+ SwitchCase(node) {
407
+ checkStatementBody(node.consequent);
408
+ },
409
+ StaticBlock(node) {
410
+ checkStatementBody(node.body);
411
+ },
412
+ TSModuleBlock(node) {
413
+ checkStatementBody(node.body);
414
+ },
415
+ TSTypeLiteral(node) {
416
+ if (!options.betweenTypeMembers) {
417
+ return;
418
+ }
419
+ checkTypeMembers(node.members);
420
+ },
421
+ BlockStatement(node) {
422
+ checkStatementBody(node.body);
423
+ },
424
+ SwitchStatement(node) {
425
+ if (!options.betweenSwitchCases) {
426
+ return;
427
+ }
428
+ for (let index = 1; index < node.cases.length; index += 1) {
429
+ const previous = node.cases[index - 1];
430
+ const next = node.cases[index];
431
+ if (previous === undefined || next === undefined || previous.consequent.length === 0) {
432
+ continue;
433
+ }
434
+ requireBlankLine(previous, next, next);
435
+ }
436
+ },
437
+ TSInterfaceBody(node) {
438
+ if (!options.betweenTypeMembers) {
439
+ return;
440
+ }
441
+ checkTypeMembers(node.body);
442
+ },
443
+ };
444
+ },
445
+ });
446
+ export default blankLine;
@@ -1,5 +1,5 @@
1
1
  import { defineRule } from "vite-plus/lint/plugins";
2
- import { isBlank, isFence, isCodeLike, isDirective, getLineGroupInfos, getCommentLineInfos, } from "./comment-utils.js";
2
+ import { isBlank, isFence, isCodeLike, isDirective, getLineGroupInfos, getCommentLineInfos, } from "./_comment-utils.js";
3
3
  const JAPANESE = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\u30FC\uFF66-\uFF9F]$/u;
4
4
  const ASCII_BOUNDARY = /^[A-Za-z0-9+#@%/&=$~^|:\\`]$/u;
5
5
  const EXCLUDED_PUNCTUATION = new Set(["、", "。", "(", ")", "「", "」"]);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * コメントを 1 行 1 文にします。
3
3
  *
4
- * 複数行のコメントで、文の途中(「、」など)で改行している箇所を検出し、次の行と結合します。文末は「。」「.」「.」「!」「?」などを判定します。
4
+ * 複数行のコメントで、文の途中(「、」など)で改行している箇所を検出し、次の行と結合します。文末は「。」「.」「.」「!」「?」「:」「:」などを判定します。
5
5
  * `requireEndPunctuation` を有効にすると、文末の句読点がない散文行も報告します。
6
6
  */
7
7
  declare const commentOneSentencePerLine: import("vite-plus/lint/plugins").Rule;
@@ -1,5 +1,5 @@
1
1
  import { defineRule } from "vite-plus/lint/plugins";
2
- import { isTag, isBlank, isFence, isCodeLike, isDirective, isLabelLine, isSentenceEnd, isListOrHeading, getLineGroupInfos, getCommentLineInfos, isSkippableProseLine, } from "./comment-utils.js";
2
+ import { isTag, isBlank, isFence, isCodeLike, isDirective, isLabelLine, isSentenceEnd, isListOrHeading, getLineGroupInfos, getCommentLineInfos, isSkippableProseLine, } from "./_comment-utils.js";
3
3
  function needsSpace(left, right) {
4
4
  const leftIsAscii = /[A-Za-z0-9`)]$/u.test(left);
5
5
  const leftIsWord = /[\p{L}\p{N}]$/u.test(left);
@@ -102,7 +102,7 @@ function collectDiagnostics(infos, requireEndPunctuation) {
102
102
  /**
103
103
  * コメントを 1 行 1 文にします。
104
104
  *
105
- * 複数行のコメントで、文の途中(「、」など)で改行している箇所を検出し、次の行と結合します。文末は「。」「.」「.」「!」「?」などを判定します。
105
+ * 複数行のコメントで、文の途中(「、」など)で改行している箇所を検出し、次の行と結合します。文末は「。」「.」「.」「!」「?」「:」「:」などを判定します。
106
106
  * `requireEndPunctuation` を有効にすると、文末の句読点がない散文行も報告します。
107
107
  */
108
108
  const commentOneSentencePerLine = defineRule({
@@ -1,5 +1,5 @@
1
1
  import { defineRule } from "vite-plus/lint/plugins";
2
- import { getNodeJSDoc, getParamName, getJSDocLines, hasExampleOrFence } from "./jsdoc-utils.js";
2
+ import { getNodeJSDoc, getParamName, getJSDocLines, hasExampleOrFence } from "./_jsdoc-utils.js";
3
3
  function getParamNameFromPattern(param) {
4
4
  if (param.type === "Identifier") {
5
5
  return param.name;
@@ -1,5 +1,5 @@
1
1
  import { defineRule } from "vite-plus/lint/plugins";
2
- import { getJSDocLines, hasExampleOrFence } from "./jsdoc-utils.js";
2
+ import { getJSDocLines, hasExampleOrFence } from "./_jsdoc-utils.js";
3
3
  function getBlankPrefix(line) {
4
4
  return line.prefix.trimEnd();
5
5
  }
@@ -1,5 +1,5 @@
1
1
  import { defineRule } from "vite-plus/lint/plugins";
2
- import { getJSDocLines, hasExampleOrFence } from "./jsdoc-utils.js";
2
+ import { getJSDocLines, hasExampleOrFence } from "./_jsdoc-utils.js";
3
3
  const TAG_ORDER = {
4
4
  see: 70,
5
5
  beta: 65,
@@ -1,8 +1,9 @@
1
1
  /**
2
- * プロパティーが 2 つ以上あるオブジェクトリテラルを複数行にします。
2
+ * プロパティーが 2 つ以上あるオブジェクトリテラルと JSON オブジェクトを複数行にします。
3
3
  *
4
4
  * Oxfmt の `objectWrap` には「プロパティー数で改行を強制する」オプションがないため、改行を挿入する最小限の fix を提供します。
5
5
  * `objectWrap: "preserve"` と組み合わせることで、後続の Oxfmt がプロパティーごとの改行とインデントを整えます。
6
+ * JSON (`JSONObjectExpression`) も同じ fix で複数行化します。
6
7
  */
7
8
  declare const objectPropertiesNewline: import("vite-plus/lint/plugins").Rule;
8
9
  export default objectPropertiesNewline;
@@ -1 +1 @@
1
- {"version":3,"file":"object-properties-newline.d.ts","sourceRoot":"","sources":["../../../src/rules/object-properties-newline.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,QAAA,MAAM,uBAAuB,uCAyC3B,CAAC;eAEY,uBAAuB"}
1
+ {"version":3,"file":"object-properties-newline.d.ts","sourceRoot":"","sources":["../../../src/rules/object-properties-newline.ts"],"names":[],"mappings":"AA2EA;;;;;;GAMG;AACH,QAAA,MAAM,uBAAuB,uCAoD3B,CAAC;eAEY,uBAAuB"}
@@ -1,15 +1,35 @@
1
1
  import { defineRule } from "vite-plus/lint/plugins";
2
- import { getSortPlan } from "./object-sort-utils.js";
2
+ import { getSortPlan } from "./_object-sort-utils.js";
3
3
  /**
4
- * プロパティーが 2 つ以上あるオブジェクトリテラルを複数行にします。
4
+ * 複数行にすべき 1 行オブジェクトかどうかを判定します。
5
+ *
6
+ * プロパティーが 2 つ以上あり、最初のプロパティーが `{` と同じ行にある場合に `true` を返します。
7
+ *
8
+ * @param node 判定対象のオブジェクトです。
9
+ *
10
+ * @returns 複数行にすべき場合に `true` です。
11
+ */
12
+ function needsMultiline(node) {
13
+ if (node.properties.length < 2) {
14
+ return false;
15
+ }
16
+ const firstProperty = node.properties[0];
17
+ if (firstProperty === undefined) {
18
+ return false;
19
+ }
20
+ return firstProperty.loc.start.line === node.loc.start.line;
21
+ }
22
+ /**
23
+ * プロパティーが 2 つ以上あるオブジェクトリテラルと JSON オブジェクトを複数行にします。
5
24
  *
6
25
  * Oxfmt の `objectWrap` には「プロパティー数で改行を強制する」オプションがないため、改行を挿入する最小限の fix を提供します。
7
26
  * `objectWrap: "preserve"` と組み合わせることで、後続の Oxfmt がプロパティーごとの改行とインデントを整えます。
27
+ * JSON (`JSONObjectExpression`) も同じ fix で複数行化します。
8
28
  */
9
29
  const objectPropertiesNewline = defineRule({
10
30
  meta: {
11
31
  docs: {
12
- description: "Enforce multiline object literals when they have two or more properties.",
32
+ description: "Enforce multiline objects when they have two or more properties.",
13
33
  },
14
34
  type: "layout",
15
35
  fixable: "whitespace",
@@ -20,16 +40,12 @@ const objectPropertiesNewline = defineRule({
20
40
  create(context) {
21
41
  return {
22
42
  ObjectExpression(node) {
23
- if (node.properties.length < 2) {
24
- return;
25
- }
26
43
  // 並び替え可能なオブジェクトは sort-object-properties が複数行化も担当します。
27
44
  if (getSortPlan(node, context.sourceCode) !== null) {
28
45
  return;
29
46
  }
30
47
  // Oxfmt は `{` の直後に改行があるかどうかで複数行を維持するため、最初のプロパティーが `{` と同じ行にある場合だけを対象にします。
31
- const firstProperty = node.properties[0];
32
- if (firstProperty === undefined || firstProperty.loc.start.line !== node.loc.start.line) {
48
+ if (!needsMultiline(node)) {
33
49
  return;
34
50
  }
35
51
  context.report({
@@ -41,6 +57,21 @@ const objectPropertiesNewline = defineRule({
41
57
  messageId: "multiline",
42
58
  });
43
59
  },
60
+ JSONObjectExpression(node) {
61
+ const target = node;
62
+ if (!needsMultiline(target)) {
63
+ return;
64
+ }
65
+ context.report({
66
+ fix(fixer) {
67
+ // JSON も `objectWrap: "preserve"` で整形されるため、JS と同じく `{` の直後に改行を 1 つ挿入するだけで十分です。
68
+ return fixer.insertTextAfterRange([target.range[0], target.range[0] + 1], "\n");
69
+ },
70
+ // `JSONObjectExpression` は `VisitorObject` の既定の型に含まれないため、報告時は `node` をそのまま渡します。
71
+ node: node,
72
+ messageId: "multiline",
73
+ });
74
+ },
44
75
  };
45
76
  },
46
77
  });