@blumintinc/eslint-plugin-blumint 1.10.0 → 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 -84
- package/lib/index.js +7 -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.js +9 -7
- 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 +23 -5
- 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 +52 -18
- package/lib/rules/no-async-array-filter.js +1 -1
- package/lib/rules/no-circular-references.js +121 -59
- 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 +2 -2
- package/lib/rules/no-jsx-in-hooks.js +4 -3
- package/lib/rules/no-margin-properties.js +25 -18
- 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-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.js +12 -4
- package/lib/rules/react-usememo-should-be-component.js +64 -18
- 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
|
@@ -12,6 +12,15 @@ const isJsxElement = (node) => {
|
|
|
12
12
|
if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
|
|
13
13
|
return isJsxElement(node.consequent) || isJsxElement(node.alternate);
|
|
14
14
|
}
|
|
15
|
+
// For logical expressions like '&&', the result can be a non-JSX value
|
|
16
|
+
if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
|
|
17
|
+
// If it's a logical AND (&&), it can return the left operand which might be non-JSX
|
|
18
|
+
if (node.operator === '&&') {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
// For other logical operators, check both sides
|
|
22
|
+
return isJsxElement(node.left) || isJsxElement(node.right);
|
|
23
|
+
}
|
|
15
24
|
return (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
16
25
|
node.type === utils_1.AST_NODE_TYPES.JSXFragment);
|
|
17
26
|
};
|
|
@@ -57,7 +66,7 @@ const isUsedMultipleTimes = (variableName, node) => {
|
|
|
57
66
|
const child = searchNode[key];
|
|
58
67
|
if (child && typeof child === 'object') {
|
|
59
68
|
if (Array.isArray(child)) {
|
|
60
|
-
child.forEach(item => {
|
|
69
|
+
child.forEach((item) => {
|
|
61
70
|
if (item && typeof item === 'object') {
|
|
62
71
|
findReferences(item);
|
|
63
72
|
}
|
|
@@ -105,7 +114,8 @@ const containsJsxInObject = (node) => {
|
|
|
105
114
|
const containsJsxInSwitchStatement = (node) => {
|
|
106
115
|
for (const switchCase of node.cases) {
|
|
107
116
|
for (const statement of switchCase.consequent) {
|
|
108
|
-
if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
117
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
118
|
+
statement.argument) {
|
|
109
119
|
if (isJsxElement(statement.argument)) {
|
|
110
120
|
return true;
|
|
111
121
|
}
|
|
@@ -144,8 +154,17 @@ const containsJsxInFunction = (node) => {
|
|
|
144
154
|
if (body.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
145
155
|
body.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
146
156
|
// Check array methods like map, filter, find, etc.
|
|
147
|
-
const arrayMethods = [
|
|
148
|
-
|
|
157
|
+
const arrayMethods = [
|
|
158
|
+
'map',
|
|
159
|
+
'filter',
|
|
160
|
+
'find',
|
|
161
|
+
'findIndex',
|
|
162
|
+
'some',
|
|
163
|
+
'every',
|
|
164
|
+
'reduce',
|
|
165
|
+
];
|
|
166
|
+
if (arrayMethods.includes(body.callee.property.name) &&
|
|
167
|
+
body.arguments.length > 0) {
|
|
149
168
|
const callback = body.arguments[0];
|
|
150
169
|
if ((callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
151
170
|
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
@@ -183,9 +202,17 @@ const containsJsxInExpression = (node) => {
|
|
|
183
202
|
}
|
|
184
203
|
switch (node.type) {
|
|
185
204
|
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
186
|
-
return containsJsxInExpression(node.consequent) ||
|
|
205
|
+
return (containsJsxInExpression(node.consequent) ||
|
|
206
|
+
containsJsxInExpression(node.alternate));
|
|
187
207
|
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
188
|
-
|
|
208
|
+
// For logical AND (&&) expressions, if the left side can be falsy,
|
|
209
|
+
// then the expression can return a non-JSX value, so we should not flag it
|
|
210
|
+
if (node.operator === '&&') {
|
|
211
|
+
// If the left side is a boolean expression or can be falsy, this can return a non-JSX value
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
return (containsJsxInExpression(node.left) ||
|
|
215
|
+
containsJsxInExpression(node.right));
|
|
189
216
|
case utils_1.AST_NODE_TYPES.ObjectExpression:
|
|
190
217
|
// Special case: If this is an object with both JSX and non-JSX properties,
|
|
191
218
|
// it's likely a data structure that happens to contain JSX, not a component
|
|
@@ -218,8 +245,8 @@ const containsJsxInExpression = (node) => {
|
|
|
218
245
|
node.callee.property.name === 'map' &&
|
|
219
246
|
node.arguments.length > 0) {
|
|
220
247
|
const callback = node.arguments[0];
|
|
221
|
-
if (
|
|
222
|
-
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression)
|
|
248
|
+
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
249
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
223
250
|
// Check if the callback returns an object with both JSX and non-JSX properties
|
|
224
251
|
if (callback.body.type !== utils_1.AST_NODE_TYPES.BlockStatement &&
|
|
225
252
|
callback.body.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
@@ -252,18 +279,27 @@ const containsJsxInExpression = (node) => {
|
|
|
252
279
|
// Check array methods
|
|
253
280
|
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
254
281
|
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
255
|
-
const arrayMethods = [
|
|
256
|
-
|
|
282
|
+
const arrayMethods = [
|
|
283
|
+
'filter',
|
|
284
|
+
'find',
|
|
285
|
+
'findIndex',
|
|
286
|
+
'some',
|
|
287
|
+
'every',
|
|
288
|
+
'reduce',
|
|
289
|
+
];
|
|
290
|
+
if (arrayMethods.includes(node.callee.property.name) &&
|
|
291
|
+
node.arguments.length > 0) {
|
|
257
292
|
const callback = node.arguments[0];
|
|
258
|
-
if (
|
|
259
|
-
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression)
|
|
293
|
+
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
294
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
260
295
|
return containsJsxInFunction(callback);
|
|
261
296
|
}
|
|
262
297
|
}
|
|
263
298
|
}
|
|
264
299
|
// Check arguments for JSX
|
|
265
300
|
for (const arg of node.arguments) {
|
|
266
|
-
if (arg.type !== utils_1.AST_NODE_TYPES.SpreadElement &&
|
|
301
|
+
if (arg.type !== utils_1.AST_NODE_TYPES.SpreadElement &&
|
|
302
|
+
containsJsxInExpression(arg)) {
|
|
267
303
|
return true;
|
|
268
304
|
}
|
|
269
305
|
}
|
|
@@ -281,7 +317,8 @@ const containsJsxInExpression = (node) => {
|
|
|
281
317
|
const containsJsxInBlockStatement = (node) => {
|
|
282
318
|
for (const statement of node.body) {
|
|
283
319
|
// Check return statements
|
|
284
|
-
if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
320
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
321
|
+
statement.argument) {
|
|
285
322
|
if (containsJsxInExpression(statement.argument)) {
|
|
286
323
|
return true;
|
|
287
324
|
}
|
|
@@ -289,7 +326,8 @@ const containsJsxInBlockStatement = (node) => {
|
|
|
289
326
|
// Check if statements
|
|
290
327
|
if (statement.type === utils_1.AST_NODE_TYPES.IfStatement) {
|
|
291
328
|
if (statement.consequent.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
292
|
-
statement.consequent.argument &&
|
|
329
|
+
statement.consequent.argument &&
|
|
330
|
+
containsJsxInExpression(statement.consequent.argument)) {
|
|
293
331
|
return true;
|
|
294
332
|
}
|
|
295
333
|
if (statement.consequent.type === utils_1.AST_NODE_TYPES.BlockStatement &&
|
|
@@ -298,7 +336,8 @@ const containsJsxInBlockStatement = (node) => {
|
|
|
298
336
|
}
|
|
299
337
|
if (statement.alternate) {
|
|
300
338
|
if (statement.alternate.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
301
|
-
statement.alternate.argument &&
|
|
339
|
+
statement.alternate.argument &&
|
|
340
|
+
containsJsxInExpression(statement.alternate.argument)) {
|
|
302
341
|
return true;
|
|
303
342
|
}
|
|
304
343
|
if (statement.alternate.type === utils_1.AST_NODE_TYPES.BlockStatement &&
|
|
@@ -311,10 +350,12 @@ const containsJsxInBlockStatement = (node) => {
|
|
|
311
350
|
return true;
|
|
312
351
|
}
|
|
313
352
|
if (statement.alternate.consequent &&
|
|
314
|
-
((statement.alternate.consequent.type ===
|
|
353
|
+
((statement.alternate.consequent.type ===
|
|
354
|
+
utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
315
355
|
statement.alternate.consequent.argument &&
|
|
316
356
|
containsJsxInExpression(statement.alternate.consequent.argument)) ||
|
|
317
|
-
(statement.alternate.consequent.type ===
|
|
357
|
+
(statement.alternate.consequent.type ===
|
|
358
|
+
utils_1.AST_NODE_TYPES.BlockStatement &&
|
|
318
359
|
containsJsxInBlockStatement(statement.alternate.consequent)))) {
|
|
319
360
|
return true;
|
|
320
361
|
}
|
|
@@ -378,6 +419,11 @@ const containsJsxInUseMemo = (node) => {
|
|
|
378
419
|
if (isJsxElement(callback.body)) {
|
|
379
420
|
return true;
|
|
380
421
|
}
|
|
422
|
+
// Special case for logical expressions that can return non-JSX values
|
|
423
|
+
if (callback.body.type === utils_1.AST_NODE_TYPES.LogicalExpression &&
|
|
424
|
+
callback.body.operator === '&&') {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
381
427
|
// For non-JSX expressions, we need to check if they contain JSX
|
|
382
428
|
return containsJsxInExpression(callback.body);
|
|
383
429
|
}
|
|
@@ -25,7 +25,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
25
25
|
function hasAllOptionalProperties(typeNode) {
|
|
26
26
|
// Handle type literals directly
|
|
27
27
|
if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
|
|
28
|
-
return typeNode.members.every(member => {
|
|
28
|
+
return typeNode.members.every((member) => {
|
|
29
29
|
if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
|
|
30
30
|
return false;
|
|
31
31
|
}
|
|
@@ -39,7 +39,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
39
39
|
return false;
|
|
40
40
|
}
|
|
41
41
|
const scope = context.getScope();
|
|
42
|
-
const variable = scope.variables.find(v => v.name === typeName.name);
|
|
42
|
+
const variable = scope.variables.find((v) => v.name === typeName.name);
|
|
43
43
|
if (!variable || !variable.defs[0]?.node) {
|
|
44
44
|
// If we can't find the type definition, assume it's a type with required properties
|
|
45
45
|
// This handles cases where the type is imported from another module
|
|
@@ -50,7 +50,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
50
50
|
return hasAllOptionalProperties(def.typeAnnotation);
|
|
51
51
|
}
|
|
52
52
|
else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
53
|
-
return def.body.body.every(member => {
|
|
53
|
+
return def.body.body.every((member) => {
|
|
54
54
|
if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
@@ -68,7 +68,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
68
68
|
}
|
|
69
69
|
// Handle interface declarations
|
|
70
70
|
if (typeNode.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
71
|
-
return typeNode.body.body.every(member => {
|
|
71
|
+
return typeNode.body.body.every((member) => {
|
|
72
72
|
if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
|
|
73
73
|
return false;
|
|
74
74
|
}
|
|
@@ -106,13 +106,14 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
108
|
// Check if the parameter has a type annotation
|
|
109
|
-
if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
109
|
+
if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
110
|
+
param.typeAnnotation) {
|
|
110
111
|
const typeAnnotation = param.typeAnnotation.typeAnnotation;
|
|
111
112
|
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
112
113
|
const typeName = typeAnnotation.typeName;
|
|
113
114
|
if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
114
115
|
const scope = context.getScope();
|
|
115
|
-
const variable = scope.variables.find(v => v.name === typeName.name);
|
|
116
|
+
const variable = scope.variables.find((v) => v.name === typeName.name);
|
|
116
117
|
if (variable && variable.defs[0]?.node) {
|
|
117
118
|
const def = variable.defs[0].node;
|
|
118
119
|
if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
@@ -121,14 +122,16 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
121
122
|
node: param,
|
|
122
123
|
messageId: 'requireDefaultParams',
|
|
123
124
|
fix(fixer) {
|
|
124
|
-
const paramText = context
|
|
125
|
+
const paramText = context
|
|
126
|
+
.getSourceCode()
|
|
127
|
+
.getText(param);
|
|
125
128
|
return fixer.replaceText(param, `${paramText} = {}`);
|
|
126
129
|
},
|
|
127
130
|
});
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
131
|
-
if (def.body.body.every(member => {
|
|
134
|
+
if (def.body.body.every((member) => {
|
|
132
135
|
if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
|
|
133
136
|
return false;
|
|
134
137
|
}
|
|
@@ -138,7 +141,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
138
141
|
node: param,
|
|
139
142
|
messageId: 'requireDefaultParams',
|
|
140
143
|
fix(fixer) {
|
|
141
|
-
const paramText = context
|
|
144
|
+
const paramText = context
|
|
145
|
+
.getSourceCode()
|
|
146
|
+
.getText(param);
|
|
142
147
|
return fixer.replaceText(param, `${paramText} = {}`);
|
|
143
148
|
},
|
|
144
149
|
});
|
|
@@ -148,8 +153,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
148
153
|
else {
|
|
149
154
|
// If we can't find the type definition, check if it's defined in the same file
|
|
150
155
|
const program = context.getSourceCode().ast;
|
|
151
|
-
const typeDefinitions = program.body.filter(node => {
|
|
152
|
-
if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
|
|
156
|
+
const typeDefinitions = program.body.filter((node) => {
|
|
157
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
|
|
158
|
+
node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
153
159
|
if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
154
160
|
return node.id.name === typeName.name;
|
|
155
161
|
}
|
|
@@ -167,14 +173,16 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
167
173
|
node: param,
|
|
168
174
|
messageId: 'requireDefaultParams',
|
|
169
175
|
fix(fixer) {
|
|
170
|
-
const paramText = context
|
|
176
|
+
const paramText = context
|
|
177
|
+
.getSourceCode()
|
|
178
|
+
.getText(param);
|
|
171
179
|
return fixer.replaceText(param, `${paramText} = {}`);
|
|
172
180
|
},
|
|
173
181
|
});
|
|
174
182
|
}
|
|
175
183
|
}
|
|
176
184
|
else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
177
|
-
if (def.body.body.every(member => {
|
|
185
|
+
if (def.body.body.every((member) => {
|
|
178
186
|
if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
|
|
179
187
|
return false;
|
|
180
188
|
}
|
|
@@ -184,7 +192,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
184
192
|
node: param,
|
|
185
193
|
messageId: 'requireDefaultParams',
|
|
186
194
|
fix(fixer) {
|
|
187
|
-
const paramText = context
|
|
195
|
+
const paramText = context
|
|
196
|
+
.getSourceCode()
|
|
197
|
+
.getText(param);
|
|
188
198
|
return fixer.replaceText(param, `${paramText} = {}`);
|
|
189
199
|
},
|
|
190
200
|
});
|
|
@@ -195,7 +205,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
195
205
|
}
|
|
196
206
|
}
|
|
197
207
|
else if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
|
|
198
|
-
if (typeAnnotation.members.every(member => {
|
|
208
|
+
if (typeAnnotation.members.every((member) => {
|
|
199
209
|
if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
|
|
200
210
|
return false;
|
|
201
211
|
}
|
|
@@ -7,7 +7,7 @@ exports.requireUseMemoObjectLiterals = (0, createRule_1.createRule)({
|
|
|
7
7
|
meta: {
|
|
8
8
|
type: 'suggestion',
|
|
9
9
|
docs: {
|
|
10
|
-
description:
|
|
10
|
+
description: "Enforce using useMemo for inline object/array literals passed as props to JSX components to prevent unnecessary re-renders. When object/array literals are defined inline in JSX, they create new references on every render, causing child components to re-render even if the values haven't changed. Wrap them in useMemo to maintain referential equality.",
|
|
11
11
|
recommended: 'error',
|
|
12
12
|
},
|
|
13
13
|
messages: {
|
|
@@ -25,7 +25,8 @@ exports.requireUseMemoObjectLiterals = (0, createRule_1.createRule)({
|
|
|
25
25
|
}
|
|
26
26
|
// Skip if the prop name is 'sx' or ends with 'Sx'
|
|
27
27
|
const propName = node.name.name;
|
|
28
|
-
if (typeof propName === 'string' &&
|
|
28
|
+
if (typeof propName === 'string' &&
|
|
29
|
+
(propName === 'sx' || propName.endsWith('Sx'))) {
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
const { expression } = node.value;
|
|
@@ -3,8 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.semanticFunctionPrefixes = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
-
const DISALLOWED_PREFIXES = new Set([
|
|
7
|
-
|
|
6
|
+
const DISALLOWED_PREFIXES = new Set([
|
|
7
|
+
'get',
|
|
8
|
+
'update',
|
|
9
|
+
'check',
|
|
10
|
+
'manage',
|
|
11
|
+
'process',
|
|
12
|
+
'do',
|
|
13
|
+
]);
|
|
14
|
+
const NEXTJS_DATA_FUNCTIONS = new Set([
|
|
15
|
+
'getServerSideProps',
|
|
16
|
+
'getStaticProps',
|
|
17
|
+
'getStaticPaths',
|
|
18
|
+
]);
|
|
8
19
|
const SUGGESTED_ALTERNATIVES = {
|
|
9
20
|
get: ['fetch', 'retrieve', 'compute', 'derive'],
|
|
10
21
|
update: ['modify', 'set', 'apply'],
|
|
@@ -75,7 +86,8 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
|
|
|
75
86
|
if (node.id) {
|
|
76
87
|
functionName = node.id.name;
|
|
77
88
|
}
|
|
78
|
-
else if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
89
|
+
else if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
90
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
79
91
|
functionName = node.parent.id.name;
|
|
80
92
|
}
|
|
81
93
|
if (!functionName)
|
|
@@ -40,14 +40,16 @@ exports.syncOnwriteNameFunc = (0, createRule_1.createRule)({
|
|
|
40
40
|
if (!nameProperty || !funcProperty) {
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
|
-
const nameValue = nameProperty.value
|
|
43
|
+
const nameValue = nameProperty.value
|
|
44
|
+
.value;
|
|
44
45
|
let funcName;
|
|
45
46
|
// Handle variable references
|
|
46
47
|
if (funcProperty.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
47
48
|
const funcIdentifier = funcProperty.value;
|
|
48
49
|
const scope = context.getScope();
|
|
49
|
-
const variable = scope.references.find(ref => ref.identifier === funcIdentifier)?.resolved;
|
|
50
|
-
if (variable?.defs[0]?.node.type ===
|
|
50
|
+
const variable = scope.references.find((ref) => ref.identifier === funcIdentifier)?.resolved;
|
|
51
|
+
if (variable?.defs[0]?.node.type ===
|
|
52
|
+
utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
51
53
|
variable.defs[0].node.init?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
52
54
|
// If the variable is initialized with another identifier, use that name
|
|
53
55
|
funcName = variable.defs[0].node.init.name;
|
package/lib/utils/ASTHelpers.js
CHANGED
|
@@ -262,9 +262,13 @@ class ASTHelpers {
|
|
|
262
262
|
default:
|
|
263
263
|
break;
|
|
264
264
|
}
|
|
265
|
-
// Removing duplicates
|
|
265
|
+
// Removing duplicates and ensuring exact matches only
|
|
266
266
|
return [
|
|
267
|
-
...new Set(dependencies.filter((dep) =>
|
|
267
|
+
...new Set(dependencies.filter((dep) => {
|
|
268
|
+
// Only include dependencies that exist exactly in the graph
|
|
269
|
+
// This prevents substring matches (e.g., 'nextMatches' vs 'nextMatchesWithResults')
|
|
270
|
+
return (graph?.[dep] !== undefined && graph?.[dep]?.type !== 'property');
|
|
271
|
+
})),
|
|
268
272
|
];
|
|
269
273
|
}
|
|
270
274
|
static isNode(value) {
|
|
@@ -63,7 +63,10 @@ class ClassGraphBuilder {
|
|
|
63
63
|
return node.type === 'MethodDefinition';
|
|
64
64
|
}
|
|
65
65
|
addDependencies(node, methodName) {
|
|
66
|
-
const newDependencies = ASTHelpers_1.ASTHelpers.classMethodDependenciesOf(node, this.graph, this.className).filter((name) =>
|
|
66
|
+
const newDependencies = ASTHelpers_1.ASTHelpers.classMethodDependenciesOf(node, this.graph, this.className).filter((name) => {
|
|
67
|
+
// Only include dependencies that exist exactly in the graph and aren't self-references
|
|
68
|
+
return !!this.graph[name] && name !== methodName;
|
|
69
|
+
});
|
|
67
70
|
if (this.graph[methodName]) {
|
|
68
71
|
// Ensure dependencies is initialized as an array
|
|
69
72
|
if (!Array.isArray(this.graph[methodName].dependencies)) {
|
|
@@ -72,7 +72,9 @@ class ClassGraphSorterReadability extends ClassGraphSorter_1.ClassGraphSorter {
|
|
|
72
72
|
visited.add(node.name);
|
|
73
73
|
dfsSortedNodes.push(node);
|
|
74
74
|
// Ensure node.dependencies exists and is an array before iterating
|
|
75
|
-
const dependencies = Array.isArray(node.dependencies)
|
|
75
|
+
const dependencies = Array.isArray(node.dependencies)
|
|
76
|
+
? node.dependencies
|
|
77
|
+
: [];
|
|
76
78
|
for (const dep of dependencies) {
|
|
77
79
|
const depNode = this.graph[String(dep)];
|
|
78
80
|
if (depNode) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blumintinc/eslint-plugin-blumint",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Custom eslint rules for use within BluMint",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Brodie McGuire",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"",
|
|
31
31
|
"lint:js": "eslint ./src",
|
|
32
32
|
"lint:shell": "shellcheck .devcontainer/git-flow-completion.bash",
|
|
33
|
-
"lint:fix": "tsc && eslint
|
|
33
|
+
"lint:fix": "tsc && eslint \"./src/**/*\" --quiet --fix",
|
|
34
34
|
"test": "jest --passWithNoTests --reporters=default --reporters=jest-junit",
|
|
35
35
|
"test:ci": "jest --ci --passWithNoTests --reporters=default --reporters=jest-junit",
|
|
36
36
|
"docs": "./scripts/make-docs.sh && npm run update:eslint-docs",
|
|
37
|
-
"update:eslint-docs": "eslint-doc-generator",
|
|
37
|
+
"update:eslint-docs": "eslint-doc-generator --init-rule-docs",
|
|
38
38
|
"build": "tsc",
|
|
39
39
|
"prepare": "husky install && npm run build",
|
|
40
40
|
"version": "git add -A src",
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const createRule_1 = require("../utils/createRule");
|
|
3
|
-
module.exports = (0, createRule_1.createRule)({
|
|
4
|
-
name: 'require-image-overlayed',
|
|
5
|
-
meta: {
|
|
6
|
-
type: 'problem',
|
|
7
|
-
docs: {
|
|
8
|
-
description: 'Enforce using ImageOverlayed component instead of next/image or img tags',
|
|
9
|
-
recommended: 'error',
|
|
10
|
-
requiresTypeChecking: false,
|
|
11
|
-
},
|
|
12
|
-
fixable: 'code',
|
|
13
|
-
schema: [
|
|
14
|
-
{
|
|
15
|
-
type: 'object',
|
|
16
|
-
properties: {
|
|
17
|
-
componentPath: {
|
|
18
|
-
type: 'string',
|
|
19
|
-
description: 'The import path for the ImageOverlayed component',
|
|
20
|
-
default: 'src/components/ImageOverlayed',
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
additionalProperties: false,
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
messages: {
|
|
27
|
-
useImageOverlayed: 'Use ImageOverlayed component from {{ componentPath }} instead of {{ component }}',
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
defaultOptions: [{ componentPath: 'src/components/ImageOverlayed' }],
|
|
31
|
-
create(context) {
|
|
32
|
-
const options = context.options[0] || {
|
|
33
|
-
componentPath: 'src/components/ImageOverlayed',
|
|
34
|
-
};
|
|
35
|
-
const sourceCode = context.getSourceCode();
|
|
36
|
-
return {
|
|
37
|
-
// Handle JSX img elements
|
|
38
|
-
JSXElement(node) {
|
|
39
|
-
if (node.openingElement.name.name === 'img') {
|
|
40
|
-
context.report({
|
|
41
|
-
node,
|
|
42
|
-
messageId: 'useImageOverlayed',
|
|
43
|
-
data: {
|
|
44
|
-
componentPath: options.componentPath,
|
|
45
|
-
component: 'img tag',
|
|
46
|
-
},
|
|
47
|
-
fix(fixer) {
|
|
48
|
-
const attributes = node.openingElement.attributes
|
|
49
|
-
.map((attr) => sourceCode.getText(attr))
|
|
50
|
-
.join(' ');
|
|
51
|
-
return fixer.replaceText(node, `<ImageOverlayed ${attributes} />`);
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
// Handle next/image imports and usage
|
|
57
|
-
ImportDeclaration(node) {
|
|
58
|
-
if (node.source.value === 'next/image' && node.specifiers.length > 0) {
|
|
59
|
-
const imageSpecifier = node.specifiers.find((spec) => (spec.type === 'ImportDefaultSpecifier' ||
|
|
60
|
-
spec.type === 'ImportSpecifier') &&
|
|
61
|
-
(spec.local.name === 'Image' || spec.imported?.name === 'Image'));
|
|
62
|
-
if (imageSpecifier) {
|
|
63
|
-
const localName = imageSpecifier.local.name;
|
|
64
|
-
// Report the import
|
|
65
|
-
context.report({
|
|
66
|
-
node,
|
|
67
|
-
messageId: 'useImageOverlayed',
|
|
68
|
-
data: {
|
|
69
|
-
componentPath: options.componentPath,
|
|
70
|
-
component: 'next/image',
|
|
71
|
-
},
|
|
72
|
-
fix(fixer) {
|
|
73
|
-
return fixer.replaceText(node, `import ${localName} from '${options.componentPath}';`);
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
//# sourceMappingURL=require-image-overlayed.js.map
|