eslint-plugin-react-hooks 7.0.0-canary-6160773f-20251023 → 7.0.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.
@@ -17648,6 +17648,10 @@ function hasOwnProperty$1(obj, key) {
17648
17648
  return Object.prototype.hasOwnProperty.call(obj, key);
17649
17649
  }
17650
17650
 
17651
+ const CODEFRAME_LINES_ABOVE = 2;
17652
+ const CODEFRAME_LINES_BELOW = 3;
17653
+ const CODEFRAME_MAX_LINES = 10;
17654
+ const CODEFRAME_ABBREVIATED_SOURCE_LINES = 5;
17651
17655
  var ErrorSeverity;
17652
17656
  (function (ErrorSeverity) {
17653
17657
  ErrorSeverity["Error"] = "Error";
@@ -17964,7 +17968,7 @@ class CompilerError extends Error {
17964
17968
  }
17965
17969
  }
17966
17970
  function printCodeFrame(source, loc, message) {
17967
- return libExports.codeFrameColumns(source, {
17971
+ const printed = libExports.codeFrameColumns(source, {
17968
17972
  start: {
17969
17973
  line: loc.start.line,
17970
17974
  column: loc.start.column + 1,
@@ -17975,7 +17979,19 @@ function printCodeFrame(source, loc, message) {
17975
17979
  },
17976
17980
  }, {
17977
17981
  message,
17982
+ linesAbove: CODEFRAME_LINES_ABOVE,
17983
+ linesBelow: CODEFRAME_LINES_BELOW,
17978
17984
  });
17985
+ const lines = printed.split(/\r?\n/);
17986
+ if (loc.end.line - loc.start.line < CODEFRAME_MAX_LINES) {
17987
+ return printed;
17988
+ }
17989
+ const pipeIndex = lines[0].indexOf('|');
17990
+ return [
17991
+ ...lines.slice(0, CODEFRAME_LINES_ABOVE + CODEFRAME_ABBREVIATED_SOURCE_LINES),
17992
+ ' '.repeat(pipeIndex) + '…',
17993
+ ...lines.slice(-(CODEFRAME_LINES_BELOW + CODEFRAME_ABBREVIATED_SOURCE_LINES)),
17994
+ ].join('\n');
17979
17995
  }
17980
17996
  function printErrorSummary(category, message) {
17981
17997
  let heading;
@@ -32096,6 +32112,7 @@ const EnvironmentConfigSchema = v4.z.object({
32096
32112
  validateNoSetStateInRender: v4.z.boolean().default(true),
32097
32113
  validateNoSetStateInEffects: v4.z.boolean().default(false),
32098
32114
  validateNoDerivedComputationsInEffects: v4.z.boolean().default(false),
32115
+ validateNoDerivedComputationsInEffects_exp: v4.z.boolean().default(false),
32099
32116
  validateNoJSXInTryStatements: v4.z.boolean().default(false),
32100
32117
  validateStaticComponents: v4.z.boolean().default(false),
32101
32118
  validateMemoizedEffectDependencies: v4.z.boolean().default(false),
@@ -52038,7 +52055,7 @@ function validateNoDerivedComputationsInEffects(fn) {
52038
52055
  });
52039
52056
  return (_a = locals.get(dep.identifier.id)) !== null && _a !== void 0 ? _a : dep.identifier.id;
52040
52057
  });
52041
- validateEffect(effectFunction.loweredFunc.func, dependencies, errors);
52058
+ validateEffect$1(effectFunction.loweredFunc.func, dependencies, errors);
52042
52059
  }
52043
52060
  }
52044
52061
  }
@@ -52048,7 +52065,7 @@ function validateNoDerivedComputationsInEffects(fn) {
52048
52065
  throw errors;
52049
52066
  }
52050
52067
  }
