@tai-kun/vp-lint-plugins 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1 -43
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +21 -0
- package/dist/src/plugin.d.ts +3 -0
- package/dist/src/plugin.d.ts.map +1 -0
- package/dist/src/plugin.js +46 -0
- package/dist/src/rules/blank-line-after-control-block.d.ts +7 -0
- package/dist/src/rules/blank-line-after-control-block.d.ts.map +1 -0
- package/dist/src/rules/blank-line-after-control-block.js +54 -0
- package/dist/src/rules/blank-line-after-declaration.d.ts +7 -0
- package/dist/src/rules/blank-line-after-declaration.d.ts.map +1 -0
- package/dist/src/rules/blank-line-after-declaration.js +79 -0
- package/dist/src/rules/blank-line-after-variable-declaration.d.ts +9 -0
- package/dist/src/rules/blank-line-after-variable-declaration.d.ts.map +1 -0
- package/dist/src/rules/blank-line-after-variable-declaration.js +118 -0
- package/dist/src/rules/blank-line-before-return.d.ts +7 -0
- package/dist/src/rules/blank-line-before-return.d.ts.map +1 -0
- package/dist/src/rules/blank-line-before-return.js +81 -0
- package/dist/src/rules/blank-line-between-class-members.d.ts +8 -0
- package/dist/src/rules/blank-line-between-class-members.d.ts.map +1 -0
- package/dist/src/rules/blank-line-between-class-members.js +85 -0
- package/dist/src/rules/blank-line-between-switch-cases.d.ts +8 -0
- package/dist/src/rules/blank-line-between-switch-cases.d.ts.map +1 -0
- package/dist/src/rules/blank-line-between-switch-cases.js +45 -0
- package/dist/src/rules/blank-line.d.ts +18 -0
- package/dist/src/rules/blank-line.d.ts.map +1 -0
- package/dist/src/rules/blank-line.js +86 -0
- package/dist/src/rules/comment-japanese-spacing.d.ts +8 -0
- package/dist/src/rules/comment-japanese-spacing.d.ts.map +1 -0
- package/dist/src/rules/comment-japanese-spacing.js +195 -0
- package/dist/src/rules/comment-one-sentence-per-line.d.ts +9 -0
- package/dist/src/rules/comment-one-sentence-per-line.d.ts.map +1 -0
- package/dist/src/rules/comment-one-sentence-per-line.js +190 -0
- package/dist/src/rules/comment-utils.d.ts +30 -0
- package/dist/src/rules/comment-utils.d.ts.map +1 -0
- package/dist/src/rules/comment-utils.js +153 -0
- package/dist/src/rules/jsdoc-param-order.d.ts +6 -0
- package/dist/src/rules/jsdoc-param-order.d.ts.map +1 -0
- package/dist/src/rules/jsdoc-param-order.js +101 -0
- package/dist/src/rules/jsdoc-tag-lines.d.ts +6 -0
- package/dist/src/rules/jsdoc-tag-lines.d.ts.map +1 -0
- package/dist/src/rules/jsdoc-tag-lines.js +128 -0
- package/dist/src/rules/jsdoc-tag-order.d.ts +6 -0
- package/dist/src/rules/jsdoc-tag-order.d.ts.map +1 -0
- package/dist/src/rules/jsdoc-tag-order.js +62 -0
- package/dist/src/rules/jsdoc-utils.d.ts +26 -0
- package/dist/src/rules/jsdoc-utils.d.ts.map +1 -0
- package/dist/src/rules/jsdoc-utils.js +132 -0
- package/dist/src/rules/member-ordering.d.ts +6 -0
- package/dist/src/rules/member-ordering.d.ts.map +1 -0
- package/dist/src/rules/member-ordering.js +66 -0
- package/dist/src/rules/object-properties-newline.d.ts +9 -0
- package/dist/src/rules/object-properties-newline.d.ts.map +1 -0
- package/dist/src/rules/object-properties-newline.js +47 -0
- package/dist/src/rules/object-sort-utils.d.ts +32 -0
- package/dist/src/rules/object-sort-utils.d.ts.map +1 -0
- package/dist/src/rules/object-sort-utils.js +221 -0
- package/dist/src/rules/overload-signatures-adjacent.d.ts +6 -0
- package/dist/src/rules/overload-signatures-adjacent.d.ts.map +1 -0
- package/dist/src/rules/overload-signatures-adjacent.js +128 -0
- package/dist/src/rules/sort-import-export-specifiers.d.ts +8 -0
- package/dist/src/rules/sort-import-export-specifiers.d.ts.map +1 -0
- package/dist/src/rules/sort-import-export-specifiers.js +130 -0
- package/dist/src/rules/sort-jsx-props.d.ts +7 -0
- package/dist/src/rules/sort-jsx-props.d.ts.map +1 -0
- package/dist/src/rules/sort-jsx-props.js +102 -0
- package/dist/src/rules/sort-object-properties.d.ts +9 -0
- package/dist/src/rules/sort-object-properties.d.ts.map +1 -0
- package/dist/src/rules/sort-object-properties.js +54 -0
- package/dist/src/rules/sort-type-members.d.ts +8 -0
- package/dist/src/rules/sort-type-members.d.ts.map +1 -0
- package/dist/src/rules/sort-type-members.js +213 -0
- package/dist/src/rules/spaced-comment.d.ts +6 -0
- package/dist/src/rules/spaced-comment.d.ts.map +1 -0
- package/dist/src/rules/spaced-comment.js +105 -0
- package/dist/src/rules/type-literal-properties-newline.d.ts +8 -0
- package/dist/src/rules/type-literal-properties-newline.d.ts.map +1 -0
- package/dist/src/rules/type-literal-properties-newline.js +39 -0
- package/package.json +40 -8
- package/src/index.ts +22 -0
- package/src/plugin.ts +48 -0
- package/src/rules/blank-line-after-control-block.ts +82 -0
- package/src/rules/blank-line-after-declaration.ts +106 -0
- package/src/rules/blank-line-after-variable-declaration.ts +165 -0
- package/src/rules/blank-line-before-return.ts +101 -0
- package/src/rules/blank-line-between-class-members.ts +112 -0
- package/src/rules/blank-line-between-switch-cases.ts +53 -0
- package/src/rules/blank-line.ts +143 -0
- package/src/rules/comment-japanese-spacing.ts +271 -0
- package/src/rules/comment-one-sentence-per-line.ts +263 -0
- package/src/rules/comment-utils.ts +218 -0
- package/src/rules/jsdoc-param-order.ts +137 -0
- package/src/rules/jsdoc-tag-lines.ts +165 -0
- package/src/rules/jsdoc-tag-order.ts +75 -0
- package/src/rules/jsdoc-utils.ts +178 -0
- package/src/rules/member-ordering.ts +93 -0
- package/src/rules/object-properties-newline.ts +54 -0
- package/src/rules/object-sort-utils.ts +338 -0
- package/src/rules/overload-signatures-adjacent.ts +178 -0
- package/src/rules/sort-import-export-specifiers.ts +194 -0
- package/src/rules/sort-jsx-props.ts +148 -0
- package/src/rules/sort-object-properties.ts +62 -0
- package/src/rules/sort-type-members.ts +299 -0
- package/src/rules/spaced-comment.ts +127 -0
- package/src/rules/type-literal-properties-newline.ts +44 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { defineRule } from "vite-plus/lint/plugins";
|
|
2
|
+
import { hasBlankLine, getBlankLineFix, getInsertionTarget } from "./blank-line.js";
|
|
3
|
+
function getKeyName(member) {
|
|
4
|
+
if (member.type === "TSIndexSignature" || member.type === "StaticBlock") {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
if (member.computed) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const key = member.key;
|
|
11
|
+
if (key.type === "Identifier") {
|
|
12
|
+
return key.name;
|
|
13
|
+
}
|
|
14
|
+
if (key.type === "Literal" && (typeof key.value === "string" || typeof key.value === "number")) {
|
|
15
|
+
return String(key.value);
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function isOverload(member) {
|
|
20
|
+
return member.type === "MethodDefinition" && member.value.body === null;
|
|
21
|
+
}
|
|
22
|
+
function isGetterSetterPair(previous, next) {
|
|
23
|
+
if (previous.type !== "MethodDefinition" || next.type !== "MethodDefinition") {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (!((previous.kind === "get" && next.kind === "set") ||
|
|
27
|
+
(previous.kind === "set" && next.kind === "get"))) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const previousName = getKeyName(previous);
|
|
31
|
+
const nextName = getKeyName(next);
|
|
32
|
+
return previousName !== null && previousName === nextName;
|
|
33
|
+
}
|
|
34
|
+
function getMemberStart(member) {
|
|
35
|
+
if (member.type === "TSIndexSignature" || member.type === "StaticBlock") {
|
|
36
|
+
return member;
|
|
37
|
+
}
|
|
38
|
+
return member.decorators[0] ?? member;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* クラスメンバーの間に空行を 1 行以上確保します。
|
|
42
|
+
*
|
|
43
|
+
* オーバーロード署名と getter/setter のペアは連続させます。
|
|
44
|
+
*/
|
|
45
|
+
const blankLineBetweenClassMembers = defineRule({
|
|
46
|
+
meta: {
|
|
47
|
+
docs: {
|
|
48
|
+
description: "Require a blank line between class members.",
|
|
49
|
+
},
|
|
50
|
+
type: "layout",
|
|
51
|
+
fixable: "whitespace",
|
|
52
|
+
messages: {
|
|
53
|
+
blankLine: "Expected a blank line between class members.",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
create(context) {
|
|
57
|
+
const sourceCode = context.sourceCode;
|
|
58
|
+
return {
|
|
59
|
+
ClassBody(node) {
|
|
60
|
+
for (let index = 1; index < node.body.length; index += 1) {
|
|
61
|
+
const previous = node.body[index - 1];
|
|
62
|
+
const next = node.body[index];
|
|
63
|
+
if (previous === undefined || next === undefined) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (isOverload(previous) || isGetterSetterPair(previous, next)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const target = getInsertionTarget(previous, getMemberStart(next), sourceCode);
|
|
70
|
+
if (hasBlankLine(previous, target)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
context.report({
|
|
74
|
+
fix(fixer) {
|
|
75
|
+
return getBlankLineFix(fixer, sourceCode, previous, target);
|
|
76
|
+
},
|
|
77
|
+
node: next,
|
|
78
|
+
messageId: "blankLine",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
export default blankLineBetweenClassMembers;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 空でない switch の case の間に空行を 1 行以上確保します。
|
|
3
|
+
*
|
|
4
|
+
* 連続する case はフォールスルーとみなし、対象外にします。
|
|
5
|
+
*/
|
|
6
|
+
declare const blankLineBetweenSwitchCases: import("vite-plus/lint/plugins").Rule;
|
|
7
|
+
export default blankLineBetweenSwitchCases;
|
|
8
|
+
//# sourceMappingURL=blank-line-between-switch-cases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blank-line-between-switch-cases.d.ts","sourceRoot":"","sources":["../../../src/rules/blank-line-between-switch-cases.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,QAAA,MAAM,2BAA2B,uCAyC/B,CAAC;eAEY,2BAA2B"}
|
|
@@ -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,18 @@
|
|
|
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
|
+
/**
|
|
14
|
+
* 指定したノードと対象の間の空行を削除します。
|
|
15
|
+
*/
|
|
16
|
+
declare function getRemoveBlankLineFix(fixer: Fixer, sourceCode: SourceCode, node: ESTree.Node, target: ESTree.Node | Comment): Fix | null;
|
|
17
|
+
export { hasBlankLine, getBlankLineFix, getNextStatement, getInsertionTarget, getPreviousStatement, getRemoveBlankLineFix, };
|
|
18
|
+
//# 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;;GAEG;AACH,iBAAS,qBAAqB,CAC5B,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,CAmBZ;AAED,OAAO,EACL,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,GACtB,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
/**
|
|
69
|
+
* 指定したノードと対象の間の空行を削除します。
|
|
70
|
+
*/
|
|
71
|
+
function getRemoveBlankLineFix(fixer, sourceCode, node, target) {
|
|
72
|
+
if (target.loc.start.line <= node.loc.end.line + 1) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const fromLineEnd = sourceCode.text.indexOf("\n", node.range[1]);
|
|
76
|
+
if (fromLineEnd === -1) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
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;
|
|
83
|
+
}
|
|
84
|
+
return fixer.replaceTextRange([fromLineEnd, target.range[0]], `\n${indent}`);
|
|
85
|
+
}
|
|
86
|
+
export { hasBlankLine, getBlankLineFix, getNextStatement, getInsertionTarget, getPreviousStatement, getRemoveBlankLineFix, };
|
|
@@ -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,uCA6F7B,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;
|