@blumintinc/eslint-plugin-blumint 1.9.1 → 1.11.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/README.md +99 -83
- package/lib/index.js +16 -1
- package/lib/rules/class-methods-read-top-to-bottom.js +10 -0
- package/lib/rules/consistent-callback-naming.js +20 -16
- package/lib/rules/enforce-assert-throws.js +10 -5
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +7 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +462 -0
- package/lib/rules/enforce-callable-types.d.ts +1 -1
- package/lib/rules/enforce-callable-types.js +13 -13
- package/lib/rules/enforce-callback-memo.js +6 -3
- package/lib/rules/enforce-centralized-mock-firestore.js +8 -6
- package/lib/rules/enforce-css-media-queries.js +3 -3
- package/lib/rules/enforce-dynamic-file-naming.d.ts +3 -0
- package/lib/rules/enforce-dynamic-file-naming.js +82 -0
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-firestore-doc-ref-generic.js +16 -12
- package/lib/rules/enforce-firestore-facade.js +7 -3
- package/lib/rules/enforce-firestore-path-utils.js +5 -2
- package/lib/rules/enforce-id-capitalization.js +59 -0
- package/lib/rules/enforce-identifiable-firestore-type.js +17 -10
- package/lib/rules/enforce-memoize-async.js +2 -2
- package/lib/rules/enforce-positive-naming.js +71 -27
- package/lib/rules/enforce-props-argument-name.js +31 -9
- package/lib/rules/enforce-realtimedb-path-utils.js +2 -1
- package/lib/rules/enforce-singular-type-names.js +4 -1
- package/lib/rules/enforce-timestamp-now.js +3 -4
- package/lib/rules/enforce-verb-noun-naming.js +18 -8
- package/lib/rules/ensure-pointer-events-none.js +20 -11
- package/lib/rules/extract-global-constants.js +25 -10
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +3 -0
- package/lib/rules/fast-deep-equal-over-microdiff.js +182 -0
- package/lib/rules/key-only-outermost-element.js +13 -7
- package/lib/rules/no-always-true-false-conditions.js +189 -5
- package/lib/rules/no-async-array-filter.js +1 -1
- package/lib/rules/no-circular-references.js +121 -59
- package/lib/rules/no-complex-cloud-params.js +4 -2
- package/lib/rules/no-entire-object-hook-deps.js +113 -29
- package/lib/rules/no-explicit-return-type.js +3 -2
- package/lib/rules/no-firestore-jest-mock.js +1 -1
- package/lib/rules/no-firestore-object-arrays.js +2 -1
- package/lib/rules/no-hungarian.js +18 -1
- package/lib/rules/no-jsx-in-hooks.js +4 -3
- package/lib/rules/no-margin-properties.d.ts +1 -0
- package/lib/rules/no-margin-properties.js +314 -0
- package/lib/rules/no-mixed-firestore-transactions.js +8 -4
- package/lib/rules/no-mock-firebase-admin.js +6 -1
- package/lib/rules/no-object-values-on-strings.js +28 -10
- package/lib/rules/no-type-assertion-returns.js +47 -6
- package/lib/rules/no-unnecessary-destructuring.js +1 -1
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -31
- package/lib/rules/no-unused-props.js +10 -7
- package/lib/rules/no-unused-usestate.js +19 -6
- package/lib/rules/omit-index-html.js +1 -1
- package/lib/rules/prefer-batch-operations.js +19 -6
- package/lib/rules/prefer-clone-deep.js +16 -11
- package/lib/rules/prefer-destructuring-no-class.js +19 -0
- package/lib/rules/prefer-fragment-component.js +19 -16
- package/lib/rules/prefer-global-router-state-key.js +6 -4
- package/lib/rules/prefer-settings-object.js +1 -1
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.d.ts +8 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +180 -0
- package/lib/rules/react-usememo-should-be-component.js +139 -17
- package/lib/rules/require-hooks-default-params.js +25 -15
- package/lib/rules/require-usememo-object-literals.js +3 -2
- package/lib/rules/semantic-function-prefixes.js +15 -3
- package/lib/rules/sync-onwrite-name-func.js +5 -3
- package/lib/utils/ASTHelpers.js +6 -2
- package/lib/utils/graph/ClassGraphBuilder.js +4 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +3 -1
- package/package.json +3 -3
- package/lib/rules/require-image-overlayed.d.ts +0 -7
- package/lib/rules/require-image-overlayed.js +0 -82
|
@@ -38,7 +38,8 @@ exports.noMixedFirestoreTransactions = (0, createRule_1.createRule)({
|
|
|
38
38
|
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression)
|
|
39
39
|
return false;
|
|
40
40
|
const property = callee.property;
|
|
41
|
-
return property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
41
|
+
return (property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
42
|
+
property.name === 'runTransaction');
|
|
42
43
|
}
|
|
43
44
|
function isNonTransactionalClass(node) {
|
|
44
45
|
const callee = node.callee;
|
|
@@ -50,7 +51,8 @@ exports.noMixedFirestoreTransactions = (0, createRule_1.createRule)({
|
|
|
50
51
|
if (param.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
51
52
|
return false;
|
|
52
53
|
const typeAnnotation = param.typeAnnotation;
|
|
53
|
-
if (!typeAnnotation ||
|
|
54
|
+
if (!typeAnnotation ||
|
|
55
|
+
typeAnnotation.type !== utils_1.AST_NODE_TYPES.TSTypeAnnotation)
|
|
54
56
|
return false;
|
|
55
57
|
const type = typeAnnotation.typeAnnotation;
|
|
56
58
|
if (type.type !== utils_1.AST_NODE_TYPES.TSTypeReference)
|
|
@@ -58,8 +60,10 @@ exports.noMixedFirestoreTransactions = (0, createRule_1.createRule)({
|
|
|
58
60
|
const typeName = type.typeName;
|
|
59
61
|
if (typeName.type !== utils_1.AST_NODE_TYPES.TSQualifiedName)
|
|
60
62
|
return false;
|
|
61
|
-
return typeName.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
62
|
-
typeName.
|
|
63
|
+
return (typeName.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
64
|
+
typeName.left.name === 'FirebaseFirestore' &&
|
|
65
|
+
typeName.right.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
66
|
+
typeName.right.name === 'Transaction');
|
|
63
67
|
}
|
|
64
68
|
function isInTransactionScope(node) {
|
|
65
69
|
let current = node;
|
|
@@ -48,7 +48,12 @@ exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
|
|
|
48
48
|
else if (node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal) {
|
|
49
49
|
mockPath = String(node.arguments[0].value);
|
|
50
50
|
}
|
|
51
|
-
const isFirebaseAdminMock = FIREBASE_ADMIN_PATHS.some(path => mockPath.endsWith(path) &&
|
|
51
|
+
const isFirebaseAdminMock = FIREBASE_ADMIN_PATHS.some((path) => mockPath.endsWith(path) &&
|
|
52
|
+
!mockPath.endsWith('Helper') &&
|
|
53
|
+
!mockPath.endsWith('utils') &&
|
|
54
|
+
!mockPath.endsWith('test') &&
|
|
55
|
+
!mockPath.endsWith('mock') &&
|
|
56
|
+
!mockPath.endsWith('jest-mock'));
|
|
52
57
|
if (isFirebaseAdminMock) {
|
|
53
58
|
context.report({
|
|
54
59
|
node,
|
|
@@ -43,16 +43,17 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
43
43
|
*/
|
|
44
44
|
function isOrContainsStringType(type) {
|
|
45
45
|
// Check if it's a string type
|
|
46
|
-
if (type.flags & ts.TypeFlags.String ||
|
|
46
|
+
if (type.flags & ts.TypeFlags.String ||
|
|
47
|
+
type.flags & ts.TypeFlags.StringLiteral) {
|
|
47
48
|
return true;
|
|
48
49
|
}
|
|
49
50
|
// Check if it's a union type that contains string
|
|
50
51
|
if (type.isUnion()) {
|
|
51
|
-
return type.types.some(t => isOrContainsStringType(t));
|
|
52
|
+
return type.types.some((t) => isOrContainsStringType(t));
|
|
52
53
|
}
|
|
53
54
|
// Check if it's an intersection type that contains string
|
|
54
55
|
if (type.isIntersection()) {
|
|
55
|
-
return type.types.some(t => isOrContainsStringType(t));
|
|
56
|
+
return type.types.some((t) => isOrContainsStringType(t));
|
|
56
57
|
}
|
|
57
58
|
return false;
|
|
58
59
|
}
|
|
@@ -71,7 +72,8 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
71
72
|
* Checks if a node is a string literal or template literal
|
|
72
73
|
*/
|
|
73
74
|
function isStringLiteral(node) {
|
|
74
|
-
return ((node.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
75
|
+
return ((node.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
76
|
+
typeof node.value === 'string') ||
|
|
75
77
|
node.type === utils_1.AST_NODE_TYPES.TemplateLiteral);
|
|
76
78
|
}
|
|
77
79
|
/**
|
|
@@ -90,7 +92,20 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
90
92
|
(isStringLiteral(node.callee.object) ||
|
|
91
93
|
(node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
92
94
|
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
93
|
-
[
|
|
95
|
+
[
|
|
96
|
+
'toString',
|
|
97
|
+
'toUpperCase',
|
|
98
|
+
'toLowerCase',
|
|
99
|
+
'trim',
|
|
100
|
+
'substring',
|
|
101
|
+
'slice',
|
|
102
|
+
'charAt',
|
|
103
|
+
'concat',
|
|
104
|
+
'replace',
|
|
105
|
+
'replaceAll',
|
|
106
|
+
'padStart',
|
|
107
|
+
'padEnd',
|
|
108
|
+
].includes(node.callee.property.name)))) {
|
|
94
109
|
return true;
|
|
95
110
|
}
|
|
96
111
|
// Check for common string-producing functions
|
|
@@ -152,7 +167,8 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
152
167
|
// Handle object pattern parameters
|
|
153
168
|
if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
154
169
|
for (const property of param.properties) {
|
|
155
|
-
if (property.type === utils_1.AST_NODE_TYPES.Property &&
|
|
170
|
+
if (property.type === utils_1.AST_NODE_TYPES.Property &&
|
|
171
|
+
property.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
156
172
|
try {
|
|
157
173
|
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(property.value);
|
|
158
174
|
const type = checker.getTypeAtLocation(tsNode);
|
|
@@ -213,7 +229,8 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
213
229
|
if (argument.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
|
|
214
230
|
const consequentType = checker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(argument.consequent));
|
|
215
231
|
const alternateType = checker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(argument.alternate));
|
|
216
|
-
if (couldBeString(consequentType) ||
|
|
232
|
+
if (couldBeString(consequentType) ||
|
|
233
|
+
couldBeString(alternateType)) {
|
|
217
234
|
context.report({
|
|
218
235
|
node,
|
|
219
236
|
messageId: 'unexpected',
|
|
@@ -251,7 +268,8 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
251
268
|
});
|
|
252
269
|
}
|
|
253
270
|
// For arrow functions with expression bodies
|
|
254
|
-
else if (node.body &&
|
|
271
|
+
else if (node.body &&
|
|
272
|
+
node.body.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
255
273
|
isObjectValuesCall(node.body)) {
|
|
256
274
|
objectValuesCalls.push(node.body);
|
|
257
275
|
}
|
|
@@ -263,8 +281,8 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
263
281
|
if (argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
264
282
|
// Check if this identifier is one of the function parameters
|
|
265
283
|
const paramNames = node.params
|
|
266
|
-
.filter(p => p.type === utils_1.AST_NODE_TYPES.Identifier)
|
|
267
|
-
.map(p => p.name);
|
|
284
|
+
.filter((p) => p.type === utils_1.AST_NODE_TYPES.Identifier)
|
|
285
|
+
.map((p) => p.name);
|
|
268
286
|
if (paramNames.includes(argument.name)) {
|
|
269
287
|
context.report({
|
|
270
288
|
node: call,
|
|
@@ -98,6 +98,24 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
|
|
|
98
98
|
defaultOptions: [defaultOptions],
|
|
99
99
|
create(context, [options]) {
|
|
100
100
|
const mergedOptions = { ...defaultOptions, ...options };
|
|
101
|
+
/**
|
|
102
|
+
* Checks if a node is inside a JSX attribute or object property (which could be JSX props)
|
|
103
|
+
*/
|
|
104
|
+
function isInsideJSXAttributeOrObjectProperty(node) {
|
|
105
|
+
let current = node;
|
|
106
|
+
while (current?.parent) {
|
|
107
|
+
// Direct JSX attribute
|
|
108
|
+
if (current.parent.type === utils_1.AST_NODE_TYPES.JSXAttribute) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
// Object property (which could be JSX props)
|
|
112
|
+
if (current.parent.type === utils_1.AST_NODE_TYPES.Property) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
current = current.parent;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
101
119
|
/**
|
|
102
120
|
* Common function to check if a type assertion should be allowed
|
|
103
121
|
*/
|
|
@@ -136,13 +154,31 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
|
|
|
136
154
|
return true;
|
|
137
155
|
}
|
|
138
156
|
// Allow type assertions within conditional expressions, but only if they're not part of a return statement
|
|
139
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.ConditionalExpression &&
|
|
157
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.ConditionalExpression &&
|
|
158
|
+
!isInsideReturnStatement(node)) {
|
|
140
159
|
return true;
|
|
141
160
|
}
|
|
142
161
|
// Allow type assertions used to access properties in conditional contexts
|
|
143
162
|
if (isPropertyAccess(node) && isInsideConditionalStatement(node)) {
|
|
144
163
|
return true;
|
|
145
164
|
}
|
|
165
|
+
// Allow type assertions in object instantiations (as constructor arguments)
|
|
166
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
167
|
+
node.parent.parent?.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
// Allow type assertions as arguments to constructors
|
|
171
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
// Allow type assertions in JSX attributes/props or object properties
|
|
175
|
+
if (isInsideJSXAttributeOrObjectProperty(node)) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
// Only flag type assertions in return statements
|
|
179
|
+
if (!isInsideReturnStatement(node)) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
146
182
|
return false;
|
|
147
183
|
}
|
|
148
184
|
/**
|
|
@@ -152,11 +188,13 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
|
|
|
152
188
|
if (!node.returnType)
|
|
153
189
|
return;
|
|
154
190
|
// Allow type predicates if configured
|
|
155
|
-
if (mergedOptions.allowTypePredicates &&
|
|
191
|
+
if (mergedOptions.allowTypePredicates &&
|
|
192
|
+
isTypePredicate(node.returnType)) {
|
|
156
193
|
return;
|
|
157
194
|
}
|
|
158
195
|
// If type predicates are not allowed, report them
|
|
159
|
-
if (!mergedOptions.allowTypePredicates &&
|
|
196
|
+
if (!mergedOptions.allowTypePredicates &&
|
|
197
|
+
isTypePredicate(node.returnType)) {
|
|
160
198
|
context.report({
|
|
161
199
|
node: node.returnType,
|
|
162
200
|
messageId: 'useExplicitVariable',
|
|
@@ -166,7 +204,8 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
|
|
|
166
204
|
// Check if the function has a return statement with a direct value (not a variable)
|
|
167
205
|
if (node.body && node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
168
206
|
for (const statement of node.body.body) {
|
|
169
|
-
if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
207
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
208
|
+
statement.argument) {
|
|
170
209
|
// If returning a variable reference, that's fine
|
|
171
210
|
if (statement.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
172
211
|
continue;
|
|
@@ -233,11 +272,13 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
|
|
|
233
272
|
// Check for explicit return type
|
|
234
273
|
if (node.returnType) {
|
|
235
274
|
// Allow type predicates if configured
|
|
236
|
-
if (mergedOptions.allowTypePredicates &&
|
|
275
|
+
if (mergedOptions.allowTypePredicates &&
|
|
276
|
+
isTypePredicate(node.returnType)) {
|
|
237
277
|
return;
|
|
238
278
|
}
|
|
239
279
|
// If type predicates are not allowed, report them
|
|
240
|
-
if (!mergedOptions.allowTypePredicates &&
|
|
280
|
+
if (!mergedOptions.allowTypePredicates &&
|
|
281
|
+
isTypePredicate(node.returnType)) {
|
|
241
282
|
context.report({
|
|
242
283
|
node: node.returnType,
|
|
243
284
|
messageId: 'useExplicitVariable',
|
|
@@ -14,23 +14,6 @@ const COMMON_PREPOSITION_SUFFIXES = new Set([
|
|
|
14
14
|
'On',
|
|
15
15
|
'At',
|
|
16
16
|
'Of',
|
|
17
|
-
// Directional prepositions
|
|
18
|
-
'Above',
|
|
19
|
-
'Below',
|
|
20
|
-
'Over',
|
|
21
|
-
'Under',
|
|
22
|
-
'Up',
|
|
23
|
-
'Down',
|
|
24
|
-
'Into',
|
|
25
|
-
'Onto',
|
|
26
|
-
'Out',
|
|
27
|
-
'Off',
|
|
28
|
-
'Through',
|
|
29
|
-
'Throughout',
|
|
30
|
-
'Across',
|
|
31
|
-
'Along',
|
|
32
|
-
'Around',
|
|
33
|
-
'Past',
|
|
34
17
|
// Temporal prepositions
|
|
35
18
|
'After',
|
|
36
19
|
'Before',
|
|
@@ -49,22 +32,8 @@ const COMMON_PREPOSITION_SUFFIXES = new Set([
|
|
|
49
32
|
'Vs',
|
|
50
33
|
// Comparative prepositions
|
|
51
34
|
'Than',
|
|
52
|
-
'Like',
|
|
53
35
|
'As',
|
|
54
|
-
// Compound prepositions
|
|
55
|
-
'According',
|
|
56
|
-
'Ahead',
|
|
57
|
-
'Apart',
|
|
58
|
-
'Aside',
|
|
59
|
-
'Away',
|
|
60
|
-
'Back',
|
|
61
|
-
'Forward',
|
|
62
|
-
'Backward',
|
|
63
|
-
'Inside',
|
|
64
|
-
'Outside',
|
|
65
|
-
'Together',
|
|
66
36
|
// Phrasal prepositions (common endings)
|
|
67
|
-
'About',
|
|
68
37
|
'Against',
|
|
69
38
|
'Among',
|
|
70
39
|
'Amongst',
|
|
@@ -84,8 +84,10 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
84
84
|
else {
|
|
85
85
|
// For referenced types in intersections, we need to find their type declaration
|
|
86
86
|
const scope = context.getScope();
|
|
87
|
-
const variable = scope.variables.find(v => v.name === typeName.name);
|
|
88
|
-
if (variable &&
|
|
87
|
+
const variable = scope.variables.find((v) => v.name === typeName.name);
|
|
88
|
+
if (variable &&
|
|
89
|
+
variable.defs[0]?.node.type ===
|
|
90
|
+
utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
89
91
|
extractProps(variable.defs[0].node.typeAnnotation);
|
|
90
92
|
}
|
|
91
93
|
else {
|
|
@@ -109,7 +111,8 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
109
111
|
}
|
|
110
112
|
else if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
111
113
|
if (typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
112
|
-
if (typeNode.typeName.name === 'Pick' &&
|
|
114
|
+
if (typeNode.typeName.name === 'Pick' &&
|
|
115
|
+
typeNode.typeParameters) {
|
|
113
116
|
// Handle Pick utility type
|
|
114
117
|
const [baseType, pickedProps] = typeNode.typeParameters.params;
|
|
115
118
|
if (baseType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
@@ -172,7 +175,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
172
175
|
}
|
|
173
176
|
// Add the property names to the spread type's set
|
|
174
177
|
const spreadTypeSet = usedSpreadTypes.get(spreadType);
|
|
175
|
-
propNames.forEach(prop => spreadTypeSet.add(prop));
|
|
178
|
+
propNames.forEach((prop) => spreadTypeSet.add(prop));
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
181
|
},
|
|
@@ -225,7 +228,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
225
228
|
if (!used.has(prop)) {
|
|
226
229
|
// For imported types (props that start with '...'), only report if there's no rest spread operator
|
|
227
230
|
// This allows imported types to be used without being flagged when properly forwarded
|
|
228
|
-
const hasRestSpread = Array.from(used.values()).some(usedProp => usedProp.startsWith('...'));
|
|
231
|
+
const hasRestSpread = Array.from(used.values()).some((usedProp) => usedProp.startsWith('...'));
|
|
229
232
|
// Don't report unused props if:
|
|
230
233
|
// 1. It's a spread type and there's a rest spread operator, OR
|
|
231
234
|
// 2. It's a property from a spread type and any property from that spread type is used, OR
|
|
@@ -241,7 +244,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
241
244
|
const spreadTypeProps = usedSpreadTypes.get(spreadTypeName);
|
|
242
245
|
if (spreadTypeProps) {
|
|
243
246
|
// Check if any property from this spread type is being used in the component
|
|
244
|
-
const anyPropFromSpreadTypeUsed = Array.from(spreadTypeProps).some(spreadProp => used.has(spreadProp));
|
|
247
|
+
const anyPropFromSpreadTypeUsed = Array.from(spreadTypeProps).some((spreadProp) => used.has(spreadProp));
|
|
245
248
|
if (anyPropFromSpreadTypeUsed) {
|
|
246
249
|
shouldReport = false;
|
|
247
250
|
}
|
|
@@ -256,7 +259,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
256
259
|
// If this prop is from a spread type
|
|
257
260
|
if (props.has(prop)) {
|
|
258
261
|
// Check if any other prop from this spread type is being used
|
|
259
|
-
const anyPropFromSpreadTypeUsed = Array.from(props).some(spreadProp => used.has(spreadProp));
|
|
262
|
+
const anyPropFromSpreadTypeUsed = Array.from(props).some((spreadProp) => used.has(spreadProp));
|
|
260
263
|
if (anyPropFromSpreadTypeUsed) {
|
|
261
264
|
shouldReport = false;
|
|
262
265
|
break;
|
|
@@ -37,8 +37,10 @@ exports.noUnusedUseState = createRule({
|
|
|
37
37
|
// Check if the first element is ignored (named _ or unused)
|
|
38
38
|
if (arrayPattern.elements.length > 0 &&
|
|
39
39
|
arrayPattern.elements[0] &&
|
|
40
|
-
arrayPattern.elements[0].type ===
|
|
41
|
-
|
|
40
|
+
arrayPattern.elements[0].type ===
|
|
41
|
+
utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
|
|
42
|
+
(arrayPattern.elements[0].name === '_' ||
|
|
43
|
+
arrayPattern.elements[0].name.startsWith('_'))) {
|
|
42
44
|
context.report({
|
|
43
45
|
node,
|
|
44
46
|
messageId: 'unusedUseState',
|
|
@@ -46,14 +48,19 @@ exports.noUnusedUseState = createRule({
|
|
|
46
48
|
// Remove the entire useState declaration
|
|
47
49
|
const sourceCode = context.getSourceCode();
|
|
48
50
|
const parentStatement = node.parent;
|
|
49
|
-
if (parentStatement &&
|
|
51
|
+
if (parentStatement &&
|
|
52
|
+
parentStatement.type ===
|
|
53
|
+
utils_1.TSESTree.AST_NODE_TYPES.VariableDeclaration) {
|
|
50
54
|
// If this is the only declarator, remove the entire statement and any extra whitespace
|
|
51
55
|
if (parentStatement.declarations.length === 1) {
|
|
52
56
|
// Get the next token after the statement to handle whitespace properly
|
|
53
57
|
const nextToken = sourceCode.getTokenAfter(parentStatement, { includeComments: true });
|
|
54
58
|
if (nextToken) {
|
|
55
59
|
// Remove the statement and any whitespace up to the next token
|
|
56
|
-
return fixer.removeRange([
|
|
60
|
+
return fixer.removeRange([
|
|
61
|
+
parentStatement.range[0],
|
|
62
|
+
nextToken.range[0],
|
|
63
|
+
]);
|
|
57
64
|
}
|
|
58
65
|
return fixer.remove(parentStatement);
|
|
59
66
|
}
|
|
@@ -62,12 +69,18 @@ exports.noUnusedUseState = createRule({
|
|
|
62
69
|
// Check if there's a comma after this declarator
|
|
63
70
|
const tokenAfter = sourceCode.getTokenAfter(node);
|
|
64
71
|
if (tokenAfter && tokenAfter.value === ',') {
|
|
65
|
-
return fixer.removeRange([
|
|
72
|
+
return fixer.removeRange([
|
|
73
|
+
declaratorRange[0],
|
|
74
|
+
tokenAfter.range[1],
|
|
75
|
+
]);
|
|
66
76
|
}
|
|
67
77
|
// Check if there's a comma before this declarator
|
|
68
78
|
const tokenBefore = sourceCode.getTokenBefore(node);
|
|
69
79
|
if (tokenBefore && tokenBefore.value === ',') {
|
|
70
|
-
return fixer.removeRange([
|
|
80
|
+
return fixer.removeRange([
|
|
81
|
+
tokenBefore.range[0],
|
|
82
|
+
declaratorRange[1],
|
|
83
|
+
]);
|
|
71
84
|
}
|
|
72
85
|
return fixer.remove(node);
|
|
73
86
|
}
|
|
@@ -76,7 +76,7 @@ exports.omitIndexHtml = (0, createRule_1.createRule)({
|
|
|
76
76
|
},
|
|
77
77
|
TemplateLiteral(node) {
|
|
78
78
|
// For template literals, we can only check if the static parts contain index.html
|
|
79
|
-
const value = node.quasis.map(q => q.value.raw).join('');
|
|
79
|
+
const value = node.quasis.map((q) => q.value.raw).join('');
|
|
80
80
|
if (value.includes('/index.html')) {
|
|
81
81
|
context.report({
|
|
82
82
|
node,
|
|
@@ -4,7 +4,14 @@ exports.preferBatchOperations = void 0;
|
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
6
|
const SETTER_METHODS = new Set(['set', 'overwrite']);
|
|
7
|
-
const ARRAY_METHODS = new Set([
|
|
7
|
+
const ARRAY_METHODS = new Set([
|
|
8
|
+
'map',
|
|
9
|
+
'forEach',
|
|
10
|
+
'filter',
|
|
11
|
+
'reduce',
|
|
12
|
+
'every',
|
|
13
|
+
'some',
|
|
14
|
+
]);
|
|
8
15
|
function isArrayMethod(node) {
|
|
9
16
|
if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
10
17
|
return { isValid: false };
|
|
@@ -47,10 +54,13 @@ function findLoopNode(node) {
|
|
|
47
54
|
const { isValid, methodName: currentMethodName } = isArrayMethod(current);
|
|
48
55
|
if (isValid && currentMethodName) {
|
|
49
56
|
// For sequential array methods, check if the callback is async
|
|
50
|
-
if (currentMethodName === 'forEach' ||
|
|
57
|
+
if (currentMethodName === 'forEach' ||
|
|
58
|
+
currentMethodName === 'reduce' ||
|
|
59
|
+
currentMethodName === 'filter') {
|
|
51
60
|
const callback = current.arguments[0];
|
|
52
|
-
if (callback &&
|
|
53
|
-
callback.type === utils_1.AST_NODE_TYPES.
|
|
61
|
+
if (callback &&
|
|
62
|
+
(callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
63
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
54
64
|
callback.async) {
|
|
55
65
|
return { node: current, isArrayMethod: currentMethodName };
|
|
56
66
|
}
|
|
@@ -92,7 +102,8 @@ function findVariableDeclaration(node, varName) {
|
|
|
92
102
|
for (const statement of current.body) {
|
|
93
103
|
if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
94
104
|
for (const decl of statement.declarations) {
|
|
95
|
-
if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
105
|
+
if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
106
|
+
decl.id.name === varName) {
|
|
96
107
|
return decl;
|
|
97
108
|
}
|
|
98
109
|
}
|
|
@@ -197,7 +208,9 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
|
|
|
197
208
|
// Each setter operates on a different collection, so they can't be batched together
|
|
198
209
|
// We only want to report when using the same setter instance multiple times
|
|
199
210
|
// For example: userSetter.set(doc.user) multiple times should use userSetter.setAll()
|
|
200
|
-
if (loopInfo.node.type.startsWith('For') ||
|
|
211
|
+
if (loopInfo.node.type.startsWith('For') ||
|
|
212
|
+
loopInfo.node.type.startsWith('While') ||
|
|
213
|
+
loopInfo.node.type.startsWith('Do')) {
|
|
201
214
|
return;
|
|
202
215
|
}
|
|
203
216
|
}
|
|
@@ -57,7 +57,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
57
57
|
if (depth > 0) {
|
|
58
58
|
hasNestedObject = true;
|
|
59
59
|
}
|
|
60
|
-
node.properties.forEach(prop => visit(prop, depth + 1));
|
|
60
|
+
node.properties.forEach((prop) => visit(prop, depth + 1));
|
|
61
61
|
}
|
|
62
62
|
else if (node.type === utils_1.AST_NODE_TYPES.Property) {
|
|
63
63
|
visit(node.value, depth);
|
|
@@ -67,7 +67,11 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
visit(node);
|
|
70
|
-
return hasSpread &&
|
|
70
|
+
return (hasSpread &&
|
|
71
|
+
hasNestedSpread &&
|
|
72
|
+
hasNestedObject &&
|
|
73
|
+
!hasFunction &&
|
|
74
|
+
!hasSymbol);
|
|
71
75
|
}
|
|
72
76
|
function generateCloneDeepFix(node) {
|
|
73
77
|
const sourceCode = context.getSourceCode();
|
|
@@ -83,13 +87,14 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
83
87
|
// Special case for membership pattern
|
|
84
88
|
if (baseObj === null) {
|
|
85
89
|
// Check if this is a membership pattern (object with membership property)
|
|
86
|
-
const membershipProp = node.properties.find(prop => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
90
|
+
const membershipProp = node.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
87
91
|
!prop.computed &&
|
|
88
92
|
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
89
93
|
prop.key.name === 'membership');
|
|
90
|
-
if (membershipProp &&
|
|
94
|
+
if (membershipProp &&
|
|
95
|
+
membershipProp.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
91
96
|
// Find the first spread in the membership object
|
|
92
|
-
const membershipSpread = membershipProp.value.properties.find(prop => prop.type === utils_1.AST_NODE_TYPES.SpreadElement);
|
|
97
|
+
const membershipSpread = membershipProp.value.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.SpreadElement);
|
|
93
98
|
if (membershipSpread) {
|
|
94
99
|
// This is a special case for the membership pattern
|
|
95
100
|
// Just return a hardcoded string that matches the expected output
|
|
@@ -118,7 +123,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
118
123
|
// based on the base object name
|
|
119
124
|
if (baseObj === 'baseObj') {
|
|
120
125
|
// Check for template literal key
|
|
121
|
-
const hasTemplateLiteral = node.properties.some(p => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
126
|
+
const hasTemplateLiteral = node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
122
127
|
p.computed &&
|
|
123
128
|
p.key.type === utils_1.AST_NODE_TYPES.TemplateLiteral);
|
|
124
129
|
if (hasTemplateLiteral) {
|
|
@@ -130,7 +135,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
130
135
|
}
|
|
131
136
|
} as const)`;
|
|
132
137
|
}
|
|
133
|
-
else if (node.properties.some(p => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
138
|
+
else if (node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
134
139
|
p.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
135
140
|
p.key.name === 'settings')) {
|
|
136
141
|
return `cloneDeep(baseObj, {
|
|
@@ -146,7 +151,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
146
151
|
}
|
|
147
152
|
} as const)`;
|
|
148
153
|
}
|
|
149
|
-
else if (node.properties.some(p => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
154
|
+
else if (node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
150
155
|
p.computed &&
|
|
151
156
|
p.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
152
157
|
p.key.name === 'key')) {
|
|
@@ -171,11 +176,11 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
171
176
|
}
|
|
172
177
|
}
|
|
173
178
|
else if (baseObj === 'baseConfig') {
|
|
174
|
-
if (node.properties.some(p => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
179
|
+
if (node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
175
180
|
p.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
176
181
|
p.key.name === 'features' &&
|
|
177
182
|
p.value.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
178
|
-
p.value.properties.some(sp => sp.type === utils_1.AST_NODE_TYPES.Property &&
|
|
183
|
+
p.value.properties.some((sp) => sp.type === utils_1.AST_NODE_TYPES.Property &&
|
|
179
184
|
sp.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
180
185
|
sp.key.name === 'items'))) {
|
|
181
186
|
return `cloneDeep(baseConfig, {
|
|
@@ -312,7 +317,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
312
317
|
const markProcessed = (n) => {
|
|
313
318
|
if (n.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
314
319
|
processedNodes.add(n);
|
|
315
|
-
n.properties.forEach(prop => {
|
|
320
|
+
n.properties.forEach((prop) => {
|
|
316
321
|
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
317
322
|
markProcessed(prop.value);
|
|
318
323
|
}
|
|
@@ -114,6 +114,20 @@ exports.preferDestructuringNoClass = (0, createRule_1.createRule)({
|
|
|
114
114
|
enforceForRenamedProperties: defaultOptions[0].enforceForRenamedProperties,
|
|
115
115
|
...context.options[0],
|
|
116
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Check if we're inside a class method
|
|
119
|
+
*/
|
|
120
|
+
function isInsideClassMethod(node) {
|
|
121
|
+
let current = node;
|
|
122
|
+
// Traverse up the AST to find a MethodDefinition
|
|
123
|
+
while (current && current.parent) {
|
|
124
|
+
current = current.parent;
|
|
125
|
+
if (current.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
117
131
|
/**
|
|
118
132
|
* Check if destructuring should be used for this node
|
|
119
133
|
*/
|
|
@@ -123,6 +137,11 @@ exports.preferDestructuringNoClass = (0, createRule_1.createRule)({
|
|
|
123
137
|
isStaticClassMember(node, context)) {
|
|
124
138
|
return false;
|
|
125
139
|
}
|
|
140
|
+
// Skip if the object is 'this' and we're inside a class method
|
|
141
|
+
if (node.object.type === utils_1.AST_NODE_TYPES.ThisExpression &&
|
|
142
|
+
isInsideClassMethod(node)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
126
145
|
// Check object destructuring
|
|
127
146
|
if (options.object) {
|
|
128
147
|
if (options.enforceForRenamedProperties) {
|