52051
- function validateEffect(effectFunction, effectDeps, errors) {
52068
+ function validateEffect$1(effectFunction, effectDeps, errors) {
52052
52069
  for (const operand of effectFunction.context) {
52053
52070
  if (isSetStateType(operand.identifier)) {
52054
52071
  continue;
@@ -52161,6 +52178,339 @@ function validateEffect(effectFunction, effectDeps, errors) {
52161
52178
  }
52162
52179
  }
52163
52180
 
52181
+ class DerivationCache {
52182
+ constructor() {
52183
+ this.hasChanges = false;
52184
+ this.cache = new Map();
52185
+ }
52186
+ snapshot() {
52187
+ const hasChanges = this.hasChanges;
52188
+ this.hasChanges = false;
52189
+ return hasChanges;
52190
+ }
52191
+ addDerivationEntry(derivedVar, sourcesIds, typeOfValue) {
52192
+ var _a, _b;
52193
+ let newValue = {
52194
+ place: derivedVar,
52195
+ sourcesIds: new Set(),
52196
+ typeOfValue: typeOfValue !== null && typeOfValue !== void 0 ? typeOfValue : 'ignored',
52197
+ };
52198
+ if (sourcesIds !== undefined) {
52199
+ for (const id of sourcesIds) {
52200
+ const sourcePlace = (_a = this.cache.get(id)) === null || _a === void 0 ? void 0 : _a.place;
52201
+ if (sourcePlace === undefined) {
52202
+ continue;
52203
+ }
52204
+ if (sourcePlace.identifier.name === null ||
52205
+ ((_b = sourcePlace.identifier.name) === null || _b === void 0 ? void 0 : _b.kind) === 'promoted') {
52206
+ newValue.sourcesIds.add(derivedVar.identifier.id);
52207
+ }
52208
+ else {
52209
+ newValue.sourcesIds.add(sourcePlace.identifier.id);
52210
+ }
52211
+ }
52212
+ }
52213
+ if (newValue.sourcesIds.size === 0) {
52214
+ newValue.sourcesIds.add(derivedVar.identifier.id);
52215
+ }
52216
+ const existingValue = this.cache.get(derivedVar.identifier.id);
52217
+ if (existingValue === undefined ||
52218
+ !this.isDerivationEqual(existingValue, newValue)) {
52219
+ this.cache.set(derivedVar.identifier.id, newValue);
52220
+ this.hasChanges = true;
52221
+ }
52222
+ }
52223
+ isDerivationEqual(a, b) {
52224
+ if (a.typeOfValue !== b.typeOfValue) {
52225
+ return false;
52226
+ }
52227
+ if (a.sourcesIds.size !== b.sourcesIds.size) {
52228
+ return false;
52229
+ }
52230
+ for (const id of a.sourcesIds) {
52231
+ if (!b.sourcesIds.has(id)) {
52232
+ return false;
52233
+ }
52234
+ }
52235
+ return true;
52236
+ }
52237
+ }
52238
+ function validateNoDerivedComputationsInEffects_exp(fn) {
52239
+ const functions = new Map();
52240
+ const derivationCache = new DerivationCache();
52241
+ const errors = new CompilerError();
52242
+ const effects = new Set();
52243
+ const setStateCache = new Map();
52244
+ const effectSetStateCache = new Map();
52245
+ const context = {
52246
+ functions,
52247
+ errors,
52248
+ derivationCache,
52249
+ effects,
52250
+ setStateCache,
52251
+ effectSetStateCache,
52252
+ };
52253
+ if (fn.fnType === 'Hook') {
52254
+ for (const param of fn.params) {
52255
+ if (param.kind === 'Identifier') {
52256
+ context.derivationCache.cache.set(param.identifier.id, {
52257
+ place: param,
52258
+ sourcesIds: new Set([param.identifier.id]),
52259
+ typeOfValue: 'fromProps',
52260
+ });
52261
+ context.derivationCache.hasChanges = true;
52262
+ }
52263
+ }
52264
+ }
52265
+ else if (fn.fnType === 'Component') {
52266
+ const props = fn.params[0];
52267
+ if (props != null && props.kind === 'Identifier') {
52268
+ context.derivationCache.cache.set(props.identifier.id, {
52269
+ place: props,
52270
+ sourcesIds: new Set([props.identifier.id]),
52271
+ typeOfValue: 'fromProps',
52272
+ });
52273
+ context.derivationCache.hasChanges = true;
52274
+ }
52275
+ }
52276
+ let isFirstPass = true;
52277
+ do {
52278
+ for (const block of fn.body.blocks.values()) {
52279
+ recordPhiDerivations(block, context);
52280
+ for (const instr of block.instructions) {
52281
+ recordInstructionDerivations(instr, context, isFirstPass);
52282
+ }
52283
+ }
52284
+ isFirstPass = false;
52285
+ } while (context.derivationCache.snapshot());
52286
+ for (const effect of effects) {
52287
+ validateEffect(effect, context);
52288
+ }
52289
+ if (errors.hasAnyErrors()) {
52290
+ throw errors;
52291
+ }
52292
+ }
52293
+ function recordPhiDerivations(block, context) {
52294
+ for (const phi of block.phis) {
52295
+ let typeOfValue = 'ignored';
52296
+ let sourcesIds = new Set();
52297
+ for (const operand of phi.operands.values()) {
52298
+ const operandMetadata = context.derivationCache.cache.get(operand.identifier.id);
52299
+ if (operandMetadata === undefined) {
52300
+ continue;
52301
+ }
52302
+ typeOfValue = joinValue(typeOfValue, operandMetadata.typeOfValue);
52303
+ sourcesIds.add(operand.identifier.id);
52304
+ }
52305
+ if (typeOfValue !== 'ignored') {
52306
+ context.derivationCache.addDerivationEntry(phi.place, sourcesIds, typeOfValue);
52307
+ }
52308
+ }
52309
+ }
52310
+ function joinValue(lvalueType, valueType) {
52311
+ if (lvalueType === 'ignored')
52312
+ return valueType;
52313
+ if (valueType === 'ignored')
52314
+ return lvalueType;
52315
+ if (lvalueType === valueType)
52316
+ return lvalueType;
52317
+ return 'fromPropsAndState';
52318
+ }
52319
+ function recordInstructionDerivations(instr, context, isFirstPass) {
52320
+ let typeOfValue = 'ignored';
52321
+ const sources = new Set();
52322
+ const { lvalue, value } = instr;
52323
+ if (value.kind === 'FunctionExpression') {
52324
+ context.functions.set(lvalue.identifier.id, value);
52325
+ for (const [, block] of value.loweredFunc.func.body.blocks) {
52326
+ for (const instr of block.instructions) {
52327
+ recordInstructionDerivations(instr, context, isFirstPass);
52328
+ }
52329
+ }
52330
+ }
52331
+ else if (value.kind === 'CallExpression' || value.kind === 'MethodCall') {
52332
+ const callee = value.kind === 'CallExpression' ? value.callee : value.property;
52333
+ if (isUseEffectHookType(callee.identifier) &&
52334
+ value.args.length === 2 &&
52335
+ value.args[0].kind === 'Identifier' &&
52336
+ value.args[1].kind === 'Identifier') {
52337
+ const effectFunction = context.functions.get(value.args[0].identifier.id);
52338
+ if (effectFunction != null) {
52339
+ context.effects.add(effectFunction.loweredFunc.func);
52340
+ }
52341
+ }
52342
+ else if (isUseStateType(lvalue.identifier) && value.args.length > 0) {
52343
+ const stateValueSource = value.args[0];
52344
+ if (stateValueSource.kind === 'Identifier') {
52345
+ sources.add(stateValueSource.identifier.id);
52346
+ }
52347
+ typeOfValue = joinValue(typeOfValue, 'fromState');
52348
+ }
52349
+ }
52350
+ for (const operand of eachInstructionOperand(instr)) {
52351
+ if (isSetStateType(operand.identifier) &&
52352
+ operand.loc !== GeneratedSource &&
52353
+ isFirstPass) {
52354
+ if (context.setStateCache.has(operand.loc.identifierName)) {
52355
+ context.setStateCache.get(operand.loc.identifierName).push(operand);
52356
+ }
52357
+ else {
52358
+ context.setStateCache.set(operand.loc.identifierName, [operand]);
52359
+ }
52360
+ }
52361
+ const operandMetadata = context.derivationCache.cache.get(operand.identifier.id);
52362
+ if (operandMetadata === undefined) {
52363
+ continue;
52364
+ }
52365
+ typeOfValue = joinValue(typeOfValue, operandMetadata.typeOfValue);
52366
+ for (const id of operandMetadata.sourcesIds) {
52367
+ sources.add(id);
52368
+ }
52369
+ }
52370
+ if (typeOfValue === 'ignored') {
52371
+ return;
52372
+ }
52373
+ for (const lvalue of eachInstructionLValue(instr)) {
52374
+ context.derivationCache.addDerivationEntry(lvalue, sources, typeOfValue);
52375
+ }
52376
+ for (const operand of eachInstructionOperand(instr)) {
52377
+ switch (operand.effect) {
52378
+ case Effect.Capture:
52379
+ case Effect.Store:
52380
+ case Effect.ConditionallyMutate:
52381
+ case Effect.ConditionallyMutateIterator:
52382
+ case Effect.Mutate: {
52383
+ if (isMutable(instr, operand)) {
52384
+ context.derivationCache.addDerivationEntry(operand, sources, typeOfValue);
52385
+ }
52386
+ break;
52387
+ }
52388
+ case Effect.Freeze:
52389
+ case Effect.Read: {
52390
+ break;
52391
+ }
52392
+ case Effect.Unknown: {
52393
+ CompilerError.invariant(false, {
52394
+ reason: 'Unexpected unknown effect',
52395
+ description: null,
52396
+ details: [
52397
+ {
52398
+ kind: 'error',
52399
+ loc: operand.loc,
52400
+ message: 'Unexpected unknown effect',
52401
+ },
52402
+ ],
52403
+ });
52404
+ }
52405
+ default: {
52406
+ assertExhaustive$1(operand.effect, `Unexpected effect kind \`${operand.effect}\``);
52407
+ }
52408
+ }
52409
+ }
52410
+ }
52411
+ function validateEffect(effectFunction, context) {
52412
+ const seenBlocks = new Set();
52413
+ const effectDerivedSetStateCalls = [];
52414
+ const globals = new Set();
52415
+ for (const block of effectFunction.body.blocks.values()) {
52416
+ for (const pred of block.preds) {
52417
+ if (!seenBlocks.has(pred)) {
52418
+ return;
52419
+ }
52420
+ }
52421
+ for (const instr of block.instructions) {
52422
+ if (isUseRefType(instr.lvalue.identifier)) {
52423
+ return;
52424
+ }
52425
+ for (const operand of eachInstructionOperand(instr)) {
52426
+ if (isSetStateType(operand.identifier) &&
52427
+ operand.loc !== GeneratedSource) {
52428
+ if (context.effectSetStateCache.has(operand.loc.identifierName)) {
52429
+ context.effectSetStateCache
52430
+ .get(operand.loc.identifierName)
52431
+ .push(operand);
52432
+ }
52433
+ else {
52434
+ context.effectSetStateCache.set(operand.loc.identifierName, [
52435
+ operand,
52436
+ ]);
52437
+ }
52438
+ }
52439
+ }
52440
+ if (instr.value.kind === 'CallExpression' &&
52441
+ isSetStateType(instr.value.callee.identifier) &&
52442
+ instr.value.args.length === 1 &&
52443
+ instr.value.args[0].kind === 'Identifier') {
52444
+ const argMetadata = context.derivationCache.cache.get(instr.value.args[0].identifier.id);
52445
+ if (argMetadata !== undefined) {
52446
+ effectDerivedSetStateCalls.push({
52447
+ value: instr.value,
52448
+ loc: instr.value.callee.loc,
52449
+ sourceIds: argMetadata.sourcesIds,
52450
+ typeOfValue: argMetadata.typeOfValue,
52451
+ });
52452
+ }
52453
+ }
52454
+ else if (instr.value.kind === 'CallExpression') {
52455
+ const calleeMetadata = context.derivationCache.cache.get(instr.value.callee.identifier.id);
52456
+ if (calleeMetadata !== undefined &&
52457
+ (calleeMetadata.typeOfValue === 'fromProps' ||
52458
+ calleeMetadata.typeOfValue === 'fromPropsAndState')) {
52459
+ return;
52460
+ }
52461
+ if (globals.has(instr.value.callee.identifier.id)) {
52462
+ return;
52463
+ }
52464
+ }
52465
+ else if (instr.value.kind === 'LoadGlobal') {
52466
+ globals.add(instr.lvalue.identifier.id);
52467
+ for (const operand of eachInstructionOperand(instr)) {
52468
+ globals.add(operand.identifier.id);
52469
+ }
52470
+ }
52471
+ }
52472
+ seenBlocks.add(block.id);
52473
+ }
52474
+ for (const derivedSetStateCall of effectDerivedSetStateCalls) {
52475
+ if (derivedSetStateCall.loc !== GeneratedSource &&
52476
+ context.effectSetStateCache.has(derivedSetStateCall.loc.identifierName) &&
52477
+ context.setStateCache.has(derivedSetStateCall.loc.identifierName) &&
52478
+ context.effectSetStateCache.get(derivedSetStateCall.loc.identifierName)
52479
+ .length ===
52480
+ context.setStateCache.get(derivedSetStateCall.loc.identifierName)
52481
+ .length -
52482
+ 1) {
52483
+ const derivedDepsStr = Array.from(derivedSetStateCall.sourceIds)
52484
+ .map(sourceId => {
52485
+ var _a;
52486
+ const sourceMetadata = context.derivationCache.cache.get(sourceId);
52487
+ return (_a = sourceMetadata === null || sourceMetadata === void 0 ? void 0 : sourceMetadata.place.identifier.name) === null || _a === void 0 ? void 0 : _a.value;
52488
+ })
52489
+ .filter(Boolean)
52490
+ .join(', ');
52491
+ let description;
52492
+ if (derivedSetStateCall.typeOfValue === 'fromProps') {
52493
+ description = `From props: [${derivedDepsStr}]`;
52494
+ }
52495
+ else if (derivedSetStateCall.typeOfValue === 'fromState') {
52496
+ description = `From local state: [${derivedDepsStr}]`;
52497
+ }
52498
+ else {
52499
+ description = `From props and local state: [${derivedDepsStr}]`;
52500
+ }
52501
+ context.errors.pushDiagnostic(CompilerDiagnostic.create({
52502
+ description: `Derived values (${description}) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user`,
52503
+ category: ErrorCategory.EffectDerivationsOfState,
52504
+ reason: 'You might not need an effect. Derive values in render, not effects.',
52505
+ }).withDetails({
52506
+ kind: 'error',
52507
+ loc: derivedSetStateCall.value.callee.loc,
52508
+ message: 'This should be computed during render, not in an effect',
52509
+ }));
52510
+ }
52511
+ }
52512
+ }
52513
+
52164
52514
  function nameAnonymousFunctions(fn) {
52165
52515
  if (fn.id == null) {
52166
52516
  return;
@@ -52402,6 +52752,9 @@ function runWithEnvironment(func, env) {
52402
52752
  if (env.config.validateNoDerivedComputationsInEffects) {
52403
52753
  validateNoDerivedComputationsInEffects(hir);
52404
52754
  }
52755
+ if (env.config.validateNoDerivedComputationsInEffects_exp) {
52756
+ validateNoDerivedComputationsInEffects_exp(hir);
52757
+ }
52405
52758
  if (env.config.validateNoSetStateInEffects) {
52406
52759
  env.logErrors(validateNoSetStateInEffects(hir, env));
52407
52760
  }
@@ -17639,6 +17639,10 @@ function hasOwnProperty$1(obj, key) {
17639
17639
  return Object.prototype.hasOwnProperty.call(obj, key);
17640
17640
  }
17641
17641
 
17642
+ const CODEFRAME_LINES_ABOVE = 2;
17643
+ const CODEFRAME_LINES_BELOW = 3;
17644
+ const CODEFRAME_MAX_LINES = 10;
17645
+ const CODEFRAME_ABBREVIATED_SOURCE_LINES = 5;
17642
17646
  var ErrorSeverity;
17643
17647
  (function (ErrorSeverity) {
17644
17648
  ErrorSeverity["Error"] = "Error";
@@ -17955,7 +17959,7 @@ class CompilerError extends Error {
17955
17959
  }
17956
17960
  }
17957
17961
  function printCodeFrame(source, loc, message) {
17958
- return libExports.codeFrameColumns(source, {
17962
+ const printed = libExports.codeFrameColumns(source, {
17959
17963
  start: {
17960
17964
  line: loc.start.line,
17961
17965
  column: loc.start.column + 1,
@@ -17966,7 +17970,19 @@ function printCodeFrame(source, loc, message) {
17966
17970
  },
17967
17971
  }, {
17968
17972
  message,
17973
+ linesAbove: CODEFRAME_LINES_ABOVE,
17974
+ linesBelow: CODEFRAME_LINES_BELOW,
17969
17975
  });
17976
+ const lines = printed.split(/\r?\n/);
17977
+ if (loc.end.line - loc.start.line < CODEFRAME_MAX_LINES) {
17978
+ return printed;
17979
+ }
17980
+ const pipeIndex = lines[0].indexOf('|');
17981
+ return [
17982
+ ...lines.slice(0, CODEFRAME_LINES_ABOVE + CODEFRAME_ABBREVIATED_SOURCE_LINES),
17983
+ ' '.repeat(pipeIndex) + '…',
17984
+ ...lines.slice(-(CODEFRAME_LINES_BELOW + CODEFRAME_ABBREVIATED_SOURCE_LINES)),
17985
+ ].join('\n');
17970
17986
  }
17971
17987
  function printErrorSummary(category, message) {
17972
17988
  let heading;
@@ -31923,6 +31939,7 @@ const EnvironmentConfigSchema = v4.z.object({
31923
31939
  validateNoSetStateInRender: v4.z.boolean().default(true),
31924
31940
  validateNoSetStateInEffects: v4.z.boolean().default(false),
31925
31941
  validateNoDerivedComputationsInEffects: v4.z.boolean().default(false),
31942
+ validateNoDerivedComputationsInEffects_exp: v4.z.boolean().default(false),
31926
31943
  validateNoJSXInTryStatements: v4.z.boolean().default(false),
31927
31944
  validateStaticComponents: v4.z.boolean().default(false),
31928
31945
  validateMemoizedEffectDependencies: v4.z.boolean().default(false),
@@ -51865,7 +51882,7 @@ function validateNoDerivedComputationsInEffects(fn) {
51865
51882
  });
51866
51883
  return (_a = locals.get(dep.identifier.id)) !== null && _a !== void 0 ? _a : dep.identifier.id;
51867
51884
  });
51868
- validateEffect(effectFunction.loweredFunc.func, dependencies, errors);
51885
+ validateEffect$1(effectFunction.loweredFunc.func, dependencies, errors);
51869
51886
  }
51870
51887
  }
51871
51888
  }
@@ -51875,7 +51892,7 @@ function validateNoDerivedComputationsInEffects(fn) {
51875
51892
  throw errors;
51876
51893
  }
51877
51894
  }
51878
- function validateEffect(effectFunction, effectDeps, errors) {
51895
+ function validateEffect$1(effectFunction, effectDeps, errors) {
51879
51896
  for (const operand of effectFunction.context) {
51880
51897
  if (isSetStateType(operand.identifier)) {
51881
51898
  continue;
@@ -51988,6 +52005,339 @@ function validateEffect(effectFunction, effectDeps, errors) {
51988
52005
  }
51989
52006
  }
51990
52007
 
52008
+ class DerivationCache {
52009
+ constructor() {
52010
+ this.hasChanges = false;
52011
+ this.cache = new Map();
52012
+ }
52013
+ snapshot() {
52014
+ const hasChanges = this.hasChanges;
52015
+ this.hasChanges = false;
52016
+ return hasChanges;
52017
+ }
52018
+ addDerivationEntry(derivedVar, sourcesIds, typeOfValue) {
52019
+ var _a, _b;
52020
+ let newValue = {
52021
+ place: derivedVar,
52022
+ sourcesIds: new Set(),
52023
+ typeOfValue: typeOfValue !== null && typeOfValue !== void 0 ? typeOfValue : 'ignored',
52024
+ };
52025
+ if (sourcesIds !== undefined) {
52026
+ for (const id of sourcesIds) {
52027
+ const sourcePlace = (_a = this.cache.get(id)) === null || _a === void 0 ? void 0 : _a.place;
52028
+ if (sourcePlace === undefined) {
52029
+ continue;
52030
+ }
52031
+ if (sourcePlace.identifier.name === null ||
52032
+ ((_b = sourcePlace.identifier.name) === null || _b === void 0 ? void 0 : _b.kind) === 'promoted') {
52033
+ newValue.sourcesIds.add(derivedVar.identifier.id);
52034
+ }
52035
+ else {
52036
+ newValue.sourcesIds.add(sourcePlace.identifier.id);
52037
+ }
52038
+ }
52039
+ }
52040
+ if (newValue.sourcesIds.size === 0) {
52041
+ newValue.sourcesIds.add(derivedVar.identifier.id);
52042
+ }
52043
+ const existingValue = this.cache.get(derivedVar.identifier.id);
52044
+ if (existingValue === undefined ||
52045
+ !this.isDerivationEqual(existingValue, newValue)) {
52046
+ this.cache.set(derivedVar.identifier.id, newValue);
52047
+ this.hasChanges = true;
52048
+ }
52049
+ }
52050
+ isDerivationEqual(a, b) {
52051
+ if (a.typeOfValue !== b.typeOfValue) {
52052
+ return false;
52053
+ }
52054
+ if (a.sourcesIds.size !== b.sourcesIds.size) {
52055
+ return false;
52056
+ }
52057
+ for (const id of a.sourcesIds) {
52058
+ if (!b.sourcesIds.has(id)) {
52059
+ return false;
52060
+ }
52061
+ }
52062
+ return true;
52063
+ }
52064
+ }
52065
+ function validateNoDerivedComputationsInEffects_exp(fn) {
52066
+ const functions = new Map();
52067
+ const derivationCache = new DerivationCache();
52068
+ const errors = new CompilerError();
52069
+ const effects = new Set();
52070
+ const setStateCache = new Map();
52071
+ const effectSetStateCache = new Map();
52072
+ const context = {
52073
+ functions,
52074
+ errors,
52075
+ derivationCache,
52076
+ effects,
52077
+ setStateCache,
52078
+ effectSetStateCache,
52079
+ };
52080
+ if (fn.fnType === 'Hook') {
52081
+ for (const param of fn.params) {
52082
+ if (param.kind === 'Identifier') {
52083
+ context.derivationCache.cache.set(param.identifier.id, {
52084
+ place: param,
52085
+ sourcesIds: new Set([param.identifier.id]),
52086
+ typeOfValue: 'fromProps',
52087
+ });
52088
+ context.derivationCache.hasChanges = true;
52089
+ }
52090
+ }
52091
+ }
52092
+ else if (fn.fnType === 'Component') {
52093
+ const props = fn.params[0];
52094
+ if (props != null && props.kind === 'Identifier') {
52095
+ context.derivationCache.cache.set(props.identifier.id, {
52096
+ place: props,
52097
+ sourcesIds: new Set([props.identifier.id]),
52098
+ typeOfValue: 'fromProps',
52099
+ });
52100
+ context.derivationCache.hasChanges = true;
52101
+ }
52102
+ }
52103
+ let isFirstPass = true;
52104
+ do {
52105
+ for (const block of fn.body.blocks.values()) {
52106
+ recordPhiDerivations(block, context);
52107
+ for (const instr of block.instructions) {
52108
+ recordInstructionDerivations(instr, context, isFirstPass);
52109
+ }
52110
+ }
52111
+ isFirstPass = false;
52112
+ } while (context.derivationCache.snapshot());
52113
+ for (const effect of effects) {
52114
+ validateEffect(effect, context);
52115
+ }
52116
+ if (errors.hasAnyErrors()) {
52117
+ throw errors;
52118
+ }
52119
+ }
52120
+ function recordPhiDerivations(block, context) {
52121
+ for (const phi of block.phis) {
52122
+ let typeOfValue = 'ignored';
52123
+ let sourcesIds = new Set();
52124
+ for (const operand of phi.operands.values()) {
52125
+ const operandMetadata = context.derivationCache.cache.get(operand.identifier.id);
52126
+ if (operandMetadata === undefined) {
52127
+ continue;
52128
+ }
52129
+ typeOfValue = joinValue(typeOfValue, operandMetadata.typeOfValue);
52130
+ sourcesIds.add(operand.identifier.id);
52131
+ }
52132
+ if (typeOfValue !== 'ignored') {
52133
+ context.derivationCache.addDerivationEntry(phi.place, sourcesIds, typeOfValue);
52134
+ }
52135
+ }
52136
+ }
52137
+ function joinValue(lvalueType, valueType) {
52138
+ if (lvalueType === 'ignored')
52139
+ return valueType;
52140
+ if (valueType === 'ignored')
52141
+ return lvalueType;
52142
+ if (lvalueType === valueType)
52143
+ return lvalueType;
52144
+ return 'fromPropsAndState';
52145
+ }
52146
+ function recordInstructionDerivations(instr, context, isFirstPass) {
52147
+ let typeOfValue = 'ignored';
52148
+ const sources = new Set();
52149
+ const { lvalue, value } = instr;
52150
+ if (value.kind === 'FunctionExpression') {
52151
+ context.functions.set(lvalue.identifier.id, value);
52152
+ for (const [, block] of value.loweredFunc.func.body.blocks) {
52153
+ for (const instr of block.instructions) {
52154
+ recordInstructionDerivations(instr, context, isFirstPass);
52155
+ }
52156
+ }
52157
+ }
52158
+ else if (value.kind === 'CallExpression' || value.kind === 'MethodCall') {
52159
+ const callee = value.kind === 'CallExpression' ? value.callee : value.property;
52160
+ if (isUseEffectHookType(callee.identifier) &&
52161
+ value.args.length === 2 &&
52162
+ value.args[0].kind === 'Identifier' &&
52163
+ value.args[1].kind === 'Identifier') {
52164
+ const effectFunction = context.functions.get(value.args[0].identifier.id);
52165
+ if (effectFunction != null) {
52166
+ context.effects.add(effectFunction.loweredFunc.func);
52167
+ }
52168
+ }
52169
+ else if (isUseStateType(lvalue.identifier) && value.args.length > 0) {
52170
+ const stateValueSource = value.args[0];
52171
+ if (stateValueSource.kind === 'Identifier') {
52172
+ sources.add(stateValueSource.identifier.id);
52173
+ }
52174
+ typeOfValue = joinValue(typeOfValue, 'fromState');
52175
+ }
52176
+ }
52177
+ for (const operand of eachInstructionOperand(instr)) {
52178
+ if (isSetStateType(operand.identifier) &&
52179
+ operand.loc !== GeneratedSource &&
52180
+ isFirstPass) {
52181
+ if (context.setStateCache.has(operand.loc.identifierName)) {
52182
+ context.setStateCache.get(operand.loc.identifierName).push(operand);
52183
+ }
52184
+ else {
52185
+ context.setStateCache.set(operand.loc.identifierName, [operand]);
52186
+ }
52187
+ }
52188
+ const operandMetadata = context.derivationCache.cache.get(operand.identifier.id);
52189
+ if (operandMetadata === undefined) {
52190
+ continue;
52191
+ }
52192
+ typeOfValue = joinValue(typeOfValue, operandMetadata.typeOfValue);
52193
+ for (const id of operandMetadata.sourcesIds) {
52194
+ sources.add(id);
52195
+ }
52196
+ }
52197
+ if (typeOfValue === 'ignored') {
52198
+ return;
52199
+ }
52200
+ for (const lvalue of eachInstructionLValue(instr)) {
52201
+ context.derivationCache.addDerivationEntry(lvalue, sources, typeOfValue);
52202
+ }
52203
+ for (const operand of eachInstructionOperand(instr)) {
52204
+ switch (operand.effect) {
52205
+ case Effect.Capture:
52206
+ case Effect.Store:
52207
+ case Effect.ConditionallyMutate:
52208
+ case Effect.ConditionallyMutateIterator:
52209
+ case Effect.Mutate: {
52210
+ if (isMutable(instr, operand)) {
52211
+ context.derivationCache.addDerivationEntry(operand, sources, typeOfValue);
52212
+ }
52213
+ break;
52214
+ }
52215
+ case Effect.Freeze:
52216
+ case Effect.Read: {
52217
+ break;
52218
+ }
52219
+ case Effect.Unknown: {
52220
+ CompilerError.invariant(false, {
52221
+ reason: 'Unexpected unknown effect',
52222
+ description: null,
52223
+ details: [
52224
+ {
52225
+ kind: 'error',
52226
+ loc: operand.loc,
52227
+ message: 'Unexpected unknown effect',
52228
+ },
52229
+ ],
52230
+ });
52231
+ }
52232
+ default: {
52233
+ assertExhaustive$1(operand.effect, `Unexpected effect kind \`${operand.effect}\``);
52234
+ }
52235
+ }
52236
+ }
52237
+ }
52238
+ function validateEffect(effectFunction, context) {
52239
+ const seenBlocks = new Set();
52240
+ const effectDerivedSetStateCalls = [];
52241
+ const globals = new Set();
52242
+ for (const block of effectFunction.body.blocks.values()) {
52243
+ for (const pred of block.preds) {
52244
+ if (!seenBlocks.has(pred)) {
52245
+ return;
52246
+ }
52247
+ }
52248
+ for (const instr of block.instructions) {
52249
+ if (isUseRefType(instr.lvalue.identifier)) {
52250
+ return;
52251
+ }
52252
+ for (const operand of eachInstructionOperand(instr)) {
52253
+ if (isSetStateType(operand.identifier) &&
52254
+ operand.loc !== GeneratedSource) {
52255
+ if (context.effectSetStateCache.has(operand.loc.identifierName)) {
52256
+ context.effectSetStateCache
52257
+ .get(operand.loc.identifierName)
52258
+ .push(operand);
52259
+ }
52260
+ else {
52261
+ context.effectSetStateCache.set(operand.loc.identifierName, [
52262
+ operand,
52263
+ ]);
52264
+ }
52265
+ }
52266
+ }
52267
+ if (instr.value.kind === 'CallExpression' &&
52268
+ isSetStateType(instr.value.callee.identifier) &&
52269
+ instr.value.args.length === 1 &&
52270
+ instr.value.args[0].kind === 'Identifier') {
52271
+ const argMetadata = context.derivationCache.cache.get(instr.value.args[0].identifier.id);
52272
+ if (argMetadata !== undefined) {
52273
+ effectDerivedSetStateCalls.push({
52274
+ value: instr.value,
52275
+ loc: instr.value.callee.loc,
52276
+ sourceIds: argMetadata.sourcesIds,
52277
+ typeOfValue: argMetadata.typeOfValue,
52278
+ });
52279
+ }
52280
+ }
52281
+ else if (instr.value.kind === 'CallExpression') {
52282
+ const calleeMetadata = context.derivationCache.cache.get(instr.value.callee.identifier.id);
52283
+ if (calleeMetadata !== undefined &&
52284
+ (calleeMetadata.typeOfValue === 'fromProps' ||
52285
+ calleeMetadata.typeOfValue === 'fromPropsAndState')) {
52286
+ return;
52287
+ }
52288
+ if (globals.has(instr.value.callee.identifier.id)) {
52289
+ return;
52290
+ }
52291
+ }
52292
+ else if (instr.value.kind === 'LoadGlobal') {
52293
+ globals.add(instr.lvalue.identifier.id);
52294
+ for (const operand of eachInstructionOperand(instr)) {
52295
+ globals.add(operand.identifier.id);
52296
+ }
52297
+ }
52298
+ }
52299
+ seenBlocks.add(block.id);
52300
+ }
52301
+ for (const derivedSetStateCall of effectDerivedSetStateCalls) {
52302
+ if (derivedSetStateCall.loc !== GeneratedSource &&
52303
+ context.effectSetStateCache.has(derivedSetStateCall.loc.identifierName) &&
52304
+ context.setStateCache.has(derivedSetStateCall.loc.identifierName) &&
52305
+ context.effectSetStateCache.get(derivedSetStateCall.loc.identifierName)
52306
+ .length ===
52307
+ context.setStateCache.get(derivedSetStateCall.loc.identifierName)
52308
+ .length -
52309
+ 1) {
52310
+ const derivedDepsStr = Array.from(derivedSetStateCall.sourceIds)
52311
+ .map(sourceId => {
52312
+ var _a;
52313
+ const sourceMetadata = context.derivationCache.cache.get(sourceId);
52314
+ return (_a = sourceMetadata === null || sourceMetadata === void 0 ? void 0 : sourceMetadata.place.identifier.name) === null || _a === void 0 ? void 0 : _a.value;
52315
+ })
52316
+ .filter(Boolean)
52317
+ .join(', ');
52318
+ let description;
52319
+ if (derivedSetStateCall.typeOfValue === 'fromProps') {
52320
+ description = `From props: [${derivedDepsStr}]`;
52321
+ }
52322
+ else if (derivedSetStateCall.typeOfValue === 'fromState') {
52323
+ description = `From local state: [${derivedDepsStr}]`;
52324
+ }
52325
+ else {
52326
+ description = `From props and local state: [${derivedDepsStr}]`;
52327
+ }
52328
+ context.errors.pushDiagnostic(CompilerDiagnostic.create({
52329
+ description: `Derived values (${description}) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user`,
52330
+ category: ErrorCategory.EffectDerivationsOfState,
52331
+ reason: 'You might not need an effect. Derive values in render, not effects.',
52332
+ }).withDetails({
52333
+ kind: 'error',
52334
+ loc: derivedSetStateCall.value.callee.loc,
52335
+ message: 'This should be computed during render, not in an effect',
52336
+ }));
52337
+ }
52338
+ }
52339
+ }
52340
+
51991
52341
  function nameAnonymousFunctions(fn) {
51992
52342
  if (fn.id == null) {
51993
52343
  return;
@@ -52229,6 +52579,9 @@ function runWithEnvironment(func, env) {
52229
52579
  if (env.config.validateNoDerivedComputationsInEffects) {
52230
52580
  validateNoDerivedComputationsInEffects(hir);
52231
52581
  }
52582
+ if (env.config.validateNoDerivedComputationsInEffects_exp) {
52583
+ validateNoDerivedComputationsInEffects_exp(hir);
52584
+ }
52232
52585
  if (env.config.validateNoSetStateInEffects) {
52233
52586
  env.logErrors(validateNoSetStateInEffects(hir, env));
52234
52587
  }
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.0.0-canary-6160773f-20251023",
4
+ "version": "7.0.1",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/facebook/react.git",
@@ -65,4 +65,4 @@
65
65
  "jest": "^29.5.0",
66
66
  "typescript": "^5.4.3"
67
67
  }
68
- }
68
+ }