@blumintinc/eslint-plugin-blumint 1.7.0 → 1.7.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/lib/index.js +4 -1
- package/lib/rules/enforce-exported-function-types.js +307 -8
- package/lib/rules/enforce-render-hits-memoization.d.ts +3 -0
- package/lib/rules/enforce-render-hits-memoization.js +96 -0
- package/lib/rules/extract-global-constants.js +4 -0
- package/lib/rules/no-entire-object-hook-deps.js +10 -0
- package/lib/rules/prefer-batch-operations.js +44 -0
- package/lib/rules/require-hooks-default-params.js +121 -97
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -68,10 +68,11 @@ const no_mock_firebase_admin_1 = require("./rules/no-mock-firebase-admin");
|
|
|
68
68
|
const enforce_centralized_mock_firestore_1 = require("./rules/enforce-centralized-mock-firestore");
|
|
69
69
|
const require_hooks_default_params_1 = require("./rules/require-hooks-default-params");
|
|
70
70
|
const prefer_destructuring_no_class_1 = require("./rules/prefer-destructuring-no-class");
|
|
71
|
+
const enforce_render_hits_memoization_1 = require("./rules/enforce-render-hits-memoization");
|
|
71
72
|
module.exports = {
|
|
72
73
|
meta: {
|
|
73
74
|
name: '@blumintinc/eslint-plugin-blumint',
|
|
74
|
-
version: '1.7.
|
|
75
|
+
version: '1.7.1',
|
|
75
76
|
},
|
|
76
77
|
parseOptions: {
|
|
77
78
|
ecmaVersion: 2020,
|
|
@@ -145,6 +146,7 @@ module.exports = {
|
|
|
145
146
|
'@blumintinc/blumint/enforce-centralized-mock-firestore': 'error',
|
|
146
147
|
'@blumintinc/blumint/require-hooks-default-params': 'error',
|
|
147
148
|
'@blumintinc/blumint/prefer-destructuring-no-class': 'error',
|
|
149
|
+
'@blumintinc/blumint/enforce-render-hits-memoization': 'error',
|
|
148
150
|
},
|
|
149
151
|
},
|
|
150
152
|
},
|
|
@@ -214,6 +216,7 @@ module.exports = {
|
|
|
214
216
|
'enforce-centralized-mock-firestore': enforce_centralized_mock_firestore_1.enforceCentralizedMockFirestore,
|
|
215
217
|
'require-hooks-default-params': require_hooks_default_params_1.requireHooksDefaultParams,
|
|
216
218
|
'prefer-destructuring-no-class': prefer_destructuring_no_class_1.preferDestructuringNoClass,
|
|
219
|
+
'enforce-render-hits-memoization': enforce_render_hits_memoization_1.enforceRenderHitsMemoization,
|
|
217
220
|
},
|
|
218
221
|
};
|
|
219
222
|
//# sourceMappingURL=index.js.map
|
|
@@ -91,6 +91,7 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
91
91
|
}
|
|
92
92
|
function isBuiltInType(typeName) {
|
|
93
93
|
const builtInTypes = new Set([
|
|
94
|
+
// Primitive types
|
|
94
95
|
'string',
|
|
95
96
|
'number',
|
|
96
97
|
'boolean',
|
|
@@ -101,6 +102,9 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
101
102
|
'never',
|
|
102
103
|
'unknown',
|
|
103
104
|
'object',
|
|
105
|
+
'symbol',
|
|
106
|
+
'bigint',
|
|
107
|
+
// Built-in objects
|
|
104
108
|
'Date',
|
|
105
109
|
'RegExp',
|
|
106
110
|
'Error',
|
|
@@ -113,6 +117,259 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
113
117
|
'Set',
|
|
114
118
|
'WeakMap',
|
|
115
119
|
'WeakSet',
|
|
120
|
+
'Proxy',
|
|
121
|
+
'DataView',
|
|
122
|
+
'ArrayBuffer',
|
|
123
|
+
'SharedArrayBuffer',
|
|
124
|
+
'Atomics',
|
|
125
|
+
'Intl',
|
|
126
|
+
'JSON',
|
|
127
|
+
'Math',
|
|
128
|
+
'Reflect',
|
|
129
|
+
'WeakRef',
|
|
130
|
+
'FinalizationRegistry',
|
|
131
|
+
// TypeScript utility types
|
|
132
|
+
'Record',
|
|
133
|
+
'Partial',
|
|
134
|
+
'Required',
|
|
135
|
+
'Readonly',
|
|
136
|
+
'Pick',
|
|
137
|
+
'Omit',
|
|
138
|
+
'Exclude',
|
|
139
|
+
'Extract',
|
|
140
|
+
'NonNullable',
|
|
141
|
+
'Parameters',
|
|
142
|
+
'ConstructorParameters',
|
|
143
|
+
'ReturnType',
|
|
144
|
+
'InstanceType',
|
|
145
|
+
'ThisParameterType',
|
|
146
|
+
'OmitThisParameter',
|
|
147
|
+
'ThisType',
|
|
148
|
+
'Uppercase',
|
|
149
|
+
'Lowercase',
|
|
150
|
+
'Capitalize',
|
|
151
|
+
'Uncapitalize',
|
|
152
|
+
'Awaited',
|
|
153
|
+
'PropertyKey',
|
|
154
|
+
'Mutable',
|
|
155
|
+
'DeepPartial',
|
|
156
|
+
'DeepReadonly',
|
|
157
|
+
'DeepRequired',
|
|
158
|
+
'Merge',
|
|
159
|
+
'MergeExclusive',
|
|
160
|
+
'OptionalKeys',
|
|
161
|
+
'RequiredKeys',
|
|
162
|
+
'ReadonlyKeys',
|
|
163
|
+
'WritableKeys',
|
|
164
|
+
'UnionToIntersection',
|
|
165
|
+
'UnionToTuple',
|
|
166
|
+
'Promisable',
|
|
167
|
+
// React types
|
|
168
|
+
'ReactElement',
|
|
169
|
+
'ReactNode',
|
|
170
|
+
'ReactChild',
|
|
171
|
+
'ReactChildren',
|
|
172
|
+
'ReactFragment',
|
|
173
|
+
'ReactPortal',
|
|
174
|
+
'ReactText',
|
|
175
|
+
'JSX',
|
|
176
|
+
'JSXElement',
|
|
177
|
+
'JSXElementConstructor',
|
|
178
|
+
'ComponentType',
|
|
179
|
+
'ComponentProps',
|
|
180
|
+
'ComponentRef',
|
|
181
|
+
'ElementType',
|
|
182
|
+
'ElementRef',
|
|
183
|
+
'Ref',
|
|
184
|
+
'RefObject',
|
|
185
|
+
'RefCallback',
|
|
186
|
+
'LegacyRef',
|
|
187
|
+
'PropsWithRef',
|
|
188
|
+
'PropsWithChildren',
|
|
189
|
+
'PropsWithoutRef',
|
|
190
|
+
'FC',
|
|
191
|
+
'FunctionComponent',
|
|
192
|
+
'VFC',
|
|
193
|
+
'VoidFunctionComponent',
|
|
194
|
+
'ForwardRefRenderFunction',
|
|
195
|
+
'ForwardRefExoticComponent',
|
|
196
|
+
'MutableRefObject',
|
|
197
|
+
'ClassAttributes',
|
|
198
|
+
'DOMAttributes',
|
|
199
|
+
'CSSProperties',
|
|
200
|
+
'StyleHTMLAttributes',
|
|
201
|
+
'AriaAttributes',
|
|
202
|
+
'DOMElement',
|
|
203
|
+
'ReactHTML',
|
|
204
|
+
'ReactSVG',
|
|
205
|
+
// Node.js types
|
|
206
|
+
'Buffer',
|
|
207
|
+
'ReadableStream',
|
|
208
|
+
'WritableStream',
|
|
209
|
+
'DuplexStream',
|
|
210
|
+
'TransformStream',
|
|
211
|
+
'ReadStream',
|
|
212
|
+
'WriteStream',
|
|
213
|
+
'EventEmitter',
|
|
214
|
+
'Timeout',
|
|
215
|
+
'Immediate',
|
|
216
|
+
'NodeJS',
|
|
217
|
+
'Process',
|
|
218
|
+
'Global',
|
|
219
|
+
// DOM types
|
|
220
|
+
'HTMLElement',
|
|
221
|
+
'Element',
|
|
222
|
+
'Node',
|
|
223
|
+
'Document',
|
|
224
|
+
'Window',
|
|
225
|
+
'Event',
|
|
226
|
+
'CustomEvent',
|
|
227
|
+
'MouseEvent',
|
|
228
|
+
'KeyboardEvent',
|
|
229
|
+
'TouchEvent',
|
|
230
|
+
'DragEvent',
|
|
231
|
+
'ClipboardEvent',
|
|
232
|
+
'FocusEvent',
|
|
233
|
+
'FormEvent',
|
|
234
|
+
'ChangeEvent',
|
|
235
|
+
'AnimationEvent',
|
|
236
|
+
'TransitionEvent',
|
|
237
|
+
'SVGElement',
|
|
238
|
+
'File',
|
|
239
|
+
'FileList',
|
|
240
|
+
'Blob',
|
|
241
|
+
'URL',
|
|
242
|
+
'URLSearchParams',
|
|
243
|
+
'FormData',
|
|
244
|
+
'Headers',
|
|
245
|
+
'Request',
|
|
246
|
+
'Response',
|
|
247
|
+
'WebSocket',
|
|
248
|
+
'Worker',
|
|
249
|
+
'ServiceWorker',
|
|
250
|
+
'MessagePort',
|
|
251
|
+
'Storage',
|
|
252
|
+
'CSSStyleDeclaration',
|
|
253
|
+
// Common library types
|
|
254
|
+
'Observable',
|
|
255
|
+
'Subscription',
|
|
256
|
+
'Subject',
|
|
257
|
+
'BehaviorSubject',
|
|
258
|
+
'ReplaySubject',
|
|
259
|
+
'AsyncSubject',
|
|
260
|
+
'Observer',
|
|
261
|
+
'Operator',
|
|
262
|
+
'Scheduler',
|
|
263
|
+
'Unsubscribable',
|
|
264
|
+
'SubscriptionLike',
|
|
265
|
+
'TeardownLogic',
|
|
266
|
+
'MonoTypeOperatorFunction',
|
|
267
|
+
'OperatorFunction',
|
|
268
|
+
'UnaryFunction',
|
|
269
|
+
'Action',
|
|
270
|
+
'AnyAction',
|
|
271
|
+
'Dispatch',
|
|
272
|
+
'Reducer',
|
|
273
|
+
'Store',
|
|
274
|
+
'Middleware',
|
|
275
|
+
'Selector',
|
|
276
|
+
'ThunkAction',
|
|
277
|
+
'ThunkDispatch',
|
|
278
|
+
'Query',
|
|
279
|
+
'Mutation',
|
|
280
|
+
'QueryResult',
|
|
281
|
+
'MutationResult',
|
|
282
|
+
'UseQueryResult',
|
|
283
|
+
'UseMutationResult',
|
|
284
|
+
'UseInfiniteQueryResult',
|
|
285
|
+
'Schema',
|
|
286
|
+
'Model',
|
|
287
|
+
'Document',
|
|
288
|
+
'ObjectId',
|
|
289
|
+
'Collection',
|
|
290
|
+
'Db',
|
|
291
|
+
'Client',
|
|
292
|
+
'Session',
|
|
293
|
+
'Transaction',
|
|
294
|
+
'ValidationError',
|
|
295
|
+
'ValidationResult',
|
|
296
|
+
'Validator',
|
|
297
|
+
'Logger',
|
|
298
|
+
'LogLevel',
|
|
299
|
+
'Options',
|
|
300
|
+
'Settings',
|
|
301
|
+
'Context',
|
|
302
|
+
'Next',
|
|
303
|
+
'Middleware',
|
|
304
|
+
'Route',
|
|
305
|
+
'Router',
|
|
306
|
+
'Handler',
|
|
307
|
+
'Controller',
|
|
308
|
+
'Service',
|
|
309
|
+
'Repository',
|
|
310
|
+
'Entity',
|
|
311
|
+
'DTO',
|
|
312
|
+
'Enum',
|
|
313
|
+
'Union',
|
|
314
|
+
'Intersection',
|
|
315
|
+
'Tuple',
|
|
316
|
+
'Readonly',
|
|
317
|
+
'ReadonlyArray',
|
|
318
|
+
'ReadonlyMap',
|
|
319
|
+
'ReadonlySet',
|
|
320
|
+
'Iterable',
|
|
321
|
+
'Iterator',
|
|
322
|
+
'Generator',
|
|
323
|
+
'AsyncIterator',
|
|
324
|
+
'AsyncGenerator',
|
|
325
|
+
'Thenable',
|
|
326
|
+
'PromiseLike',
|
|
327
|
+
'ArrayLike',
|
|
328
|
+
'Indexable',
|
|
329
|
+
'Nullable',
|
|
330
|
+
'Optional',
|
|
331
|
+
'NonOptional',
|
|
332
|
+
'NonNullable',
|
|
333
|
+
'NonUndefined',
|
|
334
|
+
'NonVoid',
|
|
335
|
+
'NonEmptyArray',
|
|
336
|
+
'DeepReadonly',
|
|
337
|
+
'DeepPartial',
|
|
338
|
+
'DeepRequired',
|
|
339
|
+
'DeepNonNullable',
|
|
340
|
+
'DeepMutable',
|
|
341
|
+
'Primitive',
|
|
342
|
+
'Nullish',
|
|
343
|
+
'Falsy',
|
|
344
|
+
'Truthy',
|
|
345
|
+
'Exact',
|
|
346
|
+
'Strict',
|
|
347
|
+
'Loose',
|
|
348
|
+
'Writable',
|
|
349
|
+
'Mutable',
|
|
350
|
+
'Immutable',
|
|
351
|
+
'Frozen',
|
|
352
|
+
'Sealed',
|
|
353
|
+
'Extensible',
|
|
354
|
+
'Constructable',
|
|
355
|
+
'Abstract',
|
|
356
|
+
'Concrete',
|
|
357
|
+
'Override',
|
|
358
|
+
'Overwrite',
|
|
359
|
+
'OmitByValue',
|
|
360
|
+
'PickByValue',
|
|
361
|
+
'RequiredBy',
|
|
362
|
+
'OptionalBy',
|
|
363
|
+
'ReadonlyBy',
|
|
364
|
+
'MutableBy',
|
|
365
|
+
'NullableBy',
|
|
366
|
+
'NonNullableBy',
|
|
367
|
+
'UndefinedBy',
|
|
368
|
+
'NonUndefinedBy',
|
|
369
|
+
'NeverBy',
|
|
370
|
+
'UnknownBy',
|
|
371
|
+
'AnyBy',
|
|
372
|
+
'VoidBy',
|
|
116
373
|
]);
|
|
117
374
|
return builtInTypes.has(typeName);
|
|
118
375
|
}
|
|
@@ -135,7 +392,16 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
135
392
|
}
|
|
136
393
|
}
|
|
137
394
|
}
|
|
395
|
+
function checkAndReportParameterType(param, messageId) {
|
|
396
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation) {
|
|
397
|
+
checkAndReportType(param.typeAnnotation.typeAnnotation, param.typeAnnotation, messageId);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
138
400
|
function isTypeExported(typeName) {
|
|
401
|
+
// If it's a built-in type, consider it as "exported"
|
|
402
|
+
if (isBuiltInType(typeName)) {
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
139
405
|
const sourceCode = context.getSourceCode();
|
|
140
406
|
const program = sourceCode.ast;
|
|
141
407
|
// Check for imported types first - if found, return true immediately
|
|
@@ -159,6 +425,11 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
159
425
|
if (node.declaration?.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
160
426
|
return node.declaration.id.name === typeName;
|
|
161
427
|
}
|
|
428
|
+
// Check for export { Type } statements
|
|
429
|
+
if (node.specifiers) {
|
|
430
|
+
return node.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ExportSpecifier &&
|
|
431
|
+
specifier.local.name === typeName);
|
|
432
|
+
}
|
|
162
433
|
}
|
|
163
434
|
return false;
|
|
164
435
|
});
|
|
@@ -186,15 +457,49 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
186
457
|
}
|
|
187
458
|
// Handle type aliases in variable declarations
|
|
188
459
|
if (parent.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
460
|
+
// Check if the type alias itself is exported
|
|
189
461
|
if (parent.parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
190
462
|
return true;
|
|
191
463
|
}
|
|
464
|
+
// If not exported, return false
|
|
465
|
+
return false;
|
|
192
466
|
}
|
|
193
467
|
// Handle type aliases in interface declarations
|
|
194
468
|
if (parent.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
469
|
+
// Check if the interface itself is exported
|
|
195
470
|
if (parent.parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
196
471
|
return true;
|
|
197
472
|
}
|
|
473
|
+
// If not exported, return false
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
// Handle type aliases in variable declarations
|
|
477
|
+
if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
478
|
+
if (parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
479
|
+
if (parent.parent.parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// Handle type aliases in type declarations
|
|
485
|
+
if (parent.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
486
|
+
if (parent.parent?.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
487
|
+
// Check if the type alias itself is exported
|
|
488
|
+
if (parent.parent.parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
// If not exported, return false
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// Handle type aliases in type declarations
|
|
496
|
+
if (def.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
497
|
+
// Check if the type alias itself is exported
|
|
498
|
+
if (def.node.parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
// If not exported, return false
|
|
502
|
+
return false;
|
|
198
503
|
}
|
|
199
504
|
return false;
|
|
200
505
|
}
|
|
@@ -211,10 +516,7 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
211
516
|
}
|
|
212
517
|
// Check parameter types
|
|
213
518
|
node.params.forEach((param) => {
|
|
214
|
-
|
|
215
|
-
param.typeAnnotation) {
|
|
216
|
-
checkAndReportType(param.typeAnnotation.typeAnnotation, param.typeAnnotation, 'missingExportedType');
|
|
217
|
-
}
|
|
519
|
+
checkAndReportParameterType(param, 'missingExportedType');
|
|
218
520
|
});
|
|
219
521
|
},
|
|
220
522
|
'VariableDeclarator > ArrowFunctionExpression'(node) {
|
|
@@ -231,10 +533,7 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
231
533
|
}
|
|
232
534
|
// Check parameter types
|
|
233
535
|
node.params.forEach((param) => {
|
|
234
|
-
|
|
235
|
-
param.typeAnnotation) {
|
|
236
|
-
checkAndReportType(param.typeAnnotation.typeAnnotation, param.typeAnnotation, 'missingExportedType');
|
|
237
|
-
}
|
|
536
|
+
checkAndReportParameterType(param, 'missingExportedType');
|
|
238
537
|
});
|
|
239
538
|
},
|
|
240
539
|
// Handle React components
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
type MessageIds = 'requireMemoizedTransformBefore' | 'requireMemoizedRender' | 'requireMemoizedRenderHits' | 'noDirectComponentInRender';
|
|
2
|
+
export declare const enforceRenderHitsMemoization: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
3
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceRenderHitsMemoization = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.enforceRenderHitsMemoization = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'enforce-render-hits-memoization',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce proper memoization and usage of useRenderHits and renderHits',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
requireMemoizedTransformBefore: 'transformBefore prop must be memoized using useCallback',
|
|
17
|
+
requireMemoizedRender: 'render prop must be memoized using useCallback',
|
|
18
|
+
requireMemoizedRenderHits: 'renderHits must be used inside useMemo or useCallback',
|
|
19
|
+
noDirectComponentInRender: 'Do not pass React components directly to render prop, use a memoized arrow function instead',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
const isMemoizedCall = (node) => {
|
|
25
|
+
if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
26
|
+
return false;
|
|
27
|
+
const callee = node.callee;
|
|
28
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
29
|
+
return false;
|
|
30
|
+
return callee.name === 'useCallback' || callee.name === 'useMemo';
|
|
31
|
+
};
|
|
32
|
+
const isReactComponent = (node) => {
|
|
33
|
+
if (node.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
34
|
+
return false;
|
|
35
|
+
const name = node.name;
|
|
36
|
+
return /^[A-Z]/.test(name);
|
|
37
|
+
};
|
|
38
|
+
const isInsideMemoizedCall = (node) => {
|
|
39
|
+
let current = node;
|
|
40
|
+
while (current?.parent) {
|
|
41
|
+
if (isMemoizedCall(current.parent))
|
|
42
|
+
return true;
|
|
43
|
+
current = current.parent;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
CallExpression(node) {
|
|
49
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier && node.callee.name === 'useRenderHits') {
|
|
50
|
+
if (node.arguments.length === 0)
|
|
51
|
+
return;
|
|
52
|
+
const options = node.arguments[0];
|
|
53
|
+
if (options.type !== utils_1.AST_NODE_TYPES.ObjectExpression)
|
|
54
|
+
return;
|
|
55
|
+
for (const prop of options.properties) {
|
|
56
|
+
if (prop.type !== utils_1.AST_NODE_TYPES.Property)
|
|
57
|
+
continue;
|
|
58
|
+
if (prop.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
59
|
+
continue;
|
|
60
|
+
if (prop.key.name === 'transformBefore') {
|
|
61
|
+
if (!isInsideMemoizedCall(prop.value)) {
|
|
62
|
+
context.report({
|
|
63
|
+
node: prop.value,
|
|
64
|
+
messageId: 'requireMemoizedTransformBefore',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (prop.key.name === 'render') {
|
|
69
|
+
if (isReactComponent(prop.value)) {
|
|
70
|
+
context.report({
|
|
71
|
+
node: prop.value,
|
|
72
|
+
messageId: 'noDirectComponentInRender',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else if (!isInsideMemoizedCall(prop.value)) {
|
|
76
|
+
context.report({
|
|
77
|
+
node: prop.value,
|
|
78
|
+
messageId: 'requireMemoizedRender',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier && node.callee.name === 'renderHits') {
|
|
85
|
+
if (!isInsideMemoizedCall(node)) {
|
|
86
|
+
context.report({
|
|
87
|
+
node,
|
|
88
|
+
messageId: 'requireMemoizedRenderHits',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=enforce-render-hits-memoization.js.map
|
|
@@ -40,6 +40,10 @@ function isImmutableValue(node) {
|
|
|
40
40
|
function isMutableValue(node) {
|
|
41
41
|
if (!node)
|
|
42
42
|
return false;
|
|
43
|
+
// Check for JSX elements (always mutable due to props/context)
|
|
44
|
+
if (node.type === 'JSXElement' || node.type === 'JSXFragment') {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
43
47
|
// Check for object expressions (always mutable)
|
|
44
48
|
if (node.type === 'ObjectExpression') {
|
|
45
49
|
return true;
|
|
@@ -49,6 +49,12 @@ function getObjectUsagesInHook(hookBody, objectName) {
|
|
|
49
49
|
const usages = new Set();
|
|
50
50
|
const visited = new Set();
|
|
51
51
|
let needsEntireObject = false;
|
|
52
|
+
// Built-in array methods that should not be considered as object properties
|
|
53
|
+
const ARRAY_METHODS = new Set([
|
|
54
|
+
'map', 'filter', 'reduce', 'forEach', 'some', 'every', 'find', 'findIndex',
|
|
55
|
+
'includes', 'indexOf', 'join', 'slice', 'splice', 'concat', 'push', 'pop',
|
|
56
|
+
'shift', 'unshift', 'sort', 'reverse', 'flat', 'flatMap'
|
|
57
|
+
]);
|
|
52
58
|
function buildAccessPath(node) {
|
|
53
59
|
const parts = [];
|
|
54
60
|
let current = node;
|
|
@@ -62,6 +68,10 @@ function getObjectUsagesInHook(hookBody, objectName) {
|
|
|
62
68
|
if (memberExpr.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
63
69
|
return null;
|
|
64
70
|
}
|
|
71
|
+
// Skip array methods
|
|
72
|
+
if (memberExpr.property.name && ARRAY_METHODS.has(memberExpr.property.name)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
65
75
|
parts.unshift(memberExpr.property.name);
|
|
66
76
|
if (memberExpr.optional) {
|
|
67
77
|
hasOptionalChaining = true;
|
|
@@ -69,6 +69,40 @@ function findLoopNode(node) {
|
|
|
69
69
|
}
|
|
70
70
|
return undefined;
|
|
71
71
|
}
|
|
72
|
+
function isFirestoreSetterInstance(node) {
|
|
73
|
+
// Check if it's a DocSetter instance
|
|
74
|
+
if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
75
|
+
return (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
76
|
+
node.callee.name === 'DocSetter');
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
function isMapInstance(node) {
|
|
81
|
+
// Check if it's a Map instance
|
|
82
|
+
if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
83
|
+
return (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
84
|
+
node.callee.name === 'Map');
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
function findVariableDeclaration(node, varName) {
|
|
89
|
+
let current = node;
|
|
90
|
+
while (current) {
|
|
91
|
+
if (current.type === utils_1.AST_NODE_TYPES.Program) {
|
|
92
|
+
for (const statement of current.body) {
|
|
93
|
+
if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
94
|
+
for (const decl of statement.declarations) {
|
|
95
|
+
if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier && decl.id.name === varName) {
|
|
96
|
+
return decl;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
current = current.parent;
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
72
106
|
function isSetterMethodCall(node) {
|
|
73
107
|
if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
74
108
|
return { isValid: false };
|
|
@@ -84,6 +118,16 @@ function isSetterMethodCall(node) {
|
|
|
84
118
|
if (object.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
85
119
|
return { isValid: false };
|
|
86
120
|
const setterInstance = object.name;
|
|
121
|
+
// Find the variable declaration
|
|
122
|
+
const decl = findVariableDeclaration(node, setterInstance);
|
|
123
|
+
if (!decl || !decl.init)
|
|
124
|
+
return { isValid: false };
|
|
125
|
+
// Skip if it's a Map instance
|
|
126
|
+
if (isMapInstance(decl.init))
|
|
127
|
+
return { isValid: false };
|
|
128
|
+
// Check if it's a Firestore setter instance
|
|
129
|
+
if (!isFirestoreSetterInstance(decl.init))
|
|
130
|
+
return { isValid: false };
|
|
87
131
|
// Get the method name
|
|
88
132
|
const methodName = callee.property.name;
|
|
89
133
|
return { isValid: true, methodName, setterInstance };
|
|
@@ -41,9 +41,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
41
41
|
const scope = context.getScope();
|
|
42
42
|
const variable = scope.variables.find(v => v.name === typeName.name);
|
|
43
43
|
if (!variable || !variable.defs[0]?.node) {
|
|
44
|
-
// If we can't find the type definition, assume it's a type with
|
|
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
|
|
46
|
-
return
|
|
46
|
+
return false;
|
|
47
47
|
}
|
|
48
48
|
const def = variable.defs[0].node;
|
|
49
49
|
if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
@@ -58,9 +58,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
// If we found the type definition but it's not a type alias or interface declaration,
|
|
61
|
-
// assume it's a type with
|
|
61
|
+
// assume it's a type with required properties
|
|
62
62
|
// This handles cases where the type is imported from another module
|
|
63
|
-
return
|
|
63
|
+
return false;
|
|
64
64
|
}
|
|
65
65
|
// Handle type alias declarations
|
|
66
66
|
if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
@@ -77,71 +77,130 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
77
77
|
}
|
|
78
78
|
return false;
|
|
79
79
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
return {
|
|
81
|
+
'ArrowFunctionExpression, FunctionDeclaration'(node) {
|
|
82
|
+
// Check if it's a hook function
|
|
83
|
+
let isHook = false;
|
|
84
|
+
if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
85
|
+
isHook = node.id ? isHookName(node.id.name) : false;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const parent = node.parent;
|
|
89
|
+
if (parent &&
|
|
90
|
+
parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
91
|
+
parent.id &&
|
|
92
|
+
parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
93
|
+
isHook = isHookName(parent.id.name);
|
|
86
94
|
}
|
|
87
95
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
}
|
|
96
|
+
if (!isHook) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Check if it has exactly one parameter
|
|
100
|
+
if (node.params.length !== 1) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// Check if the parameter is already an assignment pattern
|
|
104
|
+
const param = node.params[0];
|
|
105
|
+
if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Check if the parameter has a type annotation
|
|
109
|
+
if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern && param.typeAnnotation) {
|
|
110
|
+
const typeAnnotation = param.typeAnnotation.typeAnnotation;
|
|
111
|
+
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
112
|
+
const typeName = typeAnnotation.typeName;
|
|
113
|
+
if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
114
|
+
const scope = context.getScope();
|
|
115
|
+
const variable = scope.variables.find(v => v.name === typeName.name);
|
|
116
|
+
if (variable && variable.defs[0]?.node) {
|
|
117
|
+
const def = variable.defs[0].node;
|
|
118
|
+
if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
119
|
+
if (hasAllOptionalProperties(def.typeAnnotation)) {
|
|
120
|
+
context.report({
|
|
121
|
+
node: param,
|
|
122
|
+
messageId: 'requireDefaultParams',
|
|
123
|
+
fix(fixer) {
|
|
124
|
+
const paramText = context.getSourceCode().getText(param);
|
|
125
|
+
return fixer.replaceText(param, `${paramText} = {}`);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
131
|
+
if (def.body.body.every(member => {
|
|
132
|
+
if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
return member.optional === true;
|
|
136
|
+
})) {
|
|
137
|
+
context.report({
|
|
138
|
+
node: param,
|
|
139
|
+
messageId: 'requireDefaultParams',
|
|
140
|
+
fix(fixer) {
|
|
141
|
+
const paramText = context.getSourceCode().getText(param);
|
|
142
|
+
return fixer.replaceText(param, `${paramText} = {}`);
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
122
147
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
else {
|
|
149
|
+
// If we can't find the type definition, check if it's defined in the same file
|
|
150
|
+
const program = context.getSourceCode().ast;
|
|
151
|
+
const typeDefinitions = program.body.filter(node => {
|
|
152
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration || node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
153
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
154
|
+
return node.id.name === typeName.name;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
return node.id.name === typeName.name;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
127
160
|
return false;
|
|
128
|
-
}
|
|
129
|
-
return member.optional === true;
|
|
130
|
-
})) {
|
|
131
|
-
context.report({
|
|
132
|
-
node: param,
|
|
133
|
-
messageId: 'requireDefaultParams',
|
|
134
|
-
fix(fixer) {
|
|
135
|
-
const paramText = context.getSourceCode().getText(param);
|
|
136
|
-
return fixer.replaceText(param, `${paramText} = {}`);
|
|
137
|
-
},
|
|
138
161
|
});
|
|
162
|
+
if (typeDefinitions.length > 0) {
|
|
163
|
+
const def = typeDefinitions[0];
|
|
164
|
+
if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
165
|
+
if (hasAllOptionalProperties(def.typeAnnotation)) {
|
|
166
|
+
context.report({
|
|
167
|
+
node: param,
|
|
168
|
+
messageId: 'requireDefaultParams',
|
|
169
|
+
fix(fixer) {
|
|
170
|
+
const paramText = context.getSourceCode().getText(param);
|
|
171
|
+
return fixer.replaceText(param, `${paramText} = {}`);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
177
|
+
if (def.body.body.every(member => {
|
|
178
|
+
if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return member.optional === true;
|
|
182
|
+
})) {
|
|
183
|
+
context.report({
|
|
184
|
+
node: param,
|
|
185
|
+
messageId: 'requireDefaultParams',
|
|
186
|
+
fix(fixer) {
|
|
187
|
+
const paramText = context.getSourceCode().getText(param);
|
|
188
|
+
return fixer.replaceText(param, `${paramText} = {}`);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
139
194
|
}
|
|
140
195
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
196
|
+
}
|
|
197
|
+
else if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
|
|
198
|
+
if (typeAnnotation.members.every(member => {
|
|
199
|
+
if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
return member.optional === true;
|
|
203
|
+
})) {
|
|
145
204
|
context.report({
|
|
146
205
|
node: param,
|
|
147
206
|
messageId: 'requireDefaultParams',
|
|
@@ -153,41 +212,6 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
153
212
|
}
|
|
154
213
|
}
|
|
155
214
|
}
|
|
156
|
-
else if (hasAllOptionalProperties(typeAnnotation)) {
|
|
157
|
-
context.report({
|
|
158
|
-
node: param,
|
|
159
|
-
messageId: 'requireDefaultParams',
|
|
160
|
-
fix(fixer) {
|
|
161
|
-
const paramText = context.getSourceCode().getText(param);
|
|
162
|
-
return fixer.replaceText(param, `${paramText} = {}`);
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
FunctionDeclaration(node) {
|
|
170
|
-
if (!node.id || !isHookName(node.id.name)) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (node.params.length !== 1) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
checkHookParam(node.params[0]);
|
|
177
|
-
},
|
|
178
|
-
ArrowFunctionExpression(node) {
|
|
179
|
-
const parent = node.parent;
|
|
180
|
-
if (!parent ||
|
|
181
|
-
parent.type !== utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
182
|
-
!parent.id ||
|
|
183
|
-
parent.id.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
184
|
-
!isHookName(parent.id.name)) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (node.params.length !== 1) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
checkHookParam(node.params[0]);
|
|
191
215
|
},
|
|
192
216
|
};
|
|
193
217
|
},
|