@so1ve/eslint-plugin 4.1.8 → 4.2.0

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 (31) hide show
  1. package/dist/index.d.mts +25 -83
  2. package/dist/index.mjs +13 -1254
  3. package/dist/rules/function-style.d.mts +8 -0
  4. package/dist/rules/function-style.mjs +145 -0
  5. package/dist/rules/html-spaced-comment.d.mts +8 -0
  6. package/dist/rules/html-spaced-comment.mjs +41 -0
  7. package/dist/rules/import-dedupe.d.mts +8 -0
  8. package/dist/rules/import-dedupe.mjs +42 -0
  9. package/dist/rules/import-export-newline.d.mts +8 -0
  10. package/dist/rules/import-export-newline.mjs +98 -0
  11. package/dist/rules/no-import-promises-as.d.mts +8 -0
  12. package/dist/rules/no-import-promises-as.mjs +45 -0
  13. package/dist/rules/no-inline-type-modifier.d.mts +8 -0
  14. package/dist/rules/no-inline-type-modifier.mjs +86 -0
  15. package/dist/rules/no-negated-comparison.d.mts +8 -0
  16. package/dist/rules/no-negated-comparison.mjs +45 -0
  17. package/dist/rules/no-useless-template-string.d.mts +8 -0
  18. package/dist/rules/no-useless-template-string.mjs +30 -0
  19. package/dist/rules/prefer-ts-expect-error.d.mts +8 -0
  20. package/dist/rules/prefer-ts-expect-error.mjs +46 -0
  21. package/dist/rules/require-async-with-await.d.mts +8 -0
  22. package/dist/rules/require-async-with-await.mjs +51 -0
  23. package/dist/rules/sort-exports.d.mts +8 -0
  24. package/dist/rules/sort-exports.mjs +68 -0
  25. package/dist/rules/sort-imports.d.mts +10 -0
  26. package/dist/rules/sort-imports.mjs +104 -0
  27. package/dist/rules/vue-root-element-sort-attributes.d.mts +11 -0
  28. package/dist/rules/vue-root-element-sort-attributes.mjs +66 -0
  29. package/dist/utils/index.mjs +19 -0
  30. package/dist/utils/sort-imports.mjs +417 -0
  31. package/package.json +1 -1
