eslint-plugin-absolute 0.2.1 → 0.2.3
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/.absolutejs/eslint.cache.json +26 -26
- package/.absolutejs/prettier.cache.json +25 -25
- package/.absolutejs/tsconfig.tsbuildinfo +1 -1
- package/.codex +0 -0
- package/dist/index.js +215 -14
- package/eslint.config.mjs +2 -2
- package/package.json +21 -21
- package/src/rules/sort-exports.ts +180 -23
- package/src/rules/sort-keys-fixable.ts +138 -0
- package/tsconfig.json +2 -2
package/dist/index.js
CHANGED
|
@@ -186,6 +186,75 @@ var explicitObjectTypes = {
|
|
|
186
186
|
|
|
187
187
|
// src/rules/sort-keys-fixable.ts
|
|
188
188
|
var SORT_BEFORE = -1;
|
|
189
|
+
var hasDuplicateNames = (names) => {
|
|
190
|
+
const seen = new Set;
|
|
191
|
+
const nonNullNames = names.flatMap((name) => name === null ? [] : [name]);
|
|
192
|
+
for (const name of nonNullNames) {
|
|
193
|
+
if (seen.has(name)) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
seen.add(name);
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
};
|
|
200
|
+
var isSafeStaticTemplate = (node) => node.expressions.length === 0;
|
|
201
|
+
var isSafeArrayElement = (node) => {
|
|
202
|
+
if (!node || node.type === "SpreadElement") {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return isSafeToReorderExpression(node);
|
|
206
|
+
};
|
|
207
|
+
var isSafeObjectProperty = (property) => {
|
|
208
|
+
if (property.type !== "Property" || property.computed || property.kind !== "init") {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
if (property.key.type !== "Identifier" && property.key.type !== "Literal") {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
if (property.method) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return isSafeToReorderExpression(property.value);
|
|
218
|
+
};
|
|
219
|
+
var isSafeToReorderExpression = (node) => {
|
|
220
|
+
if (!node || node.type === "PrivateIdentifier") {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
switch (node.type) {
|
|
224
|
+
case "Identifier":
|
|
225
|
+
case "Literal":
|
|
226
|
+
case "ThisExpression":
|
|
227
|
+
case "FunctionExpression":
|
|
228
|
+
case "ArrowFunctionExpression":
|
|
229
|
+
case "ClassExpression":
|
|
230
|
+
return true;
|
|
231
|
+
case "TemplateLiteral":
|
|
232
|
+
return isSafeStaticTemplate(node);
|
|
233
|
+
case "UnaryExpression":
|
|
234
|
+
return isSafeToReorderExpression(node.argument);
|
|
235
|
+
case "ArrayExpression":
|
|
236
|
+
return node.elements.every(isSafeArrayElement);
|
|
237
|
+
case "ObjectExpression":
|
|
238
|
+
return node.properties.every(isSafeObjectProperty);
|
|
239
|
+
default:
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
var isSafeJSXAttributeValue = (value) => {
|
|
244
|
+
if (value === null) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
if (value.type === "Literal") {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
if (value.type !== "JSXExpressionContainer") {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
if (value.expression.type === "JSXEmptyExpression") {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
return isSafeToReorderExpression(value.expression);
|
|
257
|
+
};
|
|
189
258
|
var sortKeysFixable = {
|
|
190
259
|
create(context) {
|
|
191
260
|
const { sourceCode } = context;
|
|
@@ -352,6 +421,12 @@ ${indent}`;
|
|
|
352
421
|
node: prop
|
|
353
422
|
};
|
|
354
423
|
});
|
|
424
|
+
if (hasDuplicateNames(keys.map((key) => key.keyName))) {
|
|
425
|
+
autoFixable = false;
|
|
426
|
+
}
|
|
427
|
+
if (autoFixable && keys.some((key) => key.node.type === "Property" && !isSafeToReorderExpression(key.node.value))) {
|
|
428
|
+
autoFixable = false;
|
|
429
|
+
}
|
|
355
430
|
let fixProvided = false;
|
|
356
431
|
const createReportWithFix = (curr, shouldFix) => {
|
|
357
432
|
context.report({
|
|
@@ -441,6 +516,20 @@ ${indent}`;
|
|
|
441
516
|
if (!isOutOfOrder(names)) {
|
|
442
517
|
return;
|
|
443
518
|
}
|
|
519
|
+
if (hasDuplicateNames(names)) {
|
|
520
|
+
context.report({
|
|
521
|
+
messageId: "unsorted",
|
|
522
|
+
node: attrs[0].type === "JSXAttribute" ? attrs[0].name : attrs[0]
|
|
523
|
+
});
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (attrs.some((attr) => attr.type === "JSXAttribute" && !isSafeJSXAttributeValue(attr.value))) {
|
|
527
|
+
context.report({
|
|
528
|
+
messageId: "unsorted",
|
|
529
|
+
node: attrs[0].type === "JSXAttribute" ? attrs[0].name : attrs[0]
|
|
530
|
+
});
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
444
533
|
const braceConflict = attrs.find((currAttr, idx) => {
|
|
445
534
|
if (idx === 0) {
|
|
446
535
|
return false;
|
|
@@ -797,7 +886,26 @@ var noUnnecessaryKey = {
|
|
|
797
886
|
};
|
|
798
887
|
|
|
799
888
|
// src/rules/sort-exports.ts
|
|
800
|
-
var SORT_BEFORE2 = -1;
|
|
889
|
+
var SORT_BEFORE2 = Number.parseInt("-1", 10);
|
|
890
|
+
var hasStringTypeProperty = (value) => {
|
|
891
|
+
const maybeType = Reflect.get(value, "type");
|
|
892
|
+
return typeof maybeType === "string";
|
|
893
|
+
};
|
|
894
|
+
var isNodeLike = (value) => value !== null && value !== undefined && typeof value === "object" && ("type" in value) && hasStringTypeProperty(value);
|
|
895
|
+
var shouldSkipNodeEntry = (key, value) => key === "parent" || value === null || value === undefined;
|
|
896
|
+
var visitNodeArray = (values, visit) => values.filter(isNodeLike).forEach(visit);
|
|
897
|
+
var visitNodeEntryValue = (value, visit) => {
|
|
898
|
+
if (Array.isArray(value)) {
|
|
899
|
+
visitNodeArray(value, visit);
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (isNodeLike(value)) {
|
|
903
|
+
visit(value);
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
var visitNodeEntries = (current, visit) => Object.entries(current).filter(([key, value]) => !shouldSkipNodeEntry(key, value)).forEach(([, value]) => {
|
|
907
|
+
visitNodeEntryValue(value, visit);
|
|
908
|
+
});
|
|
801
909
|
var getVariableDeclaratorName = (declaration) => {
|
|
802
910
|
if (declaration.declarations.length !== 1) {
|
|
803
911
|
return null;
|
|
@@ -848,6 +956,76 @@ var isFixableExport = (exportNode) => {
|
|
|
848
956
|
}
|
|
849
957
|
return (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && declaration.id !== null && declaration.id.type === "Identifier";
|
|
850
958
|
};
|
|
959
|
+
var visitImmediateReferences = (node, onReference) => {
|
|
960
|
+
if (!node) {
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
const visit = (current) => {
|
|
964
|
+
if (!current) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
switch (current.type) {
|
|
968
|
+
case "Identifier":
|
|
969
|
+
onReference(current.name);
|
|
970
|
+
return;
|
|
971
|
+
case "FunctionDeclaration":
|
|
972
|
+
case "FunctionExpression":
|
|
973
|
+
case "ArrowFunctionExpression":
|
|
974
|
+
return;
|
|
975
|
+
case "MemberExpression":
|
|
976
|
+
visit(current.object);
|
|
977
|
+
if (current.computed) {
|
|
978
|
+
visit(current.property);
|
|
979
|
+
}
|
|
980
|
+
return;
|
|
981
|
+
case "Property":
|
|
982
|
+
if (current.computed) {
|
|
983
|
+
visit(current.key);
|
|
984
|
+
}
|
|
985
|
+
visit(current.value);
|
|
986
|
+
return;
|
|
987
|
+
case "PropertyDefinition":
|
|
988
|
+
if (current.computed) {
|
|
989
|
+
visit(current.key);
|
|
990
|
+
}
|
|
991
|
+
if (current.static) {
|
|
992
|
+
visit(current.value);
|
|
993
|
+
}
|
|
994
|
+
return;
|
|
995
|
+
case "MethodDefinition":
|
|
996
|
+
if (current.computed) {
|
|
997
|
+
visit(current.key);
|
|
998
|
+
}
|
|
999
|
+
return;
|
|
1000
|
+
case "StaticBlock":
|
|
1001
|
+
for (const statement of current.body) {
|
|
1002
|
+
visit(statement);
|
|
1003
|
+
}
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
visitNodeEntries(current, visit);
|
|
1007
|
+
};
|
|
1008
|
+
visit(node);
|
|
1009
|
+
};
|
|
1010
|
+
var getImmediateDependencyNames = (node) => {
|
|
1011
|
+
const names = new Set;
|
|
1012
|
+
const { declaration } = node;
|
|
1013
|
+
const addName = names.add.bind(names);
|
|
1014
|
+
const addDeclaratorDependencies = (declarator) => visitImmediateReferences(declarator.init, addName);
|
|
1015
|
+
const addClassElementDependencies = (element) => visitImmediateReferences(element, addName);
|
|
1016
|
+
if (!declaration) {
|
|
1017
|
+
return names;
|
|
1018
|
+
}
|
|
1019
|
+
if (declaration.type === "VariableDeclaration") {
|
|
1020
|
+
declaration.declarations.forEach(addDeclaratorDependencies);
|
|
1021
|
+
return names;
|
|
1022
|
+
}
|
|
1023
|
+
if (declaration.type === "ClassDeclaration") {
|
|
1024
|
+
visitImmediateReferences(declaration.superClass, addName);
|
|
1025
|
+
declaration.body.body.forEach(addClassElementDependencies);
|
|
1026
|
+
}
|
|
1027
|
+
return names;
|
|
1028
|
+
};
|
|
851
1029
|
var sortExports = {
|
|
852
1030
|
create(context) {
|
|
853
1031
|
const { sourceCode } = context;
|
|
@@ -903,15 +1081,6 @@ var sortExports = {
|
|
|
903
1081
|
}
|
|
904
1082
|
return compareStrings(left.name, right.name);
|
|
905
1083
|
};
|
|
906
|
-
const hasForwardDependency = (node, laterNames) => {
|
|
907
|
-
const text = sourceCode.getText(node);
|
|
908
|
-
for (const name of laterNames) {
|
|
909
|
-
if (text.includes(name)) {
|
|
910
|
-
return true;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
return false;
|
|
914
|
-
};
|
|
915
1084
|
const buildItems = (block) => block.map((node) => {
|
|
916
1085
|
const name = getExportName(node);
|
|
917
1086
|
if (!name) {
|
|
@@ -945,12 +1114,41 @@ var sortExports = {
|
|
|
945
1114
|
});
|
|
946
1115
|
return unsorted ? messageId : null;
|
|
947
1116
|
};
|
|
948
|
-
const
|
|
1117
|
+
const hasForwardDependenciesInOrder = (items) => {
|
|
949
1118
|
const exportNames = items.map((item) => item.name);
|
|
950
1119
|
return items.some((item, idx) => {
|
|
951
1120
|
const laterNames = new Set(exportNames.slice(idx + 1));
|
|
952
|
-
|
|
953
|
-
|
|
1121
|
+
if (laterNames.size === 0) {
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
const dependencies = getImmediateDependencyNames(item.node);
|
|
1125
|
+
for (const dependency of dependencies) {
|
|
1126
|
+
if (laterNames.has(dependency)) {
|
|
1127
|
+
return true;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return false;
|
|
1131
|
+
});
|
|
1132
|
+
};
|
|
1133
|
+
const wouldCreateForwardDependencies = (items, sortedItems) => {
|
|
1134
|
+
const sortedIndices = new Map(sortedItems.map((item, idx) => [item.name, idx]));
|
|
1135
|
+
const exportNames = new Set(items.map((item) => item.name));
|
|
1136
|
+
return items.some((item) => {
|
|
1137
|
+
const itemIndex = sortedIndices.get(item.name);
|
|
1138
|
+
if (itemIndex === undefined) {
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
const dependencies = getImmediateDependencyNames(item.node);
|
|
1142
|
+
for (const dependency of dependencies) {
|
|
1143
|
+
if (!exportNames.has(dependency)) {
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
const dependencyIndex = sortedIndices.get(dependency);
|
|
1147
|
+
if (dependencyIndex !== undefined && itemIndex < dependencyIndex) {
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return false;
|
|
954
1152
|
});
|
|
955
1153
|
};
|
|
956
1154
|
const processExportBlock = (block) => {
|
|
@@ -965,10 +1163,13 @@ var sortExports = {
|
|
|
965
1163
|
if (!messageId) {
|
|
966
1164
|
return;
|
|
967
1165
|
}
|
|
968
|
-
if (
|
|
1166
|
+
if (hasForwardDependenciesInOrder(items)) {
|
|
969
1167
|
return;
|
|
970
1168
|
}
|
|
971
1169
|
const sortedItems = items.slice().sort(sortComparator);
|
|
1170
|
+
if (wouldCreateForwardDependencies(items, sortedItems)) {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
972
1173
|
const expectedOrder = sortedItems.map((item) => item.name).join(", ");
|
|
973
1174
|
const [firstNode] = block;
|
|
974
1175
|
const lastNode = block[block.length - 1];
|
package/eslint.config.mjs
CHANGED
|
@@ -30,7 +30,7 @@ export default defineConfig([
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
|
-
files: ["**/*.{ts,js,mjs}"],
|
|
33
|
+
files: ["**/*.{ts,js,mjs,json}"],
|
|
34
34
|
plugins: {
|
|
35
35
|
absolute: absolutePlugin
|
|
36
36
|
},
|
|
@@ -92,7 +92,7 @@ export default defineConfig([
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
|
-
files: ["eslint.config.mjs"],
|
|
95
|
+
files: ["eslint.config.mjs", "package.json", "tsconfig.json"],
|
|
96
96
|
rules: {
|
|
97
97
|
"@typescript-eslint/no-unused-expressions": "off"
|
|
98
98
|
}
|
package/package.json
CHANGED
|
@@ -1,27 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "eslint-plugin-absolute",
|
|
3
|
-
"version": "0.2.1",
|
|
4
|
-
"description": "ESLint plugin for AbsoluteJS",
|
|
5
|
-
"repository": {
|
|
6
|
-
"type": "git",
|
|
7
|
-
"url": "https://github.com/absolutejs/eslint-plugin-absolute.git"
|
|
8
|
-
},
|
|
9
|
-
"type": "module",
|
|
10
|
-
"main": "./dist/index.js",
|
|
11
|
-
"license": "CC BY-NC 4.0",
|
|
12
2
|
"author": "Alex Kahn",
|
|
13
|
-
"
|
|
14
|
-
"test": "bun test tests/",
|
|
15
|
-
"build": "rm -rf dist && bun build src/index.ts --outdir dist --splitting --target=bun --external eslint --external @typescript-eslint/utils",
|
|
16
|
-
"format": "absolutejs prettier --write",
|
|
17
|
-
"release": "bun run format && bun run build && bun publish",
|
|
18
|
-
"prune": "ts-prune --error",
|
|
19
|
-
"lint": "bun run build && bun run absolutejs eslint",
|
|
20
|
-
"typecheck": "bun run tsc --noEmit"
|
|
21
|
-
},
|
|
3
|
+
"description": "ESLint plugin for AbsoluteJS",
|
|
22
4
|
"devDependencies": {
|
|
23
|
-
"@types/bun": "1.3.3",
|
|
24
5
|
"@absolutejs/absolute": "0.16.10",
|
|
6
|
+
"@types/bun": "1.3.3",
|
|
25
7
|
"@types/react": "19.2.14",
|
|
26
8
|
"@typescript-eslint/rule-tester": "8.56.0",
|
|
27
9
|
"@typescript-eslint/utils": "8.56.0",
|
|
@@ -30,5 +12,23 @@
|
|
|
30
12
|
"ts-prune": "0.10.3",
|
|
31
13
|
"typescript": "5.9.3",
|
|
32
14
|
"typescript-eslint": "8.56.0"
|
|
33
|
-
}
|
|
15
|
+
},
|
|
16
|
+
"license": "CC BY-NC 4.0",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"name": "eslint-plugin-absolute",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/absolutejs/eslint-plugin-absolute.git"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "rm -rf dist && bun build src/index.ts --outdir dist --splitting --target=bun --external eslint --external @typescript-eslint/utils",
|
|
25
|
+
"format": "absolutejs prettier --write",
|
|
26
|
+
"lint": "bun run build && bun run absolutejs eslint",
|
|
27
|
+
"prune": "ts-prune --error",
|
|
28
|
+
"release": "bun run format && bun run build && bun publish",
|
|
29
|
+
"test": "bun test tests/",
|
|
30
|
+
"typecheck": "bun run tsc --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"type": "module",
|
|
33
|
+
"version": "0.2.3"
|
|
34
34
|
}
|
|
@@ -30,7 +30,51 @@ type ExportItem = {
|
|
|
30
30
|
text: string;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
const SORT_BEFORE
|
|
33
|
+
const SORT_BEFORE = Number.parseInt("-1", 10);
|
|
34
|
+
|
|
35
|
+
const hasStringTypeProperty = (value: object) => {
|
|
36
|
+
const maybeType = Reflect.get(value, "type");
|
|
37
|
+
return typeof maybeType === "string";
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const isNodeLike = (value: unknown): value is TSESTree.Node =>
|
|
41
|
+
value !== null &&
|
|
42
|
+
value !== undefined &&
|
|
43
|
+
typeof value === "object" &&
|
|
44
|
+
"type" in value &&
|
|
45
|
+
hasStringTypeProperty(value);
|
|
46
|
+
|
|
47
|
+
const shouldSkipNodeEntry = (key: string, value: unknown) =>
|
|
48
|
+
key === "parent" || value === null || value === undefined;
|
|
49
|
+
|
|
50
|
+
const visitNodeArray = (
|
|
51
|
+
values: unknown[],
|
|
52
|
+
visit: (node: TSESTree.Node | null | undefined) => void
|
|
53
|
+
) => values.filter(isNodeLike).forEach(visit);
|
|
54
|
+
|
|
55
|
+
const visitNodeEntryValue = (
|
|
56
|
+
value: unknown,
|
|
57
|
+
visit: (node: TSESTree.Node | null | undefined) => void
|
|
58
|
+
) => {
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
visitNodeArray(value, visit);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (isNodeLike(value)) {
|
|
65
|
+
visit(value);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const visitNodeEntries = (
|
|
70
|
+
current: TSESTree.Node,
|
|
71
|
+
visit: (node: TSESTree.Node | null | undefined) => void
|
|
72
|
+
) =>
|
|
73
|
+
Object.entries(current)
|
|
74
|
+
.filter(([key, value]) => !shouldSkipNodeEntry(key, value))
|
|
75
|
+
.forEach(([, value]) => {
|
|
76
|
+
visitNodeEntryValue(value, visit);
|
|
77
|
+
});
|
|
34
78
|
|
|
35
79
|
const getVariableDeclaratorName = (
|
|
36
80
|
declaration: TSESTree.VariableDeclaration
|
|
@@ -114,6 +158,93 @@ const isFixableExport = (exportNode: TSESTree.ExportNamedDeclaration) => {
|
|
|
114
158
|
);
|
|
115
159
|
};
|
|
116
160
|
|
|
161
|
+
const visitImmediateReferences = (
|
|
162
|
+
node: TSESTree.Node | null | undefined,
|
|
163
|
+
onReference: (name: string) => void
|
|
164
|
+
) => {
|
|
165
|
+
if (!node) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const visit = (current: TSESTree.Node | null | undefined) => {
|
|
170
|
+
if (!current) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
switch (current.type) {
|
|
175
|
+
case "Identifier":
|
|
176
|
+
onReference(current.name);
|
|
177
|
+
return;
|
|
178
|
+
case "FunctionDeclaration":
|
|
179
|
+
case "FunctionExpression":
|
|
180
|
+
case "ArrowFunctionExpression":
|
|
181
|
+
return;
|
|
182
|
+
case "MemberExpression":
|
|
183
|
+
visit(current.object);
|
|
184
|
+
if (current.computed) {
|
|
185
|
+
visit(current.property);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
case "Property":
|
|
189
|
+
if (current.computed) {
|
|
190
|
+
visit(current.key);
|
|
191
|
+
}
|
|
192
|
+
visit(current.value);
|
|
193
|
+
return;
|
|
194
|
+
case "PropertyDefinition":
|
|
195
|
+
if (current.computed) {
|
|
196
|
+
visit(current.key);
|
|
197
|
+
}
|
|
198
|
+
if (current.static) {
|
|
199
|
+
visit(current.value);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
case "MethodDefinition":
|
|
203
|
+
if (current.computed) {
|
|
204
|
+
visit(current.key);
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
case "StaticBlock":
|
|
208
|
+
for (const statement of current.body) {
|
|
209
|
+
visit(statement);
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
visitNodeEntries(current, visit);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
visit(node);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const getImmediateDependencyNames = (node: TSESTree.ExportNamedDeclaration) => {
|
|
221
|
+
const names = new Set<string>();
|
|
222
|
+
const { declaration } = node;
|
|
223
|
+
const addName = names.add.bind(names);
|
|
224
|
+
const addDeclaratorDependencies = (
|
|
225
|
+
declarator: TSESTree.VariableDeclarator
|
|
226
|
+
) => visitImmediateReferences(declarator.init, addName);
|
|
227
|
+
const addClassElementDependencies = (
|
|
228
|
+
element: TSESTree.ClassElement | TSESTree.StaticBlock
|
|
229
|
+
) => visitImmediateReferences(element, addName);
|
|
230
|
+
|
|
231
|
+
if (!declaration) {
|
|
232
|
+
return names;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (declaration.type === "VariableDeclaration") {
|
|
236
|
+
declaration.declarations.forEach(addDeclaratorDependencies);
|
|
237
|
+
return names;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (declaration.type === "ClassDeclaration") {
|
|
241
|
+
visitImmediateReferences(declaration.superClass, addName);
|
|
242
|
+
declaration.body.body.forEach(addClassElementDependencies);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return names;
|
|
246
|
+
};
|
|
247
|
+
|
|
117
248
|
export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
|
|
118
249
|
create(context) {
|
|
119
250
|
const { sourceCode } = context;
|
|
@@ -213,23 +344,6 @@ export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
213
344
|
return compareStrings(left.name, right.name);
|
|
214
345
|
};
|
|
215
346
|
|
|
216
|
-
/**
|
|
217
|
-
* Very lightweight dependency check: look at the text of the node and see
|
|
218
|
-
* if it references any of the later export names.
|
|
219
|
-
*/
|
|
220
|
-
const hasForwardDependency = (
|
|
221
|
-
node: TSESTree.Node,
|
|
222
|
-
laterNames: Set<string>
|
|
223
|
-
) => {
|
|
224
|
-
const text = sourceCode.getText(node);
|
|
225
|
-
for (const name of laterNames) {
|
|
226
|
-
if (text.includes(name)) {
|
|
227
|
-
return true;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return false;
|
|
231
|
-
};
|
|
232
|
-
|
|
233
347
|
const buildItems = (block: TSESTree.ExportNamedDeclaration[]) =>
|
|
234
348
|
block
|
|
235
349
|
.map((node) => {
|
|
@@ -274,13 +388,52 @@ export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
274
388
|
return unsorted ? messageId : null;
|
|
275
389
|
};
|
|
276
390
|
|
|
277
|
-
const
|
|
391
|
+
const hasForwardDependenciesInOrder = (items: ExportItem[]) => {
|
|
278
392
|
const exportNames = items.map((item) => item.name);
|
|
279
393
|
return items.some((item, idx) => {
|
|
280
394
|
const laterNames = new Set(exportNames.slice(idx + 1));
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
395
|
+
if (laterNames.size === 0) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const dependencies = getImmediateDependencyNames(item.node);
|
|
400
|
+
for (const dependency of dependencies) {
|
|
401
|
+
if (laterNames.has(dependency)) {
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
});
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const wouldCreateForwardDependencies = (
|
|
410
|
+
items: ExportItem[],
|
|
411
|
+
sortedItems: ExportItem[]
|
|
412
|
+
) => {
|
|
413
|
+
const sortedIndices = new Map(
|
|
414
|
+
sortedItems.map((item, idx) => [item.name, idx])
|
|
415
|
+
);
|
|
416
|
+
const exportNames = new Set(items.map((item) => item.name));
|
|
417
|
+
|
|
418
|
+
return items.some((item) => {
|
|
419
|
+
const itemIndex = sortedIndices.get(item.name);
|
|
420
|
+
if (itemIndex === undefined) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const dependencies = getImmediateDependencyNames(item.node);
|
|
425
|
+
for (const dependency of dependencies) {
|
|
426
|
+
if (!exportNames.has(dependency)) {
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const dependencyIndex = sortedIndices.get(dependency);
|
|
431
|
+
if (dependencyIndex !== undefined && itemIndex < dependencyIndex) {
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return false;
|
|
284
437
|
});
|
|
285
438
|
};
|
|
286
439
|
|
|
@@ -302,12 +455,16 @@ export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
302
455
|
return;
|
|
303
456
|
}
|
|
304
457
|
|
|
305
|
-
if (
|
|
458
|
+
if (hasForwardDependenciesInOrder(items)) {
|
|
306
459
|
return;
|
|
307
460
|
}
|
|
308
461
|
|
|
309
462
|
const sortedItems = items.slice().sort(sortComparator);
|
|
310
463
|
|
|
464
|
+
if (wouldCreateForwardDependencies(items, sortedItems)) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
311
468
|
const expectedOrder = sortedItems
|
|
312
469
|
.map((item) => item.name)
|
|
313
470
|
.join(", ");
|