@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 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.0',
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
- if (param.type === utils_1.AST_NODE_TYPES.Identifier &&
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
- if (param.type === utils_1.AST_NODE_TYPES.Identifier &&
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 all optional properties
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 true;
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 all optional properties
61
+ // assume it's a type with required properties
62
62
  // This handles cases where the type is imported from another module
63
- return true;
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
- function checkHookParam(param) {
81
- // If it's already an assignment pattern, check if the left side is an object pattern
82
- if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
83
- if (param.left.type === utils_1.AST_NODE_TYPES.ObjectPattern && param.left.typeAnnotation) {
84
- if (hasAllOptionalProperties(param.left.typeAnnotation.typeAnnotation)) {
85
- return; // Already has a default value and is correctly typed
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
- return;
89
- }
90
- // If it's an object pattern, check if it needs a default value
91
- if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern && param.typeAnnotation) {
92
- const typeAnnotation = param.typeAnnotation.typeAnnotation;
93
- if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
94
- const typeName = typeAnnotation.typeName;
95
- if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
96
- const scope = context.getScope();
97
- const variable = scope.variables.find(v => v.name === typeName.name);
98
- if (!variable || !variable.defs[0]?.node) {
99
- // If we can't find the type definition, assume it's a type with all optional properties
100
- // This handles cases where the type is imported from another module
101
- context.report({
102
- node: param,
103
- messageId: 'requireDefaultParams',
104
- fix(fixer) {
105
- const paramText = context.getSourceCode().getText(param);
106
- return fixer.replaceText(param, `${paramText} = {}`);
107
- },
108
- });
109
- return;
110
- }
111
- const def = variable.defs[0].node;
112
- if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
113
- if (hasAllOptionalProperties(def.typeAnnotation)) {
114
- context.report({
115
- node: param,
116
- messageId: 'requireDefaultParams',
117
- fix(fixer) {
118
- const paramText = context.getSourceCode().getText(param);
119
- return fixer.replaceText(param, `${paramText} = {}`);
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
- else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
125
- if (def.body.body.every(member => {
126
- if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
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
- else {
142
- // If we found the type definition but it's not a type alias or interface declaration,
143
- // assume it's a type with all optional properties
144
- // This handles cases where the type is imported from another module
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
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",