@@ -0,0 +1,417 @@
1
+ import natsort from "natsort";
2
+
3
+ //#region src/utils/sort-imports.ts
4
+ const NEWLINE = /(\r?\n)/;
5
+ const hasNewline = (text) => NEWLINE.test(text);
6
+ function guessNewline(sourceCode) {
7
+ const match = NEWLINE.exec(sourceCode.text);
8
+ return match == null ? "\n" : match[0];
9
+ }
10
+ const withCode = (token, sourceCode) => ({
11
+ ...token,
12
+ code: sourceCode.getText(token)
13
+ });
14
+ function parseWhitespace(whitespace) {
15
+ const allItems = whitespace.split(NEWLINE);
16
+ return (allItems.length >= 5 ? [...allItems.slice(0, 2), ...allItems.slice(-1)] : allItems).map((spacesOrNewline, index) => index % 2 === 0 ? {
17
+ type: "Spaces",
18
+ code: spacesOrNewline
19
+ } : {
20
+ type: "Newline",
21
+ code: spacesOrNewline
22
+ }).filter((token) => token.code !== "");
23
+ }
24
+ const naturalSort = ("default" in natsort ? natsort.default : natsort)();
25
+ function compare(path1, path2) {
26
+ const path1Depth = path1.split("-").filter((p) => p === "__").length;
27
+ const path2Depth = path2.split("-").filter((p) => p === "__").length;
28
+ const path1IsDot = path1 === "_-,";
29
+ const path2IsDot = path2 === "_-,";
30
+ if (path1IsDot && !path2IsDot) return 1;
31
+ if (path2IsDot && !path1IsDot) return -1;
32
+ return path1Depth === path2Depth ? naturalSort(path1, path2) : path2Depth - path1Depth;
33
+ }
34
+ const isIdentifier = (node) => node.type === "Identifier";
35
+ const isKeyword = (node) => node.type === "Keyword";
36
+ const isPunctuator = (node, value) => node.type === "Punctuator" && typeof node.value === "string" && node.value === value;
37
+ const isBlockComment = (node) => node.type === "Block";
38
+ const isLineComment = (node) => node.type === "Line";
39
+ const isSpaces = (node) => node.type === "Spaces";
40
+ const isNewline = (node) => node.type === "Newline";
41
+ const getImportExportKind = (node) => node.importKind ?? node.exportKind ?? "value";
42
+ function getSource(node) {
43
+ const source = String(node.source.value);
44
+ return {
45
+ source: source.replace(/^[./]*\.$/, "$&/").replace(/^[./]*\/$/, "$&,").replace(/[./_-]/g, (char) => {
46
+ switch (char) {
47
+ case ".": return "_";
48
+ case "/": return "-";
49
+ case "_": return ".";
50
+ case "-": return "/";
51
+ default: throw new Error(`Unknown source substitution character: ${char}`);
52
+ }
53
+ }),
54
+ originalSource: source,
55
+ kind: getImportExportKind(node)
56
+ };
57
+ }
58
+ function findLastIndex(array, fn) {
59
+ for (let index = array.length - 1; index >= 0; index--) if (fn(array[index], index, array)) return index;
60
+ return -1;
61
+ }
62
+ const flatMap = (array, fn) => array.flatMap((item, index) => fn(item, index));
63
+ function getAllTokens(node, sourceCode) {
64
+ const tokens = sourceCode.getTokens(node);
65
+ const lastTokenIndex = tokens.length - 1;
66
+ return flatMap(tokens, (token, tokenIndex) => {
67
+ const newToken = withCode(token, sourceCode);
68
+ if (tokenIndex === lastTokenIndex) return [newToken];
69
+ const comments = sourceCode.getCommentsAfter(token);
70
+ const last = comments.length > 0 ? comments[comments.length - 1] : token;
71
+ const nextToken = tokens[tokenIndex + 1];
72
+ return [
73
+ newToken,
74
+ ...flatMap(comments, (comment, commentIndex) => {
75
+ const previous = commentIndex === 0 ? token : comments[commentIndex - 1];
76
+ return [...parseWhitespace(sourceCode.text.slice(previous.range[1], comment.range[0])), withCode(comment, sourceCode)];
77
+ }),
78
+ ...parseWhitespace(sourceCode.text.slice(last.range[1], nextToken.range[0]))
79
+ ];
80
+ });
81
+ }
82
+ const printTokens = (tokens) => tokens.map((token) => token.code).join("");
83
+ const removeBlankLines = (whitespace) => printTokens(parseWhitespace(whitespace));
84
+ function printCommentsBefore(node, comments, sourceCode) {
85
+ const lastIndex = comments.length - 1;
86
+ return comments.map((comment, index) => {
87
+ const next = index === lastIndex ? node : comments[index + 1];
88
+ return sourceCode.getText(comment) + removeBlankLines(sourceCode.text.slice(comment.range[1], next.range[0]));
89
+ }).join("");
90
+ }
91
+ const printCommentsAfter = (node, comments, sourceCode) => comments.map((comment, index) => {
92
+ const previous = index === 0 ? node : comments[index - 1];
93
+ return removeBlankLines(sourceCode.text.slice(previous.range[1], comment.range[0])) + sourceCode.getText(comment);
94
+ }).join("");
95
+ function getIndentationAt(index, sourceCode) {
96
+ const lines = sourceCode.text.slice(0, index).split(NEWLINE);
97
+ const linePrefix = lines[lines.length - 1];
98
+ return /^\s*$/u.test(linePrefix) ? linePrefix : "";
99
+ }
100
+ function getTrailingSpacesAt(index, sourceCode) {
101
+ const { text } = sourceCode;
102
+ let end = index;
103
+ while (end < text.length) {
104
+ const char = text[end];
105
+ if (char === " " || char === " ") {
106
+ end++;
107
+ continue;
108
+ }
109
+ break;
110
+ }
111
+ return text.slice(index, end);
112
+ }
113
+ const sortImportExportItems = (items) => [...items].sort((itemA, itemB) => itemA.isSideEffectImport && itemB.isSideEffectImport ? itemA.index - itemB.index : itemA.isSideEffectImport ? -1 : itemB.isSideEffectImport ? 1 : compare(itemA.source.source, itemB.source.source) || compare(itemA.source.originalSource, itemB.source.originalSource) || compare(itemA.source.kind, itemB.source.kind) || itemA.index - itemB.index);
114
+ const getSpecifierName = (node) => node.type === "Identifier" ? node.name : node.value;
115
+ const getExternalName = (node) => getSpecifierName("imported" in node ? node.imported : node.exported);
116
+ const getLocalName = (node) => getSpecifierName(node.local);
117
+ function compareImportExportKind(kindA, kindB) {
118
+ if (kindA === kindB) return 0;
119
+ return kindA === "type" ? -1 : 1;
120
+ }
121
+ const sortSpecifierItems = (items) => [...items].sort((itemA, itemB) => compareImportExportKind(getImportExportKind(itemA.node), getImportExportKind(itemB.node)) || compare(getExternalName(itemA.node), getExternalName(itemB.node)) || compare(getLocalName(itemA.node), getLocalName(itemB.node)) || itemA.index - itemB.index);
122
+ function extractChunks(parentNode, isPartOfChunk) {
123
+ const chunks = [];
124
+ let chunk = [];
125
+ let lastNode;
126
+ for (const node of parentNode.body) {
127
+ const result = isPartOfChunk(node, lastNode);
128
+ switch (result) {
129
+ case "PartOfChunk":
130
+ chunk.push(node);
131
+ break;
132
+ case "PartOfNewChunk":
133
+ if (chunk.length > 0) chunks.push(chunk);
134
+ chunk = [node];
135
+ break;
136
+ case "NotPartOfChunk":
137
+ if (chunk.length > 0) {
138
+ chunks.push(chunk);
139
+ chunk = [];
140
+ }
141
+ break;
142
+ default: {
143
+ const _never = result;
144
+ throw new Error(`Unknown chunk result: ${String(_never)}`);
145
+ }
146
+ }
147
+ lastNode = node;
148
+ }
149
+ if (chunk.length > 0) chunks.push(chunk);
150
+ return chunks;
151
+ }
152
+ function maybeReportSorting(context, sorted, start, end) {
153
+ const sourceCode = context.sourceCode;
154
+ if (sourceCode.getText().slice(start, end) !== sorted) context.report({
155
+ messageId: "sort",
156
+ loc: {
157
+ start: sourceCode.getLocFromIndex(start),
158
+ end: sourceCode.getLocFromIndex(end)
159
+ },
160
+ fix: (fixer) => fixer.replaceTextRange([start, end], sorted)
161
+ });
162
+ }
163
+ function printSortedItems(sortedItems, originalItems, sourceCode) {
164
+ const newline = guessNewline(sourceCode);
165
+ const sorted = sortedItems.map((groups) => groups.map((groupItems) => groupItems.map((item) => item.code).join(newline)).join(newline)).join(newline + newline);
166
+ const flattened = flatMap(sortedItems, (groups) => flatMap(groups, (g) => g));
167
+ const lastSortedItem = flattened[flattened.length - 1];
168
+ const lastOriginalItem = originalItems[originalItems.length - 1];
169
+ const nextToken = lastSortedItem.needsNewline ? sourceCode.getTokenAfter(lastOriginalItem.node, {
170
+ includeComments: true,
171
+ filter: (token) => token.type !== "Line" && (token.type !== "Block" || token.loc.end.line !== lastOriginalItem.node.loc.end.line)
172
+ }) : void 0;
173
+ return sorted + (nextToken != null && nextToken.loc.start.line === lastOriginalItem.node.loc.end.line ? newline : "");
174
+ }
175
+ const makeEmptyItem = () => ({
176
+ state: "before",
177
+ before: [],
178
+ after: [],
179
+ specifier: [],
180
+ hadComma: false
181
+ });
182
+ function getSpecifierItems(tokens) {
183
+ const result = {
184
+ before: [],
185
+ after: [],
186
+ items: []
187
+ };
188
+ let current = makeEmptyItem();
189
+ for (const token of tokens) switch (current.state) {
190
+ case "before":
191
+ switch (token.type) {
192
+ case "Newline":
193
+ current.before.push(token);
194
+ if (result.before.length === 0 && result.items.length === 0) {
195
+ result.before = current.before;
196
+ current = makeEmptyItem();
197
+ }
198
+ break;
199
+ case "Spaces":
200
+ case "Block":
201
+ case "Line":
202
+ current.before.push(token);
203
+ break;
204
+ default:
205
+ if (result.before.length === 0 && result.items.length === 0) {
206
+ result.before = current.before;
207
+ current = makeEmptyItem();
208
+ }
209
+ current.state = "specifier";
210
+ current.specifier.push(token);
211
+ }
212
+ break;
213
+ case "specifier":
214
+ switch (token.type) {
215
+ case "Punctuator":
216
+ if (isPunctuator(token, ",")) {
217
+ current.hadComma = true;
218
+ current.state = "after";
219
+ } else current.specifier.push(token);
220
+ break;
221
+ default: current.specifier.push(token);
222
+ }
223
+ break;
224
+ case "after":
225
+ switch (token.type) {
226
+ case "Newline":
227
+ current.after.push(token);
228
+ result.items.push(current);
229
+ current = makeEmptyItem();
230
+ break;
231
+ case "Spaces":
232
+ case "Line":
233
+ current.after.push(token);
234
+ break;
235
+ case "Block":
236
+ if (hasNewline(token.code)) {
237
+ result.items.push(current);
238
+ current = makeEmptyItem();
239
+ current.before.push(token);
240
+ } else current.after.push(token);
241
+ break;
242
+ default:
243
+ result.items.push(current);
244
+ current = makeEmptyItem();
245
+ current.state = "specifier";
246
+ current.specifier.push(token);
247
+ }
248
+ break;
249
+ default: {
250
+ const _never = current.state;
251
+ throw new Error(`Unknown state: ${String(_never)}`);
252
+ }
253
+ }
254
+ switch (current.state) {
255
+ case "before":
256
+ result.after = current.before;
257
+ break;
258
+ case "specifier": {
259
+ const lastIdentifierIndex = findLastIndex(current.specifier, (t) => isIdentifier(t) || isKeyword(t));
260
+ const specifier = current.specifier.slice(0, lastIdentifierIndex + 1);
261
+ const after = current.specifier.slice(lastIdentifierIndex + 1);
262
+ const newlineIndexRaw = after.findIndex((t) => isNewline(t));
263
+ const newlineIndex = newlineIndexRaw === -1 ? -1 : newlineIndexRaw + 1;
264
+ const multilineBlockCommentIndex = after.findIndex((t) => isBlockComment(t) && hasNewline(t.code));
265
+ const sliceIndex = newlineIndex >= 0 && multilineBlockCommentIndex !== -1 ? Math.min(newlineIndex, multilineBlockCommentIndex) : newlineIndex >= 0 ? newlineIndex : multilineBlockCommentIndex === -1 ? endsWithSpaces(after) ? after.length - 1 : -1 : multilineBlockCommentIndex;
266
+ current.specifier = specifier;
267
+ current.after = sliceIndex === -1 ? after : after.slice(0, sliceIndex);
268
+ result.items.push(current);
269
+ result.after = sliceIndex === -1 ? [] : after.slice(sliceIndex);
270
+ break;
271
+ }
272
+ case "after":
273
+ if (endsWithSpaces(current.after)) {
274
+ const last = current.after.pop();
275
+ if (last) result.after = [last];
276
+ }
277
+ result.items.push(current);
278
+ break;
279
+ default: {
280
+ const _never = current.state;
281
+ throw new Error(`Unknown state: ${String(_never)}`);
282
+ }
283
+ }
284
+ return {
285
+ before: result.before,
286
+ after: result.after,
287
+ items: result.items.map((item) => ({
288
+ before: item.before,
289
+ after: item.after,
290
+ specifier: item.specifier,
291
+ hadComma: item.hadComma
292
+ }))
293
+ };
294
+ }
295
+ function needsStartingNewline(tokens) {
296
+ const before = tokens.filter((token) => !isSpaces(token));
297
+ if (before.length === 0) return false;
298
+ const firstToken = before[0];
299
+ return isLineComment(firstToken) || isBlockComment(firstToken) && !hasNewline(firstToken.code);
300
+ }
301
+ function endsWithSpaces(tokens) {
302
+ const last = tokens.length > 0 ? tokens[tokens.length - 1] : void 0;
303
+ return last == null ? false : isSpaces(last);
304
+ }
305
+ function printWithSortedSpecifiers(node, sourceCode, getSpecifiers) {
306
+ const allTokens = getAllTokens(node, sourceCode);
307
+ const openBraceIndex = allTokens.findIndex((token) => isPunctuator(token, "{"));
308
+ const closeBraceIndex = allTokens.findIndex((token) => isPunctuator(token, "}"));
309
+ const specifiers = getSpecifiers(node);
310
+ if (openBraceIndex === -1 || closeBraceIndex === -1 || specifiers.length <= 1) return printTokens(allTokens);
311
+ const itemsResult = getSpecifierItems(allTokens.slice(openBraceIndex + 1, closeBraceIndex));
312
+ const sortedItems = sortSpecifierItems(itemsResult.items.map((originalItem, index) => ({
313
+ ...originalItem,
314
+ node: specifiers[index],
315
+ index
316
+ })));
317
+ const newline = guessNewline(sourceCode);
318
+ const hasTrailingComma = (() => {
319
+ for (let index = closeBraceIndex - 1; index > openBraceIndex; index--) {
320
+ const token = allTokens[index];
321
+ if (token.type === "Spaces" || token.type === "Newline" || token.type === "Block" || token.type === "Line") continue;
322
+ return isPunctuator(token, ",");
323
+ }
324
+ return false;
325
+ })();
326
+ const lastIndex = sortedItems.length - 1;
327
+ const sorted = flatMap(sortedItems, (item, index) => {
328
+ const previous = index === 0 ? void 0 : sortedItems[index - 1];
329
+ const maybeNewline$1 = previous != null && needsStartingNewline(item.before) && (previous.after.length <= 0 || !isNewline(previous.after[previous.after.length - 1])) ? [{
330
+ type: "Newline",
331
+ code: newline
332
+ }] : [];
333
+ if (index < lastIndex || hasTrailingComma) return [
334
+ ...maybeNewline$1,
335
+ ...item.before,
336
+ ...item.specifier,
337
+ {
338
+ type: "Comma",
339
+ code: ","
340
+ },
341
+ ...item.after
342
+ ];
343
+ const nonBlankIndex = item.after.findIndex((token) => !isNewline(token) && !isSpaces(token));
344
+ const after = item.hadComma ? nonBlankIndex === -1 ? [] : item.after.slice(nonBlankIndex) : item.after;
345
+ return [
346
+ ...maybeNewline$1,
347
+ ...item.before,
348
+ ...item.specifier,
349
+ ...after
350
+ ];
351
+ });
352
+ const maybeNewline = needsStartingNewline(itemsResult.after) && !isNewline(sorted[sorted.length - 1]) ? [{
353
+ type: "Newline",
354
+ code: newline
355
+ }] : [];
356
+ return printTokens([
357
+ ...allTokens.slice(0, openBraceIndex + 1),
358
+ ...itemsResult.before,
359
+ ...sorted,
360
+ ...maybeNewline,
361
+ ...itemsResult.after,
362
+ ...allTokens.slice(closeBraceIndex)
363
+ ]);
364
+ }
365
+ function handleLastSemicolon(chunk, sourceCode) {
366
+ const lastIndex = chunk.length - 1;
367
+ const lastNode = chunk[lastIndex];
368
+ const tokens = sourceCode.getLastTokens(lastNode, { count: 2 });
369
+ if (tokens.length < 2) return [...chunk];
370
+ const [nextToLastToken, lastToken] = tokens;
371
+ if (!(lastToken.type === "Punctuator" && lastToken.value === ";")) return [...chunk];
372
+ if (nextToLastToken.loc.end.line === lastToken.loc.start.line || sourceCode.getTokenAfter(lastToken) == null) return [...chunk];
373
+ const newLastNode = {
374
+ ...lastNode,
375
+ range: [lastNode.range[0], nextToLastToken.range[1]],
376
+ loc: {
377
+ start: lastNode.loc.start,
378
+ end: nextToLastToken.loc.end
379
+ }
380
+ };
381
+ return [...chunk.slice(0, lastIndex), newLastNode];
382
+ }
383
+ function getImportExportItems(passedChunk, sourceCode, isSideEffectImport, getSpecifiers) {
384
+ const chunk = handleLastSemicolon(passedChunk, sourceCode);
385
+ return chunk.map((node, nodeIndex) => {
386
+ const lastLine = nodeIndex === 0 ? node.loc.start.line - 1 : chunk[nodeIndex - 1].loc.end.line;
387
+ const commentsBefore = sourceCode.getCommentsBefore(node).filter((comment) => comment.loc.start.line <= node.loc.start.line && comment.loc.end.line > lastLine && (nodeIndex > 0 || comment.loc.start.line > lastLine));
388
+ const commentsAfter = sourceCode.getCommentsAfter(node).filter((comment) => comment.loc.end.line === node.loc.end.line);
389
+ const before = printCommentsBefore(node, commentsBefore, sourceCode);
390
+ const after = printCommentsAfter(node, commentsAfter, sourceCode);
391
+ const indentation = getIndentationAt((commentsBefore.length > 0 ? commentsBefore[0] : node).range[0], sourceCode);
392
+ const trailingSpaces = getTrailingSpacesAt((commentsAfter.length > 0 ? commentsAfter[commentsAfter.length - 1] : node).range[1], sourceCode);
393
+ const code = indentation + before + printWithSortedSpecifiers(node, sourceCode, getSpecifiers) + after + trailingSpaces;
394
+ const all = [
395
+ ...commentsBefore,
396
+ node,
397
+ ...commentsAfter
398
+ ];
399
+ const [start] = all[0].range;
400
+ const [, end] = all[all.length - 1].range;
401
+ const source = getSource(node);
402
+ const lastAfter = commentsAfter.length > 0 ? commentsAfter[commentsAfter.length - 1] : void 0;
403
+ return {
404
+ node,
405
+ code,
406
+ start: start - indentation.length,
407
+ end: end + trailingSpaces.length,
408
+ isSideEffectImport: isSideEffectImport(node, sourceCode),
409
+ source,
410
+ index: nodeIndex,
411
+ needsNewline: lastAfter?.type === "Line"
412
+ };
413
+ });
414
+ }
415
+
416
+ //#endregion
417
+ export { extractChunks, getImportExportItems, isPunctuator, maybeReportSorting, printSortedItems, printWithSortedSpecifiers, sortImportExportItems };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@so1ve/eslint-plugin",
3
- "version": "4.1.8",
3
+ "version": "4.2.0",
4
4
  "author": "Ray <i@mk1.io> (https://github.com/so1ve/)",
5
5
  "type": "module",
6
6
  "keywords": [