@tai-kun/vp-lint-plugins 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +2 -2
- 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 +8 -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 +62 -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 +14 -0
- package/dist/src/rules/blank-line.d.ts.map +1 -0
- package/dist/src/rules/blank-line.js +68 -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 +143 -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 +39 -3
- 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 +84 -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 +113 -0
- package/src/rules/comment-japanese-spacing.ts +271 -0
- package/src/rules/comment-one-sentence-per-line.ts +264 -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 +179 -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 +339 -0
- package/src/rules/overload-signatures-adjacent.ts +204 -0
- package/src/rules/sort-import-export-specifiers.ts +194 -0
- package/src/rules/sort-jsx-props.ts +149 -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 +129 -0
- package/src/rules/type-literal-properties-newline.ts +44 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
function getLineStarts(text) {
|
|
2
|
+
const starts = [0];
|
|
3
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
4
|
+
if (text[index] === "\n") {
|
|
5
|
+
starts.push(index + 1);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return starts;
|
|
9
|
+
}
|
|
10
|
+
function getLineIndent(text, lineStarts, line) {
|
|
11
|
+
const start = lineStarts[line - 1] ?? 0;
|
|
12
|
+
const match = /^[ \t]*/u.exec(text.slice(start));
|
|
13
|
+
return match?.[0] ?? "";
|
|
14
|
+
}
|
|
15
|
+
function getPropertyName(property) {
|
|
16
|
+
if (property.type !== "Property" || property.computed || property.kind !== "init") {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const key = property.key;
|
|
20
|
+
let name;
|
|
21
|
+
if (key.type === "Identifier") {
|
|
22
|
+
name = key.name;
|
|
23
|
+
}
|
|
24
|
+
else if (key.type === "Literal" &&
|
|
25
|
+
(typeof key.value === "string" || typeof key.value === "number")) {
|
|
26
|
+
name = String(key.value);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return name === "__proto__" ? null : name;
|
|
32
|
+
}
|
|
33
|
+
function isSortableObject(node, allowRest) {
|
|
34
|
+
if (node.properties.length < 2) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const names = new Set();
|
|
38
|
+
for (const [index, property] of node.properties.entries()) {
|
|
39
|
+
if (property.type === "RestElement") {
|
|
40
|
+
if (!allowRest || index !== node.properties.length - 1) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const name = getPropertyName(property);
|
|
46
|
+
if (name === null || names.has(name)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
names.add(name);
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
function compareNames(left, right) {
|
|
54
|
+
if (left.length !== right.length) {
|
|
55
|
+
return left.length - right.length;
|
|
56
|
+
}
|
|
57
|
+
const leftLower = left.toLowerCase();
|
|
58
|
+
const rightLower = right.toLowerCase();
|
|
59
|
+
if (leftLower !== rightLower) {
|
|
60
|
+
return leftLower < rightLower ? -1 : 1;
|
|
61
|
+
}
|
|
62
|
+
if (left === right) {
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
return left < right ? -1 : 1;
|
|
66
|
+
}
|
|
67
|
+
function compareUnits(left, right) {
|
|
68
|
+
return compareNames(left.name, right.name);
|
|
69
|
+
}
|
|
70
|
+
function sortGroup(group) {
|
|
71
|
+
const sortable = group.filter((unit) => unit.name !== null);
|
|
72
|
+
sortable.sort(compareUnits);
|
|
73
|
+
const rest = group.filter((unit) => unit.name === null);
|
|
74
|
+
return [...sortable, ...rest];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* オブジェクトリテラル/オブジェクトパターンの並び替え計画を返します。
|
|
78
|
+
*
|
|
79
|
+
* - プロパティー名の長さが短い順、同じ長さなら名前順
|
|
80
|
+
* - 空行で区切られたグループ内だけで並び替え
|
|
81
|
+
* - プロパティー直上のコメントはプロパティーと一緒に移動
|
|
82
|
+
*
|
|
83
|
+
* 並び替えが安全でない場合は null を返します。
|
|
84
|
+
*/
|
|
85
|
+
function getSortPlan(node, sourceCode, options = {}) {
|
|
86
|
+
const { allowRest = false, forceMultiline = true } = options;
|
|
87
|
+
if (!isSortableObject(node, allowRest)) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const source = sourceCode.text;
|
|
91
|
+
const lineStarts = getLineStarts(source);
|
|
92
|
+
const lineIndent = getLineIndent(source, lineStarts, node.loc.start.line);
|
|
93
|
+
const comments = sourceCode
|
|
94
|
+
.getAllComments()
|
|
95
|
+
.filter((comment) => comment.range[0] >= node.range[0] && comment.range[1] <= node.range[1]);
|
|
96
|
+
const firstProperty = node.properties[0];
|
|
97
|
+
if (firstProperty === undefined) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const inlineStart = firstProperty.loc.start.line === node.loc.start.line;
|
|
101
|
+
const singleLine = node.loc.start.line === node.loc.end.line;
|
|
102
|
+
const units = [];
|
|
103
|
+
if (inlineStart) {
|
|
104
|
+
if (comments.length > 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
for (const property of node.properties) {
|
|
108
|
+
const isRest = property.type === "RestElement";
|
|
109
|
+
units.push({
|
|
110
|
+
name: isRest ? null : getPropertyName(property),
|
|
111
|
+
text: source.slice(property.range[0], property.range[1]),
|
|
112
|
+
endLine: property.loc.end.line,
|
|
113
|
+
property,
|
|
114
|
+
startLine: property.loc.start.line,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
let previousProperty = null;
|
|
120
|
+
for (const property of node.properties) {
|
|
121
|
+
const previous = previousProperty;
|
|
122
|
+
const boundary = previous === null ? node.range[0] + 1 : previous.range[1];
|
|
123
|
+
const candidates = comments.filter((comment) => comment.range[0] >= boundary && comment.range[1] <= property.range[0]);
|
|
124
|
+
if (previous !== null &&
|
|
125
|
+
comments.some((comment) => comment.loc.start.line === previous.loc.end.line &&
|
|
126
|
+
comment.range[0] >= previous.range[1] &&
|
|
127
|
+
comment.range[1] <= property.range[0])) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const attached = [];
|
|
131
|
+
let expectedLine = property.loc.start.line;
|
|
132
|
+
for (let index = candidates.length - 1; index >= 0; index -= 1) {
|
|
133
|
+
const comment = candidates[index];
|
|
134
|
+
if (comment === undefined) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (comment.loc.end.line !== expectedLine && comment.loc.end.line !== expectedLine - 1) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
if (comment.loc.start.line === expectedLine) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
if (comment.loc.start.line === node.loc.start.line) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
attached.unshift(comment);
|
|
147
|
+
expectedLine = comment.loc.start.line;
|
|
148
|
+
}
|
|
149
|
+
if (attached.length !== candidates.length) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const first = attached[0] ?? property;
|
|
153
|
+
const startLine = first.loc.start.line;
|
|
154
|
+
const unitStart = lineStarts[startLine - 1] ?? 0;
|
|
155
|
+
const isRest = property.type === "RestElement";
|
|
156
|
+
units.push({
|
|
157
|
+
name: isRest ? null : getPropertyName(property),
|
|
158
|
+
text: source.slice(unitStart, property.range[1]),
|
|
159
|
+
endLine: property.loc.end.line,
|
|
160
|
+
property,
|
|
161
|
+
startLine,
|
|
162
|
+
});
|
|
163
|
+
previousProperty = property;
|
|
164
|
+
}
|
|
165
|
+
const lastProperty = node.properties.at(-1);
|
|
166
|
+
if (lastProperty === undefined) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
if (comments.some((comment) => comment.range[0] >= lastProperty.range[1] && comment.range[1] <= node.range[1] - 1)) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const groups = [];
|
|
174
|
+
let currentGroup = [];
|
|
175
|
+
let previousEndLine = null;
|
|
176
|
+
for (const unit of units) {
|
|
177
|
+
if (previousEndLine !== null && unit.startLine > previousEndLine + 1) {
|
|
178
|
+
groups.push(currentGroup);
|
|
179
|
+
currentGroup = [];
|
|
180
|
+
}
|
|
181
|
+
currentGroup.push(unit);
|
|
182
|
+
previousEndLine = unit.endLine;
|
|
183
|
+
}
|
|
184
|
+
if (currentGroup.length > 0) {
|
|
185
|
+
groups.push(currentGroup);
|
|
186
|
+
}
|
|
187
|
+
let needsSort = false;
|
|
188
|
+
for (const group of groups) {
|
|
189
|
+
const sorted = sortGroup(group);
|
|
190
|
+
if (sorted.some((unit, index) => unit !== group[index])) {
|
|
191
|
+
needsSort = true;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const needsFormat = forceMultiline && inlineStart;
|
|
196
|
+
if (!needsSort && !needsFormat) {
|
|
197
|
+
return {
|
|
198
|
+
needsFix: false,
|
|
199
|
+
replacement: null,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (singleLine && !forceMultiline) {
|
|
203
|
+
const sortedUnits = sortGroup(units);
|
|
204
|
+
return {
|
|
205
|
+
needsFix: true,
|
|
206
|
+
replacement: `{ ${sortedUnits.map((unit) => unit.text).join(", ")} }`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const groupsText = groups
|
|
210
|
+
.map((group) => sortGroup(group)
|
|
211
|
+
.map((unit) => unit.text)
|
|
212
|
+
.join(",\n"))
|
|
213
|
+
.join(",\n\n");
|
|
214
|
+
const lastUnit = groups.flatMap((group) => sortGroup(group)).at(-1);
|
|
215
|
+
const trailingComma = lastUnit?.property.type === "RestElement" ? "" : ",";
|
|
216
|
+
return {
|
|
217
|
+
needsFix: true,
|
|
218
|
+
replacement: `{\n${groupsText}${trailingComma}\n${lineIndent}}`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
export { getSortPlan, compareNames };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overload-signatures-adjacent.d.ts","sourceRoot":"","sources":["../../../src/rules/overload-signatures-adjacent.ts"],"names":[],"mappings":"AAiGA;;GAEG;AACH,QAAA,MAAM,0BAA0B,uCAqG9B,CAAC;eAEY,0BAA0B"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { defineRule } from "vite-plus/lint/plugins";
|
|
2
|
+
import { getNextStatement } from "./blank-line.js";
|
|
3
|
+
function getFunction(node) {
|
|
4
|
+
if (node.type === "FunctionDeclaration" || node.type === "TSDeclareFunction") {
|
|
5
|
+
return node;
|
|
6
|
+
}
|
|
7
|
+
if (node.type === "ExportNamedDeclaration" && node.declaration !== null) {
|
|
8
|
+
const declaration = node.declaration;
|
|
9
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction") {
|
|
10
|
+
return declaration;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function getKeyName(node) {
|
|
16
|
+
if (node.computed) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const key = node.key;
|
|
20
|
+
if (key.type === "Identifier") {
|
|
21
|
+
return key.name;
|
|
22
|
+
}
|
|
23
|
+
if (key.type === "Literal" && (typeof key.value === "string" || typeof key.value === "number")) {
|
|
24
|
+
return String(key.value);
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function getRemoveBlankLinesFix(fixer, sourceCode, previous, next) {
|
|
29
|
+
if (next.loc.start.line <= previous.loc.end.line + 1) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const fromLineEnd = sourceCode.text.indexOf("\n", previous.range[1]);
|
|
33
|
+
if (fromLineEnd === -1) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const nextLineStart = sourceCode.text.lastIndexOf("\n", next.range[0]) + 1;
|
|
37
|
+
const indent = sourceCode.text.slice(nextLineStart, next.range[0]);
|
|
38
|
+
if (indent.trim() !== "") {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return fixer.replaceTextRange([fromLineEnd, next.range[0]], `\n${indent}`);
|
|
42
|
+
}
|
|
43
|
+
function hasCommentBetween(sourceCode, previous, next) {
|
|
44
|
+
return sourceCode
|
|
45
|
+
.getAllComments()
|
|
46
|
+
.some((comment) => comment.range[0] >= previous.range[1] && comment.range[1] <= next.range[0]);
|
|
47
|
+
}
|
|
48
|
+
function reportIfSeparated(context, sourceCode, previous, next) {
|
|
49
|
+
if (next.loc.start.line <= previous.loc.end.line + 1 ||
|
|
50
|
+
hasCommentBetween(sourceCode, previous, next)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
context.report({
|
|
54
|
+
fix(fixer) {
|
|
55
|
+
return getRemoveBlankLinesFix(fixer, sourceCode, previous, next);
|
|
56
|
+
},
|
|
57
|
+
node: next,
|
|
58
|
+
messageId: "adjacent",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* オーバーロード署名と実装の間に空行を入れないようにします。
|
|
63
|
+
*/
|
|
64
|
+
const overloadSignaturesAdjacent = defineRule({
|
|
65
|
+
meta: {
|
|
66
|
+
docs: {
|
|
67
|
+
description: "Require overload signatures and their implementation to be adjacent.",
|
|
68
|
+
},
|
|
69
|
+
type: "layout",
|
|
70
|
+
fixable: "whitespace",
|
|
71
|
+
messages: {
|
|
72
|
+
adjacent: "Overload signatures must be adjacent to each other and to the implementation.",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
create(context) {
|
|
76
|
+
const sourceCode = context.sourceCode;
|
|
77
|
+
return {
|
|
78
|
+
ClassBody(node) {
|
|
79
|
+
for (let index = 1; index < node.body.length; index += 1) {
|
|
80
|
+
const previous = node.body[index - 1];
|
|
81
|
+
const next = node.body[index];
|
|
82
|
+
if (previous === undefined ||
|
|
83
|
+
next === undefined ||
|
|
84
|
+
previous.type !== "MethodDefinition" ||
|
|
85
|
+
previous.value.body !== null ||
|
|
86
|
+
next.type !== "MethodDefinition") {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const previousName = getKeyName(previous);
|
|
90
|
+
const nextName = getKeyName(next);
|
|
91
|
+
if (previousName === null || previousName !== nextName) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
reportIfSeparated(context, sourceCode, previous, next);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
TSDeclareFunction(node) {
|
|
98
|
+
if (node.body !== null) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const nextStatement = getNextStatement(node);
|
|
102
|
+
if (nextStatement === null) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const nextFunction = getFunction(nextStatement);
|
|
106
|
+
if (nextFunction === null || nextFunction.id?.name !== node.id?.name) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
reportIfSeparated(context, sourceCode, node, nextStatement);
|
|
110
|
+
},
|
|
111
|
+
FunctionDeclaration(node) {
|
|
112
|
+
if (node.body !== null) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const nextStatement = getNextStatement(node);
|
|
116
|
+
if (nextStatement === null) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const nextFunction = getFunction(nextStatement);
|
|
120
|
+
if (nextFunction === null || nextFunction.id?.name !== node.id?.name) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
reportIfSeparated(context, sourceCode, node, nextStatement);
|
|
124
|
+
},
|
|
125
|
+
ExportNamedDeclaration(node) {
|
|
126
|
+
const currentFunction = getFunction(node);
|
|
127
|
+
if (currentFunction === null || currentFunction.body !== null) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const nextStatement = getNextStatement(node);
|
|
131
|
+
if (nextStatement === null) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const nextFunction = getFunction(nextStatement);
|
|
135
|
+
if (nextFunction === null || nextFunction.id?.name !== currentFunction.id?.name) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
reportIfSeparated(context, sourceCode, node, nextStatement);
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
export default overloadSignaturesAdjacent;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* import / export の波括弧内の specifier を並べます。
|
|
3
|
+
*
|
|
4
|
+
* 「型 → 変数など → default」のグループ順で、各グループ内は名前の長さが短い順、同じ長さなら名前順です。
|
|
5
|
+
*/
|
|
6
|
+
declare const sortImportExportSpecifiers: import("vite-plus/lint/plugins").Rule;
|
|
7
|
+
export default sortImportExportSpecifiers;
|
|
8
|
+
//# sourceMappingURL=sort-import-export-specifiers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sort-import-export-specifiers.d.ts","sourceRoot":"","sources":["../../../src/rules/sort-import-export-specifiers.ts"],"names":[],"mappings":"AAwEA;;;;GAIG;AACH,QAAA,MAAM,0BAA0B,uCAkH9B,CAAC;eAEY,0BAA0B"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { defineRule } from "vite-plus/lint/plugins";
|
|
2
|
+
import { compareNames } from "./object-sort-utils.js";
|
|
3
|
+
function getSpecifierName(node) {
|
|
4
|
+
if (node.type === "Identifier") {
|
|
5
|
+
return node.name;
|
|
6
|
+
}
|
|
7
|
+
if (node.type === "Literal") {
|
|
8
|
+
return node.value;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function getLineIndent(source, offset) {
|
|
13
|
+
const lineStart = source.lastIndexOf("\n", offset) + 1;
|
|
14
|
+
const match = /^[ \t]*/u.exec(source.slice(lineStart, offset));
|
|
15
|
+
return match?.[0] ?? "";
|
|
16
|
+
}
|
|
17
|
+
function hasUnsafeComment(node, specifiers, sourceCode) {
|
|
18
|
+
const comments = sourceCode
|
|
19
|
+
.getAllComments()
|
|
20
|
+
.filter((comment) => comment.range[0] >= node.range[0] && comment.range[1] <= node.range[1]);
|
|
21
|
+
return comments.some((comment) => !specifiers.some((specifier) => comment.range[0] >= specifier.range[0] && comment.range[1] <= specifier.range[1]));
|
|
22
|
+
}
|
|
23
|
+
function buildReplacement(units, sourceCode, first, last) {
|
|
24
|
+
const sorted = [...units].sort((left, right) => {
|
|
25
|
+
if (left.group !== right.group) {
|
|
26
|
+
return left.group - right.group;
|
|
27
|
+
}
|
|
28
|
+
return compareNames(left.name, right.name);
|
|
29
|
+
});
|
|
30
|
+
if (sorted.every((unit, index) => unit === units[index])) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const multiline = first.loc.start.line !== last.loc.end.line;
|
|
34
|
+
const separator = multiline ? `,\n${getLineIndent(sourceCode.text, first.range[0])}` : ", ";
|
|
35
|
+
return sorted.map((unit) => unit.text).join(separator);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* import / export の波括弧内の specifier を並べます。
|
|
39
|
+
*
|
|
40
|
+
* 「型 → 変数など → default」のグループ順で、各グループ内は名前の長さが短い順、同じ長さなら名前順です。
|
|
41
|
+
*/
|
|
42
|
+
const sortImportExportSpecifiers = defineRule({
|
|
43
|
+
meta: {
|
|
44
|
+
docs: {
|
|
45
|
+
description: "Sort import and export specifiers by group (type, value, default), then by name length and name.",
|
|
46
|
+
},
|
|
47
|
+
type: "layout",
|
|
48
|
+
fixable: "whitespace",
|
|
49
|
+
messages: {
|
|
50
|
+
sort: "Import/export specifiers must be sorted by group, name length, then name.",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
create(context) {
|
|
54
|
+
const sourceCode = context.sourceCode;
|
|
55
|
+
function reportSort(node, specifiers, units) {
|
|
56
|
+
const first = specifiers[0];
|
|
57
|
+
const last = specifiers.at(-1);
|
|
58
|
+
if (first === undefined || last === undefined) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const replacement = buildReplacement(units, sourceCode, first, last);
|
|
62
|
+
if (replacement === null) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
context.report({
|
|
66
|
+
fix(fixer) {
|
|
67
|
+
return fixer.replaceTextRange([first.range[0], last.range[1]], replacement);
|
|
68
|
+
},
|
|
69
|
+
node,
|
|
70
|
+
messageId: "sort",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ImportDeclaration(node) {
|
|
75
|
+
const specifiers = node.specifiers.filter((specifier) => specifier.type === "ImportSpecifier");
|
|
76
|
+
if (specifiers.length < 2 || hasUnsafeComment(node, specifiers, sourceCode)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const units = [];
|
|
80
|
+
for (const specifier of specifiers) {
|
|
81
|
+
const name = getSpecifierName(specifier.imported);
|
|
82
|
+
if (name === null) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
let group = 1;
|
|
86
|
+
if (node.importKind === "type" || specifier.importKind === "type") {
|
|
87
|
+
group = 0;
|
|
88
|
+
}
|
|
89
|
+
else if (name === "default") {
|
|
90
|
+
group = 2;
|
|
91
|
+
}
|
|
92
|
+
units.push({
|
|
93
|
+
name,
|
|
94
|
+
text: sourceCode.text.slice(specifier.range[0], specifier.range[1]),
|
|
95
|
+
group,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
reportSort(node, specifiers, units);
|
|
99
|
+
},
|
|
100
|
+
ExportNamedDeclaration(node) {
|
|
101
|
+
const { specifiers } = node;
|
|
102
|
+
if (specifiers.length < 2 || hasUnsafeComment(node, specifiers, sourceCode)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const units = [];
|
|
106
|
+
for (const specifier of specifiers) {
|
|
107
|
+
const localName = getSpecifierName(specifier.local);
|
|
108
|
+
const exportedName = getSpecifierName(specifier.exported);
|
|
109
|
+
if (localName === null || exportedName === null) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
let group = 1;
|
|
113
|
+
if (node.exportKind === "type" || specifier.exportKind === "type") {
|
|
114
|
+
group = 0;
|
|
115
|
+
}
|
|
116
|
+
else if (localName === "default" || exportedName === "default") {
|
|
117
|
+
group = 2;
|
|
118
|
+
}
|
|
119
|
+
units.push({
|
|
120
|
+
name: localName,
|
|
121
|
+
text: sourceCode.text.slice(specifier.range[0], specifier.range[1]),
|
|
122
|
+
group,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
reportSort(node, specifiers, units);
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
export default sortImportExportSpecifiers;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sort-jsx-props.d.ts","sourceRoot":"","sources":["../../../src/rules/sort-jsx-props.ts"],"names":[],"mappings":"AAsEA;;;GAGG;AACH,QAAA,MAAM,YAAY,uCAwEhB,CAAC;eAEY,YAAY"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { defineRule } from "vite-plus/lint/plugins";
|
|
2
|
+
import { compareNames } from "./object-sort-utils.js";
|
|
3
|
+
function isNamedAttribute(attribute) {
|
|
4
|
+
return attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier";
|
|
5
|
+
}
|
|
6
|
+
function getAttributeGroup(name) {
|
|
7
|
+
if (/^on[A-Z].*/u.test(name)) {
|
|
8
|
+
return 1;
|
|
9
|
+
}
|
|
10
|
+
if (/^aria-.+/u.test(name)) {
|
|
11
|
+
return 2;
|
|
12
|
+
}
|
|
13
|
+
if (/^data-.+/u.test(name)) {
|
|
14
|
+
return 3;
|
|
15
|
+
}
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
function compareAttributes(left, right) {
|
|
19
|
+
const leftGroup = getAttributeGroup(left.name);
|
|
20
|
+
const rightGroup = getAttributeGroup(right.name);
|
|
21
|
+
if (leftGroup !== rightGroup) {
|
|
22
|
+
return leftGroup - rightGroup;
|
|
23
|
+
}
|
|
24
|
+
return compareNames(left.name, right.name);
|
|
25
|
+
}
|
|
26
|
+
function getLineIndent(source, offset) {
|
|
27
|
+
const lineStart = source.lastIndexOf("\n", offset) + 1;
|
|
28
|
+
const match = /^[ \t]*/u.exec(source.slice(lineStart, offset));
|
|
29
|
+
return match?.[0] ?? "";
|
|
30
|
+
}
|
|
31
|
+
function hasCommentBetweenAttributes(attributes, sourceCode) {
|
|
32
|
+
const comments = sourceCode.getAllComments();
|
|
33
|
+
return attributes.some((attribute, index) => {
|
|
34
|
+
const nextAttribute = attributes[index + 1];
|
|
35
|
+
if (nextAttribute === undefined) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return comments.some((comment) => comment.range[0] >= attribute.range[1] && comment.range[1] <= nextAttribute.range[0]);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* JSX の props を「一般 → on* → aria-* → data-*」のグループ順に並べます。
|
|
43
|
+
* 各グループ内ではプロパティー名の長さが短い順、同じ長さなら名前順です。
|
|
44
|
+
*/
|
|
45
|
+
const sortJsxProps = defineRule({
|
|
46
|
+
meta: {
|
|
47
|
+
docs: {
|
|
48
|
+
description: "Sort JSX props by group (general, on*, aria-*, data-*), then by name length and name.",
|
|
49
|
+
},
|
|
50
|
+
type: "layout",
|
|
51
|
+
fixable: "whitespace",
|
|
52
|
+
messages: {
|
|
53
|
+
sort: "JSX props must be sorted by group, name length, then name.",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
create(context) {
|
|
57
|
+
const sourceCode = context.sourceCode;
|
|
58
|
+
return {
|
|
59
|
+
JSXOpeningElement(node) {
|
|
60
|
+
const attributes = node.attributes.filter(isNamedAttribute);
|
|
61
|
+
if (attributes.length < 2 || attributes.length !== node.attributes.length) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const names = new Set();
|
|
65
|
+
for (const attribute of attributes) {
|
|
66
|
+
if (names.has(attribute.name.name)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
names.add(attribute.name.name);
|
|
70
|
+
}
|
|
71
|
+
if (hasCommentBetweenAttributes(attributes, sourceCode)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const units = attributes.map((attribute) => ({
|
|
75
|
+
name: attribute.name.name,
|
|
76
|
+
text: sourceCode.text.slice(attribute.range[0], attribute.range[1]),
|
|
77
|
+
}));
|
|
78
|
+
const sorted = [...units].sort(compareAttributes);
|
|
79
|
+
if (sorted.every((unit, index) => unit === units[index])) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const firstAttribute = attributes[0];
|
|
83
|
+
const lastAttribute = attributes.at(-1);
|
|
84
|
+
if (firstAttribute === undefined || lastAttribute === undefined) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const multiline = firstAttribute.loc.start.line !== lastAttribute.loc.end.line;
|
|
88
|
+
const separator = multiline
|
|
89
|
+
? `\n${getLineIndent(sourceCode.text, firstAttribute.range[0])}`
|
|
90
|
+
: " ";
|
|
91
|
+
context.report({
|
|
92
|
+
fix(fixer) {
|
|
93
|
+
return fixer.replaceTextRange([firstAttribute.range[0], lastAttribute.range[1]], sorted.map((unit) => unit.text).join(separator));
|
|
94
|
+
},
|
|
95
|
+
node,
|
|
96
|
+
messageId: "sort",
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
export default sortJsxProps;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* オブジェクトリテラルのプロパティーを、名前の長さが短い順、同じ長さなら名前順に並べます。
|
|
3
|
+
*
|
|
4
|
+
* 空行で区切られたグループは意図的なまとまりとみなし、グループ内だけで並べ替えます。
|
|
5
|
+
* プロパティー直上のコメントはプロパティーと一緒に移動します。
|
|
6
|
+
*/
|
|
7
|
+
declare const sortObjectProperties: import("vite-plus/lint/plugins").Rule;
|
|
8
|
+
export default sortObjectProperties;
|
|
9
|
+
//# sourceMappingURL=sort-object-properties.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sort-object-properties.d.ts","sourceRoot":"","sources":["../../../src/rules/sort-object-properties.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,QAAA,MAAM,oBAAoB,uCAiDxB,CAAC;eAEY,oBAAoB"}
|