@blumintinc/eslint-plugin-blumint 1.6.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/README.md +62 -39
- package/lib/index.js +22 -1
- package/lib/rules/enforce-assert-throws.js +23 -0
- package/lib/rules/enforce-centralized-mock-firestore.d.ts +1 -0
- package/lib/rules/enforce-centralized-mock-firestore.js +55 -0
- 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/enforce-verb-noun-naming.js +1 -0
- package/lib/rules/extract-global-constants.js +4 -0
- package/lib/rules/no-entire-object-hook-deps.js +10 -0
- package/lib/rules/no-firestore-jest-mock.d.ts +1 -0
- package/lib/rules/no-firestore-jest-mock.js +59 -0
- package/lib/rules/no-mock-firebase-admin.d.ts +1 -0
- package/lib/rules/no-mock-firebase-admin.js +50 -0
- package/lib/rules/prefer-batch-operations.js +44 -0
- package/lib/rules/prefer-clone-deep.d.ts +1 -0
- package/lib/rules/prefer-clone-deep.js +72 -0
- package/lib/rules/prefer-settings-object.js +28 -0
- package/lib/rules/require-hooks-default-params.js +121 -97
- package/package.json +2 -2
|
@@ -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;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noFirestoreJestMock: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noFirestoreJestMock", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noFirestoreJestMock = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.noFirestoreJestMock = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'no-firestore-jest-mock',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Prevent importing firestore-jest-mock in test files',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
noFirestoreJestMock: 'Do not import from firestore-jest-mock. Use mockFirestore from the centralized mock utility instead.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
const filename = context.getFilename();
|
|
23
|
+
// Only apply rule to test files
|
|
24
|
+
if (!filename.endsWith('.test.ts')) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
ImportDeclaration(node) {
|
|
29
|
+
if (node.source.value === 'firestore-jest-mock') {
|
|
30
|
+
context.report({
|
|
31
|
+
node,
|
|
32
|
+
messageId: 'noFirestoreJestMock',
|
|
33
|
+
fix(fixer) {
|
|
34
|
+
// Replace with mockFirestore import
|
|
35
|
+
return fixer.replaceText(node, "import { mockFirestore } from '../../../../../__mocks__/functions/src/config/mockFirestore';");
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
CallExpression(node) {
|
|
41
|
+
// Check for jest.mock('firestore-jest-mock')
|
|
42
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
43
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
44
|
+
node.callee.object.name === 'jest' &&
|
|
45
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
46
|
+
node.callee.property.name === 'mock' &&
|
|
47
|
+
node.arguments.length > 0 &&
|
|
48
|
+
node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
|
|
49
|
+
node.arguments[0].value === 'firestore-jest-mock') {
|
|
50
|
+
context.report({
|
|
51
|
+
node,
|
|
52
|
+
messageId: 'noFirestoreJestMock',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=no-firestore-jest-mock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noMockFirebaseAdmin: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noMockFirebaseAdmin", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noMockFirebaseAdmin = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const FIREBASE_ADMIN_PATHS = [
|
|
7
|
+
'../../config/firebaseAdmin',
|
|
8
|
+
'../config/firebaseAdmin',
|
|
9
|
+
'./config/firebaseAdmin',
|
|
10
|
+
'functions/src/config/firebaseAdmin',
|
|
11
|
+
];
|
|
12
|
+
exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
|
|
13
|
+
name: 'no-mock-firebase-admin',
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'problem',
|
|
16
|
+
docs: {
|
|
17
|
+
description: 'Prevent mocking of functions/src/config/firebaseAdmin',
|
|
18
|
+
recommended: 'error',
|
|
19
|
+
},
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
noMockFirebaseAdmin: 'Do not mock firebaseAdmin directly. Use mockFirestore from __mocks__/functions/src/config/mockFirestore instead.',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultOptions: [],
|
|
26
|
+
create(context) {
|
|
27
|
+
return {
|
|
28
|
+
CallExpression(node) {
|
|
29
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
30
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
31
|
+
node.callee.object.name === 'jest' &&
|
|
32
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
33
|
+
node.callee.property.name === 'mock' &&
|
|
34
|
+
node.arguments.length > 0 &&
|
|
35
|
+
node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
|
|
36
|
+
typeof node.arguments[0].value === 'string') {
|
|
37
|
+
const mockPath = node.arguments[0].value;
|
|
38
|
+
const isFirebaseAdminMock = FIREBASE_ADMIN_PATHS.some(path => mockPath.endsWith(path) || mockPath.includes('firebaseAdmin'));
|
|
39
|
+
if (isFirebaseAdminMock) {
|
|
40
|
+
context.report({
|
|
41
|
+
node,
|
|
42
|
+
messageId: 'noMockFirebaseAdmin',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=no-mock-firebase-admin.js.map
|