@pobammer-ts/eslint-cease-nonsense-rules 0.10.1 → 0.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.
Files changed (32) hide show
  1. package/dist/rules/ban-react-fc.js +2 -30
  2. package/dist/rules/ban-react-fc.js.map +1 -1
  3. package/dist/rules/enforce-ianitor-check-type.js +69 -157
  4. package/dist/rules/enforce-ianitor-check-type.js.map +1 -1
  5. package/dist/rules/no-color3-constructor.d.ts +0 -17
  6. package/dist/rules/no-color3-constructor.js +9 -31
  7. package/dist/rules/no-color3-constructor.js.map +1 -1
  8. package/dist/rules/no-instance-methods-without-this.js +2 -3
  9. package/dist/rules/no-instance-methods-without-this.js.map +1 -1
  10. package/dist/rules/no-print.d.ts +0 -15
  11. package/dist/rules/no-print.js +0 -21
  12. package/dist/rules/no-print.js.map +1 -1
  13. package/dist/rules/no-shorthand-names.d.ts +0 -24
  14. package/dist/rules/no-shorthand-names.js +18 -72
  15. package/dist/rules/no-shorthand-names.js.map +1 -1
  16. package/dist/rules/no-warn.d.ts +0 -15
  17. package/dist/rules/no-warn.js +0 -21
  18. package/dist/rules/no-warn.js.map +1 -1
  19. package/dist/rules/prefer-sequence-overloads.js +9 -16
  20. package/dist/rules/prefer-sequence-overloads.js.map +1 -1
  21. package/dist/rules/prefer-udim2-shorthand.d.ts +0 -14
  22. package/dist/rules/prefer-udim2-shorthand.js +17 -46
  23. package/dist/rules/prefer-udim2-shorthand.js.map +1 -1
  24. package/dist/rules/require-named-effect-functions.js +197 -59
  25. package/dist/rules/require-named-effect-functions.js.map +1 -1
  26. package/dist/rules/require-react-component-keys.d.ts +3 -39
  27. package/dist/rules/require-react-component-keys.js +40 -141
  28. package/dist/rules/require-react-component-keys.js.map +1 -1
  29. package/dist/rules/use-exhaustive-dependencies.d.ts +0 -42
  30. package/dist/rules/use-exhaustive-dependencies.js +100 -227
  31. package/dist/rules/use-exhaustive-dependencies.js.map +1 -1
  32. package/package.json +9 -7
@@ -11,9 +11,6 @@ const UNSTABLE_VALUES = new Set([
11
11
  TSESTree.AST_NODE_TYPES.ObjectExpression,
12
12
  TSESTree.AST_NODE_TYPES.ArrayExpression,
13
13
  ]);
