@tai-kun/vp-lint-plugins 0.0.0 → 0.0.1

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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -43
  3. package/dist/src/index.d.ts +22 -0
  4. package/dist/src/index.d.ts.map +1 -0
  5. package/dist/src/index.js +21 -0
  6. package/dist/src/plugin.d.ts +3 -0
  7. package/dist/src/plugin.d.ts.map +1 -0
  8. package/dist/src/plugin.js +46 -0
  9. package/dist/src/rules/blank-line-after-control-block.d.ts +7 -0
  10. package/dist/src/rules/blank-line-after-control-block.d.ts.map +1 -0
  11. package/dist/src/rules/blank-line-after-control-block.js +54 -0
  12. package/dist/src/rules/blank-line-after-declaration.d.ts +7 -0
  13. package/dist/src/rules/blank-line-after-declaration.d.ts.map +1 -0
  14. package/dist/src/rules/blank-line-after-declaration.js +79 -0
  15. package/dist/src/rules/blank-line-after-variable-declaration.d.ts +8 -0
  16. package/dist/src/rules/blank-line-after-variable-declaration.d.ts.map +1 -0
  17. package/dist/src/rules/blank-line-after-variable-declaration.js +62 -0
  18. package/dist/src/rules/blank-line-before-return.d.ts +7 -0
  19. package/dist/src/rules/blank-line-before-return.d.ts.map +1 -0
  20. package/dist/src/rules/blank-line-before-return.js +81 -0
  21. package/dist/src/rules/blank-line-between-class-members.d.ts +8 -0
  22. package/dist/src/rules/blank-line-between-class-members.d.ts.map +1 -0
  23. package/dist/src/rules/blank-line-between-class-members.js +85 -0
  24. package/dist/src/rules/blank-line-between-switch-cases.d.ts +8 -0
  25. package/dist/src/rules/blank-line-between-switch-cases.d.ts.map +1 -0
  26. package/dist/src/rules/blank-line-between-switch-cases.js +45 -0
  27. package/dist/src/rules/blank-line.d.ts +14 -0
  28. package/dist/src/rules/blank-line.d.ts.map +1 -0
  29. package/dist/src/rules/blank-line.js +68 -0
  30. package/dist/src/rules/comment-japanese-spacing.d.ts +8 -0
  31. package/dist/src/rules/comment-japanese-spacing.d.ts.map +1 -0
  32. package/dist/src/rules/comment-japanese-spacing.js +195 -0
  33. package/dist/src/rules/comment-one-sentence-per-line.d.ts +9 -0
  34. package/dist/src/rules/comment-one-sentence-per-line.d.ts.map +1 -0
  35. package/dist/src/rules/comment-one-sentence-per-line.js +190 -0
  36. package/dist/src/rules/comment-utils.d.ts +30 -0
  37. package/dist/src/rules/comment-utils.d.ts.map +1 -0
  38. package/dist/src/rules/comment-utils.js +153 -0
  39. package/dist/src/rules/jsdoc-param-order.d.ts +6 -0
  40. package/dist/src/rules/jsdoc-param-order.d.ts.map +1 -0
  41. package/dist/src/rules/jsdoc-param-order.js +101 -0
  42. package/dist/src/rules/jsdoc-tag-lines.d.ts +6 -0
  43. package/dist/src/rules/jsdoc-tag-lines.d.ts.map +1 -0
  44. package/dist/src/rules/jsdoc-tag-lines.js +128 -0
  45. package/dist/src/rules/jsdoc-tag-order.d.ts +6 -0
  46. package/dist/src/rules/jsdoc-tag-order.d.ts.map +1 -0
  47. package/dist/src/rules/jsdoc-tag-order.js +62 -0
  48. package/dist/src/rules/jsdoc-utils.d.ts +26 -0
  49. package/dist/src/rules/jsdoc-utils.d.ts.map +1 -0
  50. package/dist/src/rules/jsdoc-utils.js +132 -0
  51. package/dist/src/rules/member-ordering.d.ts +6 -0
  52. package/dist/src/rules/member-ordering.d.ts.map +1 -0
  53. package/dist/src/rules/member-ordering.js +66 -0
  54. package/dist/src/rules/object-properties-newline.d.ts +9 -0
  55. package/dist/src/rules/object-properties-newline.d.ts.map +1 -0
  56. package/dist/src/rules/object-properties-newline.js +47 -0
  57. package/dist/src/rules/object-sort-utils.d.ts +32 -0
  58. package/dist/src/rules/object-sort-utils.d.ts.map +1 -0
  59. package/dist/src/rules/object-sort-utils.js +221 -0
  60. package/dist/src/rules/overload-signatures-adjacent.d.ts +6 -0
  61. package/dist/src/rules/overload-signatures-adjacent.d.ts.map +1 -0
  62. package/dist/src/rules/overload-signatures-adjacent.js +143 -0
  63. package/dist/src/rules/sort-import-export-specifiers.d.ts +8 -0
  64. package/dist/src/rules/sort-import-export-specifiers.d.ts.map +1 -0
  65. package/dist/src/rules/sort-import-export-specifiers.js +130 -0
  66. package/dist/src/rules/sort-jsx-props.d.ts +7 -0
  67. package/dist/src/rules/sort-jsx-props.d.ts.map +1 -0
  68. package/dist/src/rules/sort-jsx-props.js +102 -0
  69. package/dist/src/rules/sort-object-properties.d.ts +9 -0
  70. package/dist/src/rules/sort-object-properties.d.ts.map +1 -0
  71. package/dist/src/rules/sort-object-properties.js +54 -0
  72. package/dist/src/rules/sort-type-members.d.ts +8 -0
  73. package/dist/src/rules/sort-type-members.d.ts.map +1 -0
  74. package/dist/src/rules/sort-type-members.js +213 -0
  75. package/dist/src/rules/spaced-comment.d.ts +6 -0
  76. package/dist/src/rules/spaced-comment.d.ts.map +1 -0
  77. package/dist/src/rules/spaced-comment.js +105 -0
  78. package/dist/src/rules/type-literal-properties-newline.d.ts +8 -0
  79. package/dist/src/rules/type-literal-properties-newline.d.ts.map +1 -0
  80. package/dist/src/rules/type-literal-properties-newline.js +39 -0
  81. package/package.json +40 -8
  82. package/src/index.ts +22 -0
  83. package/src/plugin.ts +48 -0
  84. package/src/rules/blank-line-after-control-block.ts +82 -0
  85. package/src/rules/blank-line-after-declaration.ts +106 -0
  86. package/src/rules/blank-line-after-variable-declaration.ts +84 -0
  87. package/src/rules/blank-line-before-return.ts +101 -0
  88. package/src/rules/blank-line-between-class-members.ts +112 -0
  89. package/src/rules/blank-line-between-switch-cases.ts +53 -0
  90. package/src/rules/blank-line.ts +113 -0
  91. package/src/rules/comment-japanese-spacing.ts +271 -0
  92. package/src/rules/comment-one-sentence-per-line.ts +264 -0
  93. package/src/rules/comment-utils.ts +218 -0
  94. package/src/rules/jsdoc-param-order.ts +137 -0
  95. package/src/rules/jsdoc-tag-lines.ts +165 -0
  96. package/src/rules/jsdoc-tag-order.ts +75 -0
  97. package/src/rules/jsdoc-utils.ts +179 -0
  98. package/src/rules/member-ordering.ts +93 -0
  99. package/src/rules/object-properties-newline.ts +54 -0
  100. package/src/rules/object-sort-utils.ts +339 -0
  101. package/src/rules/overload-signatures-adjacent.ts +204 -0
  102. package/src/rules/sort-import-export-specifiers.ts +194 -0
  103. package/src/rules/sort-jsx-props.ts +149 -0
  104. package/src/rules/sort-object-properties.ts +62 -0
  105. package/src/rules/sort-type-members.ts +299 -0
  106. package/src/rules/spaced-comment.ts +129 -0
  107. package/src/rules/type-literal-properties-newline.ts +44 -0
