@borela-tech/eslint-config 2.0.0 → 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/bin/lint +2 -1
- package/bin/typecheck +8 -0
- package/dist/index.js +478 -51
- 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__/individualImports.test.ts +2 -2
- package/src/rules/__tests__/individualReExports.test.ts +62 -0
- package/src/rules/__tests__/sortedImports.test.ts +38 -4
- 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 +9 -2
- package/src/rules/sortedImports/categorizeImports.ts +3 -3
- package/src/rules/sortedImports/checkAlphabeticalSorting.ts +4 -3
- package/src/rules/sortedImports/checkGroupOrdering.ts +2 -3
- package/src/rules/sortedImports/checkSpecifiersSorting.ts +2 -2
- package/src/rules/sortedImports/createFix/buildSortedCode.ts +4 -4
- package/src/rules/sortedImports/createFix/findLastImportIndex.ts +2 -2
- package/src/rules/sortedImports/createFix/formatNamedImport.ts +5 -8
- package/src/rules/sortedImports/createFix/getReplacementRange.ts +6 -18
- package/src/rules/sortedImports/createFix/groupImportsByType.ts +1 -0
- package/src/rules/sortedImports/createFix/index.ts +5 -6
- package/src/rules/sortedImports/createFix/sortImportGroups.ts +5 -2
- package/src/rules/sortedImports/getImportDeclarations.ts +3 -4
- package/src/rules/sortedImports/getNamedSpecifiers.ts +3 -4
- package/src/rules/sortedImports/getSortKey.ts +7 -7
- package/src/rules/sortedImports/getSpecifierName.ts +2 -2
- package/src/rules/sortedImports/index.ts +6 -4
- 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
|
}
|
|
@@ -50,6 +193,8 @@ var individualImports = {
|
|
|
50
193
|
|
|
51
194
|
// src/rules/sortedImports/categorizeImport.ts
|
|
52
195
|
function categorizeImport(declaration) {
|
|
196
|
+
if (declaration.importKind === "type")
|
|
197
|
+
return "type";
|
|
53
198
|
if (declaration.specifiers.length === 0)
|
|
54
199
|
return "side-effect";
|
|
55
200
|
if (declaration.specifiers.some((s) => s.type === "ImportDefaultSpecifier"))
|
|
@@ -61,14 +206,15 @@ function categorizeImport(declaration) {
|
|
|
61
206
|
function getSortKey(declaration) {
|
|
62
207
|
const group = categorizeImport(declaration);
|
|
63
208
|
if (group === "side-effect")
|
|
64
|
-
return declaration.source.value
|
|
209
|
+
return declaration.source.value;
|
|
65
210
|
if (group === "default") {
|
|
66
211
|
const defaultSpecifier = declaration.specifiers.find(
|
|
67
212
|
(s) => s.type === "ImportDefaultSpecifier"
|
|
68
213
|
);
|
|
69
|
-
return defaultSpecifier?.local.name
|
|
214
|
+
return defaultSpecifier?.local.name ?? "";
|
|
70
215
|
}
|
|
71
|
-
|
|
216
|
+
const specifier = declaration.specifiers[0];
|
|
217
|
+
return specifier.local.name;
|
|
72
218
|
}
|
|
73
219
|
|
|
74
220
|
// src/rules/sortedImports/categorizeImports.ts
|
|
@@ -80,12 +226,25 @@ function categorizeImports(declarations) {
|
|
|
80
226
|
}));
|
|
81
227
|
}
|
|
82
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
|
+
|
|
83
242
|
// src/rules/sortedImports/checkAlphabeticalSorting.ts
|
|
84
243
|
function checkAlphabeticalSorting(categorized) {
|
|
85
244
|
const errors = [];
|
|
86
|
-
for (const group of
|
|
245
|
+
for (const group of importGroupOrder) {
|
|
87
246
|
const groupImports = categorized.filter((c) => c.group === group);
|
|
88
|
-
const sorted = [...groupImports].sort((a, b) => a.sortKey
|
|
247
|
+
const sorted = [...groupImports].sort((a, b) => compare(a.sortKey, b.sortKey));
|
|
89
248
|
for (let i = 0; i < groupImports.length; i++) {
|
|
90
249
|
if (groupImports[i] !== sorted[i]) {
|
|
91
250
|
errors.push({
|
|
@@ -100,11 +259,10 @@ function checkAlphabeticalSorting(categorized) {
|
|
|
100
259
|
|
|
101
260
|
// src/rules/sortedImports/checkGroupOrdering.ts
|
|
102
261
|
function checkGroupOrdering(categorized) {
|
|
103
|
-
const groupOrder = ["side-effect", "default", "named"];
|
|
104
262
|
const errors = [];
|
|
105
263
|
let currentGroupIndex = -1;
|
|
106
264
|
for (const { declaration, group } of categorized) {
|
|
107
|
-
const groupIndex =
|
|
265
|
+
const groupIndex = importGroupOrder.indexOf(group);
|
|
108
266
|
if (groupIndex < currentGroupIndex) {
|
|
109
267
|
errors.push({
|
|
110
268
|
node: declaration,
|
|
@@ -124,9 +282,7 @@ function getSpecifierName(specifier) {
|
|
|
124
282
|
// src/rules/sortedImports/areSpecifiersSorted.ts
|
|
125
283
|
function areSpecifiersSorted(specifiers) {
|
|
126
284
|
const names = specifiers.map((s) => getSpecifierName(s));
|
|
127
|
-
const sorted = [...names].sort(
|
|
128
|
-
(a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
|
|
129
|
-
);
|
|
285
|
+
const sorted = [...names].sort((a, b) => compare(a, b));
|
|
130
286
|
return names.every((name, i) => name === sorted[i]);
|
|
131
287
|
}
|
|
132
288
|
|
|
@@ -156,9 +312,9 @@ function checkSpecifiersSorting(categorized) {
|
|
|
156
312
|
// src/rules/sortedImports/sortSpecifiersText.ts
|
|
157
313
|
function sortSpecifiersText(specifiers, sourceCode) {
|
|
158
314
|
const sorted = [...specifiers].sort((a, b) => {
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
return
|
|
315
|
+
const nameA = getSpecifierName(a);
|
|
316
|
+
const nameB = getSpecifierName(b);
|
|
317
|
+
return compare(nameA, nameB);
|
|
162
318
|
});
|
|
163
319
|
return sorted.map((s) => sourceCode.getText(s)).join(", ");
|
|
164
320
|
}
|
|
@@ -167,24 +323,20 @@ function sortSpecifiersText(specifiers, sourceCode) {
|
|
|
167
323
|
function formatNamedImport(declaration, sourceCode) {
|
|
168
324
|
const specifiers = getNamedSpecifiers(declaration);
|
|
169
325
|
if (specifiers.length > 1 && !areSpecifiersSorted(specifiers)) {
|
|
170
|
-
const importText = sourceCode.getText(declaration);
|
|
171
|
-
const specifiersStart = importText.indexOf("{");
|
|
172
|
-
const specifiersEnd = importText.lastIndexOf("}");
|
|
173
|
-
const before = importText.substring(0, specifiersStart + 1);
|
|
174
|
-
const after = importText.substring(specifiersEnd);
|
|
175
326
|
const sortedSpecifiers = sortSpecifiersText(specifiers, sourceCode);
|
|
176
|
-
|
|
327
|
+
const source = declaration.source.value;
|
|
328
|
+
const prefix = declaration.importKind === "type" ? "import type " : "import ";
|
|
329
|
+
return `${prefix}{${sortedSpecifiers}} from '${source}'`;
|
|
177
330
|
}
|
|
178
331
|
return sourceCode.getText(declaration);
|
|
179
332
|
}
|
|
180
333
|
|
|
181
334
|
// src/rules/sortedImports/createFix/buildSortedCode.ts
|
|
182
335
|
function buildSortedCode(grouped, sourceCode) {
|
|
183
|
-
const groupOrder = ["side-effect", "default", "named"];
|
|
184
336
|
const sortedCode = [];
|
|
185
|
-
for (const group of
|
|
337
|
+
for (const group of importGroupOrder) {
|
|
186
338
|
for (const { declaration } of grouped[group]) {
|
|
187
|
-
if (group === "named")
|
|
339
|
+
if (group === "named" || group === "type")
|
|
188
340
|
sortedCode.push(formatNamedImport(declaration, sourceCode));
|
|
189
341
|
else
|
|
190
342
|
sortedCode.push(sourceCode.getText(declaration));
|
|
@@ -206,20 +358,12 @@ function findLastImportIndex(programBody) {
|
|
|
206
358
|
}
|
|
207
359
|
|
|
208
360
|
// src/rules/sortedImports/createFix/getReplacementRange.ts
|
|
209
|
-
function getReplacementRange(programBody
|
|
210
|
-
const fullText = sourceCode.getText();
|
|
361
|
+
function getReplacementRange(programBody) {
|
|
211
362
|
const lastIndex = findLastImportIndex(programBody);
|
|
212
363
|
const firstImport = programBody[0];
|
|
213
364
|
const lastImport = programBody[lastIndex];
|
|
214
365
|
const start = firstImport.range[0];
|
|
215
|
-
|
|
216
|
-
for (let i = end; i < fullText.length; i++) {
|
|
217
|
-
const char = fullText[i];
|
|
218
|
-
if (char === "\n" || char === " " || char === " ")
|
|
219
|
-
end++;
|
|
220
|
-
else
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
366
|
+
const end = lastImport.range[1];
|
|
223
367
|
return { start, end };
|
|
224
368
|
}
|
|
225
369
|
|
|
@@ -228,7 +372,8 @@ function groupImportsByType(categorized) {
|
|
|
228
372
|
const grouped = {
|
|
229
373
|
"side-effect": [],
|
|
230
374
|
default: [],
|
|
231
|
-
named: []
|
|
375
|
+
named: [],
|
|
376
|
+
type: []
|
|
232
377
|
};
|
|
233
378
|
for (const item of categorized)
|
|
234
379
|
grouped[item.group].push(item);
|
|
@@ -237,13 +382,15 @@ function groupImportsByType(categorized) {
|
|
|
237
382
|
|
|
238
383
|
// src/rules/sortedImports/createFix/sortImportGroups.ts
|
|
239
384
|
function sortImportGroups(grouped) {
|
|
240
|
-
grouped["side-effect"].sort((a, b) => a.sortKey
|
|
241
|
-
grouped["default"].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));
|
|
242
389
|
}
|
|
243
390
|
|
|
244
391
|
// src/rules/sortedImports/createFix/index.ts
|
|
245
392
|
function createFix(fixer, importDeclarations, sourceCode, programBody) {
|
|
246
|
-
const range = getReplacementRange(programBody
|
|
393
|
+
const range = getReplacementRange(programBody);
|
|
247
394
|
const categorized = categorizeImports(importDeclarations);
|
|
248
395
|
const grouped = groupImportsByType(categorized);
|
|
249
396
|
sortImportGroups(grouped);
|
|
@@ -280,7 +427,8 @@ var sortedImports = {
|
|
|
280
427
|
create(context) {
|
|
281
428
|
return {
|
|
282
429
|
Program(node) {
|
|
283
|
-
const
|
|
430
|
+
const body = node.body;
|
|
431
|
+
const declarations = getImportDeclarations(body);
|
|
284
432
|
if (declarations.length === 0)
|
|
285
433
|
return;
|
|
286
434
|
const categorized = categorizeImports(declarations);
|
|
@@ -295,7 +443,280 @@ var sortedImports = {
|
|
|
295
443
|
messageId: error.messageId,
|
|
296
444
|
fix(fixer) {
|
|
297
445
|
const sourceCode = context.sourceCode;
|
|
298
|
-
return createFix(fixer, declarations, sourceCode,
|
|
446
|
+
return createFix(fixer, declarations, sourceCode, body);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
};
|
|
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);
|
|
299
720
|
}
|
|
300
721
|
});
|
|
301
722
|
}
|
|
@@ -335,14 +756,20 @@ var CONFIG = defineConfig([
|
|
|
335
756
|
plugins: {
|
|
336
757
|
"@borela-tech": {
|
|
337
758
|
rules: {
|
|
759
|
+
"imports-and-re-exports-at-top": importsAndReExportsAtTop,
|
|
338
760
|
"individual-imports": individualImports,
|
|
339
|
-
"
|
|
761
|
+
"individual-re-exports": individualReExports,
|
|
762
|
+
"sorted-imports": sortedImports,
|
|
763
|
+
"sorted-re-exports": sortedReExports
|
|
340
764
|
}
|
|
341
765
|
}
|
|
342
766
|
},
|
|
343
767
|
rules: {
|
|
768
|
+
"@borela-tech/imports-and-re-exports-at-top": "error",
|
|
344
769
|
"@borela-tech/individual-imports": "error",
|
|
345
|
-
"@borela-tech/
|
|
770
|
+
"@borela-tech/individual-re-exports": "error",
|
|
771
|
+
"@borela-tech/sorted-imports": "error",
|
|
772
|
+
"@borela-tech/sorted-re-exports": "error"
|
|
346
773
|
}
|
|
347
774
|
},
|
|
348
775
|
{
|