eslint-plugin-react-hooks 7.1.0-canary-fd524fe0-20251121 → 7.1.0-canary-7dc903cd-20251203

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.
@@ -18347,7 +18347,7 @@ function getRuleForCategoryImpl(category) {
18347
18347
  severity: ErrorSeverity.Error,
18348
18348
  name: 'memo-dependencies',
18349
18349
  description: 'Validates that useMemo() and useCallback() specify comprehensive dependencies without extraneous values. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
18350
- preset: LintRulePreset.RecommendedLatest,
18350
+ preset: LintRulePreset.Off,
18351
18351
  };
18352
18352
  }
18353
18353
  case ErrorCategory.IncompatibleLibrary: {
@@ -18863,6 +18863,10 @@ function isSubPath(subpath, path) {
18863
18863
  subpath.every((item, ix) => item.property === path[ix].property &&
18864
18864
  item.optional === path[ix].optional));
18865
18865
  }
18866
+ function isSubPathIgnoringOptionals(subpath, path) {
18867
+ return (subpath.length <= path.length &&
18868
+ subpath.every((item, ix) => item.property === path[ix].property));
18869
+ }
18866
18870
  function getPlaceScope(id, place) {
18867
18871
  const scope = place.identifier.scope;
18868
18872
  if (scope !== null && isScopeActive(scope, id)) {
@@ -19066,6 +19070,9 @@ function isUseInsertionEffectHookType(id) {
19066
19070
  return (id.type.kind === 'Function' &&
19067
19071
  id.type.shapeId === 'BuiltInUseInsertionEffectHook');
19068
19072
  }
19073
+ function isUseEffectEventType(id) {
19074
+ return (id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseEffectEvent');
19075
+ }
19069
19076
  function isUseContextHookType(id) {
19070
19077
  return (id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseContextHook');
19071
19078
  }
@@ -32198,7 +32205,7 @@ const EnvironmentConfigSchema = v4.z.object({
32198
32205
  enableResetCacheOnSourceFileChanges: v4.z.nullable(v4.z.boolean()).default(null),
32199
32206
  enablePreserveExistingMemoizationGuarantees: v4.z.boolean().default(true),
32200
32207
  validatePreserveExistingMemoizationGuarantees: v4.z.boolean().default(true),
32201
- validateExhaustiveMemoizationDependencies: v4.z.boolean().default(false),
32208
+ validateExhaustiveMemoizationDependencies: v4.z.boolean().default(true),
32202
32209
  enablePreserveExistingManualUseMemo: v4.z.boolean().default(false),
32203
32210
  enableForest: v4.z.boolean().default(false),
32204
32211
  enableUseTypeAnnotations: v4.z.boolean().default(false),
@@ -34482,6 +34489,19 @@ function evaluateInstruction(constants, instr) {
34482
34489
  constantPropagationImpl(value.loweredFunc.func, constants);
34483
34490
  return null;
34484
34491
  }
34492
+ case 'StartMemoize': {
34493
+ if (value.deps != null) {
34494
+ for (const dep of value.deps) {
34495
+ if (dep.root.kind === 'NamedLocal') {
34496
+ const placeValue = read(constants, dep.root.value);
34497
+ if (placeValue != null && placeValue.kind === 'Primitive') {
34498
+ dep.root.constant = true;
34499
+ }
34500
+ }
34501
+ }
34502
+ }
34503
+ return null;
34504
+ }
34485
34505
  default: {
34486
34506
  return null;
34487
34507
  }
@@ -44599,6 +44619,7 @@ function collectMaybeMemoDependencies(value, maybeDeps, optional) {
44599
44619
  identifierName: value.binding.name,
44600
44620
  },
44601
44621
  path: [],
44622
+ loc: value.loc,
44602
44623
  };
44603
44624
  }
44604
44625
  case 'PropertyLoad': {
@@ -44607,6 +44628,7 @@ function collectMaybeMemoDependencies(value, maybeDeps, optional) {
44607
44628
  return {
44608
44629
  root: object.root,
44609
44630
  path: [...object.path, { property: value.property, optional }],
44631
+ loc: value.loc,
44610
44632
  };
44611
44633
  }
44612
44634
  break;
@@ -44623,8 +44645,10 @@ function collectMaybeMemoDependencies(value, maybeDeps, optional) {
44623
44645
  root: {
44624
44646
  kind: 'NamedLocal',
44625
44647
  value: Object.assign({}, value.place),
44648
+ constant: false,
44626
44649
  },
44627
44650
  path: [],
44651
+ loc: value.place.loc,
44628
44652
  };
44629
44653
  }
44630
44654
  break;
@@ -50186,6 +50210,7 @@ function validateInferredDep(dep, temporaries, declsWithinMemoBlock, validDepsIn
50186
50210
  normalizedDep = {
50187
50211
  root: maybeNormalizedRoot.root,
50188
50212
  path: [...maybeNormalizedRoot.path, ...dep.path],
50213
+ loc: maybeNormalizedRoot.loc,
50189
50214
  };
50190
50215
  }
50191
50216
  else {
@@ -50211,8 +50236,10 @@ function validateInferredDep(dep, temporaries, declsWithinMemoBlock, validDepsIn
50211
50236
  effect: Effect.Read,
50212
50237
  reactive: false,
50213
50238
  },
50239
+ constant: false,
50214
50240
  },
50215
50241
  path: [...dep.path],
50242
+ loc: GeneratedSource,
50216
50243
  };
50217
50244
  }
50218
50245
  for (const decl of declsWithinMemoBlock) {
@@ -50298,8 +50325,10 @@ class Visitor extends ReactiveFunctionVisitor {
50298
50325
  root: {
50299
50326
  kind: 'NamedLocal',
50300
50327
  value: storeTarget,
50328
+ constant: false,
50301
50329
  },
50302
50330
  path: [],
50331
+ loc: storeTarget.loc,
50303
50332
  });
50304
50333
  }
50305
50334
  }