@@ -0,0 +1,271 @@
1
+ import { defineRule } from "vite-plus/lint/plugins";
2
+ import type { Comment, Context } from "vite-plus/lint/plugins";
3
+
4
+ import type { CommentLine } from "./comment-utils.js";
5
+ import {
6
+ isBlank,
7
+ isFence,
8
+ isCodeLike,
9
+ isDirective,
10
+ getLineGroupInfos,
11
+ getCommentLineInfos,
12
+ } from "./comment-utils.js";
13
+
14
+ interface SpacingEdit {
15
+ end: number;
16
+ start: number;
17
+ text: string;
18
+ }
19
+
20
+ interface ProtectedRange {
21
+ end: number;
22
+ start: number;
23
+ }
24
+
25
+ interface ProtectedRanges {
26
+ inlineCode: ProtectedRange[];
27
+ url: ProtectedRange[];
28
+ }
29
+
30
+ const JAPANESE = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\u30FC\uFF66-\uFF9F]$/u;
31
+ const ASCII_BOUNDARY = /^[A-Za-z0-9+#@%/&=$~^|:\\`]$/u;
32
+ const EXCLUDED_PUNCTUATION = new Set(["、", "。", "(", ")", "「", "」"]);
33
+ const URL = /https?:\/\/[!-~]+/gu;
34
+ const INLINE_CODE = /(?<ticks>`+)[\s\S]*?\k<ticks>/gu;
35
+
36
+ function isJapanese(char: string | undefined): boolean {
37
+ return char !== undefined && JAPANESE.test(char);
38
+ }
39
+
40
+ function isAsciiBoundary(char: string | undefined): boolean {
41
+ return char !== undefined && ASCII_BOUNDARY.test(char);
42
+ }
43
+
44
+ function isExcluded(char: string | undefined): boolean {
45
+ return char !== undefined && EXCLUDED_PUNCTUATION.has(char);
46
+ }
47
+
48
+ function isSpacing(char: string): boolean {
49
+ return char === " " || char === "\t" || char === "\u3000";
50
+ }
51
+
52
+ function getProtectedRanges(text: string): ProtectedRanges {
53
+ const inlineCode: ProtectedRange[] = [];
54
+ const url: ProtectedRange[] = [];
55
+
56
+ for (const match of text.matchAll(INLINE_CODE)) {
57
+ const start = match.index;
58
+ const end = start + match[0].length;
59
+
60
+ if (end > start + 2) {
61
+ inlineCode.push({
62
+ end: end - 1,
63
+ start: start + 1,
64
+ });
65
+ }
66
+ }
67
+
68
+ for (const match of text.matchAll(URL)) {
69
+ const start = match.index;
70
+ const end = start + match[0].length;
71
+
72
+ if (end > start + 1) {
73
+ url.push({
74
+ end,
75
+ start,
76
+ });
77
+ }
78
+ }
79
+
80
+ return {
81
+ url,
82
+ inlineCode,
83
+ };
84
+ }
85
+
86
+ function isProtected(offset: number, ranges: ProtectedRanges): boolean {
87
+ return (
88
+ ranges.inlineCode.some((range) => offset >= range.start && offset <= range.end) ||
89
+ ranges.url.some((range) => offset > range.start && offset < range.end)
90
+ );
91
+ }
92
+
93
+ /**
94
+ * 日本語と半角文字の間に半角スペースを 1 つ挿入します。
95
+ */
96
+ function findSpacingEdits(text: string): SpacingEdit[] {
97
+ const edits: SpacingEdit[] = [];
98
+ const protectedRanges = getProtectedRanges(text);
99
+
100
+ for (let index = 0; index < text.length; index += 1) {
101
+ const left = text[index];
102
+
103
+ if (!isJapanese(left) && !isAsciiBoundary(left)) {
104
+ continue;
105
+ }
106
+
107
+ let nextIndex = index + 1;
108
+
109
+ while (nextIndex < text.length && isSpacing(text[nextIndex] ?? "")) {
110
+ nextIndex += 1;
111
+ }
112
+
113
+ const right = text[nextIndex];
114
+ const isBoundary =
115
+ (isJapanese(left) && isAsciiBoundary(right)) || (isAsciiBoundary(left) && isJapanese(right));
116
+
117
+ if (!isBoundary || isExcluded(left) || isExcluded(right)) {
118
+ continue;
119
+ }
120
+
121
+ if (isProtected(index + 1, protectedRanges)) {
122
+ continue;
123
+ }
124
+
125
+ if (nextIndex === index + 1) {
126
+ edits.push({
127
+ end: index + 1,
128
+ text: " ",
129
+ start: index + 1,
130
+ });
131
+ continue;
132
+ }
133
+
134
+ const spacing = text.slice(index + 1, nextIndex);
135
+
136
+ if (spacing !== " ") {
137
+ edits.push({
138
+ end: nextIndex,
139
+ text: " ",
140
+ start: index + 1,
141
+ });
142
+ }
143
+ }
144
+
145
+ return edits;
146
+ }
147
+
148
+ function checkLines(context: Context, infos: readonly (CommentLine | null)[]): void {
149
+ let inExample = false;
150
+ let inFence = false;
151
+
152
+ for (const info of infos) {
153
+ if (info === null) {
154
+ continue;
155
+ }
156
+
157
+ const trimmed = info.content.trim();
158
+
159
+ if (isBlank(trimmed)) {
160
+ continue;
161
+ }
162
+
163
+ if (inExample) {
164
+ if (trimmed.slice(0, 1) === "@") {
165
+ inExample = false;
166
+ } else {
167
+ continue;
168
+ }
169
+ }
170
+
171
+ if (isFence(trimmed)) {
172
+ inFence = !inFence;
173
+ continue;
174
+ }
175
+
176
+ if (inFence) {
177
+ continue;
178
+ }
179
+
180
+ if (trimmed.slice(0, 8) === "@example") {
181
+ inExample = true;
182
+ continue;
183
+ }
184
+
185
+ if (isDirective(trimmed) || isCodeLike(trimmed)) {
186
+ continue;
187
+ }
188
+
189
+ for (const edit of findSpacingEdits(info.content)) {
190
+ context.report({
191
+ fix(fixer) {
192
+ return fixer.replaceTextRange(
193
+ [info.start + edit.start, info.start + edit.end],
194
+ edit.text,
195
+ );
196
+ },
197
+ loc: {
198
+ end: {
199
+ line: info.line,
200
+ column: info.column + edit.end,
201
+ },
202
+ start: {
203
+ line: info.line,
204
+ column: info.column + edit.start,
205
+ },
206
+ },
207
+ messageId: "spacing",
208
+ });
209
+ }
210
+ }
211
+ }
212
+
213
+ /**
214
+ * 日本語と半角文字の間に半角スペースを 1 つ要求します。
215
+ *
216
+ * `、` `。` `(` `)` `「` `」` の前後は対象外にします。
217
+ */
218
+ const commentJapaneseSpacing = defineRule({
219
+ meta: {
220
+ docs: {
221
+ description: "Require a half-width space between Japanese and half-width characters.",
222
+ },
223
+ type: "layout",
224
+ fixable: "whitespace",
225
+ messages: {
226
+ spacing: "日本語と半角文字の間には半角スペースを 1 つ入れてください。",
227
+ },
228
+ },
229
+ create(context) {
230
+ const sourceCode = context.sourceCode;
231
+
232
+ return {
233
+ "Program:exit"(): void {
234
+ const comments = sourceCode.getAllComments();
235
+ let lineGroup: Comment[] = [];
236
+
237
+ function flushLineGroup(): void {
238
+ if (lineGroup.length === 0) {
239
+ return;
240
+ }
241
+
242
+ checkLines(context, getLineGroupInfos(lineGroup, sourceCode));
243
+ lineGroup = [];
244
+ }
245
+
246
+ for (const comment of comments) {
247
+ if (comment.type === "Line") {
248
+ const previous = lineGroup.at(-1);
249
+
250
+ if (previous !== undefined && comment.loc.start.line !== previous.loc.end.line + 1) {
251
+ flushLineGroup();
252
+ }
253
+
254
+ lineGroup.push(comment);
255
+ continue;
256
+ }
257
+
258
+ flushLineGroup();
259
+
260
+ if (comment.type === "Block") {
261
+ checkLines(context, getCommentLineInfos(comment, sourceCode));
262
+ }
263
+ }
264
+
265
+ flushLineGroup();
266
+ },
267
+ };
268
+ },
269
+ });
270
+
271
+ export default commentJapaneseSpacing;
@@ -0,0 +1,264 @@
1
+ import { defineRule } from "vite-plus/lint/plugins";
2
+ import type { Comment, Location } from "vite-plus/lint/plugins";
3
+
4
+ import type { CommentLine } from "./comment-utils.js";
5
+ import {
6
+ isTag,
7
+ isBlank,
8
+ isFence,
9
+ isCodeLike,
10
+ isDirective,
11
+ isLabelLine,
12
+ isSentenceEnd,
13
+ isListOrHeading,
14
+ getLineGroupInfos,
15
+ getCommentLineInfos,
16
+ isSkippableProseLine,
17
+ } from "./comment-utils.js";
18
+
19
+ interface SentenceFix {
20
+ end: number;
21
+ loc: Location;
22
+ start: number;
23
+ text: string;
24
+ }
25
+
26
+ interface CommentDiagnostics {
27
+ fixes: SentenceFix[];
28
+ missingPunctuation: CommentLine[];
29
+ }
30
+
31
+ interface CommentOptions {
32
+ requireEndPunctuation: boolean;
33
+ }
34
+
35
+ function needsSpace(left: string, right: string): boolean {
36
+ const leftIsAscii = /[A-Za-z0-9`)]$/u.test(left);
37
+ const leftIsWord = /[\p{L}\p{N}]$/u.test(left);
38
+ const leftIsOperator = /[+\-*/=<>]$/u.test(left);
39
+ const rightIsAscii = /^[A-Za-z0-9`(]/u.test(right);
40
+
41
+ return (leftIsAscii || leftIsWord || leftIsOperator) && rightIsAscii;
42
+ }
43
+
44
+ function canContinue(current: string, next: CommentLine): boolean {
45
+ if (isBlank(next.content)) {
46
+ return false;
47
+ }
48
+
49
+ const nextText = next.content.trimStart();
50
+
51
+ if (
52
+ isDirective(nextText) ||
53
+ isLabelLine(nextText) ||
54
+ isTag(nextText) ||
55
+ isListOrHeading(nextText) ||
56
+ isCodeLike(nextText)
57
+ ) {
58
+ return false;
59
+ }
60
+
61
+ return !isDirective(current) && !isCodeLike(current);
62
+ }
63
+
64
+ function isObject(value: unknown): value is Record<string, unknown> {
65
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66
+ }
67
+
68
+ function getOptions(options: readonly unknown[]): CommentOptions {
69
+ const option = options[0];
70
+
71
+ return {
72
+ requireEndPunctuation: isObject(option) && option["requireEndPunctuation"] === true,
73
+ };
74
+ }
75
+
76
+ function collectDiagnostics(
77
+ infos: readonly (CommentLine | null)[],
78
+ requireEndPunctuation: boolean,
79
+ ): CommentDiagnostics {
80
+ const fixes: SentenceFix[] = [];
81
+ const missingPunctuation: CommentLine[] = [];
82
+ let inExample = false;
83
+ let inFence = false;
84
+
85
+ for (const [index, info] of infos.entries()) {
86
+ if (info === null) {
87
+ continue;
88
+ }
89
+
90
+ const content = info.content;
91
+ const trimmed = content.trim();
92
+
93
+ if (inExample) {
94
+ if (trimmed.slice(0, 1) === "@") {
95
+ inExample = false;
96
+ } else {
97
+ continue;
98
+ }
99
+ }
100
+
101
+ if (trimmed === "") {
102
+ continue;
103
+ }
104
+
105
+ if (isFence(trimmed)) {
106
+ inFence = !inFence;
107
+ continue;
108
+ }
109
+
110
+ if (inFence) {
111
+ continue;
112
+ }
113
+
114
+ if (trimmed.slice(0, 8) === "@example") {
115
+ inExample = true;
116
+ continue;
117
+ }
118
+
119
+ if (isSkippableProseLine(trimmed)) {
120
+ continue;
121
+ }
122
+
123
+ if (isSentenceEnd(content)) {
124
+ continue;
125
+ }
126
+
127
+ const next = infos[index + 1];
128
+
129
+ if (next !== undefined && next !== null && canContinue(content, next)) {
130
+ const nextLeadingSpace = /^\s*/u.exec(next.content)?.[0] ?? "";
131
+ const separator = nextLeadingSpace === "" && needsSpace(content, next.content) ? " " : "";
132
+
133
+ fixes.push({
134
+ end: next.start,
135
+ loc: {
136
+ end: {
137
+ line: info.line,
138
+ column: info.column + content.length,
139
+ },
140
+ start: {
141
+ line: info.line,
142
+ column: info.column,
143
+ },
144
+ },
145
+ text: separator,
146
+ start: info.end,
147
+ });
148
+ continue;
149
+ }
150
+
151
+ if (requireEndPunctuation) {
152
+ missingPunctuation.push(info);
153
+ }
154
+ }
155
+
156
+ return {
157
+ fixes,
158
+ missingPunctuation,
159
+ };
160
+ }
161
+
162
+ /**
163
+ * コメントを 1 行 1 文にします。
164
+ *
165
+ * 複数行のコメントで、文の途中(「、」など)で改行している箇所を検出し、次の行と結合します。文末は「。」「.」「.」「!」「?」などを判定します。
166
+ * `requireEndPunctuation` を有効にすると、文末の句読点がない散文行も報告します。
167
+ */
168
+ const commentOneSentencePerLine = defineRule({
169
+ meta: {
170
+ docs: {
171
+ description: "Enforce one sentence per comment line.",
172
+ },
173
+ type: "layout",
174
+ schema: [
175
+ {
176
+ type: "object",
177
+ properties: {
178
+ requireEndPunctuation: { type: "boolean" },
179
+ },
180
+ additionalProperties: false,
181
+ },
182
+ ],
183
+ fixable: "whitespace",
184
+ messages: {
185
+ oneSentence: "文の終わり(。..!? など)で改行してください。",
186
+ missingEndPunctuation: "文末に句読点(。..!? など)を付けてください。",
187
+ },
188
+ },
189
+ create(context) {
190
+ const sourceCode = context.sourceCode;
191
+ const { requireEndPunctuation } = getOptions(context.options);
192
+
193
+ function report(infos: readonly (CommentLine | null)[]): void {
194
+ const diagnostics = collectDiagnostics(infos, requireEndPunctuation);
195
+
196
+ for (const fix of diagnostics.fixes) {
197
+ context.report({
198
+ fix(fixer) {
199
+ return fixer.replaceTextRange([fix.start, fix.end], fix.text);
200
+ },
201
+ loc: fix.loc,
202
+ messageId: "oneSentence",
203
+ });
204
+ }
205
+
206
+ for (const info of diagnostics.missingPunctuation) {
207
+ const column = info.column + info.content.trimEnd().length;
208
+
209
+ context.report({
210
+ loc: {
211
+ end: {
212
+ line: info.line,
213
+ column,
214
+ },
215
+ start: {
216
+ line: info.line,
217
+ column,
218
+ },
219
+ },
220
+ messageId: "missingEndPunctuation",
221
+ });
222
+ }
223
+ }
224
+
225
+ return {
226
+ "Program:exit"(): void {
227
+ const comments = sourceCode.getAllComments();
228
+ let lineGroup: Comment[] = [];
229
+
230
+ function flushLineGroup(): void {
231
+ if (lineGroup.length === 0) {
232
+ return;
233
+ }
234
+
235
+ report(getLineGroupInfos(lineGroup, sourceCode));
236
+ lineGroup = [];
237
+ }
238
+
239
+ for (const comment of comments) {
240
+ if (comment.type === "Line") {
241
+ const previous = lineGroup.at(-1);
242
+
243
+ if (previous !== undefined && comment.loc.start.line !== previous.loc.end.line + 1) {
244
+ flushLineGroup();
245
+ }
246
+
247
+ lineGroup.push(comment);
248
+ continue;
249
+ }
250
+
251
+ flushLineGroup();
252
+
253
+ if (comment.type === "Block") {
254
+ report(getCommentLineInfos(comment, sourceCode));
255
+ }
256
+ }
257
+
258
+ flushLineGroup();
259
+ },
260
+ };
261
+ },
262
+ });
263
+
264
+ export default commentOneSentencePerLine;