eslint-plugin-absolute 0.2.0 → 0.2.2
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 +49 -0
- package/.absolutejs/prettier.cache.json +49 -0
- package/.absolutejs/tsconfig.tsbuildinfo +1 -0
- package/.claude/settings.local.json +8 -3
- package/.codex +0 -0
- package/dist/index.js +1500 -1421
- package/eslint.config.mjs +107 -0
- package/package.json +22 -20
- package/src/index.ts +15 -15
- package/src/rules/explicit-object-types.ts +42 -40
- package/src/rules/inline-style-limit.ts +56 -54
- package/src/rules/localize-react-props.ts +261 -266
- package/src/rules/max-depth-extended.ts +55 -66
- package/src/rules/max-jsx-nesting.ts +28 -36
- package/src/rules/min-var-length.ts +238 -208
- package/src/rules/no-button-navigation.ts +114 -156
- package/src/rules/no-explicit-return-types.ts +32 -36
- package/src/rules/no-inline-prop-types.ts +30 -30
- package/src/rules/no-multi-style-objects.ts +35 -42
- package/src/rules/no-nested-jsx-return.ts +100 -105
- package/src/rules/no-or-none-component.ts +17 -19
- package/src/rules/no-transition-cssproperties.ts +76 -70
- package/src/rules/no-unnecessary-div.ts +26 -34
- package/src/rules/no-unnecessary-key.ts +41 -75
- package/src/rules/no-useless-function.ts +18 -20
- package/src/rules/seperate-style-files.ts +19 -21
- package/src/rules/sort-exports.ts +382 -256
- package/src/rules/sort-keys-fixable.ts +486 -322
- package/src/rules/spring-naming-convention.ts +104 -89
- package/tsconfig.json +3 -1
|
@@ -30,52 +30,225 @@ type ExportItem = {
|
|
|
30
30
|
text: string;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
+
});
|
|
78
|
+
|
|
79
|
+
const getVariableDeclaratorName = (
|
|
80
|
+
declaration: TSESTree.VariableDeclaration
|
|
81
|
+
) => {
|
|
82
|
+
if (declaration.declarations.length !== 1) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const [firstDeclarator] = declaration.declarations;
|
|
86
|
+
if (firstDeclarator && firstDeclarator.id.type === "Identifier") {
|
|
87
|
+
return firstDeclarator.id.name;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const getDeclarationName = (
|
|
93
|
+
declaration: TSESTree.ExportNamedDeclaration["declaration"]
|
|
94
|
+
) => {
|
|
95
|
+
if (!declaration) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (declaration.type === "VariableDeclaration") {
|
|
100
|
+
return getVariableDeclaratorName(declaration);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
(declaration.type === "FunctionDeclaration" ||
|
|
105
|
+
declaration.type === "ClassDeclaration") &&
|
|
106
|
+
declaration.id &&
|
|
107
|
+
declaration.id.type === "Identifier"
|
|
108
|
+
) {
|
|
109
|
+
return declaration.id.name;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const getSpecifierName = (node: TSESTree.ExportNamedDeclaration) => {
|
|
116
|
+
if (node.specifiers.length !== 1) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const [spec] = node.specifiers;
|
|
120
|
+
if (!spec) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (spec.exported.type === "Identifier") {
|
|
124
|
+
return spec.exported.name;
|
|
125
|
+
}
|
|
126
|
+
if (
|
|
127
|
+
spec.exported.type === "Literal" &&
|
|
128
|
+
typeof spec.exported.value === "string"
|
|
129
|
+
) {
|
|
130
|
+
return spec.exported.value;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const getExportName = (node: TSESTree.ExportNamedDeclaration) =>
|
|
136
|
+
getDeclarationName(node.declaration) ?? getSpecifierName(node);
|
|
137
|
+
|
|
138
|
+
const isFixableExport = (exportNode: TSESTree.ExportNamedDeclaration) => {
|
|
139
|
+
const { declaration } = exportNode;
|
|
140
|
+
|
|
141
|
+
if (!declaration) {
|
|
142
|
+
return exportNode.specifiers.length === 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (
|
|
146
|
+
declaration.type === "VariableDeclaration" &&
|
|
147
|
+
declaration.declarations.length === 1
|
|
148
|
+
) {
|
|
149
|
+
const [firstDecl] = declaration.declarations;
|
|
150
|
+
return firstDecl !== undefined && firstDecl.id.type === "Identifier";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
(declaration.type === "FunctionDeclaration" ||
|
|
155
|
+
declaration.type === "ClassDeclaration") &&
|
|
156
|
+
declaration.id !== null &&
|
|
157
|
+
declaration.id.type === "Identifier"
|
|
158
|
+
);
|
|
159
|
+
};
|
|
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;
|
|
71
172
|
}
|
|
72
|
-
},
|
|
73
173
|
|
|
74
|
-
|
|
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
|
+
};
|
|
75
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
|
+
|
|
248
|
+
export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
|
|
76
249
|
create(context) {
|
|
77
|
-
const sourceCode = context
|
|
78
|
-
const option = context.options
|
|
250
|
+
const { sourceCode } = context;
|
|
251
|
+
const [option] = context.options;
|
|
79
252
|
|
|
80
253
|
const order: "asc" | "desc" =
|
|
81
254
|
option && option.order ? option.order : "asc";
|
|
@@ -98,217 +271,170 @@ export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
98
271
|
? option.variablesBeforeFunctions
|
|
99
272
|
: false;
|
|
100
273
|
|
|
101
|
-
|
|
102
|
-
|
|
274
|
+
const generateExportText = (node: TSESTree.ExportNamedDeclaration) =>
|
|
275
|
+
sourceCode
|
|
103
276
|
.getText(node)
|
|
104
277
|
.trim()
|
|
105
278
|
.replace(/\s*;?\s*$/, ";");
|
|
106
|
-
}
|
|
107
279
|
|
|
108
|
-
|
|
109
|
-
let
|
|
110
|
-
let
|
|
280
|
+
const compareStrings = (strLeft: string, strRight: string) => {
|
|
281
|
+
let left = strLeft;
|
|
282
|
+
let right = strRight;
|
|
111
283
|
|
|
112
284
|
if (!caseSensitive) {
|
|
113
|
-
|
|
114
|
-
|
|
285
|
+
left = left.toLowerCase();
|
|
286
|
+
right = right.toLowerCase();
|
|
115
287
|
}
|
|
116
288
|
|
|
117
289
|
const cmp = natural
|
|
118
|
-
?
|
|
119
|
-
:
|
|
290
|
+
? left.localeCompare(right, undefined, { numeric: true })
|
|
291
|
+
: left.localeCompare(right);
|
|
120
292
|
|
|
121
293
|
return order === "asc" ? cmp : -cmp;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function getExportName(
|
|
125
|
-
node: TSESTree.ExportNamedDeclaration
|
|
126
|
-
): string | null {
|
|
127
|
-
const declaration = node.declaration;
|
|
128
|
-
|
|
129
|
-
if (declaration) {
|
|
130
|
-
if (declaration.type === "VariableDeclaration") {
|
|
131
|
-
if (declaration.declarations.length === 1) {
|
|
132
|
-
const firstDeclarator = declaration.declarations[0];
|
|
133
|
-
if (
|
|
134
|
-
firstDeclarator &&
|
|
135
|
-
firstDeclarator.id.type === "Identifier"
|
|
136
|
-
) {
|
|
137
|
-
return firstDeclarator.id.name;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
} else if (
|
|
141
|
-
declaration.type === "FunctionDeclaration" ||
|
|
142
|
-
declaration.type === "ClassDeclaration"
|
|
143
|
-
) {
|
|
144
|
-
const id = declaration.id;
|
|
145
|
-
if (id && id.type === "Identifier") {
|
|
146
|
-
return id.name;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
} else if (node.specifiers.length === 1) {
|
|
150
|
-
const spec = node.specifiers[0];
|
|
151
|
-
if (!spec) {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
if (spec.exported.type === "Identifier") {
|
|
155
|
-
return spec.exported.name;
|
|
156
|
-
}
|
|
157
|
-
if (
|
|
158
|
-
spec.exported.type === "Literal" &&
|
|
159
|
-
typeof spec.exported.value === "string"
|
|
160
|
-
) {
|
|
161
|
-
return spec.exported.value;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
294
|
+
};
|
|
167
295
|
|
|
168
|
-
|
|
169
|
-
const declaration = node
|
|
296
|
+
const isFunctionExport = (node: TSESTree.ExportNamedDeclaration) => {
|
|
297
|
+
const { declaration } = node;
|
|
170
298
|
|
|
171
299
|
if (!declaration) {
|
|
172
300
|
return false;
|
|
173
301
|
}
|
|
174
302
|
|
|
175
|
-
if (declaration.type === "VariableDeclaration") {
|
|
176
|
-
if (declaration.declarations.length === 1) {
|
|
177
|
-
const firstDeclarator = declaration.declarations[0];
|
|
178
|
-
if (!firstDeclarator) {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
const init = firstDeclarator.init;
|
|
182
|
-
if (!init) {
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
return (
|
|
186
|
-
init.type === "FunctionExpression" ||
|
|
187
|
-
init.type === "ArrowFunctionExpression"
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
303
|
if (declaration.type === "FunctionDeclaration") {
|
|
194
304
|
return true;
|
|
195
305
|
}
|
|
196
306
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
function sortComparator(a: ExportItem, b: ExportItem) {
|
|
201
|
-
const kindA = a.node.exportKind ?? "value";
|
|
202
|
-
const kindB = b.node.exportKind ?? "value";
|
|
203
|
-
|
|
204
|
-
if (kindA !== kindB) {
|
|
205
|
-
return kindA === "type" ? -1 : 1;
|
|
307
|
+
if (declaration.type !== "VariableDeclaration") {
|
|
308
|
+
return false;
|
|
206
309
|
}
|
|
207
310
|
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
return a.isFunction ? 1 : -1;
|
|
211
|
-
}
|
|
311
|
+
if (declaration.declarations.length !== 1) {
|
|
312
|
+
return false;
|
|
212
313
|
}
|
|
213
314
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Very lightweight dependency check: look at the text of the node and see
|
|
219
|
-
* if it references any of the later export names. This avoids reordering
|
|
220
|
-
* when there might be a forward dependency.
|
|
221
|
-
*/
|
|
222
|
-
function hasForwardDependency(
|
|
223
|
-
node: TSESTree.Node,
|
|
224
|
-
laterNames: Set<string>
|
|
225
|
-
) {
|
|
226
|
-
const text = sourceCode.getText(node);
|
|
227
|
-
for (const name of laterNames) {
|
|
228
|
-
if (text.includes(name)) {
|
|
229
|
-
return true;
|
|
230
|
-
}
|
|
315
|
+
const [firstDeclarator] = declaration.declarations;
|
|
316
|
+
if (!firstDeclarator) {
|
|
317
|
+
return false;
|
|
231
318
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
function processExportBlock(block: TSESTree.ExportNamedDeclaration[]) {
|
|
236
|
-
if (block.length < minKeys) {
|
|
237
|
-
return;
|
|
319
|
+
const { init } = firstDeclarator;
|
|
320
|
+
if (!init) {
|
|
321
|
+
return false;
|
|
238
322
|
}
|
|
323
|
+
return (
|
|
324
|
+
init.type === "FunctionExpression" ||
|
|
325
|
+
init.type === "ArrowFunctionExpression"
|
|
326
|
+
);
|
|
327
|
+
};
|
|
239
328
|
|
|
240
|
-
|
|
329
|
+
const sortComparator = (left: ExportItem, right: ExportItem) => {
|
|
330
|
+
const kindA = left.node.exportKind ?? "value";
|
|
331
|
+
const kindB = right.node.exportKind ?? "value";
|
|
241
332
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (!name) {
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
items.push({
|
|
249
|
-
name,
|
|
250
|
-
node,
|
|
251
|
-
isFunction: isFunctionExport(node),
|
|
252
|
-
text: sourceCode.getText(node)
|
|
253
|
-
});
|
|
333
|
+
if (kindA !== kindB) {
|
|
334
|
+
return kindA === "type" ? SORT_BEFORE : 1;
|
|
254
335
|
}
|
|
255
336
|
|
|
256
|
-
if (
|
|
257
|
-
|
|
337
|
+
if (
|
|
338
|
+
variablesBeforeFunctions &&
|
|
339
|
+
left.isFunction !== right.isFunction
|
|
340
|
+
) {
|
|
341
|
+
return left.isFunction ? 1 : SORT_BEFORE;
|
|
258
342
|
}
|
|
259
343
|
|
|
260
|
-
|
|
344
|
+
return compareStrings(left.name, right.name);
|
|
345
|
+
};
|
|
261
346
|
|
|
262
|
-
|
|
347
|
+
const buildItems = (block: TSESTree.ExportNamedDeclaration[]) =>
|
|
348
|
+
block
|
|
349
|
+
.map((node) => {
|
|
350
|
+
const name = getExportName(node);
|
|
351
|
+
if (!name) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
const item: ExportItem = {
|
|
355
|
+
isFunction: isFunctionExport(node),
|
|
356
|
+
name,
|
|
357
|
+
node,
|
|
358
|
+
text: sourceCode.getText(node)
|
|
359
|
+
};
|
|
360
|
+
return item;
|
|
361
|
+
})
|
|
362
|
+
.filter((item): item is ExportItem => item !== null);
|
|
363
|
+
|
|
364
|
+
const findFirstUnsorted = (items: ExportItem[]) => {
|
|
263
365
|
let messageId: MessageIds = "alphabetical";
|
|
264
366
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (!prev || !current) {
|
|
270
|
-
continue;
|
|
367
|
+
const unsorted = items.some((current, idx) => {
|
|
368
|
+
if (idx === 0) {
|
|
369
|
+
return false;
|
|
271
370
|
}
|
|
371
|
+
const prev = items[idx - 1];
|
|
372
|
+
if (!prev) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
if (sortComparator(prev, current) <= 0) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
if (
|
|
379
|
+
variablesBeforeFunctions &&
|
|
380
|
+
prev.isFunction &&
|
|
381
|
+
!current.isFunction
|
|
382
|
+
) {
|
|
383
|
+
messageId = "variablesBeforeFunctions";
|
|
384
|
+
}
|
|
385
|
+
return true;
|
|
386
|
+
});
|
|
272
387
|
|
|
273
|
-
|
|
274
|
-
|
|
388
|
+
return unsorted ? messageId : null;
|
|
389
|
+
};
|
|
275
390
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
391
|
+
const checkForwardDependencies = (items: ExportItem[]) => {
|
|
392
|
+
const exportNames = items.map((item) => item.name);
|
|
393
|
+
return items.some((item, idx) => {
|
|
394
|
+
const laterNames = new Set(exportNames.slice(idx + 1));
|
|
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;
|
|
282
403
|
}
|
|
283
|
-
break;
|
|
284
404
|
}
|
|
285
|
-
|
|
405
|
+
return false;
|
|
406
|
+
});
|
|
407
|
+
};
|
|
286
408
|
|
|
287
|
-
|
|
409
|
+
const processExportBlock = (
|
|
410
|
+
block: TSESTree.ExportNamedDeclaration[]
|
|
411
|
+
) => {
|
|
412
|
+
if (block.length < minKeys) {
|
|
288
413
|
return;
|
|
289
414
|
}
|
|
290
415
|
|
|
291
|
-
const
|
|
416
|
+
const items = buildItems(block);
|
|
292
417
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
const laterNames = new Set(exportNames.slice(i + 1));
|
|
299
|
-
const nodeToCheck: TSESTree.Node =
|
|
300
|
-
item.node.declaration ?? item.node;
|
|
418
|
+
if (items.length < minKeys) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
301
421
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
422
|
+
const messageId = findFirstUnsorted(items);
|
|
423
|
+
if (!messageId) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (checkForwardDependencies(items)) {
|
|
428
|
+
return;
|
|
305
429
|
}
|
|
306
430
|
|
|
431
|
+
const sortedItems = items.slice().sort(sortComparator);
|
|
432
|
+
|
|
307
433
|
const expectedOrder = sortedItems
|
|
308
434
|
.map((item) => item.name)
|
|
309
435
|
.join(", ");
|
|
310
436
|
|
|
311
|
-
const firstNode = block
|
|
437
|
+
const [firstNode] = block;
|
|
312
438
|
const lastNode = block[block.length - 1];
|
|
313
439
|
|
|
314
440
|
if (!firstNode || !lastNode) {
|
|
@@ -316,49 +442,11 @@ export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
316
442
|
}
|
|
317
443
|
|
|
318
444
|
context.report({
|
|
319
|
-
node: firstNode,
|
|
320
|
-
messageId,
|
|
321
445
|
data: {
|
|
322
446
|
expectedOrder
|
|
323
447
|
},
|
|
324
448
|
fix(fixer) {
|
|
325
|
-
const fixableNodes
|
|
326
|
-
|
|
327
|
-
for (const n of block) {
|
|
328
|
-
const declaration = n.declaration;
|
|
329
|
-
|
|
330
|
-
if (declaration) {
|
|
331
|
-
if (
|
|
332
|
-
declaration.type === "VariableDeclaration" &&
|
|
333
|
-
declaration.declarations.length === 1
|
|
334
|
-
) {
|
|
335
|
-
const firstDecl = declaration.declarations[0];
|
|
336
|
-
if (
|
|
337
|
-
firstDecl &&
|
|
338
|
-
firstDecl.id.type === "Identifier"
|
|
339
|
-
) {
|
|
340
|
-
fixableNodes.push(n);
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (
|
|
346
|
-
(declaration.type === "FunctionDeclaration" ||
|
|
347
|
-
declaration.type === "ClassDeclaration") &&
|
|
348
|
-
declaration.id &&
|
|
349
|
-
declaration.id.type === "Identifier"
|
|
350
|
-
) {
|
|
351
|
-
fixableNodes.push(n);
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
continue;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (n.specifiers.length === 1) {
|
|
359
|
-
fixableNodes.push(n);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
449
|
+
const fixableNodes = block.filter(isFixableExport);
|
|
362
450
|
|
|
363
451
|
if (fixableNodes.length < minKeys) {
|
|
364
452
|
return null;
|
|
@@ -368,8 +456,8 @@ export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
368
456
|
.map((item) => generateExportText(item.node))
|
|
369
457
|
.join("\n");
|
|
370
458
|
|
|
371
|
-
const rangeStart = firstNode.range
|
|
372
|
-
const rangeEnd = lastNode.range
|
|
459
|
+
const [rangeStart] = firstNode.range;
|
|
460
|
+
const [, rangeEnd] = lastNode.range;
|
|
373
461
|
|
|
374
462
|
const fullText = sourceCode.getText();
|
|
375
463
|
const originalText = fullText.slice(rangeStart, rangeEnd);
|
|
@@ -382,39 +470,77 @@ export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
382
470
|
[rangeStart, rangeEnd],
|
|
383
471
|
sortedText
|
|
384
472
|
);
|
|
385
|
-
}
|
|
473
|
+
},
|
|
474
|
+
messageId,
|
|
475
|
+
node: firstNode
|
|
386
476
|
});
|
|
387
|
-
}
|
|
477
|
+
};
|
|
388
478
|
|
|
389
479
|
return {
|
|
390
480
|
"Program:exit"(node: TSESTree.Program) {
|
|
391
|
-
const body = node
|
|
481
|
+
const { body } = node;
|
|
392
482
|
const block: TSESTree.ExportNamedDeclaration[] = [];
|
|
393
483
|
|
|
394
|
-
|
|
395
|
-
const stmt = body[i];
|
|
396
|
-
if (!stmt) {
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
|
|
484
|
+
body.forEach((stmt) => {
|
|
400
485
|
if (
|
|
401
486
|
stmt.type === "ExportNamedDeclaration" &&
|
|
402
487
|
!stmt.source &&
|
|
403
488
|
getExportName(stmt) !== null
|
|
404
489
|
) {
|
|
405
490
|
block.push(stmt);
|
|
406
|
-
|
|
407
|
-
if (block.length > 0) {
|
|
408
|
-
processExportBlock(block);
|
|
409
|
-
block.length = 0;
|
|
410
|
-
}
|
|
491
|
+
return;
|
|
411
492
|
}
|
|
412
|
-
|
|
493
|
+
|
|
494
|
+
if (block.length > 0) {
|
|
495
|
+
processExportBlock(block);
|
|
496
|
+
block.length = 0;
|
|
497
|
+
}
|
|
498
|
+
});
|
|
413
499
|
|
|
414
500
|
if (block.length > 0) {
|
|
415
501
|
processExportBlock(block);
|
|
416
502
|
}
|
|
417
503
|
}
|
|
418
504
|
};
|
|
505
|
+
},
|
|
506
|
+
defaultOptions: [{}],
|
|
507
|
+
meta: {
|
|
508
|
+
docs: {
|
|
509
|
+
description:
|
|
510
|
+
"Enforce that top-level export declarations are sorted by exported name and, optionally, that variable exports come before function exports"
|
|
511
|
+
},
|
|
512
|
+
fixable: "code",
|
|
513
|
+
messages: {
|
|
514
|
+
alphabetical:
|
|
515
|
+
"Export declarations are not sorted alphabetically. Expected order: {{expectedOrder}}.",
|
|
516
|
+
variablesBeforeFunctions:
|
|
517
|
+
"Non-function exports should come before function exports."
|
|
518
|
+
},
|
|
519
|
+
schema: [
|
|
520
|
+
{
|
|
521
|
+
additionalProperties: false,
|
|
522
|
+
properties: {
|
|
523
|
+
caseSensitive: {
|
|
524
|
+
type: "boolean"
|
|
525
|
+
},
|
|
526
|
+
minKeys: {
|
|
527
|
+
minimum: 2,
|
|
528
|
+
type: "integer"
|
|
529
|
+
},
|
|
530
|
+
natural: {
|
|
531
|
+
type: "boolean"
|
|
532
|
+
},
|
|
533
|
+
order: {
|
|
534
|
+
enum: ["asc", "desc"],
|
|
535
|
+
type: "string"
|
|
536
|
+
},
|
|
537
|
+
variablesBeforeFunctions: {
|
|
538
|
+
type: "boolean"
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
type: "object"
|
|
542
|
+
}
|
|
543
|
+
],
|
|
544
|
+
type: "suggestion"
|
|
419
545
|
}
|
|
420
546
|
};
|