eslint-plugin-absolute 0.1.6 → 0.2.1
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 +10 -0
- package/dist/index.js +1787 -1457
- package/eslint.config.mjs +107 -0
- package/package.json +15 -12
- package/src/index.ts +45 -0
- package/src/rules/explicit-object-types.ts +75 -0
- package/src/rules/inline-style-limit.ts +88 -0
- package/src/rules/localize-react-props.ts +454 -0
- package/src/rules/max-depth-extended.ts +153 -0
- package/src/rules/{max-jsx-nesting.js → max-jsx-nesting.ts} +37 -38
- package/src/rules/min-var-length.ts +360 -0
- package/src/rules/no-button-navigation.ts +270 -0
- package/src/rules/no-explicit-return-types.ts +83 -0
- package/src/rules/no-inline-prop-types.ts +68 -0
- package/src/rules/no-multi-style-objects.ts +80 -0
- package/src/rules/no-nested-jsx-return.ts +205 -0
- package/src/rules/no-or-none-component.ts +63 -0
- package/src/rules/no-transition-cssproperties.ts +131 -0
- package/src/rules/no-unnecessary-div.ts +65 -0
- package/src/rules/no-unnecessary-key.ts +111 -0
- package/src/rules/no-useless-function.ts +56 -0
- package/src/rules/seperate-style-files.ts +79 -0
- package/src/rules/sort-exports.ts +424 -0
- package/src/rules/sort-keys-fixable.ts +647 -0
- package/src/rules/spring-naming-convention.ts +160 -0
- package/tsconfig.json +4 -1
- package/src/index.js +0 -45
- package/src/rules/explicit-object-types.js +0 -54
- package/src/rules/inline-style-limit.js +0 -77
- package/src/rules/localize-react-props.js +0 -418
- package/src/rules/max-depth-extended.js +0 -124
- package/src/rules/min-var-length.js +0 -300
- package/src/rules/no-button-navigation.js +0 -232
- package/src/rules/no-explicit-return-types.js +0 -64
- package/src/rules/no-inline-prop-types.js +0 -55
- package/src/rules/no-multi-style-objects.js +0 -70
- package/src/rules/no-nested-jsx-return.js +0 -154
- package/src/rules/no-or-none-component.js +0 -50
- package/src/rules/no-transition-cssproperties.js +0 -102
- package/src/rules/no-unnecessary-div.js +0 -40
- package/src/rules/no-unnecessary-key.js +0 -128
- package/src/rules/no-useless-function.js +0 -43
- package/src/rules/seperate-style-files.js +0 -62
- package/src/rules/sort-exports.js +0 -397
- package/src/rules/sort-keys-fixable.js +0 -459
- package/src/rules/spring-naming-convention.js +0 -111
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Enforce that top-level export declarations are sorted.
|
|
3
|
-
*
|
|
4
|
-
* This rule supports the following options:
|
|
5
|
-
* - order: "asc" or "desc" (default: "asc")
|
|
6
|
-
* - caseSensitive: boolean (default: false)
|
|
7
|
-
* - natural: boolean (default: false)
|
|
8
|
-
* - minKeys: integer, minimum number of exports in a contiguous block to check (default: 2)
|
|
9
|
-
* - variablesBeforeFunctions: boolean (default: false)
|
|
10
|
-
*
|
|
11
|
-
* For example, given this code:
|
|
12
|
-
*
|
|
13
|
-
* export const a = 1;
|
|
14
|
-
* export const c = 2;
|
|
15
|
-
* export const b = 3;
|
|
16
|
-
*
|
|
17
|
-
* The rule will report an error and auto-fix it (if run with --fix) to:
|
|
18
|
-
*
|
|
19
|
-
* export const a = 1;
|
|
20
|
-
* export const b = 3;
|
|
21
|
-
* export const c = 2;
|
|
22
|
-
*
|
|
23
|
-
* When variablesBeforeFunctions is true, exports whose values are functions should come after
|
|
24
|
-
* exports whose values are not functions.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
export default {
|
|
28
|
-
meta: {
|
|
29
|
-
type: "suggestion",
|
|
30
|
-
docs: {
|
|
31
|
-
description:
|
|
32
|
-
"Enforce that top-level export declarations are sorted by exported name and, optionally, that variable exports come before function exports",
|
|
33
|
-
category: "Stylistic Issues",
|
|
34
|
-
recommended: false
|
|
35
|
-
},
|
|
36
|
-
fixable: "code",
|
|
37
|
-
schema: [
|
|
38
|
-
{
|
|
39
|
-
type: "object",
|
|
40
|
-
properties: {
|
|
41
|
-
order: {
|
|
42
|
-
enum: ["asc", "desc"]
|
|
43
|
-
},
|
|
44
|
-
caseSensitive: {
|
|
45
|
-
type: "boolean"
|
|
46
|
-
},
|
|
47
|
-
natural: {
|
|
48
|
-
type: "boolean"
|
|
49
|
-
},
|
|
50
|
-
minKeys: {
|
|
51
|
-
type: "integer",
|
|
52
|
-
minimum: 2
|
|
53
|
-
},
|
|
54
|
-
variablesBeforeFunctions: {
|
|
55
|
-
type: "boolean"
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
additionalProperties: false
|
|
59
|
-
}
|
|
60
|
-
],
|
|
61
|
-
messages: {
|
|
62
|
-
alphabetical:
|
|
63
|
-
"Export declarations are not sorted alphabetically. Expected order: {{expectedOrder}}.",
|
|
64
|
-
variablesBeforeFunctions:
|
|
65
|
-
"Non-function exports should come before function exports."
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
create(context) {
|
|
70
|
-
const sourceCode = context.getSourceCode();
|
|
71
|
-
const options = context.options[0] || {};
|
|
72
|
-
const order = options.order || "asc";
|
|
73
|
-
const caseSensitive =
|
|
74
|
-
options.caseSensitive !== undefined ? options.caseSensitive : false;
|
|
75
|
-
const natural = options.natural !== undefined ? options.natural : false;
|
|
76
|
-
const minKeys = options.minKeys !== undefined ? options.minKeys : 2;
|
|
77
|
-
const variablesBeforeFunctions =
|
|
78
|
-
options.variablesBeforeFunctions !== undefined
|
|
79
|
-
? options.variablesBeforeFunctions
|
|
80
|
-
: false;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Helper to generate normalized export text.
|
|
84
|
-
* @param {ASTNode} node
|
|
85
|
-
* @returns {string}
|
|
86
|
-
*/
|
|
87
|
-
function generateExportText(node) {
|
|
88
|
-
// Normalize by trimming and ensuring a consistent semicolon.
|
|
89
|
-
return sourceCode
|
|
90
|
-
.getText(node)
|
|
91
|
-
.trim()
|
|
92
|
-
.replace(/\s*;?\s*$/, ";");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Compare two strings using the options.
|
|
97
|
-
* @param {string} a
|
|
98
|
-
* @param {string} b
|
|
99
|
-
* @returns {number}
|
|
100
|
-
*/
|
|
101
|
-
function compareStrings(a, b) {
|
|
102
|
-
let strA = a;
|
|
103
|
-
let strB = b;
|
|
104
|
-
if (!caseSensitive) {
|
|
105
|
-
strA = strA.toLowerCase();
|
|
106
|
-
strB = strB.toLowerCase();
|
|
107
|
-
}
|
|
108
|
-
let cmp = natural
|
|
109
|
-
? strA.localeCompare(strB, undefined, { numeric: true })
|
|
110
|
-
: strA.localeCompare(strB);
|
|
111
|
-
return order === "asc" ? cmp : -cmp;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Extracts the exported name from an ExportNamedDeclaration.
|
|
116
|
-
* Supports declarations (e.g. export const a = 1) with a single declarator
|
|
117
|
-
* and export specifiers (e.g. export { a }).
|
|
118
|
-
* @param {ASTNode} node
|
|
119
|
-
* @returns {string|null}
|
|
120
|
-
*/
|
|
121
|
-
function getExportName(node) {
|
|
122
|
-
if (node.declaration) {
|
|
123
|
-
const decl = node.declaration;
|
|
124
|
-
if (decl.type === "VariableDeclaration") {
|
|
125
|
-
if (decl.declarations.length === 1) {
|
|
126
|
-
const id = decl.declarations[0].id;
|
|
127
|
-
if (id.type === "Identifier") {
|
|
128
|
-
return id.name;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
} else if (
|
|
132
|
-
decl.type === "FunctionDeclaration" ||
|
|
133
|
-
decl.type === "ClassDeclaration"
|
|
134
|
-
) {
|
|
135
|
-
if (decl.id && decl.id.type === "Identifier") {
|
|
136
|
-
return decl.id.name;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
} else if (node.specifiers && node.specifiers.length === 1) {
|
|
140
|
-
const spec = node.specifiers[0];
|
|
141
|
-
return spec.exported.name || spec.exported.value;
|
|
142
|
-
}
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Determines if the export is a function export.
|
|
148
|
-
* @param {ASTNode} node
|
|
149
|
-
* @returns {boolean}
|
|
150
|
-
*/
|
|
151
|
-
function isFunctionExport(node) {
|
|
152
|
-
if (node.declaration) {
|
|
153
|
-
const decl = node.declaration;
|
|
154
|
-
if (decl.type === "VariableDeclaration") {
|
|
155
|
-
if (decl.declarations.length === 1) {
|
|
156
|
-
const init = decl.declarations[0].init;
|
|
157
|
-
return (
|
|
158
|
-
init &&
|
|
159
|
-
(init.type === "FunctionExpression" ||
|
|
160
|
-
init.type === "ArrowFunctionExpression")
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
} else if (decl.type === "FunctionDeclaration") {
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
// Treat ClassDeclaration as non-function by default (can be adjusted if desired)
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
// For export specifiers, we cannot reliably tell.
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Comparator that implements the full sort order.
|
|
175
|
-
* - First, type exports (exportKind: "type") come before value exports.
|
|
176
|
-
* - Next, if variablesBeforeFunctions is enabled, non-function exports come before function exports.
|
|
177
|
-
* - Finally, sort by name using the provided options.
|
|
178
|
-
* @param {object} a
|
|
179
|
-
* @param {object} b
|
|
180
|
-
* @returns {number}
|
|
181
|
-
*/
|
|
182
|
-
function sortComparator(a, b) {
|
|
183
|
-
const kindA = a.node.exportKind || "value";
|
|
184
|
-
const kindB = b.node.exportKind || "value";
|
|
185
|
-
if (kindA !== kindB) {
|
|
186
|
-
return kindA === "type" ? -1 : 1;
|
|
187
|
-
}
|
|
188
|
-
if (variablesBeforeFunctions) {
|
|
189
|
-
if (a.isFunction !== b.isFunction) {
|
|
190
|
-
return a.isFunction ? 1 : -1;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return compareStrings(a.name, b.name);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Recursively traverses a node to check for Identifier references.
|
|
198
|
-
* Returns true if any Identifier matches one of the names in laterNames.
|
|
199
|
-
* Uses a WeakSet to avoid infinite recursion on cyclic structures.
|
|
200
|
-
* @param {ASTNode} node
|
|
201
|
-
* @param {Set<string>} laterNames
|
|
202
|
-
* @param {WeakSet<Object>} visited
|
|
203
|
-
* @returns {boolean}
|
|
204
|
-
*/
|
|
205
|
-
function hasForwardDependency(
|
|
206
|
-
node,
|
|
207
|
-
laterNames,
|
|
208
|
-
visited = new WeakSet()
|
|
209
|
-
) {
|
|
210
|
-
if (!node || typeof node !== "object") {
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
if (visited.has(node)) {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
visited.add(node);
|
|
217
|
-
|
|
218
|
-
if (node.type === "Identifier" && laterNames.has(node.name)) {
|
|
219
|
-
return true;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
for (const key in node) {
|
|
223
|
-
if (Object.prototype.hasOwnProperty.call(node, key)) {
|
|
224
|
-
const value = node[key];
|
|
225
|
-
if (Array.isArray(value)) {
|
|
226
|
-
for (const element of value) {
|
|
227
|
-
if (element && typeof element === "object") {
|
|
228
|
-
if (
|
|
229
|
-
hasForwardDependency(
|
|
230
|
-
element,
|
|
231
|
-
laterNames,
|
|
232
|
-
visited
|
|
233
|
-
)
|
|
234
|
-
) {
|
|
235
|
-
return true;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
} else if (value && typeof value === "object") {
|
|
240
|
-
if (hasForwardDependency(value, laterNames, visited)) {
|
|
241
|
-
return true;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Process a contiguous block of eligible ExportNamedDeclaration nodes.
|
|
251
|
-
* @param {ASTNode[]} block
|
|
252
|
-
*/
|
|
253
|
-
function processExportBlock(block) {
|
|
254
|
-
if (block.length < minKeys) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const items = block
|
|
259
|
-
.map((node) => {
|
|
260
|
-
const name = getExportName(node);
|
|
261
|
-
if (name === null) {
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
return {
|
|
265
|
-
name,
|
|
266
|
-
node,
|
|
267
|
-
isFunction: isFunctionExport(node),
|
|
268
|
-
text: sourceCode.getText(node)
|
|
269
|
-
};
|
|
270
|
-
})
|
|
271
|
-
.filter(Boolean);
|
|
272
|
-
|
|
273
|
-
if (items.length < minKeys) {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Create a sorted copy using our comparator.
|
|
278
|
-
const sortedItems = items.slice().sort(sortComparator);
|
|
279
|
-
|
|
280
|
-
// Determine if the block is unsorted.
|
|
281
|
-
let reportNeeded = false;
|
|
282
|
-
let messageId = "alphabetical";
|
|
283
|
-
for (let i = 1; i < items.length; i++) {
|
|
284
|
-
if (sortComparator(items[i - 1], items[i]) > 0) {
|
|
285
|
-
reportNeeded = true;
|
|
286
|
-
if (
|
|
287
|
-
variablesBeforeFunctions &&
|
|
288
|
-
items[i - 1].isFunction &&
|
|
289
|
-
!items[i].isFunction
|
|
290
|
-
) {
|
|
291
|
-
messageId = "variablesBeforeFunctions";
|
|
292
|
-
}
|
|
293
|
-
break;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// If already sorted, do nothing.
|
|
298
|
-
if (!reportNeeded) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Dependency check: if any export in the block references a later export,
|
|
303
|
-
// skip reporting an error (to preserve the dependency order).
|
|
304
|
-
const exportNames = items.map((item) => item.name);
|
|
305
|
-
for (let i = 0; i < items.length; i++) {
|
|
306
|
-
const laterNames = new Set(exportNames.slice(i + 1));
|
|
307
|
-
const nodeToCheck = items[i].node.declaration || items[i].node;
|
|
308
|
-
if (hasForwardDependency(nodeToCheck, laterNames)) {
|
|
309
|
-
// A forward dependency exists; do not report an error for this block.
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Report the error only if there is no dependency issue.
|
|
315
|
-
const expectedOrder = sortedItems
|
|
316
|
-
.map((item) => item.name)
|
|
317
|
-
.join(", ");
|
|
318
|
-
context.report({
|
|
319
|
-
node: items[0].node,
|
|
320
|
-
messageId,
|
|
321
|
-
data: {
|
|
322
|
-
expectedOrder
|
|
323
|
-
},
|
|
324
|
-
fix(fixer) {
|
|
325
|
-
// Only fix if all nodes in the block are fixable.
|
|
326
|
-
const fixableNodes = block.filter((n) => {
|
|
327
|
-
if (n.declaration) {
|
|
328
|
-
if (
|
|
329
|
-
n.declaration.type === "VariableDeclaration" &&
|
|
330
|
-
n.declaration.declarations.length === 1 &&
|
|
331
|
-
n.declaration.declarations[0].id.type ===
|
|
332
|
-
"Identifier"
|
|
333
|
-
) {
|
|
334
|
-
return true;
|
|
335
|
-
}
|
|
336
|
-
if (
|
|
337
|
-
(n.declaration.type === "FunctionDeclaration" ||
|
|
338
|
-
n.declaration.type ===
|
|
339
|
-
"ClassDeclaration") &&
|
|
340
|
-
n.declaration.id &&
|
|
341
|
-
n.declaration.id.type === "Identifier"
|
|
342
|
-
) {
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
if (n.specifiers && n.specifiers.length === 1) {
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
return false;
|
|
351
|
-
});
|
|
352
|
-
if (fixableNodes.length < minKeys) {
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
const sortedText = sortedItems
|
|
356
|
-
.map((item) => generateExportText(item.node))
|
|
357
|
-
.join("\n");
|
|
358
|
-
const first = block[0].range[0];
|
|
359
|
-
const last = block[block.length - 1].range[1];
|
|
360
|
-
// Prevent circular fixes: only apply the change if the text differs.
|
|
361
|
-
const originalText = sourceCode
|
|
362
|
-
.getText()
|
|
363
|
-
.slice(first, last);
|
|
364
|
-
if (originalText === sortedText) {
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
return fixer.replaceTextRange([first, last], sortedText);
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return {
|
|
373
|
-
"Program:exit"(node) {
|
|
374
|
-
const body = node.body;
|
|
375
|
-
let block = [];
|
|
376
|
-
for (let i = 0; i < body.length; i++) {
|
|
377
|
-
const n = body[i];
|
|
378
|
-
if (
|
|
379
|
-
n.type === "ExportNamedDeclaration" &&
|
|
380
|
-
!n.source && // skip re-exports like export * from '...'
|
|
381
|
-
getExportName(n) !== null
|
|
382
|
-
) {
|
|
383
|
-
block.push(n);
|
|
384
|
-
} else {
|
|
385
|
-
if (block.length) {
|
|
386
|
-
processExportBlock(block);
|
|
387
|
-
block = [];
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
if (block.length) {
|
|
392
|
-
processExportBlock(block);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
};
|