@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,45 @@
1
+ import { defineRule } from "vite-plus/lint/plugins";
2
+ import { hasBlankLine, getBlankLineFix, getInsertionTarget } from "./blank-line.js";
3
+ /**
4
+ * 空でない switch の case の間に空行を 1 行以上確保します。
5
+ *
6
+ * 連続する case はフォールスルーとみなし、対象外にします。
7
+ */
8
+ const blankLineBetweenSwitchCases = defineRule({
9
+ meta: {
10
+ docs: {
11
+ description: "Require a blank line between non-empty switch cases.",
12
+ },
13
+ type: "layout",
14
+ fixable: "whitespace",
15
+ messages: {
16
+ blankLine: "Expected a blank line between switch cases.",
17
+ },
18
+ },
19
+ create(context) {
20
+ const sourceCode = context.sourceCode;
21
+ return {
22
+ SwitchStatement(node) {
23
+ for (let index = 1; index < node.cases.length; index += 1) {
24
+ const previous = node.cases[index - 1];
25
+ const next = node.cases[index];
26
+ if (previous === undefined || next === undefined || previous.consequent.length === 0) {
27
+ continue;
28
+ }
29
+ const target = getInsertionTarget(previous, next, sourceCode);
30
+ if (hasBlankLine(previous, target)) {
31
+ continue;
32
+ }
33
+ context.report({
34
+ fix(fixer) {
35
+ return getBlankLineFix(fixer, sourceCode, previous, target);
36
+ },
37
+ node: next,
38
+ messageId: "blankLine",
39
+ });
40
+ }
41
+ },
42
+ };
43
+ },
44
+ });
45
+ export default blankLineBetweenSwitchCases;
@@ -0,0 +1,14 @@
1
+ import type { Fix, Fixer, ESTree, Comment, SourceCode } from "vite-plus/lint/plugins";
2
+ declare function getNextStatement(node: ESTree.Node): ESTree.Node | null;
3
+ declare function getPreviousStatement(node: ESTree.Node): ESTree.Node | null;
4
+ /**
5
+ * 空行の挿入位置を返します。
6
+ *
7
+ * 次の文に付くコメントがある場合は、そのコメントの前を挿入位置にします。
8
+ * 同じ行の末尾コメントは対象外です。
9
+ */
10
+ declare function getInsertionTarget(node: ESTree.Node, nextStatement: ESTree.Node, sourceCode: SourceCode): ESTree.Node | Comment;
11
+ declare function hasBlankLine(node: ESTree.Node, target: ESTree.Node | Comment): boolean;
12
+ declare function getBlankLineFix(fixer: Fixer, sourceCode: SourceCode, node: ESTree.Node, target: ESTree.Node | Comment): Fix | null;
13
+ export { hasBlankLine, getBlankLineFix, getNextStatement, getInsertionTarget, getPreviousStatement, };
14
+ //# sourceMappingURL=blank-line.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blank-line.d.ts","sourceRoot":"","sources":["../../../src/rules/blank-line.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AA4BtF,iBAAS,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,IAAI,CAc/D;AAED,iBAAS,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,IAAI,CAcnE;AAED;;;;;GAKG;AACH,iBAAS,kBAAkB,CACzB,IAAI,EAAE,MAAM,CAAC,IAAI,EACjB,aAAa,EAAE,MAAM,CAAC,IAAI,EAC1B,UAAU,EAAE,UAAU,GACrB,MAAM,CAAC,IAAI,GAAG,OAAO,CAWvB;AAED,iBAAS,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,GAAG,OAAO,CAE/E;AAED,iBAAS,eAAe,CACtB,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,CAAC,IAAI,EACjB,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,GAC5B,GAAG,GAAG,IAAI,CAYZ;AAED,OAAO,EACL,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,GACrB,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * 文の配列を持つ親から、その文が属する配列を取得します。
3
+ */
4
+ function getStatementBody(node) {
5
+ const { parent } = node;
6
+ if (parent === null) {
7
+ return null;
8
+ }
9
+ if (parent.type === "Program" ||
10
+ parent.type === "BlockStatement" ||
11
+ parent.type === "StaticBlock" ||
12
+ parent.type === "TSModuleBlock") {
13
+ return parent.body;
14
+ }
15
+ if (parent.type === "SwitchCase") {
16
+ return parent.consequent;
17
+ }
18
+ return null;
19
+ }
20
+ function getNextStatement(node) {
21
+ const body = getStatementBody(node);
22
+ if (body === null) {
23
+ return null;
24
+ }
25
+ const index = body.indexOf(node);
26
+ if (index === -1 || index === body.length - 1) {
27
+ return null;
28
+ }
29
+ return body[index + 1] ?? null;
30
+ }
31
+ function getPreviousStatement(node) {
32
+ const body = getStatementBody(node);
33
+ if (body === null) {
34
+ return null;
35
+ }
36
+ const index = body.indexOf(node);
37
+ if (index <= 0) {
38
+ return null;
39
+ }
40
+ return body[index - 1] ?? null;
41
+ }
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);
54
+ }
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");
61
+ }
62
+ const lineEnd = sourceCode.text.indexOf("\n", node.range[1]);
63
+ if (lineEnd === -1) {
64
+ return null;
65
+ }
66
+ return fixer.insertTextBeforeRange([lineEnd, lineEnd + 1], "\n");
67
+ }
68
+ export { hasBlankLine, getBlankLineFix, getNextStatement, getInsertionTarget, getPreviousStatement, };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 日本語と半角文字の間に半角スペースを 1 つ要求します。
3
+ *
4
+ * `、` `。` `(` `)` `「` `」` の前後は対象外にします。
5
+ */
6
+ declare const commentJapaneseSpacing: import("vite-plus/lint/plugins").Rule;
7
+ export default commentJapaneseSpacing;
8
+ //# sourceMappingURL=comment-japanese-spacing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-japanese-spacing.d.ts","sourceRoot":"","sources":["../../../src/rules/comment-japanese-spacing.ts"],"names":[],"mappings":"AAoNA;;;;GAIG;AACH,QAAA,MAAM,sBAAsB,uCAmD1B,CAAC;eAEY,sBAAsB"}
@@ -0,0 +1,195 @@
1
+ import { defineRule } from "vite-plus/lint/plugins";
2
+ import { isBlank, isFence, isCodeLike, isDirective, getLineGroupInfos, getCommentLineInfos, } from "./comment-utils.js";
3
+ const JAPANESE = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\u30FC\uFF66-\uFF9F]$/u;
4
+ const ASCII_BOUNDARY = /^[A-Za-z0-9+#@%/&=$~^|:\\`]$/u;
5
+ const EXCLUDED_PUNCTUATION = new Set(["、", "。", "(", ")", "「", "」"]);
6
+ const URL = /https?:\/\/[!-~]+/gu;
7
+ const INLINE_CODE = /(?<ticks>`+)[\s\S]*?\k<ticks>/gu;
8
+ function isJapanese(char) {
9
+ return char !== undefined && JAPANESE.test(char);
10
+ }
11
+ function isAsciiBoundary(char) {
12
+ return char !== undefined && ASCII_BOUNDARY.test(char);
13
+ }
14
+ function isExcluded(char) {
15
+ return char !== undefined && EXCLUDED_PUNCTUATION.has(char);
16
+ }
17
+ function isSpacing(char) {
18
+ return char === " " || char === "\t" || char === "\u3000";
19
+ }
20
+ function getProtectedRanges(text) {
21
+ const inlineCode = [];
22
+ const url = [];
23
+ for (const match of text.matchAll(INLINE_CODE)) {
24
+ const start = match.index;
25
+ const end = start + match[0].length;
26
+ if (end > start + 2) {
27
+ inlineCode.push({
28
+ end: end - 1,
29
+ start: start + 1,
30
+ });
31
+ }
32
+ }
33
+ for (const match of text.matchAll(URL)) {
34
+ const start = match.index;
35
+ const end = start + match[0].length;
36
+ if (end > start + 1) {
37
+ url.push({
38
+ end,
39
+ start,
40
+ });
41
+ }
42
+ }
43
+ return {
44
+ url,
45
+ inlineCode,
46
+ };
47
+ }
48
+ function isProtected(offset, ranges) {
49
+ return (ranges.inlineCode.some((range) => offset >= range.start && offset <= range.end) ||
50
+ ranges.url.some((range) => offset > range.start && offset < range.end));
51
+ }
52
+ /**
53
+ * 日本語と半角文字の間に半角スペースを 1 つ挿入します。
54
+ */
55
+ function findSpacingEdits(text) {
56
+ const edits = [];
57
+ const protectedRanges = getProtectedRanges(text);
58
+ for (let index = 0; index < text.length; index += 1) {
59
+ const left = text[index];
60
+ if (!isJapanese(left) && !isAsciiBoundary(left)) {
61
+ continue;
62
+ }
63
+ let nextIndex = index + 1;
64
+ while (nextIndex < text.length && isSpacing(text[nextIndex] ?? "")) {
65
+ nextIndex += 1;
66
+ }
67
+ const right = text[nextIndex];
68
+ const isBoundary = (isJapanese(left) && isAsciiBoundary(right)) || (isAsciiBoundary(left) && isJapanese(right));
69
+ if (!isBoundary || isExcluded(left) || isExcluded(right)) {
70
+ continue;
71
+ }
72
+ if (isProtected(index + 1, protectedRanges)) {
73
+ continue;
74
+ }
75
+ if (nextIndex === index + 1) {
76
+ edits.push({
77
+ end: index + 1,
78
+ text: " ",
79
+ start: index + 1,
80
+ });
81
+ continue;
82
+ }
83
+ const spacing = text.slice(index + 1, nextIndex);
84
+ if (spacing !== " ") {
85
+ edits.push({
86
+ end: nextIndex,
87
+ text: " ",
88
+ start: index + 1,
89
+ });
90
+ }
91
+ }
92
+ return edits;
93
+ }
94
+ function checkLines(context, infos) {
95
+ let inExample = false;
96
+ let inFence = false;
97
+ for (const info of infos) {
98
+ if (info === null) {
99
+ continue;
100
+ }
101
+ const trimmed = info.content.trim();
102
+ if (isBlank(trimmed)) {
103
+ continue;
104
+ }
105
+ if (inExample) {
106
+ if (trimmed.slice(0, 1) === "@") {
107
+ inExample = false;
108
+ }
109
+ else {
110
+ continue;
111
+ }
112
+ }
113
+ if (isFence(trimmed)) {
114
+ inFence = !inFence;
115
+ continue;
116
+ }
117
+ if (inFence) {
118
+ continue;
119
+ }
120
+ if (trimmed.slice(0, 8) === "@example") {
121
+ inExample = true;
122
+ continue;
123
+ }
124
+ if (isDirective(trimmed) || isCodeLike(trimmed)) {
125
+ continue;
126
+ }
127
+ for (const edit of findSpacingEdits(info.content)) {
128
+ context.report({
129
+ fix(fixer) {
130
+ return fixer.replaceTextRange([info.start + edit.start, info.start + edit.end], edit.text);
131
+ },
132
+ loc: {
133
+ end: {
134
+ line: info.line,
135
+ column: info.column + edit.end,
136
+ },
137
+ start: {
138
+ line: info.line,
139
+ column: info.column + edit.start,
140
+ },
141
+ },
142
+ messageId: "spacing",
143
+ });
144
+ }
145
+ }
146
+ }
147
+ /**
148
+ * 日本語と半角文字の間に半角スペースを 1 つ要求します。
149
+ *
150
+ * `、` `。` `(` `)` `「` `」` の前後は対象外にします。
151
+ */
152
+ const commentJapaneseSpacing = defineRule({
153
+ meta: {
154
+ docs: {
155
+ description: "Require a half-width space between Japanese and half-width characters.",
156
+ },
157
+ type: "layout",
158
+ fixable: "whitespace",
159
+ messages: {
160
+ spacing: "日本語と半角文字の間には半角スペースを 1 つ入れてください。",
161
+ },
162
+ },
163
+ create(context) {
164
+ const sourceCode = context.sourceCode;
165
+ return {
166
+ "Program:exit"() {
167
+ const comments = sourceCode.getAllComments();
168
+ let lineGroup = [];
169
+ function flushLineGroup() {
170
+ if (lineGroup.length === 0) {
171
+ return;
172
+ }
173
+ checkLines(context, getLineGroupInfos(lineGroup, sourceCode));
174
+ lineGroup = [];
175
+ }
176
+ for (const comment of comments) {
177
+ if (comment.type === "Line") {
178
+ const previous = lineGroup.at(-1);
179
+ if (previous !== undefined && comment.loc.start.line !== previous.loc.end.line + 1) {
180
+ flushLineGroup();
181
+ }
182
+ lineGroup.push(comment);
183
+ continue;
184
+ }
185
+ flushLineGroup();
186
+ if (comment.type === "Block") {
187
+ checkLines(context, getCommentLineInfos(comment, sourceCode));
188
+ }
189
+ }
190
+ flushLineGroup();
191
+ },
192
+ };
193
+ },
194
+ });
195
+ export default commentJapaneseSpacing;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * コメントを 1 行 1 文にします。
3
+ *
4
+ * 複数行のコメントで、文の途中(「、」など)で改行している箇所を検出し、次の行と結合します。文末は「。」「.」「.」「!」「?」などを判定します。
5
+ * `requireEndPunctuation` を有効にすると、文末の句読点がない散文行も報告します。
6
+ */
7
+ declare const commentOneSentencePerLine: import("vite-plus/lint/plugins").Rule;
8
+ export default commentOneSentencePerLine;
9
+ //# sourceMappingURL=comment-one-sentence-per-line.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-one-sentence-per-line.d.ts","sourceRoot":"","sources":["../../../src/rules/comment-one-sentence-per-line.ts"],"names":[],"mappings":"AAiKA;;;;;GAKG;AACH,QAAA,MAAM,yBAAyB,uCA8F7B,CAAC;eAEY,yBAAyB"}
@@ -0,0 +1,190 @@
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";
3
+ function needsSpace(left, right) {
4
+ const leftIsAscii = /[A-Za-z0-9`)]$/u.test(left);
5
+ const leftIsWord = /[\p{L}\p{N}]$/u.test(left);
6
+ const leftIsOperator = /[+\-*/=<>]$/u.test(left);
7
+ const rightIsAscii = /^[A-Za-z0-9`(]/u.test(right);
8
+ return (leftIsAscii || leftIsWord || leftIsOperator) && rightIsAscii;
9
+ }
10
+ function canContinue(current, next) {
11
+ if (isBlank(next.content)) {
12
+ return false;
13
+ }
14
+ const nextText = next.content.trimStart();
15
+ if (isDirective(nextText) ||
16
+ isLabelLine(nextText) ||
17
+ isTag(nextText) ||
18
+ isListOrHeading(nextText) ||
19
+ isCodeLike(nextText)) {
20
+ return false;
21
+ }
22
+ return !isDirective(current) && !isCodeLike(current);
23
+ }
24
+ function isObject(value) {
25
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26
+ }
27
+ function getOptions(options) {
28
+ const option = options[0];
29
+ return {
30
+ requireEndPunctuation: isObject(option) && option["requireEndPunctuation"] === true,
31
+ };
32
+ }
33
+ function collectDiagnostics(infos, requireEndPunctuation) {
34
+ const fixes = [];
35
+ const missingPunctuation = [];
36
+ let inExample = false;
37
+ let inFence = false;
38
+ for (const [index, info] of infos.entries()) {
39
+ if (info === null) {
40
+ continue;
41
+ }
42
+ const content = info.content;
43
+ const trimmed = content.trim();
44
+ if (inExample) {
45
+ if (trimmed.slice(0, 1) === "@") {
46
+ inExample = false;
47
+ }
48
+ else {
49
+ continue;
50
+ }
51
+ }
52
+ if (trimmed === "") {
53
+ continue;
54
+ }
55
+ if (isFence(trimmed)) {
56
+ inFence = !inFence;
57
+ continue;
58
+ }
59
+ if (inFence) {
60
+ continue;
61
+ }
62
+ if (trimmed.slice(0, 8) === "@example") {
63
+ inExample = true;
64
+ continue;
65
+ }
66
+ if (isSkippableProseLine(trimmed)) {
67
+ continue;
68
+ }
69
+ if (isSentenceEnd(content)) {
70
+ continue;
71
+ }
72
+ const next = infos[index + 1];
73
+ if (next !== undefined && next !== null && canContinue(content, next)) {
74
+ const nextLeadingSpace = /^\s*/u.exec(next.content)?.[0] ?? "";
75
+ const separator = nextLeadingSpace === "" && needsSpace(content, next.content) ? " " : "";
76
+ fixes.push({
77
+ end: next.start,
78
+ loc: {
79
+ end: {
80
+ line: info.line,
81
+ column: info.column + content.length,
82
+ },
83
+ start: {
84
+ line: info.line,
85
+ column: info.column,
86
+ },
87
+ },
88
+ text: separator,
89
+ start: info.end,
90
+ });
91
+ continue;
92
+ }
93
+ if (requireEndPunctuation) {
94
+ missingPunctuation.push(info);
95
+ }
96
+ }
97
+ return {
98
+ fixes,
99
+ missingPunctuation,
100
+ };
101
+ }
102
+ /**
103
+ * コメントを 1 行 1 文にします。
104
+ *
105
+ * 複数行のコメントで、文の途中(「、」など)で改行している箇所を検出し、次の行と結合します。文末は「。」「.」「.」「!」「?」などを判定します。
106
+ * `requireEndPunctuation` を有効にすると、文末の句読点がない散文行も報告します。
107
+ */
108
+ const commentOneSentencePerLine = defineRule({
109
+ meta: {
110
+ docs: {
111
+ description: "Enforce one sentence per comment line.",
112
+ },
113
+ type: "layout",
114
+ schema: [
115
+ {
116
+ type: "object",
117
+ properties: {
118
+ requireEndPunctuation: { type: "boolean" },
119
+ },
120
+ additionalProperties: false,
121
+ },
122
+ ],
123
+ fixable: "whitespace",
124
+ messages: {
125
+ oneSentence: "文の終わり(。..!? など)で改行してください。",
126
+ missingEndPunctuation: "文末に句読点(。..!? など)を付けてください。",
127
+ },
128
+ },
129
+ create(context) {
130
+ const sourceCode = context.sourceCode;
131
+ const { requireEndPunctuation } = getOptions(context.options);
132
+ function report(infos) {
133
+ const diagnostics = collectDiagnostics(infos, requireEndPunctuation);
134
+ for (const fix of diagnostics.fixes) {
135
+ context.report({
136
+ fix(fixer) {
137
+ return fixer.replaceTextRange([fix.start, fix.end], fix.text);
138
+ },
139
+ loc: fix.loc,
140
+ messageId: "oneSentence",
141
+ });
142
+ }
143
+ for (const info of diagnostics.missingPunctuation) {
144
+ const column = info.column + info.content.trimEnd().length;
145
+ context.report({
146
+ loc: {
147
+ end: {
148
+ line: info.line,
149
+ column,
150
+ },
151
+ start: {
152
+ line: info.line,
153
+ column,
154
+ },
155
+ },
156
+ messageId: "missingEndPunctuation",
157
+ });
158
+ }
159
+ }
160
+ return {
161
+ "Program:exit"() {
162
+ const comments = sourceCode.getAllComments();
163
+ let lineGroup = [];
164
+ function flushLineGroup() {
165
+ if (lineGroup.length === 0) {
166
+ return;
167
+ }
168
+ report(getLineGroupInfos(lineGroup, sourceCode));
169
+ lineGroup = [];
170
+ }
171
+ for (const comment of comments) {
172
+ if (comment.type === "Line") {
173
+ const previous = lineGroup.at(-1);
174
+ if (previous !== undefined && comment.loc.start.line !== previous.loc.end.line + 1) {
175
+ flushLineGroup();
176
+ }
177
+ lineGroup.push(comment);
178
+ continue;
179
+ }
180
+ flushLineGroup();
181
+ if (comment.type === "Block") {
182
+ report(getCommentLineInfos(comment, sourceCode));
183
+ }
184
+ }
185
+ flushLineGroup();
186
+ },
187
+ };
188
+ },
189
+ });
190
+ export default commentOneSentencePerLine;
@@ -0,0 +1,30 @@
1
+ import type { Comment, SourceCode } from "vite-plus/lint/plugins";
2
+ interface CommentLine {
3
+ column: number;
4
+ content: string;
5
+ end: number;
6
+ line: number;
7
+ start: number;
8
+ }
9
+ declare function isBlank(text: string): boolean;
10
+ declare function isDirective(text: string): boolean;
11
+ declare function isLabelLine(text: string): boolean;
12
+ declare function isTag(text: string): boolean;
13
+ declare function isListOrHeading(text: string): boolean;
14
+ declare function isCodeLike(text: string): boolean;
15
+ declare function isFence(text: string): boolean;
16
+ declare function isSentenceEnd(text: string): boolean;
17
+ /**
18
+ * 散文として扱わない行かどうかを返します。
19
+ */
20
+ declare function isSkippableProseLine(text: string): boolean;
21
+ declare function getLineGroupInfos(group: readonly Comment[], sourceCode: SourceCode): CommentLine[];
22
+ /**
23
+ * コメントの内容行を返します。
24
+ *
25
+ * ブロックコメントで `*` の付かない行がある場合は、その行を null として返します。
26
+ */
27
+ declare function getCommentLineInfos(comment: Comment, sourceCode: SourceCode): (CommentLine | null)[];
28
+ export type { CommentLine };
29
+ export { isTag, isBlank, isFence, isCodeLike, isDirective, isLabelLine, isSentenceEnd, isListOrHeading, getLineGroupInfos, getCommentLineInfos, isSkippableProseLine, };
30
+ //# sourceMappingURL=comment-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-utils.d.ts","sourceRoot":"","sources":["../../../src/rules/comment-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAElE,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AA4BD,iBAAS,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtC;AAED,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI1C;AAED,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI1C;AAED,iBAAS,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpC;AAED,iBAAS,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQzC;AAED,iBAAS,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAItC;AAED,iBAAS,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI5C;AAED;;GAEG;AACH,iBAAS,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAcnD;AAiDD,iBAAS,iBAAiB,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,UAAU,EAAE,UAAU,GAAG,WAAW,EAAE,CAe3F;AAiBD;;;;GAIG;AACH,iBAAS,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,CAgB7F;AAED,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,OAAO,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,UAAU,EACV,WAAW,EACX,WAAW,EACX,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,GACrB,CAAC"}