@borela-tech/eslint-config 2.0.1 → 2.1.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.
- package/.github/workflows/ci.yml +82 -0
- package/README.md +67 -0
- package/dist/index.js +466 -38
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
- package/src/index.ts +9 -0
- package/src/lib/compare.ts +3 -0
- package/src/rules/__tests__/importsAndReExportsAtTop.test.ts +118 -0
- package/src/rules/__tests__/individualReExports.test.ts +62 -0
- package/src/rules/__tests__/sortedImports.test.ts +3 -3
- package/src/rules/__tests__/sortedReExports.test.ts +151 -0
- package/src/rules/importsAndReExportsAtTop/CategorizedStatements.ts +8 -0
- package/src/rules/importsAndReExportsAtTop/ReExport.ts +5 -0
- package/src/rules/importsAndReExportsAtTop/StatementIndices.ts +5 -0
- package/src/rules/importsAndReExportsAtTop/categorizeStatements.ts +27 -0
- package/src/rules/importsAndReExportsAtTop/findFirstIndices.ts +25 -0
- package/src/rules/importsAndReExportsAtTop/generateSortedText.ts +18 -0
- package/src/rules/importsAndReExportsAtTop/getStatementType.ts +17 -0
- package/src/rules/importsAndReExportsAtTop/hasViolation.ts +29 -0
- package/src/rules/importsAndReExportsAtTop/index.ts +45 -0
- package/src/rules/importsAndReExportsAtTop/statementType.ts +4 -0
- package/src/rules/individualImports.ts +5 -14
- package/src/rules/individualReExports.ts +52 -0
- package/src/rules/sortedImports/CategorizedImport.ts +2 -2
- package/src/rules/sortedImports/ImportError.ts +2 -2
- package/src/rules/sortedImports/ImportGroup.ts +5 -1
- package/src/rules/sortedImports/ImportGroupOrder.ts +8 -0
- package/src/rules/sortedImports/areSpecifiersSorted.ts +4 -5
- package/src/rules/sortedImports/categorizeImport.ts +4 -0
- package/src/rules/sortedImports/checkAlphabeticalSorting.ts +4 -3
- package/src/rules/sortedImports/checkGroupOrdering.ts +2 -3
- package/src/rules/sortedImports/createFix/buildSortedCode.ts +2 -2
- package/src/rules/sortedImports/createFix/formatNamedImport.ts +5 -8
- package/src/rules/sortedImports/createFix/getReplacementRange.ts +1 -1
- package/src/rules/sortedImports/createFix/sortImportGroups.ts +5 -4
- package/src/rules/sortedImports/getNamedSpecifiers.ts +3 -4
- package/src/rules/sortedImports/getSortKey.ts +3 -3
- package/src/rules/sortedImports/getSpecifierName.ts +2 -2
- package/src/rules/sortedImports/sortSpecifiersText.ts +7 -6
- package/src/rules/sortedReExports/CategorizedNamedReExport.ts +6 -0
- package/src/rules/sortedReExports/CategorizedReExport.ts +15 -0
- package/src/rules/sortedReExports/ReExportDeclaration.ts +5 -0
- package/src/rules/sortedReExports/ReExportError.ts +6 -0
- package/src/rules/sortedReExports/ReExportGroup.ts +4 -0
- package/src/rules/sortedReExports/ReExportGroupOrder.ts +7 -0
- package/src/rules/sortedReExports/areSpecifiersSorted.ts +9 -0
- package/src/rules/sortedReExports/categorizeReExport.ts +17 -0
- package/src/rules/sortedReExports/categorizeReExports.ts +14 -0
- package/src/rules/sortedReExports/checkAlphabeticalSorting.ts +25 -0
- package/src/rules/sortedReExports/checkGroupOrdering.ts +21 -0
- package/src/rules/sortedReExports/checkSpecifiersSorting.ts +23 -0
- package/src/rules/sortedReExports/createFix/buildSortedCode.ts +28 -0
- package/src/rules/sortedReExports/createFix/findFirstExportIndex.ts +11 -0
- package/src/rules/sortedReExports/createFix/findLastExportIndex.ts +12 -0
- package/src/rules/sortedReExports/createFix/formatNamedReExport.ts +20 -0
- package/src/rules/sortedReExports/createFix/getReplacementRange.ts +22 -0
- package/src/rules/sortedReExports/createFix/groupReExportsByType.ts +17 -0
- package/src/rules/sortedReExports/createFix/index.ts +29 -0
- package/src/rules/sortedReExports/createFix/sortExportGroups.ts +11 -0
- package/src/rules/sortedReExports/getNamedSpecifiers.ts +9 -0
- package/src/rules/sortedReExports/getReExportDeclarations.ts +12 -0
- package/src/rules/sortedReExports/getSortKey.ts +16 -0
- package/src/rules/sortedReExports/getSpecifierName.ts +7 -0
- package/src/rules/sortedReExports/index.ts +54 -0
- package/src/rules/sortedReExports/isNamedReExport.ts +6 -0
- package/src/rules/sortedReExports/sortSpecifiersText.ts +15 -0
- /package/src/{rules/sortedImports/createFix → lib}/ReplacementRange.ts +0 -0
package/dist/index.js
CHANGED
|
@@ -6,6 +6,118 @@ import stylistic from "@stylistic/eslint-plugin";
|
|
|
6
6
|
import typescript from "typescript-eslint";
|
|
7
7
|
import { defineConfig } from "eslint/config";
|
|
8
8
|
|
|
9
|
+
// src/rules/importsAndReExportsAtTop/getStatementType.ts
|
|
10
|
+
function getStatementType(statement) {
|
|
11
|
+
if (statement.type === "ImportDeclaration")
|
|
12
|
+
return "import";
|
|
13
|
+
if (statement.type === "ExportAllDeclaration")
|
|
14
|
+
return "re-export";
|
|
15
|
+
if (statement.type === "ExportNamedDeclaration") {
|
|
16
|
+
if (statement.source !== null)
|
|
17
|
+
return "re-export";
|
|
18
|
+
}
|
|
19
|
+
return "other";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/rules/importsAndReExportsAtTop/categorizeStatements.ts
|
|
23
|
+
function categorizeStatements(statements) {
|
|
24
|
+
const result = {
|
|
25
|
+
imports: [],
|
|
26
|
+
reExports: [],
|
|
27
|
+
other: []
|
|
28
|
+
};
|
|
29
|
+
for (const statement of statements) {
|
|
30
|
+
const type = getStatementType(statement);
|
|
31
|
+
if (type === "import")
|
|
32
|
+
result.imports.push(statement);
|
|
33
|
+
else if (type === "re-export")
|
|
34
|
+
result.reExports.push(statement);
|
|
35
|
+
else
|
|
36
|
+
result.other.push(statement);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/rules/importsAndReExportsAtTop/findFirstIndices.ts
|
|
42
|
+
function findFirstIndices(statements) {
|
|
43
|
+
let firstImport = Infinity;
|
|
44
|
+
let firstReExport = Infinity;
|
|
45
|
+
let firstOther = -1;
|
|
46
|
+
for (let i = 0; i < statements.length; i++) {
|
|
47
|
+
const type = getStatementType(statements[i]);
|
|
48
|
+
if (type === "import" && firstImport === Infinity)
|
|
49
|
+
firstImport = i;
|
|
50
|
+
else if (type === "re-export" && firstReExport === Infinity)
|
|
51
|
+
firstReExport = i;
|
|
52
|
+
else if (type === "other" && firstOther === -1)
|
|
53
|
+
firstOther = i;
|
|
54
|
+
}
|
|
55
|
+
return { firstImport, firstReExport, firstOther };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/rules/importsAndReExportsAtTop/generateSortedText.ts
|
|
59
|
+
function generateSortedText(context, categories) {
|
|
60
|
+
const allStatements = [
|
|
61
|
+
...categories.imports,
|
|
62
|
+
...categories.reExports,
|
|
63
|
+
...categories.other
|
|
64
|
+
];
|
|
65
|
+
return allStatements.map(
|
|
66
|
+
(node) => context.sourceCode.getText(node)
|
|
67
|
+
).join("\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/rules/importsAndReExportsAtTop/hasViolation.ts
|
|
71
|
+
function hasViolation(indices, categories) {
|
|
72
|
+
const {
|
|
73
|
+
firstImport,
|
|
74
|
+
firstReExport,
|
|
75
|
+
firstOther
|
|
76
|
+
} = indices;
|
|
77
|
+
if (categories.imports.length === 0 || categories.reExports.length === 0)
|
|
78
|
+
return false;
|
|
79
|
+
const firstImportOrReExport = Math.min(firstImport, firstReExport);
|
|
80
|
+
const hasOtherBeforeImportOrReExport = firstOther !== -1 && firstOther < firstImportOrReExport;
|
|
81
|
+
if (hasOtherBeforeImportOrReExport || firstImport > firstReExport)
|
|
82
|
+
return true;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/rules/importsAndReExportsAtTop/index.ts
|
|
87
|
+
var importsAndReExportsAtTop = {
|
|
88
|
+
meta: {
|
|
89
|
+
type: "suggestion",
|
|
90
|
+
docs: {
|
|
91
|
+
description: "Enforce imports and re-exports at the top of the file",
|
|
92
|
+
recommended: false
|
|
93
|
+
},
|
|
94
|
+
fixable: "code",
|
|
95
|
+
messages: {
|
|
96
|
+
importsAndReExportsAtTop: "Imports and re-exports should be at the top of the file."
|
|
97
|
+
},
|
|
98
|
+
schema: []
|
|
99
|
+
},
|
|
100
|
+
create(context) {
|
|
101
|
+
return {
|
|
102
|
+
Program(node) {
|
|
103
|
+
const statements = node.body;
|
|
104
|
+
const categories = categorizeStatements(statements);
|
|
105
|
+
const indices = findFirstIndices(statements);
|
|
106
|
+
if (!hasViolation(indices, categories))
|
|
107
|
+
return;
|
|
108
|
+
context.report({
|
|
109
|
+
node,
|
|
110
|
+
messageId: "importsAndReExportsAtTop",
|
|
111
|
+
fix(fixer) {
|
|
112
|
+
const sortedText = generateSortedText(context, categories);
|
|
113
|
+
return fixer.replaceText(node, sortedText);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
9
121
|
// src/rules/individualImports.ts
|
|
10
122
|
var individualImports = {
|
|
11
123
|
meta: {
|
|
@@ -30,17 +142,48 @@ var individualImports = {
|
|
|
30
142
|
messageId: "individualImports",
|
|
31
143
|
fix(fixer) {
|
|
32
144
|
const source = node.source.raw;
|
|
33
|
-
const specifiers = node.specifiers.map((
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
145
|
+
const specifiers = node.specifiers.filter((s) => s.type === "ImportSpecifier").map((s) => `import {${s.local.name}} from ${source}`).join("\n");
|
|
146
|
+
return fixer.replaceText(node, specifiers);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/rules/individualReExports.ts
|
|
155
|
+
var individualReExports = {
|
|
156
|
+
meta: {
|
|
157
|
+
docs: {
|
|
158
|
+
description: "Enforce individual exports instead of grouped exports",
|
|
159
|
+
recommended: true
|
|
160
|
+
},
|
|
161
|
+
fixable: "code",
|
|
162
|
+
messages: {
|
|
163
|
+
individualReExports: "Use individual exports instead of grouped exports."
|
|
164
|
+
},
|
|
165
|
+
schema: [],
|
|
166
|
+
type: "suggestion"
|
|
167
|
+
},
|
|
168
|
+
create(context) {
|
|
169
|
+
return {
|
|
170
|
+
ExportNamedDeclaration(node) {
|
|
171
|
+
const exportNode = node;
|
|
172
|
+
if (!exportNode.source || exportNode.specifiers.length <= 1)
|
|
173
|
+
return;
|
|
174
|
+
context.report({
|
|
175
|
+
node,
|
|
176
|
+
messageId: "individualReExports",
|
|
177
|
+
fix(fixer) {
|
|
178
|
+
const source = exportNode.source.value;
|
|
179
|
+
const typeKeyword = exportNode.exportKind === "type" ? "type " : "";
|
|
180
|
+
const specifiers = exportNode.specifiers.map((s) => {
|
|
181
|
+
const localName = s.local.type === "Identifier" ? s.local.name : s.local.value;
|
|
182
|
+
const exportedName = s.exported.type === "Identifier" ? s.exported.name : s.exported.value;
|
|
183
|
+
const name = localName === exportedName ? localName : `${localName} as ${exportedName}`;
|
|
184
|
+
return `export ${typeKeyword}{${name}} from '${source}'`;
|
|
185
|
+
}).join("\n");
|
|
186
|
+
return fixer.replaceText(node, specifiers);
|
|
44
187
|
}
|
|
45
188
|
});
|
|
46
189
|
}
|
|
@@ -63,15 +206,15 @@ function categorizeImport(declaration) {
|
|
|
63
206
|
function getSortKey(declaration) {
|
|
64
207
|
const group = categorizeImport(declaration);
|
|
65
208
|
if (group === "side-effect")
|
|
66
|
-
return declaration.source.value
|
|
209
|
+
return declaration.source.value;
|
|
67
210
|
if (group === "default") {
|
|
68
211
|
const defaultSpecifier = declaration.specifiers.find(
|
|
69
212
|
(s) => s.type === "ImportDefaultSpecifier"
|
|
70
213
|
);
|
|
71
|
-
return defaultSpecifier?.local.name
|
|
214
|
+
return defaultSpecifier?.local.name ?? "";
|
|
72
215
|
}
|
|
73
216
|
const specifier = declaration.specifiers[0];
|
|
74
|
-
return specifier.local.name
|
|
217
|
+
return specifier.local.name;
|
|
75
218
|
}
|
|
76
219
|
|
|
77
220
|
// src/rules/sortedImports/categorizeImports.ts
|
|
@@ -83,12 +226,25 @@ function categorizeImports(declarations) {
|
|
|
83
226
|
}));
|
|
84
227
|
}
|
|
85
228
|
|
|
229
|
+
// src/lib/compare.ts
|
|
230
|
+
function compare(a, b) {
|
|
231
|
+
return a.localeCompare(b, "en", { sensitivity: "case" });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/rules/sortedImports/ImportGroupOrder.ts
|
|
235
|
+
var importGroupOrder = [
|
|
236
|
+
"side-effect",
|
|
237
|
+
"default",
|
|
238
|
+
"named",
|
|
239
|
+
"type"
|
|
240
|
+
];
|
|
241
|
+
|
|
86
242
|
// src/rules/sortedImports/checkAlphabeticalSorting.ts
|
|
87
243
|
function checkAlphabeticalSorting(categorized) {
|
|
88
244
|
const errors = [];
|
|
89
|
-
for (const group of
|
|
245
|
+
for (const group of importGroupOrder) {
|
|
90
246
|
const groupImports = categorized.filter((c) => c.group === group);
|
|
91
|
-
const sorted = [...groupImports].sort((a, b) => a.sortKey
|
|
247
|
+
const sorted = [...groupImports].sort((a, b) => compare(a.sortKey, b.sortKey));
|
|
92
248
|
for (let i = 0; i < groupImports.length; i++) {
|
|
93
249
|
if (groupImports[i] !== sorted[i]) {
|
|
94
250
|
errors.push({
|
|
@@ -103,11 +259,10 @@ function checkAlphabeticalSorting(categorized) {
|
|
|
103
259
|
|
|
104
260
|
// src/rules/sortedImports/checkGroupOrdering.ts
|
|
105
261
|
function checkGroupOrdering(categorized) {
|
|
106
|
-
const groupOrder = ["side-effect", "default", "named", "type"];
|
|
107
262
|
const errors = [];
|
|
108
263
|
let currentGroupIndex = -1;
|
|
109
264
|
for (const { declaration, group } of categorized) {
|
|
110
|
-
const groupIndex =
|
|
265
|
+
const groupIndex = importGroupOrder.indexOf(group);
|
|
111
266
|
if (groupIndex < currentGroupIndex) {
|
|
112
267
|
errors.push({
|
|
113
268
|
node: declaration,
|
|
@@ -127,9 +282,7 @@ function getSpecifierName(specifier) {
|
|
|
127
282
|
// src/rules/sortedImports/areSpecifiersSorted.ts
|
|
128
283
|
function areSpecifiersSorted(specifiers) {
|
|
129
284
|
const names = specifiers.map((s) => getSpecifierName(s));
|
|
130
|
-
const sorted = [...names].sort(
|
|
131
|
-
(a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
|
|
132
|
-
);
|
|
285
|
+
const sorted = [...names].sort((a, b) => compare(a, b));
|
|
133
286
|
return names.every((name, i) => name === sorted[i]);
|
|
134
287
|
}
|
|
135
288
|
|
|
@@ -159,9 +312,9 @@ function checkSpecifiersSorting(categorized) {
|
|
|
159
312
|
// src/rules/sortedImports/sortSpecifiersText.ts
|
|
160
313
|
function sortSpecifiersText(specifiers, sourceCode) {
|
|
161
314
|
const sorted = [...specifiers].sort((a, b) => {
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
return
|
|
315
|
+
const nameA = getSpecifierName(a);
|
|
316
|
+
const nameB = getSpecifierName(b);
|
|
317
|
+
return compare(nameA, nameB);
|
|
165
318
|
});
|
|
166
319
|
return sorted.map((s) => sourceCode.getText(s)).join(", ");
|
|
167
320
|
}
|
|
@@ -170,22 +323,18 @@ function sortSpecifiersText(specifiers, sourceCode) {
|
|
|
170
323
|
function formatNamedImport(declaration, sourceCode) {
|
|
171
324
|
const specifiers = getNamedSpecifiers(declaration);
|
|
172
325
|
if (specifiers.length > 1 && !areSpecifiersSorted(specifiers)) {
|
|
173
|
-
const importText = sourceCode.getText(declaration);
|
|
174
|
-
const specifiersStart = importText.indexOf("{");
|
|
175
|
-
const specifiersEnd = importText.lastIndexOf("}");
|
|
176
|
-
const before = importText.substring(0, specifiersStart + 1);
|
|
177
|
-
const after = importText.substring(specifiersEnd);
|
|
178
326
|
const sortedSpecifiers = sortSpecifiersText(specifiers, sourceCode);
|
|
179
|
-
|
|
327
|
+
const source = declaration.source.value;
|
|
328
|
+
const prefix = declaration.importKind === "type" ? "import type " : "import ";
|
|
329
|
+
return `${prefix}{${sortedSpecifiers}} from '${source}'`;
|
|
180
330
|
}
|
|
181
331
|
return sourceCode.getText(declaration);
|
|
182
332
|
}
|
|
183
333
|
|
|
184
334
|
// src/rules/sortedImports/createFix/buildSortedCode.ts
|
|
185
335
|
function buildSortedCode(grouped, sourceCode) {
|
|
186
|
-
const groupOrder = ["side-effect", "default", "named", "type"];
|
|
187
336
|
const sortedCode = [];
|
|
188
|
-
for (const group of
|
|
337
|
+
for (const group of importGroupOrder) {
|
|
189
338
|
for (const { declaration } of grouped[group]) {
|
|
190
339
|
if (group === "named" || group === "type")
|
|
191
340
|
sortedCode.push(formatNamedImport(declaration, sourceCode));
|
|
@@ -233,10 +382,10 @@ function groupImportsByType(categorized) {
|
|
|
233
382
|
|
|
234
383
|
// src/rules/sortedImports/createFix/sortImportGroups.ts
|
|
235
384
|
function sortImportGroups(grouped) {
|
|
236
|
-
grouped["side-effect"].sort((a, b) => a.sortKey
|
|
237
|
-
grouped["default"].sort((a, b) => a.sortKey
|
|
238
|
-
grouped["named"].sort((a, b) => a.sortKey
|
|
239
|
-
grouped["type"].sort((a, b) => a.sortKey
|
|
385
|
+
grouped["side-effect"].sort((a, b) => compare(a.sortKey, b.sortKey));
|
|
386
|
+
grouped["default"].sort((a, b) => compare(a.sortKey, b.sortKey));
|
|
387
|
+
grouped["named"].sort((a, b) => compare(a.sortKey, b.sortKey));
|
|
388
|
+
grouped["type"].sort((a, b) => compare(a.sortKey, b.sortKey));
|
|
240
389
|
}
|
|
241
390
|
|
|
242
391
|
// src/rules/sortedImports/createFix/index.ts
|
|
@@ -303,6 +452,279 @@ var sortedImports = {
|
|
|
303
452
|
}
|
|
304
453
|
};
|
|
305
454
|
|
|
455
|
+
// src/rules/sortedReExports/categorizeReExport.ts
|
|
456
|
+
function categorizeReExport(declaration) {
|
|
457
|
+
if (declaration.type === "ExportAllDeclaration")
|
|
458
|
+
return "re-export-all";
|
|
459
|
+
if (declaration.exportKind === "type")
|
|
460
|
+
return "re-export-type";
|
|
461
|
+
return "re-export-named";
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/rules/sortedReExports/getSortKey.ts
|
|
465
|
+
function getSortKey2(declaration) {
|
|
466
|
+
if (declaration.type === "ExportAllDeclaration")
|
|
467
|
+
return declaration.source.value;
|
|
468
|
+
const specifier = declaration.specifiers[0];
|
|
469
|
+
if (!specifier)
|
|
470
|
+
return "";
|
|
471
|
+
return specifier.local.type === "Identifier" ? specifier.local.name : specifier.local.value;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/rules/sortedReExports/categorizeReExports.ts
|
|
475
|
+
function categorizeReExports(declarations) {
|
|
476
|
+
return declarations.map((declaration) => {
|
|
477
|
+
return {
|
|
478
|
+
declaration,
|
|
479
|
+
group: categorizeReExport(declaration),
|
|
480
|
+
sortKey: getSortKey2(declaration)
|
|
481
|
+
};
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/rules/sortedReExports/ReExportGroupOrder.ts
|
|
486
|
+
var reExportGroupOrder = [
|
|
487
|
+
"re-export-all",
|
|
488
|
+
"re-export-named",
|
|
489
|
+
"re-export-type"
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
// src/rules/sortedReExports/checkAlphabeticalSorting.ts
|
|
493
|
+
function checkAlphabeticalSorting2(categorized) {
|
|
494
|
+
const errors = [];
|
|
495
|
+
for (const group of reExportGroupOrder) {
|
|
496
|
+
const groupReExports = categorized.filter((c) => c.group === group);
|
|
497
|
+
const sorted = [...groupReExports].sort(
|
|
498
|
+
(a, b) => compare(a.sortKey, b.sortKey)
|
|
499
|
+
);
|
|
500
|
+
for (let i = 0; i < groupReExports.length; i++) {
|
|
501
|
+
if (groupReExports[i] !== sorted[i]) {
|
|
502
|
+
errors.push({
|
|
503
|
+
node: groupReExports[i].declaration,
|
|
504
|
+
messageId: "sortedReExports"
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return errors;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/rules/sortedReExports/checkGroupOrdering.ts
|
|
513
|
+
function checkGroupOrdering2(categorized) {
|
|
514
|
+
const errors = [];
|
|
515
|
+
let currentGroupIndex = -1;
|
|
516
|
+
for (const { declaration, group } of categorized) {
|
|
517
|
+
const groupIndex = reExportGroupOrder.indexOf(group);
|
|
518
|
+
if (groupIndex < currentGroupIndex) {
|
|
519
|
+
errors.push({
|
|
520
|
+
node: declaration,
|
|
521
|
+
messageId: "wrongGroup"
|
|
522
|
+
});
|
|
523
|
+
} else
|
|
524
|
+
currentGroupIndex = groupIndex;
|
|
525
|
+
}
|
|
526
|
+
return errors;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/rules/sortedReExports/getSpecifierName.ts
|
|
530
|
+
function getSpecifierName2(specifier) {
|
|
531
|
+
return specifier.local.type === "Identifier" ? specifier.local.name : String(specifier.local.value);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/rules/sortedReExports/areSpecifiersSorted.ts
|
|
535
|
+
function areSpecifiersSorted2(specifiers) {
|
|
536
|
+
const names = specifiers.map((s) => getSpecifierName2(s));
|
|
537
|
+
const sorted = [...names].sort((a, b) => compare(a, b));
|
|
538
|
+
return names.every((name, i) => name === sorted[i]);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/rules/sortedReExports/getNamedSpecifiers.ts
|
|
542
|
+
function getNamedSpecifiers2(declaration) {
|
|
543
|
+
return declaration.specifiers.filter(
|
|
544
|
+
(s) => s.type === "ExportSpecifier" && s.local.type === "Identifier"
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/rules/sortedReExports/isNamedReExport.ts
|
|
549
|
+
function isNamedReExport(x) {
|
|
550
|
+
return x.group !== "re-export-all";
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/rules/sortedReExports/checkSpecifiersSorting.ts
|
|
554
|
+
function checkSpecifiersSorting2(categorized) {
|
|
555
|
+
const errors = [];
|
|
556
|
+
const namedReExports = categorized.filter(isNamedReExport);
|
|
557
|
+
for (const { declaration } of namedReExports) {
|
|
558
|
+
const specifiers = getNamedSpecifiers2(declaration);
|
|
559
|
+
const isSorted = areSpecifiersSorted2(specifiers);
|
|
560
|
+
if (specifiers.length > 1 && !isSorted) {
|
|
561
|
+
errors.push({
|
|
562
|
+
node: declaration,
|
|
563
|
+
messageId: "sortedNames"
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return errors;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/rules/sortedReExports/sortSpecifiersText.ts
|
|
571
|
+
function sortSpecifiersText2(specifiers, sourceCode) {
|
|
572
|
+
const sorted = [...specifiers].sort((a, b) => {
|
|
573
|
+
const nameA = getSpecifierName2(a);
|
|
574
|
+
const nameB = getSpecifierName2(b);
|
|
575
|
+
return compare(nameA, nameB);
|
|
576
|
+
});
|
|
577
|
+
return sorted.map((s) => sourceCode.getText(s)).join(", ");
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/rules/sortedReExports/createFix/formatNamedReExport.ts
|
|
581
|
+
function formatNamedReExport(declaration, sourceCode) {
|
|
582
|
+
const specifiers = getNamedSpecifiers2(declaration);
|
|
583
|
+
if (specifiers.length > 1 && !areSpecifiersSorted2(specifiers)) {
|
|
584
|
+
const sortedSpecifiers = sortSpecifiersText2(specifiers, sourceCode);
|
|
585
|
+
const source = declaration.source.value;
|
|
586
|
+
const prefix = declaration.exportKind === "type" ? "export type " : "export ";
|
|
587
|
+
return `${prefix}{${sortedSpecifiers}} from '${source}'`;
|
|
588
|
+
}
|
|
589
|
+
return sourceCode.getText(declaration);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/rules/sortedReExports/createFix/buildSortedCode.ts
|
|
593
|
+
function buildSortedCode2(grouped, sourceCode) {
|
|
594
|
+
const sortedCode = [];
|
|
595
|
+
for (const group of reExportGroupOrder) {
|
|
596
|
+
for (const item of grouped[group]) {
|
|
597
|
+
if (isNamedReExport(item)) {
|
|
598
|
+
sortedCode.push(
|
|
599
|
+
formatNamedReExport(
|
|
600
|
+
item.declaration,
|
|
601
|
+
sourceCode
|
|
602
|
+
)
|
|
603
|
+
);
|
|
604
|
+
} else
|
|
605
|
+
sortedCode.push(sourceCode.getText(item.declaration));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return sortedCode;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/rules/sortedReExports/createFix/findFirstExportIndex.ts
|
|
612
|
+
function findFirstExportIndex(programBody) {
|
|
613
|
+
for (let i = 0; i < programBody.length; i++) {
|
|
614
|
+
if (programBody[i].type === "ExportNamedDeclaration" || programBody[i].type === "ExportAllDeclaration") {
|
|
615
|
+
return i;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return -1;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/rules/sortedReExports/createFix/findLastExportIndex.ts
|
|
622
|
+
function findLastExportIndex(programBody) {
|
|
623
|
+
let lastIndex = -1;
|
|
624
|
+
for (let i = 0; i < programBody.length; i++) {
|
|
625
|
+
if (programBody[i].type === "ExportNamedDeclaration" || programBody[i].type === "ExportAllDeclaration") {
|
|
626
|
+
lastIndex = i;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return lastIndex;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// src/rules/sortedReExports/createFix/getReplacementRange.ts
|
|
633
|
+
function getReplacementRange2(programBody) {
|
|
634
|
+
const firstIndex = findFirstExportIndex(programBody);
|
|
635
|
+
const lastIndex = findLastExportIndex(programBody);
|
|
636
|
+
if (firstIndex === -1 || lastIndex === -1)
|
|
637
|
+
return { start: 0, end: 0 };
|
|
638
|
+
const firstExport = programBody[firstIndex];
|
|
639
|
+
const lastExport = programBody[lastIndex];
|
|
640
|
+
const start = firstExport.range[0];
|
|
641
|
+
const end = lastExport.range[1];
|
|
642
|
+
return { start, end };
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/rules/sortedReExports/createFix/groupReExportsByType.ts
|
|
646
|
+
function groupReExportsByType(categorized) {
|
|
647
|
+
const grouped = {
|
|
648
|
+
"re-export-all": [],
|
|
649
|
+
"re-export-named": [],
|
|
650
|
+
"re-export-type": []
|
|
651
|
+
};
|
|
652
|
+
for (const item of categorized)
|
|
653
|
+
grouped[item.group].push(item);
|
|
654
|
+
return grouped;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/rules/sortedReExports/createFix/sortExportGroups.ts
|
|
658
|
+
function sortExportGroups(grouped) {
|
|
659
|
+
grouped["re-export-all"].sort((a, b) => compare(a.sortKey, b.sortKey));
|
|
660
|
+
grouped["re-export-named"].sort((a, b) => compare(a.sortKey, b.sortKey));
|
|
661
|
+
grouped["re-export-type"].sort((a, b) => compare(a.sortKey, b.sortKey));
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/rules/sortedReExports/createFix/index.ts
|
|
665
|
+
function createFix2(fixer, reExportDeclarations, sourceCode, programBody) {
|
|
666
|
+
const range = getReplacementRange2(programBody);
|
|
667
|
+
const categorized = categorizeReExports(reExportDeclarations);
|
|
668
|
+
const grouped = groupReExportsByType(categorized);
|
|
669
|
+
sortExportGroups(grouped);
|
|
670
|
+
const sortedCode = buildSortedCode2(grouped, sourceCode).join("\n");
|
|
671
|
+
return fixer.replaceTextRange(
|
|
672
|
+
[range.start, range.end],
|
|
673
|
+
sortedCode
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/rules/sortedReExports/getReExportDeclarations.ts
|
|
678
|
+
function getReExportDeclarations(programBody) {
|
|
679
|
+
return programBody.filter(
|
|
680
|
+
(statement) => statement.type === "ExportNamedDeclaration" && statement.source !== null || statement.type === "ExportAllDeclaration"
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/rules/sortedReExports/index.ts
|
|
685
|
+
var sortedReExports = {
|
|
686
|
+
meta: {
|
|
687
|
+
docs: {
|
|
688
|
+
description: "Enforce sorted exports alphabetically",
|
|
689
|
+
recommended: true
|
|
690
|
+
},
|
|
691
|
+
fixable: "code",
|
|
692
|
+
messages: {
|
|
693
|
+
sortedReExports: "Exports should be sorted alphabetically",
|
|
694
|
+
sortedNames: "Named exports should be sorted alphabetically",
|
|
695
|
+
wrongGroup: "Export is in wrong group"
|
|
696
|
+
},
|
|
697
|
+
schema: [],
|
|
698
|
+
type: "suggestion"
|
|
699
|
+
},
|
|
700
|
+
create(context) {
|
|
701
|
+
return {
|
|
702
|
+
Program(node) {
|
|
703
|
+
const body = node.body;
|
|
704
|
+
const declarations = getReExportDeclarations(body);
|
|
705
|
+
if (declarations.length === 0)
|
|
706
|
+
return;
|
|
707
|
+
const categorized = categorizeReExports(declarations);
|
|
708
|
+
const errors = [
|
|
709
|
+
...checkGroupOrdering2(categorized),
|
|
710
|
+
...checkAlphabeticalSorting2(categorized),
|
|
711
|
+
...checkSpecifiersSorting2(categorized)
|
|
712
|
+
];
|
|
713
|
+
for (const error of errors) {
|
|
714
|
+
context.report({
|
|
715
|
+
node: error.node,
|
|
716
|
+
messageId: error.messageId,
|
|
717
|
+
fix(fixer) {
|
|
718
|
+
const sourceCode = context.sourceCode;
|
|
719
|
+
return createFix2(fixer, declarations, sourceCode, body);
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
306
728
|
// src/index.ts
|
|
307
729
|
var CONFIG = defineConfig([
|
|
308
730
|
{
|
|
@@ -334,14 +756,20 @@ var CONFIG = defineConfig([
|
|
|
334
756
|
plugins: {
|
|
335
757
|
"@borela-tech": {
|
|
336
758
|
rules: {
|
|
759
|
+
"imports-and-re-exports-at-top": importsAndReExportsAtTop,
|
|
337
760
|
"individual-imports": individualImports,
|
|
338
|
-
"
|
|
761
|
+
"individual-re-exports": individualReExports,
|
|
762
|
+
"sorted-imports": sortedImports,
|
|
763
|
+
"sorted-re-exports": sortedReExports
|
|
339
764
|
}
|
|
340
765
|
}
|
|
341
766
|
},
|
|
342
767
|
rules: {
|
|
768
|
+
"@borela-tech/imports-and-re-exports-at-top": "error",
|
|
343
769
|
"@borela-tech/individual-imports": "error",
|
|
344
|
-
"@borela-tech/
|
|
770
|
+
"@borela-tech/individual-re-exports": "error",
|
|
771
|
+
"@borela-tech/sorted-imports": "error",
|
|
772
|
+
"@borela-tech/sorted-re-exports": "error"
|
|
345
773
|
}
|
|
346
774
|
},
|
|
347
775
|
{
|