eslint-plugin-absolute 0.1.6 → 0.2.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/.claude/settings.local.json +5 -0
- package/dist/index.js +1226 -798
- package/package.json +12 -11
- package/src/index.ts +45 -0
- package/src/rules/explicit-object-types.ts +73 -0
- package/src/rules/{inline-style-limit.js → inline-style-limit.ts} +16 -7
- package/src/rules/localize-react-props.ts +459 -0
- package/src/rules/max-depth-extended.ts +164 -0
- package/src/rules/{max-jsx-nesting.js → max-jsx-nesting.ts} +15 -8
- package/src/rules/{min-var-length.js → min-var-length.ts} +75 -45
- package/src/rules/no-button-navigation.ts +312 -0
- package/src/rules/no-explicit-return-types.ts +87 -0
- package/src/rules/{no-inline-prop-types.js → no-inline-prop-types.ts} +22 -9
- package/src/rules/no-multi-style-objects.ts +87 -0
- package/src/rules/no-nested-jsx-return.ts +210 -0
- package/src/rules/no-or-none-component.ts +65 -0
- package/src/rules/{no-transition-cssproperties.js → no-transition-cssproperties.ts} +50 -27
- package/src/rules/no-unnecessary-div.ts +73 -0
- package/src/rules/{no-unnecessary-key.js → no-unnecessary-key.ts} +43 -26
- package/src/rules/no-useless-function.ts +58 -0
- package/src/rules/seperate-style-files.ts +81 -0
- package/src/rules/sort-exports.ts +420 -0
- package/src/rules/sort-keys-fixable.ts +621 -0
- package/src/rules/spring-naming-convention.ts +145 -0
- package/tsconfig.json +2 -1
- package/src/index.js +0 -45
- package/src/rules/explicit-object-types.js +0 -54
- package/src/rules/localize-react-props.js +0 -418
- package/src/rules/max-depth-extended.js +0 -124
- package/src/rules/no-button-navigation.js +0 -232
- package/src/rules/no-explicit-return-types.js +0 -64
- 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-unnecessary-div.js +0 -40
- 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,459 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Enforce sorted keys in object literals (like ESLint’s built-in sort-keys)
|
|
3
|
-
* with an auto-fix for simple cases that preserves comments.
|
|
4
|
-
*
|
|
5
|
-
* Note: This rule reports errors just like the original sort-keys rule.
|
|
6
|
-
* However, the auto-fix only applies if all properties are “fixable” – i.e.:
|
|
7
|
-
* - They are of type Property (not SpreadElement, etc.)
|
|
8
|
-
* - They are not computed (e.g. [foo])
|
|
9
|
-
* - Their key is an Identifier or a Literal.
|
|
10
|
-
*
|
|
11
|
-
* Comments attached to the properties are preserved in the auto-fix.
|
|
12
|
-
*
|
|
13
|
-
* Use this rule with a grain of salt. I did not test every edge case and there’s
|
|
14
|
-
* a reason the original rule doesn’t have auto-fix. Computed keys, spread elements,
|
|
15
|
-
* comments, and formatting are not handled perfectly.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export default {
|
|
19
|
-
meta: {
|
|
20
|
-
type: "suggestion",
|
|
21
|
-
docs: {
|
|
22
|
-
description:
|
|
23
|
-
"enforce sorted keys in object literals with auto-fix (limited to simple cases, preserving comments)",
|
|
24
|
-
recommended: false
|
|
25
|
-
},
|
|
26
|
-
fixable: "code",
|
|
27
|
-
// The schema supports the same options as the built-in sort-keys rule plus:
|
|
28
|
-
// variablesBeforeFunctions: boolean (when true, non-function properties come before function properties)
|
|
29
|
-
schema: [
|
|
30
|
-
{
|
|
31
|
-
type: "object",
|
|
32
|
-
properties: {
|
|
33
|
-
order: {
|
|
34
|
-
enum: ["asc", "desc"]
|
|
35
|
-
},
|
|
36
|
-
caseSensitive: {
|
|
37
|
-
type: "boolean"
|
|
38
|
-
},
|
|
39
|
-
natural: {
|
|
40
|
-
type: "boolean"
|
|
41
|
-
},
|
|
42
|
-
minKeys: {
|
|
43
|
-
type: "integer",
|
|
44
|
-
minimum: 2
|
|
45
|
-
},
|
|
46
|
-
variablesBeforeFunctions: {
|
|
47
|
-
type: "boolean"
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
additionalProperties: false
|
|
51
|
-
}
|
|
52
|
-
],
|
|
53
|
-
messages: {
|
|
54
|
-
unsorted: "Object keys are not sorted."
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
create(context) {
|
|
59
|
-
const sourceCode = context.getSourceCode();
|
|
60
|
-
const options = context.options[0] || {};
|
|
61
|
-
const order = options.order || "asc";
|
|
62
|
-
const caseSensitive =
|
|
63
|
-
options.caseSensitive !== undefined ? options.caseSensitive : false;
|
|
64
|
-
const natural = options.natural !== undefined ? options.natural : false;
|
|
65
|
-
const minKeys = options.minKeys !== undefined ? options.minKeys : 2;
|
|
66
|
-
const variablesBeforeFunctions =
|
|
67
|
-
options.variablesBeforeFunctions !== undefined
|
|
68
|
-
? options.variablesBeforeFunctions
|
|
69
|
-
: false;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Compare two key strings based on the provided options.
|
|
73
|
-
* This function mimics the behavior of the built-in rule.
|
|
74
|
-
*
|
|
75
|
-
* @param {string} a First key.
|
|
76
|
-
* @param {string} b Second key.
|
|
77
|
-
* @returns {number} Negative if a comes before b, positive if after, 0 if equal.
|
|
78
|
-
*/
|
|
79
|
-
function compareKeys(a, b) {
|
|
80
|
-
let keyA = a;
|
|
81
|
-
let keyB = b;
|
|
82
|
-
if (!caseSensitive) {
|
|
83
|
-
keyA = keyA.toLowerCase();
|
|
84
|
-
keyB = keyB.toLowerCase();
|
|
85
|
-
}
|
|
86
|
-
if (natural) {
|
|
87
|
-
return keyA.localeCompare(keyB, undefined, { numeric: true });
|
|
88
|
-
}
|
|
89
|
-
return keyA.localeCompare(keyB);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Determines if a property is a function property.
|
|
94
|
-
*
|
|
95
|
-
* @param {ASTNode} prop The property node.
|
|
96
|
-
* @returns {boolean} True if the property's value is a function.
|
|
97
|
-
*/
|
|
98
|
-
function isFunctionProperty(prop) {
|
|
99
|
-
return (
|
|
100
|
-
prop.value &&
|
|
101
|
-
(prop.value.type === "FunctionExpression" ||
|
|
102
|
-
prop.value.type === "ArrowFunctionExpression" ||
|
|
103
|
-
prop.method === true)
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Build the sorted text from the fixable properties while preserving comments.
|
|
109
|
-
*
|
|
110
|
-
* @param {ASTNode[]} fixableProps Array of fixable property nodes.
|
|
111
|
-
* @returns {string} The concatenated text representing the sorted properties.
|
|
112
|
-
*/
|
|
113
|
-
function buildSortedText(fixableProps) {
|
|
114
|
-
// Create a sorted copy of the properties.
|
|
115
|
-
const sorted = fixableProps.slice().sort((a, b) => {
|
|
116
|
-
if (variablesBeforeFunctions) {
|
|
117
|
-
const aIsFunc = isFunctionProperty(a);
|
|
118
|
-
const bIsFunc = isFunctionProperty(b);
|
|
119
|
-
if (aIsFunc !== bIsFunc) {
|
|
120
|
-
return aIsFunc ? 1 : -1;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const keyA =
|
|
124
|
-
a.key.type === "Identifier"
|
|
125
|
-
? a.key.name
|
|
126
|
-
: String(a.key.value);
|
|
127
|
-
const keyB =
|
|
128
|
-
b.key.type === "Identifier"
|
|
129
|
-
? b.key.name
|
|
130
|
-
: String(b.key.value);
|
|
131
|
-
let res = compareKeys(keyA, keyB);
|
|
132
|
-
if (order === "desc") {
|
|
133
|
-
res = -res;
|
|
134
|
-
}
|
|
135
|
-
return res;
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// Reconstruct each property along with its comments.
|
|
139
|
-
return sorted
|
|
140
|
-
.map((prop) => {
|
|
141
|
-
// Retrieve leading and trailing comments.
|
|
142
|
-
const leadingComments =
|
|
143
|
-
sourceCode.getCommentsBefore(prop) || [];
|
|
144
|
-
const trailingComments =
|
|
145
|
-
sourceCode.getCommentsAfter(prop) || [];
|
|
146
|
-
// Assemble the comment text.
|
|
147
|
-
const leadingText =
|
|
148
|
-
leadingComments.length > 0
|
|
149
|
-
? leadingComments
|
|
150
|
-
.map((comment) =>
|
|
151
|
-
sourceCode.getText(comment)
|
|
152
|
-
)
|
|
153
|
-
.join("\n") + "\n"
|
|
154
|
-
: "";
|
|
155
|
-
const trailingText =
|
|
156
|
-
trailingComments.length > 0
|
|
157
|
-
? "\n" +
|
|
158
|
-
trailingComments
|
|
159
|
-
.map((comment) =>
|
|
160
|
-
sourceCode.getText(comment)
|
|
161
|
-
)
|
|
162
|
-
.join("\n")
|
|
163
|
-
: "";
|
|
164
|
-
// Return the property text with its comments.
|
|
165
|
-
return (
|
|
166
|
-
leadingText + sourceCode.getText(prop) + trailingText
|
|
167
|
-
);
|
|
168
|
-
})
|
|
169
|
-
.join(", ");
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Checks an ObjectExpression node for unsorted keys.
|
|
174
|
-
* Reports an error on each out-of-order key.
|
|
175
|
-
*
|
|
176
|
-
* For auto-fix purposes, only simple properties are considered fixable.
|
|
177
|
-
* (Computed keys, spread elements, or non-Identifier/Literal keys disable the fix.)
|
|
178
|
-
*
|
|
179
|
-
* @param {ASTNode} node The ObjectExpression node.
|
|
180
|
-
*/
|
|
181
|
-
function checkObjectExpression(node) {
|
|
182
|
-
if (node.properties.length < minKeys) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Build an array of objects: { keyName, node, isFunction }.
|
|
187
|
-
let autoFixable = true;
|
|
188
|
-
const keys = node.properties.map((prop) => {
|
|
189
|
-
let keyName = null;
|
|
190
|
-
let isFunc = false;
|
|
191
|
-
if (prop.type === "Property") {
|
|
192
|
-
if (prop.computed) {
|
|
193
|
-
// Computed keys are not handled in the fixer.
|
|
194
|
-
autoFixable = false;
|
|
195
|
-
}
|
|
196
|
-
if (prop.key.type === "Identifier") {
|
|
197
|
-
keyName = prop.key.name;
|
|
198
|
-
} else if (prop.key.type === "Literal") {
|
|
199
|
-
keyName = String(prop.key.value);
|
|
200
|
-
} else {
|
|
201
|
-
autoFixable = false;
|
|
202
|
-
}
|
|
203
|
-
// Determine if the property is a function.
|
|
204
|
-
if (prop.value) {
|
|
205
|
-
if (
|
|
206
|
-
prop.value.type === "FunctionExpression" ||
|
|
207
|
-
prop.value.type === "ArrowFunctionExpression" ||
|
|
208
|
-
prop.method === true
|
|
209
|
-
) {
|
|
210
|
-
isFunc = true;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
} else {
|
|
214
|
-
// Spread elements or other non-Property nodes.
|
|
215
|
-
autoFixable = false;
|
|
216
|
-
}
|
|
217
|
-
return { keyName, node: prop, isFunction: isFunc };
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// Report an error for each adjacent pair of keys that is out of order.
|
|
221
|
-
// For auto-fix, only the first reported error for a given object gets a fix function.
|
|
222
|
-
let fixProvided = false;
|
|
223
|
-
for (let i = 1; i < keys.length; i++) {
|
|
224
|
-
const prev = keys[i - 1];
|
|
225
|
-
const curr = keys[i];
|
|
226
|
-
|
|
227
|
-
// Skip comparison if keyName is null.
|
|
228
|
-
if (prev.keyName === null || curr.keyName === null) {
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (variablesBeforeFunctions) {
|
|
233
|
-
// When sorting variables before functions, if the previous property is a function and the current one is not,
|
|
234
|
-
// then they are out of order.
|
|
235
|
-
if (prev.isFunction && !curr.isFunction) {
|
|
236
|
-
context.report({
|
|
237
|
-
node: curr.node.key,
|
|
238
|
-
messageId: "unsorted",
|
|
239
|
-
fix:
|
|
240
|
-
!fixProvided && autoFixable
|
|
241
|
-
? (fixer) => {
|
|
242
|
-
const fixableProps =
|
|
243
|
-
node.properties.filter(
|
|
244
|
-
(prop) =>
|
|
245
|
-
(prop.type ===
|
|
246
|
-
"Property" &&
|
|
247
|
-
!prop.computed &&
|
|
248
|
-
(prop.key.type ===
|
|
249
|
-
"Identifier" ||
|
|
250
|
-
prop.key
|
|
251
|
-
.type ===
|
|
252
|
-
"Literal")) ||
|
|
253
|
-
false
|
|
254
|
-
);
|
|
255
|
-
if (fixableProps.length < minKeys) {
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
const sortedText =
|
|
259
|
-
buildSortedText(fixableProps);
|
|
260
|
-
const firstProp = fixableProps[0];
|
|
261
|
-
const lastProp =
|
|
262
|
-
fixableProps[
|
|
263
|
-
fixableProps.length - 1
|
|
264
|
-
];
|
|
265
|
-
return fixer.replaceTextRange(
|
|
266
|
-
[
|
|
267
|
-
firstProp.range[0],
|
|
268
|
-
lastProp.range[1]
|
|
269
|
-
],
|
|
270
|
-
sortedText
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
: null
|
|
274
|
-
});
|
|
275
|
-
fixProvided = true;
|
|
276
|
-
continue;
|
|
277
|
-
} else if (prev.isFunction === curr.isFunction) {
|
|
278
|
-
// If both properties are of the same type, compare keys alphabetically.
|
|
279
|
-
if (compareKeys(prev.keyName, curr.keyName) > 0) {
|
|
280
|
-
context.report({
|
|
281
|
-
node: curr.node.key,
|
|
282
|
-
messageId: "unsorted",
|
|
283
|
-
fix:
|
|
284
|
-
!fixProvided && autoFixable
|
|
285
|
-
? (fixer) => {
|
|
286
|
-
const fixableProps =
|
|
287
|
-
node.properties.filter(
|
|
288
|
-
(prop) =>
|
|
289
|
-
(prop.type ===
|
|
290
|
-
"Property" &&
|
|
291
|
-
!prop.computed &&
|
|
292
|
-
(prop.key
|
|
293
|
-
.type ===
|
|
294
|
-
"Identifier" ||
|
|
295
|
-
prop.key
|
|
296
|
-
.type ===
|
|
297
|
-
"Literal")) ||
|
|
298
|
-
false
|
|
299
|
-
);
|
|
300
|
-
if (
|
|
301
|
-
fixableProps.length <
|
|
302
|
-
minKeys
|
|
303
|
-
) {
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
const sortedText =
|
|
307
|
-
buildSortedText(
|
|
308
|
-
fixableProps
|
|
309
|
-
);
|
|
310
|
-
const firstProp =
|
|
311
|
-
fixableProps[0];
|
|
312
|
-
const lastProp =
|
|
313
|
-
fixableProps[
|
|
314
|
-
fixableProps.length - 1
|
|
315
|
-
];
|
|
316
|
-
return fixer.replaceTextRange(
|
|
317
|
-
[
|
|
318
|
-
firstProp.range[0],
|
|
319
|
-
lastProp.range[1]
|
|
320
|
-
],
|
|
321
|
-
sortedText
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
: null
|
|
325
|
-
});
|
|
326
|
-
fixProvided = true;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
} else {
|
|
330
|
-
// Original behavior: compare keys alphabetically.
|
|
331
|
-
if (compareKeys(prev.keyName, curr.keyName) > 0) {
|
|
332
|
-
context.report({
|
|
333
|
-
node: curr.node.key,
|
|
334
|
-
messageId: "unsorted",
|
|
335
|
-
fix:
|
|
336
|
-
!fixProvided && autoFixable
|
|
337
|
-
? (fixer) => {
|
|
338
|
-
const fixableProps =
|
|
339
|
-
node.properties.filter(
|
|
340
|
-
(prop) =>
|
|
341
|
-
(prop.type ===
|
|
342
|
-
"Property" &&
|
|
343
|
-
!prop.computed &&
|
|
344
|
-
(prop.key.type ===
|
|
345
|
-
"Identifier" ||
|
|
346
|
-
prop.key
|
|
347
|
-
.type ===
|
|
348
|
-
"Literal")) ||
|
|
349
|
-
false
|
|
350
|
-
);
|
|
351
|
-
if (fixableProps.length < minKeys) {
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
const sortedText =
|
|
355
|
-
buildSortedText(fixableProps);
|
|
356
|
-
const firstProp = fixableProps[0];
|
|
357
|
-
const lastProp =
|
|
358
|
-
fixableProps[
|
|
359
|
-
fixableProps.length - 1
|
|
360
|
-
];
|
|
361
|
-
return fixer.replaceTextRange(
|
|
362
|
-
[
|
|
363
|
-
firstProp.range[0],
|
|
364
|
-
lastProp.range[1]
|
|
365
|
-
],
|
|
366
|
-
sortedText
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
: null
|
|
370
|
-
});
|
|
371
|
-
fixProvided = true;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Also check object literals inside JSX prop expressions
|
|
378
|
-
function checkJSXAttributeObject(attr) {
|
|
379
|
-
if (
|
|
380
|
-
attr.value &&
|
|
381
|
-
attr.value.type === "JSXExpressionContainer" &&
|
|
382
|
-
attr.value.expression &&
|
|
383
|
-
attr.value.expression.type === "ObjectExpression"
|
|
384
|
-
) {
|
|
385
|
-
checkObjectExpression(attr.value.expression);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Also sort JSX attributes on elements
|
|
390
|
-
function checkJSXOpeningElement(node) {
|
|
391
|
-
const attrs = node.attributes;
|
|
392
|
-
if (attrs.length < minKeys) return;
|
|
393
|
-
|
|
394
|
-
// Only fix when all are simple JSXAttribute with JSXIdentifier names and no spreads.
|
|
395
|
-
if (attrs.some((a) => a.type !== "JSXAttribute")) return;
|
|
396
|
-
if (attrs.some((a) => a.name.type !== "JSXIdentifier")) return;
|
|
397
|
-
|
|
398
|
-
const names = attrs.map((a) => a.name.name);
|
|
399
|
-
const cmp = (a, b) => {
|
|
400
|
-
let res = compareKeys(a, b);
|
|
401
|
-
return order === "desc" ? -res : res;
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
let outOfOrder = false;
|
|
405
|
-
for (let i = 1; i < names.length; i++) {
|
|
406
|
-
if (cmp(names[i - 1], names[i]) > 0) {
|
|
407
|
-
outOfOrder = true;
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
if (!outOfOrder) return;
|
|
412
|
-
|
|
413
|
-
// Be conservative: only fix if there are no JSX comments/braces between attributes.
|
|
414
|
-
for (let i = 1; i < attrs.length; i++) {
|
|
415
|
-
const between = sourceCode.text.slice(
|
|
416
|
-
attrs[i - 1].range[1],
|
|
417
|
-
attrs[i].range[0]
|
|
418
|
-
);
|
|
419
|
-
if (between.includes("{")) {
|
|
420
|
-
// Likely contains a JSX comment or expression gap; report without fix.
|
|
421
|
-
context.report({
|
|
422
|
-
node: attrs[i].name,
|
|
423
|
-
messageId: "unsorted"
|
|
424
|
-
});
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const sorted = attrs
|
|
430
|
-
.slice()
|
|
431
|
-
.sort((a, b) => cmp(a.name.name, b.name.name));
|
|
432
|
-
|
|
433
|
-
const first = attrs[0];
|
|
434
|
-
const last = attrs[attrs.length - 1];
|
|
435
|
-
const replacement = sorted
|
|
436
|
-
.map((a) => sourceCode.getText(a))
|
|
437
|
-
.join(" ");
|
|
438
|
-
|
|
439
|
-
context.report({
|
|
440
|
-
node: first.name,
|
|
441
|
-
messageId: "unsorted",
|
|
442
|
-
fix(fixer) {
|
|
443
|
-
return fixer.replaceTextRange(
|
|
444
|
-
[first.range[0], last.range[1]],
|
|
445
|
-
replacement
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return {
|
|
452
|
-
ObjectExpression: checkObjectExpression,
|
|
453
|
-
JSXAttribute(node) {
|
|
454
|
-
checkJSXAttributeObject(node);
|
|
455
|
-
},
|
|
456
|
-
JSXOpeningElement: checkJSXOpeningElement
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
};
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
meta: {
|
|
3
|
-
type: "problem",
|
|
4
|
-
docs: {
|
|
5
|
-
description:
|
|
6
|
-
"Enforce correct naming for useSpring and useSprings hook destructuring",
|
|
7
|
-
category: "Stylistic Issues",
|
|
8
|
-
recommended: false
|
|
9
|
-
},
|
|
10
|
-
schema: []
|
|
11
|
-
},
|
|
12
|
-
create(context) {
|
|
13
|
-
return {
|
|
14
|
-
VariableDeclarator(node) {
|
|
15
|
-
if (
|
|
16
|
-
node.init &&
|
|
17
|
-
node.init.type === "CallExpression" &&
|
|
18
|
-
node.init.callee &&
|
|
19
|
-
node.init.callee.type === "Identifier" &&
|
|
20
|
-
(node.init.callee.name === "useSpring" ||
|
|
21
|
-
node.init.callee.name === "useSprings")
|
|
22
|
-
) {
|
|
23
|
-
const hookName = node.init.callee.name;
|
|
24
|
-
if (node.id && node.id.type === "ArrayPattern") {
|
|
25
|
-
const elements = node.id.elements;
|
|
26
|
-
if (elements.length < 2) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const firstElem = elements[0];
|
|
30
|
-
const secondElem = elements[1];
|
|
31
|
-
if (
|
|
32
|
-
!firstElem ||
|
|
33
|
-
firstElem.type !== "Identifier" ||
|
|
34
|
-
!secondElem ||
|
|
35
|
-
secondElem.type !== "Identifier"
|
|
36
|
-
) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const firstName = firstElem.name;
|
|
40
|
-
const secondName = secondElem.name;
|
|
41
|
-
if (hookName === "useSpring") {
|
|
42
|
-
if (!firstName.endsWith("Springs")) {
|
|
43
|
-
context.report({
|
|
44
|
-
node: firstElem,
|
|
45
|
-
message:
|
|
46
|
-
"The first variable from useSpring must end with 'Springs'."
|
|
47
|
-
});
|
|
48
|
-
} else {
|
|
49
|
-
const base = firstName.slice(
|
|
50
|
-
0,
|
|
51
|
-
-"Springs".length
|
|
52
|
-
);
|
|
53
|
-
if (!base) {
|
|
54
|
-
context.report({
|
|
55
|
-
node: firstElem,
|
|
56
|
-
message:
|
|
57
|
-
"The first variable must have a non-empty name before 'Springs'."
|
|
58
|
-
});
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const expectedSecond = base + "Api";
|
|
62
|
-
if (secondName !== expectedSecond) {
|
|
63
|
-
context.report({
|
|
64
|
-
node: secondElem,
|
|
65
|
-
message: `The second variable from useSpring must be named '${expectedSecond}'.`
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
} else if (hookName === "useSprings") {
|
|
70
|
-
if (!firstName.endsWith("Springs")) {
|
|
71
|
-
context.report({
|
|
72
|
-
node: firstElem,
|
|
73
|
-
message:
|
|
74
|
-
"The first variable from useSprings must end with 'Springs'."
|
|
75
|
-
});
|
|
76
|
-
} else {
|
|
77
|
-
const basePlural = firstName.slice(
|
|
78
|
-
0,
|
|
79
|
-
-"Springs".length
|
|
80
|
-
);
|
|
81
|
-
if (!basePlural) {
|
|
82
|
-
context.report({
|
|
83
|
-
node: firstElem,
|
|
84
|
-
message:
|
|
85
|
-
"The first variable must have a non-empty name before 'Springs'."
|
|
86
|
-
});
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (!basePlural.endsWith("s")) {
|
|
90
|
-
context.report({
|
|
91
|
-
node: firstElem,
|
|
92
|
-
message:
|
|
93
|
-
"The first variable for useSprings should be a plural name (ending with an 's') before 'Springs'."
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
const expectedSecond = basePlural + "Api";
|
|
97
|
-
if (secondName !== expectedSecond) {
|
|
98
|
-
context.report({
|
|
99
|
-
node: secondElem,
|
|
100
|
-
message: `The second variable from useSprings must be named '${expectedSecond}'.`
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
};
|