@@ -50326,8 +50355,10 @@ class Visitor extends ReactiveFunctionVisitor {
50326
50355
  root: {
50327
50356
  kind: 'NamedLocal',
50328
50357
  value: Object.assign({}, lvalue),
50358
+ constant: false,
50329
50359
  },
50330
50360
  path: [],
50361
+ loc: lvalue.loc,
50331
50362
  });
50332
50363
  }
50333
50364
  }
@@ -51172,7 +51203,16 @@ function validateNoSetStateInEffects(fn, env) {
51172
51203
  const callee = instr.value.kind === 'MethodCall'
51173
51204
  ? instr.value.receiver
51174
51205
  : instr.value.callee;
51175
- if (isUseEffectHookType(callee.identifier) ||
51206
+ if (isUseEffectEventType(callee.identifier)) {
51207
+ const arg = instr.value.args[0];
51208
+ if (arg !== undefined && arg.kind === 'Identifier') {
51209
+ const setState = setStateFunctions.get(arg.identifier.id);
51210
+ if (setState !== undefined) {
51211
+ setStateFunctions.set(instr.lvalue.identifier.id, setState);
51212
+ }
51213
+ }
51214
+ }
51215
+ else if (isUseEffectHookType(callee.identifier) ||
51176
51216
  isUseLayoutEffectHookType(callee.identifier) ||
51177
51217
  isUseInsertionEffectHookType(callee.identifier)) {
51178
51218
  const arg = instr.value.args[0];
@@ -53447,7 +53487,7 @@ function validateExhaustiveDependencies(fn) {
53447
53487
  locals.clear();
53448
53488
  }
53449
53489
  function onFinishMemoize(value, dependencies, locals) {
53450
- var _b, _c, _d;
53490
+ var _b, _c, _d, _e, _f, _g, _h, _j;
53451
53491
  CompilerError.simpleInvariant(startMemo != null && startMemo.manualMemoId === value.manualMemoId, {
53452
53492
  reason: 'Found FinishMemoize without corresponding StartMemoize',
53453
53493
  loc: value.loc,
@@ -53526,80 +53566,123 @@ function validateExhaustiveDependencies(fn) {
53526
53566
  reason: 'Unexpected function dependency',
53527
53567
  loc: value.loc,
53528
53568
  });
53529
- const isRequiredDependency = reactive.has(inferredDependency.identifier.id) ||
53530
- !isStableType(inferredDependency.identifier);
53531
53569
  let hasMatchingManualDependency = false;
53532
53570
  for (const manualDependency of manualDependencies) {
53533
53571
  if (manualDependency.root.kind === 'NamedLocal' &&
53534
53572
  manualDependency.root.value.identifier.id ===
53535
53573
  inferredDependency.identifier.id &&
53536
53574
  (areEqualPaths(manualDependency.path, inferredDependency.path) ||
53537
- isSubPath(manualDependency.path, inferredDependency.path))) {
53575
+ isSubPathIgnoringOptionals(manualDependency.path, inferredDependency.path))) {
53538
53576
  hasMatchingManualDependency = true;
53539
53577
  matched.add(manualDependency);
53540
- if (!isRequiredDependency) {
53541
- extra.push(manualDependency);
53542
- }
53543
53578
  }
53544
53579
  }
53545
- if (isRequiredDependency && !hasMatchingManualDependency) {
53546
- missing.push(inferredDependency);
53580
+ if (hasMatchingManualDependency ||
53581
+ isOptionalDependency(inferredDependency, reactive)) {
53582
+ continue;
53547
53583
  }
53584
+ missing.push(inferredDependency);
53548
53585
  }
53549
53586
  for (const dep of (_c = startMemo.deps) !== null && _c !== void 0 ? _c : []) {
53550
53587
  if (matched.has(dep)) {
53551
53588
  continue;
53552
53589
  }
53590
+ if (dep.root.kind === 'NamedLocal' && dep.root.constant) {
53591
+ CompilerError.simpleInvariant(!dep.root.value.reactive &&
53592
+ isPrimitiveType(dep.root.value.identifier), {
53593
+ reason: 'Expected constant-folded dependency to be non-reactive',
53594
+ loc: dep.root.value.loc,
53595
+ });
53596
+ continue;
53597
+ }
53553
53598
  extra.push(dep);
53554
53599
  }
53555
53600
  if (missing.length !== 0 || extra.length !== 0) {
53556
- let suggestions = null;
53601
+ let suggestion = null;
53557
53602
  if (startMemo.depsLoc != null && typeof startMemo.depsLoc !== 'symbol') {
53558
- suggestions = [
53559
- {
53560
- description: 'Update dependencies',
53561
- range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
53562
- op: CompilerSuggestionOperation.Replace,
53563
- text: `[${inferred.map(printInferredDependency).join(', ')}]`,
53564
- },
53565
- ];
53603
+ suggestion = {
53604
+ description: 'Update dependencies',
53605
+ range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
53606
+ op: CompilerSuggestionOperation.Replace,
53607
+ text: `[${inferred
53608
+ .filter(dep => dep.kind === 'Local' && !isOptionalDependency(dep, reactive))
53609
+ .map(printInferredDependency)
53610
+ .join(', ')}]`,
53611
+ };
53566
53612
  }
53567
- if (missing.length !== 0) {
53568
- const diagnostic = CompilerDiagnostic.create({
53569
- category: ErrorCategory.MemoDependencies,
53570
- reason: 'Found non-exhaustive dependencies',
53571
- description: 'Missing dependencies can cause a value not to update when those inputs change, ' +
53572
- 'resulting in stale UI',
53573
- suggestions,
53613
+ const diagnostic = CompilerDiagnostic.create({
53614
+ category: ErrorCategory.MemoDependencies,
53615
+ reason: 'Found missing/extra memoization dependencies',
53616
+ description: [
53617
+ missing.length !== 0
53618
+ ? 'Missing dependencies can cause a value to update less often than it should, ' +
53619
+ 'resulting in stale UI'
53620
+ : null,
53621
+ extra.length !== 0
53622
+ ? 'Extra dependencies can cause a value to update more often than it should, ' +
53623
+ 'resulting in performance problems such as excessive renders or effects firing too often'
53624
+ : null,
53625
+ ]
53626
+ .filter(Boolean)
53627
+ .join('. '),
53628
+ suggestions: suggestion != null ? [suggestion] : null,
53629
+ });
53630
+ for (const dep of missing) {
53631
+ let reactiveStableValueHint = '';
53632
+ if (isStableType(dep.identifier)) {
53633
+ reactiveStableValueHint =
53634
+ '. Refs, setState functions, and other "stable" values generally do not need to be added ' +
53635
+ 'as dependencies, but this variable may change over time to point to different values';
53636
+ }
53637
+ diagnostic.withDetails({
53638
+ kind: 'error',
53639
+ message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
53640
+ loc: dep.loc,
53574
53641
  });
53575
- for (const dep of missing) {
53576
- let reactiveStableValueHint = '';
53577
- if (isStableType(dep.identifier)) {
53578
- reactiveStableValueHint =
53579
- '. Refs, setState functions, and other "stable" values generally do not need to be added as dependencies, but this variable may change over time to point to different values';
53580
- }
53642
+ }
53643
+ for (const dep of extra) {
53644
+ if (dep.root.kind === 'Global') {
53581
53645
  diagnostic.withDetails({
53582
53646
  kind: 'error',
53583
- message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
53584
- loc: dep.loc,
53647
+ message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\`. ` +
53648
+ 'Values declared outside of a component/hook should not be listed as ' +
53649
+ 'dependencies as the component will not re-render if they change',
53650
+ loc: (_e = (_d = dep.loc) !== null && _d !== void 0 ? _d : startMemo.depsLoc) !== null && _e !== void 0 ? _e : value.loc,
53651
+ });
53652
+ error.pushDiagnostic(diagnostic);
53653
+ }
53654
+ else {
53655
+ const root = dep.root.value;
53656
+ const matchingInferred = inferred.find((inferredDep) => {
53657
+ return (inferredDep.kind === 'Local' &&
53658
+ inferredDep.identifier.id === root.identifier.id &&
53659
+ isSubPathIgnoringOptionals(inferredDep.path, dep.path));
53585
53660
  });
53661
+ if (matchingInferred != null &&
53662
+ !isOptionalDependency(matchingInferred, reactive)) {
53663
+ diagnostic.withDetails({
53664
+ kind: 'error',
53665
+ message: `Overly precise dependency \`${printManualMemoDependency(dep)}\`, ` +
53666
+ `use \`${printInferredDependency(matchingInferred)}\` instead`,
53667
+ loc: (_g = (_f = dep.loc) !== null && _f !== void 0 ? _f : startMemo.depsLoc) !== null && _g !== void 0 ? _g : value.loc,
53668
+ });
53669
+ }
53670
+ else {
53671
+ diagnostic.withDetails({
53672
+ kind: 'error',
53673
+ message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\``,
53674
+ loc: (_j = (_h = dep.loc) !== null && _h !== void 0 ? _h : startMemo.depsLoc) !== null && _j !== void 0 ? _j : value.loc,
53675
+ });
53676
+ }
53586
53677
  }
53587
- error.pushDiagnostic(diagnostic);
53588
53678
  }
53589
- else if (extra.length !== 0) {
53590
- const diagnostic = CompilerDiagnostic.create({
53591
- category: ErrorCategory.MemoDependencies,
53592
- reason: 'Found unnecessary memoization dependencies',
53593
- description: 'Unnecessary dependencies can cause a value to update more often than necessary, ' +
53594
- 'which can cause effects to run more than expected',
53595
- });
53679
+ if (suggestion != null) {
53596
53680
  diagnostic.withDetails({
53597
- kind: 'error',
53598
- message: `Unnecessary dependencies ${extra.map(dep => `\`${printManualMemoDependency(dep)}\``).join(', ')}`,
53599
- loc: (_d = startMemo.depsLoc) !== null && _d !== void 0 ? _d : value.loc,
53681
+ kind: 'hint',
53682
+ message: `Inferred dependencies: \`${suggestion.text}\``,
53600
53683
  });
53601
- error.pushDiagnostic(diagnostic);
53602
53684
  }
53685
+ error.pushDiagnostic(diagnostic);
53603
53686
  }
53604
53687
  dependencies.clear();
53605
53688
  locals.clear();
@@ -53990,6 +54073,11 @@ function findOptionalPlaces(fn) {
53990
54073
  }
53991
54074
  return optionals;
53992
54075
  }
54076
+ function isOptionalDependency(inferredDependency, reactive) {
54077
+ return (!reactive.has(inferredDependency.identifier.id) &&
54078
+ (isStableType(inferredDependency.identifier) ||
54079
+ isPrimitiveType(inferredDependency.identifier)));
54080
+ }
53993
54081
 
53994
54082
  function run(func, config, fnType, mode, programContext, logger, filename, code) {
53995
54083
  var _a, _b;
@@ -54116,8 +54204,10 @@ function runWithEnvironment(func, env) {
54116
54204
  }
54117
54205
  inferReactivePlaces(hir);
54118
54206
  log({ kind: 'hir', name: 'InferReactivePlaces', value: hir });
54119
- if (env.config.validateExhaustiveMemoizationDependencies) {
54120
- validateExhaustiveDependencies(hir).unwrap();
54207
+ if (env.enableValidations) {
54208
+ if (env.config.validateExhaustiveMemoizationDependencies) {
54209
+ validateExhaustiveDependencies(hir).unwrap();
54210
+ }
54121
54211
  }
54122
54212
  rewriteInstructionKindsBasedOnReassignment(hir);
54123
54213
  log({
@@ -54380,7 +54470,7 @@ function findProgramSuppressions(programComments, ruleNames, flowSuppressions) {
54380
54470
  let disableNextLinePattern = null;
54381
54471
  let disablePattern = null;
54382
54472
  let enablePattern = null;
54383
- if (ruleNames.length !== 0) {
54473
+ if (ruleNames != null && ruleNames.length !== 0) {
54384
54474
  const rulePattern = `(${ruleNames.join('|')})`;
54385
54475
  disableNextLinePattern = new RegExp(`eslint-disable-next-line ${rulePattern}`);
54386
54476
  disablePattern = new RegExp(`eslint-disable ${rulePattern}`);
@@ -54717,7 +54807,10 @@ function compileProgram(program, pass) {
54717
54807
  handleError(restrictedImportsErr, pass, null);
54718
54808
  return null;
54719
54809
  }
54720
- const suppressions = findProgramSuppressions(pass.comments, (_a = pass.opts.eslintSuppressionRules) !== null && _a !== void 0 ? _a : DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions);
54810
+ const suppressions = findProgramSuppressions(pass.comments, pass.opts.environment.validateExhaustiveMemoizationDependencies &&
54811
+ pass.opts.environment.validateHooksUsage
54812
+ ? null
54813
+ : ((_a = pass.opts.eslintSuppressionRules) !== null && _a !== void 0 ? _a : DEFAULT_ESLINT_SUPPRESSIONS), pass.opts.flowSuppressions);
54721
54814
  const programContext = new ProgramContext({
54722
54815
  program: program,
54723
54816
  opts: pass.opts,
@@ -18338,7 +18338,7 @@ function getRuleForCategoryImpl(category) {
18338
18338
  severity: ErrorSeverity.Error,
18339
18339
  name: 'memo-dependencies',
18340
18340
  description: 'Validates that useMemo() and useCallback() specify comprehensive dependencies without extraneous values. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
18341
- preset: LintRulePreset.RecommendedLatest,
18341
+ preset: LintRulePreset.Off,
18342
18342
  };
18343
18343
  }
18344
18344
  case ErrorCategory.IncompatibleLibrary: {
@@ -18854,6 +18854,10 @@ function isSubPath(subpath, path) {
18854
18854
  subpath.every((item, ix) => item.property === path[ix].property &&
18855
18855
  item.optional === path[ix].optional));
18856
18856
  }
18857
+ function isSubPathIgnoringOptionals(subpath, path) {
18858
+ return (subpath.length <= path.length &&
18859
+ subpath.every((item, ix) => item.property === path[ix].property));
18860
+ }
18857
18861
  function getPlaceScope(id, place) {
18858
18862
  const scope = place.identifier.scope;
18859
18863
  if (scope !== null && isScopeActive(scope, id)) {
@@ -19057,6 +19061,9 @@ function isUseInsertionEffectHookType(id) {
19057
19061
  return (id.type.kind === 'Function' &&
19058
19062
  id.type.shapeId === 'BuiltInUseInsertionEffectHook');
19059
19063
  }
19064
+ function isUseEffectEventType(id) {
19065
+ return (id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseEffectEvent');
19066
+ }
19060
19067
  function isUseContextHookType(id) {
19061
19068
  return (id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseContextHook');
19062
19069
  }
@@ -32025,7 +32032,7 @@ const EnvironmentConfigSchema = v4.z.object({
32025
32032
  enableResetCacheOnSourceFileChanges: v4.z.nullable(v4.z.boolean()).default(null),
32026
32033
  enablePreserveExistingMemoizationGuarantees: v4.z.boolean().default(true),
32027
32034
  validatePreserveExistingMemoizationGuarantees: v4.z.boolean().default(true),
32028
- validateExhaustiveMemoizationDependencies: v4.z.boolean().default(false),
32035
+ validateExhaustiveMemoizationDependencies: v4.z.boolean().default(true),
32029
32036
  enablePreserveExistingManualUseMemo: v4.z.boolean().default(false),
32030
32037
  enableForest: v4.z.boolean().default(false),
32031
32038
  enableUseTypeAnnotations: v4.z.boolean().default(false),
@@ -34309,6 +34316,19 @@ function evaluateInstruction(constants, instr) {
34309
34316
  constantPropagationImpl(value.loweredFunc.func, constants);
34310
34317
  return null;
34311
34318
  }
34319
+ case 'StartMemoize': {
34320
+ if (value.deps != null) {
34321
+ for (const dep of value.deps) {
34322
+ if (dep.root.kind === 'NamedLocal') {
34323
+ const placeValue = read(constants, dep.root.value);
34324
+ if (placeValue != null && placeValue.kind === 'Primitive') {
34325
+ dep.root.constant = true;
34326
+ }
34327
+ }
34328
+ }
34329
+ }
34330
+ return null;
34331
+ }
34312
34332
  default: {
34313
34333
  return null;
34314
34334
  }
@@ -44426,6 +44446,7 @@ function collectMaybeMemoDependencies(value, maybeDeps, optional) {
44426
44446
  identifierName: value.binding.name,
44427
44447
  },
44428
44448
  path: [],
44449
+ loc: value.loc,
44429
44450
  };
44430
44451
  }
44431
44452
  case 'PropertyLoad': {
@@ -44434,6 +44455,7 @@ function collectMaybeMemoDependencies(value, maybeDeps, optional) {
44434
44455
  return {
44435
44456
  root: object.root,
44436
44457
  path: [...object.path, { property: value.property, optional }],
44458
+ loc: value.loc,
44437
44459
  };
44438
44460
  }
44439
44461
  break;
@@ -44450,8 +44472,10 @@ function collectMaybeMemoDependencies(value, maybeDeps, optional) {
44450
44472
  root: {
44451
44473
  kind: 'NamedLocal',
44452
44474
  value: Object.assign({}, value.place),
44475
+ constant: false,
44453
44476
  },
44454
44477
  path: [],
44478
+ loc: value.place.loc,
44455
44479
  };
44456
44480
  }
44457
44481
  break;
@@ -50013,6 +50037,7 @@ function validateInferredDep(dep, temporaries, declsWithinMemoBlock, validDepsIn
50013
50037
  normalizedDep = {
50014
50038
  root: maybeNormalizedRoot.root,
50015
50039
  path: [...maybeNormalizedRoot.path, ...dep.path],
50040
+ loc: maybeNormalizedRoot.loc,
50016
50041
  };
50017
50042
  }
50018
50043
  else {
@@ -50038,8 +50063,10 @@ function validateInferredDep(dep, temporaries, declsWithinMemoBlock, validDepsIn
50038
50063
  effect: Effect.Read,
50039
50064
  reactive: false,
50040
50065
  },
50066
+ constant: false,
50041
50067
  },
50042
50068
  path: [...dep.path],
50069
+ loc: GeneratedSource,
50043
50070
  };
50044
50071
  }
50045
50072
  for (const decl of declsWithinMemoBlock) {
@@ -50125,8 +50152,10 @@ class Visitor extends ReactiveFunctionVisitor {
50125
50152
  root: {
50126
50153
  kind: 'NamedLocal',
50127
50154
  value: storeTarget,
50155
+ constant: false,
50128
50156
  },
50129
50157
  path: [],
50158
+ loc: storeTarget.loc,
50130
50159
  });
50131
50160
  }
50132
50161
  }
@@ -50153,8 +50182,10 @@ class Visitor extends ReactiveFunctionVisitor {
50153
50182
  root: {
50154
50183
  kind: 'NamedLocal',
50155
50184
  value: Object.assign({}, lvalue),
50185
+ constant: false,
50156
50186
  },
50157
50187
  path: [],
50188
+ loc: lvalue.loc,
50158
50189
  });
50159
50190
  }
50160
50191
  }
@@ -50999,7 +51030,16 @@ function validateNoSetStateInEffects(fn, env) {
50999
51030
  const callee = instr.value.kind === 'MethodCall'
51000
51031
  ? instr.value.receiver
51001
51032
  : instr.value.callee;
51002
- if (isUseEffectHookType(callee.identifier) ||
51033
+ if (isUseEffectEventType(callee.identifier)) {
51034
+ const arg = instr.value.args[0];
51035
+ if (arg !== undefined && arg.kind === 'Identifier') {
51036
+ const setState = setStateFunctions.get(arg.identifier.id);
51037
+ if (setState !== undefined) {
51038
+ setStateFunctions.set(instr.lvalue.identifier.id, setState);
51039
+ }
51040
+ }
51041
+ }
51042
+ else if (isUseEffectHookType(callee.identifier) ||
51003
51043
  isUseLayoutEffectHookType(callee.identifier) ||
51004
51044
  isUseInsertionEffectHookType(callee.identifier)) {
51005
51045
  const arg = instr.value.args[0];
@@ -53274,7 +53314,7 @@ function validateExhaustiveDependencies(fn) {
53274
53314
  locals.clear();
53275
53315
  }
53276
53316
  function onFinishMemoize(value, dependencies, locals) {
53277
- var _b, _c, _d;
53317
+ var _b, _c, _d, _e, _f, _g, _h, _j;
53278
53318
  CompilerError.simpleInvariant(startMemo != null && startMemo.manualMemoId === value.manualMemoId, {
53279
53319
  reason: 'Found FinishMemoize without corresponding StartMemoize',
53280
53320
  loc: value.loc,
@@ -53353,80 +53393,123 @@ function validateExhaustiveDependencies(fn) {
53353
53393
  reason: 'Unexpected function dependency',
53354
53394
  loc: value.loc,
53355
53395
  });
53356
- const isRequiredDependency = reactive.has(inferredDependency.identifier.id) ||
53357
- !isStableType(inferredDependency.identifier);
53358
53396
  let hasMatchingManualDependency = false;
53359
53397
  for (const manualDependency of manualDependencies) {
53360
53398
  if (manualDependency.root.kind === 'NamedLocal' &&
53361
53399
  manualDependency.root.value.identifier.id ===
53362
53400
  inferredDependency.identifier.id &&
53363
53401
  (areEqualPaths(manualDependency.path, inferredDependency.path) ||
53364
- isSubPath(manualDependency.path, inferredDependency.path))) {
53402
+ isSubPathIgnoringOptionals(manualDependency.path, inferredDependency.path))) {
53365
53403
  hasMatchingManualDependency = true;
53366
53404
  matched.add(manualDependency);
53367
- if (!isRequiredDependency) {
53368
- extra.push(manualDependency);
53369
- }
53370
53405
  }
53371
53406
  }
53372
- if (isRequiredDependency && !hasMatchingManualDependency) {
53373
- missing.push(inferredDependency);
53407
+ if (hasMatchingManualDependency ||
53408
+ isOptionalDependency(inferredDependency, reactive)) {
53409
+ continue;
53374
53410
  }
53411
+ missing.push(inferredDependency);
53375
53412
  }
53376
53413
  for (const dep of (_c = startMemo.deps) !== null && _c !== void 0 ? _c : []) {
53377
53414
  if (matched.has(dep)) {
53378
53415
  continue;
53379
53416
  }
53417
+ if (dep.root.kind === 'NamedLocal' && dep.root.constant) {
53418
+ CompilerError.simpleInvariant(!dep.root.value.reactive &&
53419
+ isPrimitiveType(dep.root.value.identifier), {
53420
+ reason: 'Expected constant-folded dependency to be non-reactive',
53421
+ loc: dep.root.value.loc,
53422
+ });
53423
+ continue;
53424
+ }
53380
53425
  extra.push(dep);
53381
53426
  }
53382
53427
  if (missing.length !== 0 || extra.length !== 0) {
53383
- let suggestions = null;
53428
+ let suggestion = null;
53384
53429
  if (startMemo.depsLoc != null && typeof startMemo.depsLoc !== 'symbol') {
53385
- suggestions = [
53386
- {
53387
- description: 'Update dependencies',
53388
- range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
53389
- op: CompilerSuggestionOperation.Replace,
53390
- text: `[${inferred.map(printInferredDependency).join(', ')}]`,
53391
- },
53392
- ];
53430
+ suggestion = {
53431
+ description: 'Update dependencies',
53432
+ range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
53433
+ op: CompilerSuggestionOperation.Replace,
53434
+ text: `[${inferred
53435
+ .filter(dep => dep.kind === 'Local' && !isOptionalDependency(dep, reactive))
53436
+ .map(printInferredDependency)
53437
+ .join(', ')}]`,
53438
+ };
53393
53439
  }
53394
- if (missing.length !== 0) {
53395
- const diagnostic = CompilerDiagnostic.create({
53396
- category: ErrorCategory.MemoDependencies,
53397
- reason: 'Found non-exhaustive dependencies',
53398
- description: 'Missing dependencies can cause a value not to update when those inputs change, ' +
53399
- 'resulting in stale UI',
53400
- suggestions,
53440
+ const diagnostic = CompilerDiagnostic.create({
53441
+ category: ErrorCategory.MemoDependencies,
53442
+ reason: 'Found missing/extra memoization dependencies',
53443
+ description: [
53444
+ missing.length !== 0
53445
+ ? 'Missing dependencies can cause a value to update less often than it should, ' +
53446
+ 'resulting in stale UI'
53447
+ : null,
53448
+ extra.length !== 0
53449
+ ? 'Extra dependencies can cause a value to update more often than it should, ' +
53450
+ 'resulting in performance problems such as excessive renders or effects firing too often'
53451
+ : null,
53452
+ ]
53453
+ .filter(Boolean)
53454
+ .join('. '),
53455
+ suggestions: suggestion != null ? [suggestion] : null,
53456
+ });
53457
+ for (const dep of missing) {
53458
+ let reactiveStableValueHint = '';
53459
+ if (isStableType(dep.identifier)) {
53460
+ reactiveStableValueHint =
53461
+ '. Refs, setState functions, and other "stable" values generally do not need to be added ' +
53462
+ 'as dependencies, but this variable may change over time to point to different values';
53463
+ }
53464
+ diagnostic.withDetails({
53465
+ kind: 'error',
53466
+ message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
53467
+ loc: dep.loc,
53401
53468
  });
53402
- for (const dep of missing) {
53403
- let reactiveStableValueHint = '';
53404
- if (isStableType(dep.identifier)) {
53405
- reactiveStableValueHint =
53406
- '. Refs, setState functions, and other "stable" values generally do not need to be added as dependencies, but this variable may change over time to point to different values';
53407
- }
53469
+ }
53470
+ for (const dep of extra) {
53471
+ if (dep.root.kind === 'Global') {
53408
53472
  diagnostic.withDetails({
53409
53473
  kind: 'error',
53410
- message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
53411
- loc: dep.loc,
53474
+ message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\`. ` +
53475
+ 'Values declared outside of a component/hook should not be listed as ' +
53476
+ 'dependencies as the component will not re-render if they change',
53477
+ loc: (_e = (_d = dep.loc) !== null && _d !== void 0 ? _d : startMemo.depsLoc) !== null && _e !== void 0 ? _e : value.loc,
53478
+ });
53479
+ error.pushDiagnostic(diagnostic);
53480
+ }
53481
+ else {
53482
+ const root = dep.root.value;
53483
+ const matchingInferred = inferred.find((inferredDep) => {
53484
+ return (inferredDep.kind === 'Local' &&
53485
+ inferredDep.identifier.id === root.identifier.id &&
53486
+ isSubPathIgnoringOptionals(inferredDep.path, dep.path));
53412
53487
  });
53488
+ if (matchingInferred != null &&
53489
+ !isOptionalDependency(matchingInferred, reactive)) {
53490
+ diagnostic.withDetails({
53491
+ kind: 'error',
53492
+ message: `Overly precise dependency \`${printManualMemoDependency(dep)}\`, ` +
53493
+ `use \`${printInferredDependency(matchingInferred)}\` instead`,
53494
+ loc: (_g = (_f = dep.loc) !== null && _f !== void 0 ? _f : startMemo.depsLoc) !== null && _g !== void 0 ? _g : value.loc,
53495
+ });
53496
+ }
53497
+ else {
53498
+ diagnostic.withDetails({
53499
+ kind: 'error',
53500
+ message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\``,
53501
+ loc: (_j = (_h = dep.loc) !== null && _h !== void 0 ? _h : startMemo.depsLoc) !== null && _j !== void 0 ? _j : value.loc,
53502
+ });
53503
+ }
53413
53504
  }
53414
- error.pushDiagnostic(diagnostic);
53415
53505
  }
53416
- else if (extra.length !== 0) {
53417
- const diagnostic = CompilerDiagnostic.create({
53418
- category: ErrorCategory.MemoDependencies,
53419
- reason: 'Found unnecessary memoization dependencies',
53420
- description: 'Unnecessary dependencies can cause a value to update more often than necessary, ' +
53421
- 'which can cause effects to run more than expected',
53422
- });
53506
+ if (suggestion != null) {
53423
53507
  diagnostic.withDetails({
53424
- kind: 'error',
53425
- message: `Unnecessary dependencies ${extra.map(dep => `\`${printManualMemoDependency(dep)}\``).join(', ')}`,
53426
- loc: (_d = startMemo.depsLoc) !== null && _d !== void 0 ? _d : value.loc,
53508
+ kind: 'hint',
53509
+ message: `Inferred dependencies: \`${suggestion.text}\``,
53427
53510
  });
53428
- error.pushDiagnostic(diagnostic);
53429
53511
  }
53512
+ error.pushDiagnostic(diagnostic);
53430
53513
  }
53431
53514
  dependencies.clear();
53432
53515
  locals.clear();
@@ -53817,6 +53900,11 @@ function findOptionalPlaces(fn) {
53817
53900
  }
53818
53901
  return optionals;
53819
53902
  }
53903
+ function isOptionalDependency(inferredDependency, reactive) {
53904
+ return (!reactive.has(inferredDependency.identifier.id) &&
53905
+ (isStableType(inferredDependency.identifier) ||
53906
+ isPrimitiveType(inferredDependency.identifier)));
53907
+ }
53820
53908
 
53821
53909
  function run(func, config, fnType, mode, programContext, logger, filename, code) {
53822
53910
  var _a, _b;
@@ -53943,8 +54031,10 @@ function runWithEnvironment(func, env) {
53943
54031
  }
53944
54032
  inferReactivePlaces(hir);
53945
54033
  log({ kind: 'hir', name: 'InferReactivePlaces', value: hir });
53946
- if (env.config.validateExhaustiveMemoizationDependencies) {
53947
- validateExhaustiveDependencies(hir).unwrap();
54034
+ if (env.enableValidations) {
54035
+ if (env.config.validateExhaustiveMemoizationDependencies) {
54036
+ validateExhaustiveDependencies(hir).unwrap();
54037
+ }
53948
54038
  }
53949
54039
  rewriteInstructionKindsBasedOnReassignment(hir);
53950
54040
  log({
@@ -54207,7 +54297,7 @@ function findProgramSuppressions(programComments, ruleNames, flowSuppressions) {
54207
54297
  let disableNextLinePattern = null;
54208
54298
  let disablePattern = null;
54209
54299
  let enablePattern = null;
54210
- if (ruleNames.length !== 0) {
54300
+ if (ruleNames != null && ruleNames.length !== 0) {
54211
54301
  const rulePattern = `(${ruleNames.join('|')})`;
54212
54302
  disableNextLinePattern = new RegExp(`eslint-disable-next-line ${rulePattern}`);
54213
54303
  disablePattern = new RegExp(`eslint-disable ${rulePattern}`);
@@ -54544,7 +54634,10 @@ function compileProgram(program, pass) {
54544
54634
  handleError(restrictedImportsErr, pass, null);
54545
54635
  return null;
54546
54636
  }
54547
- const suppressions = findProgramSuppressions(pass.comments, (_a = pass.opts.eslintSuppressionRules) !== null && _a !== void 0 ? _a : DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions);
54637
+ const suppressions = findProgramSuppressions(pass.comments, pass.opts.environment.validateExhaustiveMemoizationDependencies &&
54638
+ pass.opts.environment.validateHooksUsage
54639
+ ? null
54640
+ : ((_a = pass.opts.eslintSuppressionRules) !== null && _a !== void 0 ? _a : DEFAULT_ESLINT_SUPPRESSIONS), pass.opts.flowSuppressions);
54548
54641
  const programContext = new ProgramContext({
54549
54642
  program: program,
54550
54643
  opts: pass.opts,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-hooks",
3
3
  "description": "ESLint rules for React Hooks",
4
- "version": "7.1.0-canary-fd524fe0-20251121",
4
+ "version": "7.1.0-canary-7dc903cd-20251203",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/facebook/react.git",