14
- /**
15
- * Internal metrics used for testing to ensure specific branches execute.
16
- */
17
14
  const testingMetrics = {
18
15
  moduleLevelStableConst: 0,
19
16
  outerScopeSkip: 0,
@@ -22,9 +19,6 @@ function resetTestingMetrics() {
22
19
  testingMetrics.moduleLevelStableConst = 0;
23
20
  testingMetrics.outerScopeSkip = 0;
24
21
  }
25
- /**
26
- * Default hooks to check for exhaustive dependencies.
27
- */
28
22
  const DEFAULT_HOOKS = new Map([
29
23
  ["useEffect", { closureIndex: 0, dependenciesIndex: 1 }],
30
24
  ["useLayoutEffect", { closureIndex: 0, dependenciesIndex: 1 }],
@@ -32,38 +26,23 @@ const DEFAULT_HOOKS = new Map([
32
26
  ["useCallback", { closureIndex: 0, dependenciesIndex: 1 }],
33
27
  ["useMemo", { closureIndex: 0, dependenciesIndex: 1 }],
34
28
  ["useImperativeHandle", { closureIndex: 1, dependenciesIndex: 2 }],
35
- // React Spring hooks (function factory pattern)
36
- // Note: These hooks support both function and object patterns.
37
- // Only the function pattern is analyzed for dependencies.
38
29
  ["useSpring", { closureIndex: 0, dependenciesIndex: 1 }],
39
30
  ["useSprings", { closureIndex: 1, dependenciesIndex: 2 }],
40
31
  ["useTrail", { closureIndex: 1, dependenciesIndex: 2 }],
41
32
  ]);
42
- /**
43
- * Hooks with stable results that don't need to be in dependencies.
44
- */
45
33
  const STABLE_HOOKS = new Map([
46
- ["useState", new Set([1])], // setter at index 1
47
- ["useReducer", new Set([1])], // dispatch at index 1
48
- ["useTransition", new Set([1])], // startTransition at index 1
49
- ["useRef", true], // entire result is stable
50
- ["useBinding", true], // React Lua: both binding and setter are stable
34
+ ["useState", new Set([1])],
35
+ ["useReducer", new Set([1])],
36
+ ["useTransition", new Set([1])],
37
+ ["useRef", true],
38
+ ["useBinding", true],
51
39
  ]);
52
- /**
53
- * Values that don't need to be in dependencies (imported, constants, etc.).
54
- */
55
40
  const STABLE_VALUE_TYPES = new Set(["ImportBinding", "FunctionDeclaration", "ClassDeclaration", "FunctionName"]);
56
- /**
57
- * Global built-in identifiers that are always stable and should never be dependencies.
58
- * Includes JavaScript/TypeScript globals, constructors, and type-only names.
59
- */
60
41
  const GLOBAL_BUILTINS = new Set([
61
- // Primitive values
62
42
  "undefined",
63
43
  "null",
64
44
  "Infinity",
65
45
  "NaN",
66
- // Constructors
67
46
  "Array",
68
47
  "Object",
69
48
  "String",
@@ -72,20 +51,16 @@ const GLOBAL_BUILTINS = new Set([
72
51
  "Symbol",
73
52
  "BigInt",
74
53
  "Function",
75
- // Collections
76
54
  "Map",
77
55
  "Set",
78
56
  "WeakMap",
79
57
  "WeakSet",
80
- // Promises and async
81
58
  "Promise",
82
- // Utility
83
59
  "Date",
84
60
  "RegExp",
85
61
  "Error",
86
62
  "Math",
87
63
  "JSON",
88
- // Global functions
89
64
  "parseInt",
90
65
  "parseFloat",
91
66
  "isNaN",
@@ -94,7 +69,6 @@ const GLOBAL_BUILTINS = new Set([
94
69
  "encodeURIComponent",
95
70
  "decodeURI",
96
71
  "decodeURIComponent",
97
- // TypeScript utility types (appear in type annotations but shouldn't be dependencies)
98
72
  "ReadonlyArray",
99
73
  "ReadonlyMap",
100
74
  "ReadonlySet",
@@ -110,109 +84,65 @@ const GLOBAL_BUILTINS = new Set([
110
84
  "ReturnType",
111
85
  "InstanceType",
112
86
  "Parameters",
113
- // Web/Node globals commonly seen
114
87
  "console",
115
88
  "setTimeout",
116
89
  "setInterval",
117
90
  "clearTimeout",
118
91
  "clearInterval",
119
- // Common DOM/Web types
120
92
  "Element",
121
93
  "Node",
122
94
  "Document",
123
95
  "Window",
124
96
  "Event",
125
97
  ]);
126
- /**
127
- * Gets the hook name from a call expression.
128
- *
129
- * @param node - The call expression node.
130
- * @returns The hook name or undefined.
131
- */
132
98
  function getHookName(node) {
133
99
  const { callee } = node;
134
- // Direct call: useEffect(...)
135
100
  if (callee.type === "Identifier") {
136
101
  return callee.name;
137
102
  }
138
- // Member expression: React.useEffect(...)
139
103
  if (callee.type === "MemberExpression" && callee.property.type === "Identifier") {
140
104
  return callee.property.name;
141
105
  }
142
106
  return undefined;
143
107
  }
144
- /**
145
- * Gets the member expression depth (number of property accesses).
146
- *
147
- * @param node - The node to analyze.
148
- * @returns The depth count.
149
- */
150
108
  function getMemberExpressionDepth(node) {
151
109
  let depth = 0;
152
110
  let current = node;
153
- while (current.type === "MemberExpression") {
111
+ while (current.type === TSESTree.AST_NODE_TYPES.MemberExpression) {
154
112
  depth += 1;
155
113
  current = current.object;
156
114
  }
157
115
  return depth;
158
116
  }
159
- /**
160
- * Gets the root identifier from a member expression.
161
- *
162
- * @param node - The node to analyze.
163
- * @returns The root identifier or undefined.
164
- */
165
117
  function getRootIdentifier(node) {
166
118
  let current = node;
167
- while (current.type === "MemberExpression")
119
+ while (current.type === TSESTree.AST_NODE_TYPES.MemberExpression)
168
120
  current = current.object;
169
- return current.type === "Identifier" ? current : undefined;
121
+ return current.type === TSESTree.AST_NODE_TYPES.Identifier ? current : undefined;
170
122
  }
171
- /**
172
- * Converts a node to a dependency string representation.
173
- *
174
- * @param node - The node to convert.
175
- * @param sourceCode - The source code instance.
176
- * @returns The dependency string.
177
- */
178
123
  function nodeToDependencyString(node, sourceCode) {
179
124
  return sourceCode.getText(node);
180
125
  }
181
- /**
182
- * Checks if a stable array index is being accessed.
183
- *
184
- * @param stableResult - The stable result set.
185
- * @param node - The variable declarator node.
186
- * @param identifierName - The identifier name being accessed.
187
- * @returns True if accessing a stable array index.
188
- */
189
126
  function isStableArrayIndex(stableResult, node, identifierName) {
190
127
  if (!stableResult)
191
128
  return false;
192
- if (!(stableResult instanceof Set) || node.type !== "VariableDeclarator" || node.id.type !== "ArrayPattern")
129
+ if (!(stableResult instanceof Set) ||
130
+ node.type !== TSESTree.AST_NODE_TYPES.VariableDeclarator ||
131
+ node.id.type !== TSESTree.AST_NODE_TYPES.ArrayPattern)
193
132
  return false;
194
133
  const elements = node.id.elements;
195
134
  for (let index = 0; index < elements.length; index += 1) {
196
135
  const element = elements[index];
197
- if (element?.type === "Identifier" && element.name === identifierName)
136
+ if (element?.type === TSESTree.AST_NODE_TYPES.Identifier && element.name === identifierName)
198
137
  return stableResult.has(index);
199
138
  }
200
139
  return false;
201
140
  }
202
- /**
203
- * Checks if a value is from a stable hook.
204
- *
205
- * @param init - The initializer expression.
206
- * @param node - The variable declarator node.
207
- * @param identifierName - The identifier name being accessed.
208
- * @param stableHooks - Map of stable hooks.
209
- * @returns True if the value is from a stable hook.
210
- */
211
141
  function isStableHookValue(init, node, identifierName, stableHooks) {
212
- const initNode = init;
213
- if (initNode.type !== "CallExpression")
142
+ const castInit = init;
143
+ if (castInit.type !== TSESTree.AST_NODE_TYPES.CallExpression)
214
144
  return false;
215
- const hookName = getHookName(initNode);
145
+ const hookName = getHookName(castInit);
216
146
  if (!hookName)
217
147
  return false;
218
148
  const stableResult = stableHooks.get(hookName);
@@ -220,67 +150,52 @@ function isStableHookValue(init, node, identifierName, stableHooks) {
220
150
  return true;
221
151
  return isStableArrayIndex(stableResult, node, identifierName);
222
152
  }
223
- /**
224
- * Checks if a value is stable (doesn't need to be in dependencies).
225
- *
226
- * @param variable - The variable to check.
227
- * @param identifierName - The identifier name being accessed.
228
- * @param stableHooks - Map of stable hooks.
229
- * @returns True if the value is stable.
230
- */
231
153
  /* eslint-disable jsdoc/require-param, jsdoc/require-returns */
232
154
  function isStableValue(variable, identifierName, stableHooks) {
233
155
  if (!variable)
234
156
  return false;
235
- const { defs } = variable;
236
- if (defs.length === 0)
157
+ const definitions = variable.defs;
158
+ if (definitions.length === 0)
237
159
  return false;
238
- for (const def of defs) {
239
- const { node, type } = def;
240
- // Imports, functions, classes are stable
160
+ for (const definition of definitions) {
161
+ const { node, type } = definition;
241
162
  if (STABLE_VALUE_TYPES.has(type))
242
163
  return true;
243
- // Check for const declarations with constant initializers
244
164
  if (type === "Variable" && node.type === "VariableDeclarator") {
245
165
  const parent = node.parent;
246
- if (!parent || parent.type !== "VariableDeclaration" || parent.kind !== "const")
166
+ if (!parent || parent.type !== TSESTree.AST_NODE_TYPES.VariableDeclaration || parent.kind !== "const")
247
167
  continue;
248
168
  const { init } = node;
249
- // Check if it's from a stable hook first
250
169
  // @ts-expect-error - Type mismatch between ESLint and TypeScript AST types
251
170
  if (init && isStableHookValue(init, node, identifierName, stableHooks))
252
171
  return true;
253
- // Check for React Lua bindings - bindings are always stable
254
- if (init?.type === "CallExpression") {
172
+ if (init?.type === TSESTree.AST_NODE_TYPES.CallExpression) {
255
173
  const { callee } = init;
256
- // React.joinBindings() returns a stable binding
257
- if (callee.type === "MemberExpression" &&
258
- callee.object.type === "Identifier" &&
174
+ if (callee.type === TSESTree.AST_NODE_TYPES.MemberExpression &&
175
+ callee.object.type === TSESTree.AST_NODE_TYPES.Identifier &&
259
176
  callee.object.name === "React" &&
260
- callee.property.type === "Identifier" &&
177
+ callee.property.type === TSESTree.AST_NODE_TYPES.Identifier &&
261
178
  callee.property.name === "joinBindings")
262
179
  return true;
263
- // .map() on bindings returns a stable binding
264
- // This covers: binding.map(...), React.joinBindings(...).map(...), etc.
265
- if (callee.type === "MemberExpression" &&
266
- callee.property.type === "Identifier" &&
180
+ if (callee.type === TSESTree.AST_NODE_TYPES.MemberExpression &&
181
+ callee.property.type === TSESTree.AST_NODE_TYPES.Identifier &&
267
182
  callee.property.name === "map")
268
183
  return true;
269
184
  }
270
- // Check for literal values FIRST (stable regardless of scope)
271
185
  if (init) {
272
- if (init.type === "Literal" || init.type === "TemplateLiteral")
186
+ if (init.type === TSESTree.AST_NODE_TYPES.Literal ||
187
+ init.type === TSESTree.AST_NODE_TYPES.TemplateLiteral)
273
188
  return true;
274
- if (init.type === "UnaryExpression" && init.argument.type === "Literal")
189
+ if (init.type === TSESTree.AST_NODE_TYPES.UnaryExpression &&
190
+ init.argument.type === TSESTree.AST_NODE_TYPES.Literal)
275
191
  return true;
276
192
  }
277
- // For non-literal constants, only module-level is stable
278
- // Component-scoped non-literal constants are recreated on every render
279
- const varDef = variable.defs.find((d) => d.node === node);
280
- if (varDef && varDef.node.type === "VariableDeclarator") {
281
- const declParent = varDef.node.parent?.parent;
282
- // Module-level (Program or ExportNamedDeclaration)
283
- if (declParent && (declParent.type === "Program" || declParent.type === "ExportNamedDeclaration")) {
193
+ const variableDefinition = variable.defs.find((definition) => definition.node === node);
194
+ if (variableDefinition && variableDefinition.node.type === TSESTree.AST_NODE_TYPES.VariableDeclarator) {
195
+ const declarationParent = variableDefinition.node.parent?.parent;
196
+ if (declarationParent &&
197
+ (declarationParent.type === TSESTree.AST_NODE_TYPES.Program ||
198
+ declarationParent.type === TSESTree.AST_NODE_TYPES.ExportNamedDeclaration)) {
284
199
  testingMetrics.moduleLevelStableConst += 1;
285
200
  return true;
286
201
  }
@@ -297,9 +212,9 @@ function isStableValue(variable, identifierName, stableHooks) {
297
212
  */
298
213
  function findTopmostMemberExpression(node) {
299
214
  let current = node;
300
- let parent = node.parent;
215
+ let { parent } = node;
301
216
  // Walk up the tree while we're the object of a member expression
302
- while (parent?.type === "MemberExpression" && parent.object === current) {
217
+ while (parent?.type === TSESTree.AST_NODE_TYPES.MemberExpression && parent.object === current) {
303
218
  current = parent;
304
219
  parent = parent.parent;
305
220
  }
@@ -350,18 +265,18 @@ function isDeclaredInComponentBody(variable, closureNode) {
350
265
  // Capture parent in a const so TypeScript understands it's stable in closures
351
266
  const functionParent = parent;
352
267
  // Check if variable is a parameter of this function (props)
353
- const isParameter = variable.defs.some((def) => {
354
- if (def.type !== "Parameter")
268
+ const isParameter = variable.defs.some((definition) => {
269
+ if (definition.type !== "Parameter")
355
270
  return false;
356
271
  // For parameters, the def.node is the function itself
357
272
  // Just check if the definition's node is the current function parent
358
- return def.node === functionParent;
273
+ return definition.node === functionParent;
359
274
  });
360
275
  if (isParameter)
361
276
  return true; // Props are reactive
362
277
  // Check if variable is defined inside this function
363
- return variable.defs.some((def) => {
364
- let node = def.node.parent;
278
+ return variable.defs.some((definition) => {
279
+ let node = definition.node.parent;
365
280
  while (node && node !== functionParent)
366
281
  node = node.parent;
367
282
  return node === functionParent;
@@ -391,48 +306,28 @@ function resolveFunctionReference(identifier, scope) {
391
306
  if (!variable || variable.defs.length === 0)
392
307
  return undefined;
393
308
  // Check all definitions for a function
394
- for (const def of variable.defs) {
395
- const { node } = def;
309
+ for (const definition of variable.defs) {
310
+ const { node } = definition;
396
311
  // Direct function declaration
397
- if (node.type === "FunctionDeclaration")
312
+ if (node.type === TSESTree.AST_NODE_TYPES.FunctionDeclaration)
398
313
  return node;
399
314
  // Variable declarator with function initializer
400
- if (node.type === "VariableDeclarator" &&
315
+ if (node.type === TSESTree.AST_NODE_TYPES.VariableDeclarator &&
401
316
  node.init &&
402
- (node.init.type === "ArrowFunctionExpression" || node.init.type === "FunctionExpression"))
317
+ (node.init.type === TSESTree.AST_NODE_TYPES.ArrowFunctionExpression ||
318
+ node.init.type === TSESTree.AST_NODE_TYPES.FunctionExpression))
403
319
  return node.init;
404
320
  }
405
321
  return undefined;
406
322
  }
407
- /**
408
- * Collects all captured identifiers from a closure.
409
- *
410
- * @param node - The closure node (function/arrow function).
411
- * @param scope - The scope of the closure.
412
- * @param sourceCode - The source code instance.
413
- * @returns Array of captured identifiers.
414
- */
415
323
  function collectCaptures(node, scope, sourceCode) {
416
324
  const captures = new Array();
417
325
  const captureSet = new Set();
418
- /**
419
- * Recursively visits nodes to find identifier references.
420
- *
421
- * @param current - The current node.
422
- */
423
326
  function visit(current) {
424
- if (current.type === "Identifier") {
327
+ if (current.type === TSESTree.AST_NODE_TYPES.Identifier) {
425
328
  const { name } = current;
426
- // Skip if already captured
427
- if (captureSet.has(name))
428
- return;
429
- // Skip global built-ins (always stable, never need to be in dependencies)
430
- if (GLOBAL_BUILTINS.has(name))
329
+ if (captureSet.has(name) || GLOBAL_BUILTINS.has(name) || isInTypePosition(current))
431
330
  return;
432
- // Skip TypeScript type-only positions (type parameters, annotations, etc.)
433
- if (isInTypePosition(current))
434
- return;
435
- // Look up the variable in the scope chain
436
331
  let variable;
437
332
  let currentScope = scope;
438
333
  while (currentScope) {
@@ -441,24 +336,20 @@ function collectCaptures(node, scope, sourceCode) {
441
336
  break;
442
337
  currentScope = currentScope.upper;
443
338
  }
444
- // Only capture if variable is defined outside the closure
445
339
  if (variable) {
446
- const isDefinedInClosure = variable.defs.some((def) => {
447
- let defNode = def.node;
448
- while (defNode) {
449
- if (defNode === node)
340
+ const isDefinedInClosure = variable.defs.some((definition) => {
341
+ let definitionNode = definition.node;
342
+ while (definitionNode) {
343
+ if (definitionNode === node)
450
344
  return true;
451
- defNode = defNode.parent;
345
+ definitionNode = definitionNode.parent;
452
346
  }
453
347
  return false;
454
348
  });
455
349
  if (!isDefinedInClosure) {
456
- // Only capture variables declared in the component body
457
- // Per React rules, only "variables declared directly inside the component body" are reactive
458
- // Variables from outer scopes (module-level, parent functions) are non-reactive and stable
459
350
  if (!isDeclaredInComponentBody(variable, node)) {
460
351
  testingMetrics.outerScopeSkip += 1;
461
- return; // From outer scope - skip
352
+ return;
462
353
  }
463
354
  captureSet.add(name);
464
355
  const depthNode = findTopmostMemberExpression(current);
@@ -473,23 +364,20 @@ function collectCaptures(node, scope, sourceCode) {
473
364
  }
474
365
  }
475
366
  }
476
- // Unwrap TypeScript type expressions to visit the actual expression
477
- if (current.type === "TSSatisfiesExpression" ||
478
- current.type === "TSAsExpression" ||
479
- current.type === "TSTypeAssertion" ||
480
- current.type === "TSNonNullExpression") {
367
+ if (current.type === TSESTree.AST_NODE_TYPES.TSSatisfiesExpression ||
368
+ current.type === TSESTree.AST_NODE_TYPES.TSAsExpression ||
369
+ current.type === TSESTree.AST_NODE_TYPES.TSTypeAssertion ||
370
+ current.type === TSESTree.AST_NODE_TYPES.TSNonNullExpression) {
481
371
  visit(current.expression);
482
372
  return;
483
373
  }
484
- // Traverse member expressions
485
- if (current.type === "MemberExpression") {
374
+ if (current.type === TSESTree.AST_NODE_TYPES.MemberExpression) {
486
375
  visit(current.object);
487
376
  if (current.computed)
488
377
  visit(current.property);
489
378
  return;
490
379
  }
491
- // Visit children
492
- const keys = sourceCode.visitorKeys?.[current.type] || [];
380
+ const keys = sourceCode.visitorKeys?.[current.type] ?? [];
493
381
  for (const key of keys) {
494
382
  const value = current[key];
495
383
  if (Array.isArray(value)) {
@@ -504,17 +392,10 @@ function collectCaptures(node, scope, sourceCode) {
504
392
  visit(node);
505
393
  return captures;
506
394
  }
507
- /**
508
- * Parses dependencies from a dependency array expression.
509
- *
510
- * @param node - The dependency array node.
511
- * @param sourceCode - The source code instance.
512
- * @returns Array of dependency information.
513
- */
514
395
  function parseDependencies(node, sourceCode) {
515
396
  const dependencies = new Array();
516
397
  for (const element of node.elements) {
517
- if (!element || element.type === "SpreadElement")
398
+ if (!element || element.type === TSESTree.AST_NODE_TYPES.SpreadElement)
518
399
  continue;
519
400
  const name = nodeToDependencyString(element, sourceCode);
520
401
  const depth = getMemberExpressionDepth(element);
@@ -526,23 +407,11 @@ function parseDependencies(node, sourceCode) {
526
407
  }
527
408
  return dependencies;
528
409
  }
529
- /**
530
- * Checks if a dependency or capture is an inline function or object (unstable).
531
- *
532
- * @param node - The node to check.
533
- * @returns True if the node is an unstable value.
534
- */
535
410
  function isUnstableValue(node) {
536
411
  return node ? UNSTABLE_VALUES.has(node.type) : false;
537
412
  }
538
413
  const isNumberArray = Compile(Type.Array(Type.Number(), { minItems: 1, readOnly: true }));
539
414
  const isStringArray = Compile(Type.Array(Type.String(), { minItems: 1, readOnly: true }));
540
- /**
541
- * Converts stableResult configuration to internal format.
542
- *
543
- * @param stableResult - The stable result configuration.
544
- * @returns The internal stable result format.
545
- */
546
415
  function convertStableResult(stableResult) {
547
416
  if (typeof stableResult === "boolean")
548
417
  return stableResult;
@@ -565,12 +434,12 @@ const useExhaustiveDependencies = {
565
434
  // Build hook configuration map
566
435
  const hookConfigs = new Map(DEFAULT_HOOKS);
567
436
  for (const customHook of options.hooks) {
568
- if (customHook.closureIndex !== undefined && customHook.dependenciesIndex !== undefined) {
569
- hookConfigs.set(customHook.name, {
570
- closureIndex: customHook.closureIndex,
571
- dependenciesIndex: customHook.dependenciesIndex,
572
- });
573
- }
437
+ if (customHook.closureIndex === undefined || customHook.dependenciesIndex === undefined)
438
+ continue;
439
+ hookConfigs.set(customHook.name, {
440
+ closureIndex: customHook.closureIndex,
441
+ dependenciesIndex: customHook.dependenciesIndex,
442
+ });
574
443
  }
575
444
  // Build stable hooks map
576
445
  const stableHooks = new Map(STABLE_HOOKS);
@@ -600,33 +469,33 @@ const useExhaustiveDependencies = {
600
469
  const callNode = node;
601
470
  // Early exit: get hook name
602
471
  const hookName = getHookName(callNode);
603
- if (!hookName)
472
+ if (hookName === undefined || hookName === "")
604
473
  return;
605
474
  // Early exit: check if this hook needs dependency checking
606
475
  const hookConfig = hookConfigs.get(hookName);
607
476
  if (!hookConfig)
608
477
  return;
609
478
  const { closureIndex, dependenciesIndex } = hookConfig;
610
- const { arguments: args } = callNode;
479
+ const parameters = callNode.arguments;
611
480
  // Early exit: check if closure argument exists
612
- const closureArg = args[closureIndex];
613
- if (!closureArg)
481
+ const closureArgument = parameters[closureIndex];
482
+ if (closureArgument === undefined)
614
483
  return;
615
484
  // Resolve the actual closure function (handles both inline and reference cases)
616
485
  let closureFunction;
617
- if (closureArg.type === "ArrowFunctionExpression" || closureArg.type === "FunctionExpression") {
618
- closureFunction = closureArg;
619
- }
620
- else if (closureArg.type === "Identifier") {
486
+ if (closureArgument.type === TSESTree.AST_NODE_TYPES.ArrowFunctionExpression ||
487
+ closureArgument.type === TSESTree.AST_NODE_TYPES.FunctionExpression)
488
+ closureFunction = closureArgument;
489
+ else if (closureArgument.type === TSESTree.AST_NODE_TYPES.Identifier) {
621
490
  // Function reference - try to resolve it
622
491
  const scope = getScope(callNode);
623
- closureFunction = resolveFunctionReference(closureArg, scope);
492
+ closureFunction = resolveFunctionReference(closureArgument, scope);
624
493
  }
625
494
  // Early exit: check if we have a valid closure function
626
495
  if (!closureFunction)
627
496
  return;
628
497
  // Get dependencies argument
629
- const dependenciesArgument = args[dependenciesIndex];
498
+ const dependenciesArgument = parameters[dependenciesIndex];
630
499
  // Report missing dependencies array if configured
631
500
  if (!dependenciesArgument && options.reportMissingDependenciesArray) {
632
501
  // Collect captures to see if any are needed
@@ -649,8 +518,8 @@ const useExhaustiveDependencies = {
649
518
  desc: `Add dependencies array: ${depsArrayString}`,
650
519
  fix(fixer) {
651
520
  // Insert the dependencies array after the closure argument
652
- const closureArgNode = args[closureIndex];
653
- return fixer.insertTextAfter(closureArgNode, `, ${depsArrayString}`);
521
+ const closureArgumentNode = parameters[closureIndex];
522
+ return fixer.insertTextAfter(closureArgumentNode, `, ${depsArrayString}`);
654
523
  },
655
524
  },
656
525
  ],
@@ -662,14 +531,14 @@ const useExhaustiveDependencies = {
662
531
  if (!dependenciesArgument)
663
532
  return;
664
533
  // Dependencies must be an array
665
- if (dependenciesArgument.type !== "ArrayExpression")
534
+ if (dependenciesArgument.type !== TSESTree.AST_NODE_TYPES.ArrayExpression)
666
535
  return;
667
- const depsArray = dependenciesArgument;
536
+ const dependenciesArray = dependenciesArgument;
668
537
  // Collect captures from closure
669
538
  const scope = getScope(closureFunction);
670
539
  const captures = collectCaptures(closureFunction, scope, context.sourceCode);
671
540
  // Parse dependencies array
672
- const dependencies = parseDependencies(depsArray, context.sourceCode);
541
+ const dependencies = parseDependencies(dependenciesArray, context.sourceCode);
673
542
  // Check for unnecessary dependencies first (for consistent error ordering)
674
543
  for (const dependency of dependencies) {
675
544
  const dependencyRootIdentifier = getRootIdentifier(dependency.node);
@@ -682,8 +551,10 @@ const useExhaustiveDependencies = {
682
551
  if (matchingCaptures.length === 0) {
683
552
  if (options.reportUnnecessaryDependencies) {
684
553
  // Generate fix suggestion
685
- const newDeps = dependencies.filter((d) => d.name !== dependency.name).map((d) => d.name);
686
- const newDepsString = `[${newDeps.join(", ")}]`;
554
+ const newDependencies = dependencies
555
+ .filter((value) => value.name !== dependency.name)
556
+ .map(({ name }) => name);
557
+ const dependenciesString = `[${newDependencies.join(", ")}]`;
687
558
  context.report({
688
559
  data: { name: dependency.name },
689
560
  messageId: "unnecessaryDependency",
@@ -692,7 +563,7 @@ const useExhaustiveDependencies = {
692
563
  {
693
564
  desc: `Remove '${dependency.name}' from dependencies array`,
694
565
  fix(fixer) {
695
- return fixer.replaceText(depsArray, newDepsString);
566
+ return fixer.replaceText(dependenciesArray, dependenciesString);
696
567
  },
697
568
  },
698
569
  ],
@@ -702,11 +573,13 @@ const useExhaustiveDependencies = {
702
573
  }
703
574
  // Check if dependency is more specific than any usage
704
575
  // dep.depth > all capture depths means the dep is too specific
705
- const maxCaptureDepth = Math.max(...matchingCaptures.map((c) => c.depth));
576
+ const maxCaptureDepth = Math.max(...matchingCaptures.map(({ depth }) => depth));
706
577
  if (dependency.depth > maxCaptureDepth && options.reportUnnecessaryDependencies) {
707
578
  // Generate fix suggestion
708
- const newDeps = dependencies.filter((d) => d.name !== dependency.name).map((d) => d.name);
709
- const newDepsString = `[${newDeps.join(", ")}]`;
579
+ const newDependencies = dependencies
580
+ .filter(({ name }) => name !== dependency.name)
581
+ .map(({ name }) => name);
582
+ const newDependenciesString = `[${newDependencies.join(", ")}]`;
710
583
  context.report({
711
584
  data: { name: dependency.name },
712
585
  messageId: "unnecessaryDependency",
@@ -715,7 +588,7 @@ const useExhaustiveDependencies = {
715
588
  {
716
589
  desc: `Remove '${dependency.name}' from dependencies array`,
717
590
  fix(fixer) {
718
- return fixer.replaceText(depsArray, newDepsString);
591
+ return fixer.replaceText(dependenciesArray, newDependenciesString);
719
592
  },
720
593
  },
721
594
  ],
@@ -759,12 +632,12 @@ const useExhaustiveDependencies = {
759
632
  context.report({
760
633
  data: { name: firstMissing.usagePath },
761
634
  messageId: "missingDependency",
762
- node: lastDependency?.node || depsArray,
635
+ node: lastDependency?.node || dependenciesArray,
763
636
  suggest: [
764
637
  {
765
638
  desc: `Add '${firstMissing.usagePath}' to dependencies array`,
766
639
  fix(fixer) {
767
- return fixer.replaceText(depsArray, newDependenciesString);
640
+ return fixer.replaceText(dependenciesArray, newDependenciesString);
768
641
  },
769
642
  },
770
643
  ],
@@ -776,12 +649,12 @@ const useExhaustiveDependencies = {
776
649
  context.report({
777
650
  data: { names: missingNames },
778
651
  messageId: "missingDependencies",
779
- node: lastDependency?.node || depsArray,
652
+ node: lastDependency?.node || dependenciesArray,
780
653
  suggest: [
781
654
  {
782
655
  desc: "Add missing dependencies to array",
783
656
  fix(fixer) {
784
- return fixer.replaceText(depsArray, newDependenciesString);
657
+ return fixer.replaceText(dependenciesArray, newDependenciesString);
785
658
  },
786
659
  },
787
660
  ],