eslint-plugin-react-hooks 7.1.0-canary-378973b3-20251205 → 7.1.0-canary-55480b4d-20251208
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.
|
@@ -18033,7 +18033,8 @@ function printErrorSummary(category, message) {
|
|
|
18033
18033
|
case ErrorCategory.Syntax:
|
|
18034
18034
|
case ErrorCategory.UseMemo:
|
|
18035
18035
|
case ErrorCategory.VoidUseMemo:
|
|
18036
|
-
case ErrorCategory.MemoDependencies:
|
|
18036
|
+
case ErrorCategory.MemoDependencies:
|
|
18037
|
+
case ErrorCategory.EffectExhaustiveDependencies: {
|
|
18037
18038
|
heading = 'Error';
|
|
18038
18039
|
break;
|
|
18039
18040
|
}
|
|
@@ -18073,6 +18074,7 @@ var ErrorCategory;
|
|
|
18073
18074
|
ErrorCategory["Globals"] = "Globals";
|
|
18074
18075
|
ErrorCategory["Refs"] = "Refs";
|
|
18075
18076
|
ErrorCategory["EffectDependencies"] = "EffectDependencies";
|
|
18077
|
+
ErrorCategory["EffectExhaustiveDependencies"] = "EffectExhaustiveDependencies";
|
|
18076
18078
|
ErrorCategory["EffectSetState"] = "EffectSetState";
|
|
18077
18079
|
ErrorCategory["EffectDerivationsOfState"] = "EffectDerivationsOfState";
|
|
18078
18080
|
ErrorCategory["ErrorBoundaries"] = "ErrorBoundaries";
|
|
@@ -18139,6 +18141,15 @@ function getRuleForCategoryImpl(category) {
|
|
|
18139
18141
|
preset: LintRulePreset.Off,
|
|
18140
18142
|
};
|
|
18141
18143
|
}
|
|
18144
|
+
case ErrorCategory.EffectExhaustiveDependencies: {
|
|
18145
|
+
return {
|
|
18146
|
+
category,
|
|
18147
|
+
severity: ErrorSeverity.Error,
|
|
18148
|
+
name: 'exhaustive-effect-dependencies',
|
|
18149
|
+
description: 'Validates that effect dependencies are exhaustive and without extraneous values',
|
|
18150
|
+
preset: LintRulePreset.Off,
|
|
18151
|
+
};
|
|
18152
|
+
}
|
|
18142
18153
|
case ErrorCategory.EffectDerivationsOfState: {
|
|
18143
18154
|
return {
|
|
18144
18155
|
category,
|
|
@@ -18153,7 +18164,9 @@ function getRuleForCategoryImpl(category) {
|
|
|
18153
18164
|
category,
|
|
18154
18165
|
severity: ErrorSeverity.Error,
|
|
18155
18166
|
name: 'set-state-in-effect',
|
|
18156
|
-
description: 'Validates against calling setState synchronously in an effect
|
|
18167
|
+
description: 'Validates against calling setState synchronously in an effect. ' +
|
|
18168
|
+
'This can indicate non-local derived data, a derived event pattern, or ' +
|
|
18169
|
+
'improper external data synchronization.',
|
|
18157
18170
|
preset: LintRulePreset.Recommended,
|
|
18158
18171
|
};
|
|
18159
18172
|
}
|
|
@@ -32206,6 +32219,7 @@ const EnvironmentConfigSchema = v4.z.object({
|
|
|
32206
32219
|
enablePreserveExistingMemoizationGuarantees: v4.z.boolean().default(true),
|
|
32207
32220
|
validatePreserveExistingMemoizationGuarantees: v4.z.boolean().default(true),
|
|
32208
32221
|
validateExhaustiveMemoizationDependencies: v4.z.boolean().default(true),
|
|
32222
|
+
validateExhaustiveEffectDependencies: v4.z.boolean().default(false),
|
|
32209
32223
|
enablePreserveExistingManualUseMemo: v4.z.boolean().default(false),
|
|
32210
32224
|
enableForest: v4.z.boolean().default(false),
|
|
32211
32225
|
enableUseTypeAnnotations: v4.z.boolean().default(false),
|
|
@@ -32258,6 +32272,7 @@ const EnvironmentConfigSchema = v4.z.object({
|
|
|
32258
32272
|
validateNoVoidUseMemo: v4.z.boolean().default(true),
|
|
32259
32273
|
validateNoDynamicallyCreatedComponentsOrHooks: v4.z.boolean().default(false),
|
|
32260
32274
|
enableAllowSetStateFromRefsInEffects: v4.z.boolean().default(true),
|
|
32275
|
+
enableVerboseNoSetStateInEffect: v4.z.boolean().default(false),
|
|
32261
32276
|
enableInferEventHandlers: v4.z.boolean().default(false),
|
|
32262
32277
|
});
|
|
32263
32278
|
class Environment {
|
|
@@ -33139,12 +33154,6 @@ function findDisjointMutableValues(fn) {
|
|
|
33139
33154
|
for (const operand of eachInstructionOperand(instr)) {
|
|
33140
33155
|
if (isMutable(instr, operand) &&
|
|
33141
33156
|
operand.identifier.mutableRange.start > 0) {
|
|
33142
|
-
if (instr.value.kind === 'FunctionExpression' ||
|
|
33143
|
-
instr.value.kind === 'ObjectMethod') {
|
|
33144
|
-
if (operand.identifier.type.kind === 'Primitive') {
|
|
33145
|
-
continue;
|
|
33146
|
-
}
|
|
33147
|
-
}
|
|
33148
33157
|
operands.push(operand.identifier);
|
|
33149
33158
|
}
|
|
33150
33159
|
}
|
|
@@ -51239,21 +51248,48 @@ function validateNoSetStateInEffects(fn, env) {
|
|
|
51239
51248
|
if (arg !== undefined && arg.kind === 'Identifier') {
|
|
51240
51249
|
const setState = setStateFunctions.get(arg.identifier.id);
|
|
51241
51250
|
if (setState !== undefined) {
|
|
51242
|
-
|
|
51243
|
-
|
|
51244
|
-
|
|
51245
|
-
|
|
51246
|
-
'
|
|
51247
|
-
'
|
|
51248
|
-
|
|
51249
|
-
|
|
51250
|
-
|
|
51251
|
-
|
|
51252
|
-
|
|
51253
|
-
|
|
51254
|
-
|
|
51255
|
-
|
|
51256
|
-
|
|
51251
|
+
const enableVerbose = env.config.enableVerboseNoSetStateInEffect;
|
|
51252
|
+
if (enableVerbose) {
|
|
51253
|
+
errors.pushDiagnostic(CompilerDiagnostic.create({
|
|
51254
|
+
category: ErrorCategory.EffectSetState,
|
|
51255
|
+
reason: 'Calling setState synchronously within an effect can trigger cascading renders',
|
|
51256
|
+
description: 'Effects are intended to synchronize state between React and external systems. ' +
|
|
51257
|
+
'Calling setState synchronously causes cascading renders that hurt performance.\n\n' +
|
|
51258
|
+
'This pattern may indicate one of several issues:\n\n' +
|
|
51259
|
+
'**1. Non-local derived data**: If the value being set could be computed from props/state ' +
|
|
51260
|
+
'but requires data from a parent component, consider restructuring state ownership so the ' +
|
|
51261
|
+
'derivation can happen during render in the component that owns the relevant state.\n\n' +
|
|
51262
|
+
"**2. Derived event pattern**: If you're detecting when a prop changes (e.g., `isPlaying` " +
|
|
51263
|
+
'transitioning from false to true), this often indicates the parent should provide an event ' +
|
|
51264
|
+
'callback (like `onPlay`) instead of just the current state. Request access to the original event.\n\n' +
|
|
51265
|
+
"**3. Force update / external sync**: If you're forcing a re-render to sync with an external " +
|
|
51266
|
+
'data source (mutable values outside React), use `useSyncExternalStore` to properly subscribe ' +
|
|
51267
|
+
'to external state changes.\n\n' +
|
|
51268
|
+
'See: https://react.dev/learn/you-might-not-need-an-effect',
|
|
51269
|
+
suggestions: null,
|
|
51270
|
+
}).withDetails({
|
|
51271
|
+
kind: 'error',
|
|
51272
|
+
loc: setState.loc,
|
|
51273
|
+
message: 'Avoid calling setState() directly within an effect',
|
|
51274
|
+
}));
|
|
51275
|
+
}
|
|
51276
|
+
else {
|
|
51277
|
+
errors.pushDiagnostic(CompilerDiagnostic.create({
|
|
51278
|
+
category: ErrorCategory.EffectSetState,
|
|
51279
|
+
reason: 'Calling setState synchronously within an effect can trigger cascading renders',
|
|
51280
|
+
description: 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' +
|
|
51281
|
+
'In general, the body of an effect should do one or both of the following:\n' +
|
|
51282
|
+
'* Update external systems with the latest state from React.\n' +
|
|
51283
|
+
'* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' +
|
|
51284
|
+
'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' +
|
|
51285
|
+
'(https://react.dev/learn/you-might-not-need-an-effect)',
|
|
51286
|
+
suggestions: null,
|
|
51287
|
+
}).withDetails({
|
|
51288
|
+
kind: 'error',
|
|
51289
|
+
loc: setState.loc,
|
|
51290
|
+
message: 'Avoid calling setState() directly within an effect',
|
|
51291
|
+
}));
|
|
51292
|
+
}
|
|
51257
51293
|
}
|
|
51258
51294
|
}
|
|
51259
51295
|
}
|
|
@@ -53483,6 +53519,7 @@ function isKnownEventHandler(_tag, prop) {
|
|
|
53483
53519
|
}
|
|
53484
53520
|
|
|
53485
53521
|
function validateExhaustiveDependencies(fn) {
|
|
53522
|
+
const env = fn.env;
|
|
53486
53523
|
const reactive = collectReactiveIdentifiersHIR(fn);
|
|
53487
53524
|
const temporaries = new Map();
|
|
53488
53525
|
for (const param of fn.params) {
|
|
@@ -53507,215 +53544,259 @@ function validateExhaustiveDependencies(fn) {
|
|
|
53507
53544
|
locals.clear();
|
|
53508
53545
|
}
|
|
53509
53546
|
function onFinishMemoize(value, dependencies, locals) {
|
|
53510
|
-
var
|
|
53547
|
+
var _a;
|
|
53511
53548
|
CompilerError.simpleInvariant(startMemo != null && startMemo.manualMemoId === value.manualMemoId, {
|
|
53512
53549
|
reason: 'Found FinishMemoize without corresponding StartMemoize',
|
|
53513
53550
|
loc: value.loc,
|
|
53514
53551
|
});
|
|
53515
|
-
|
|
53516
|
-
|
|
53517
|
-
|
|
53518
|
-
|
|
53519
|
-
if (
|
|
53520
|
-
|
|
53521
|
-
}
|
|
53522
|
-
|
|
53523
|
-
|
|
53524
|
-
|
|
53525
|
-
|
|
53526
|
-
|
|
53527
|
-
|
|
53528
|
-
|
|
53529
|
-
|
|
53530
|
-
|
|
53531
|
-
|
|
53532
|
-
|
|
53533
|
-
|
|
53534
|
-
|
|
53552
|
+
if (env.config.validateExhaustiveMemoizationDependencies) {
|
|
53553
|
+
visitCandidateDependency(value.decl, temporaries, dependencies, locals);
|
|
53554
|
+
const inferred = Array.from(dependencies);
|
|
53555
|
+
const diagnostic = validateDependencies(inferred, (_a = startMemo.deps) !== null && _a !== void 0 ? _a : [], reactive, startMemo.depsLoc, ErrorCategory.MemoDependencies);
|
|
53556
|
+
if (diagnostic != null) {
|
|
53557
|
+
error.pushDiagnostic(diagnostic);
|
|
53558
|
+
}
|
|
53559
|
+
}
|
|
53560
|
+
dependencies.clear();
|
|
53561
|
+
locals.clear();
|
|
53562
|
+
startMemo = null;
|
|
53563
|
+
}
|
|
53564
|
+
collectDependencies(fn, temporaries, {
|
|
53565
|
+
onStartMemoize,
|
|
53566
|
+
onFinishMemoize,
|
|
53567
|
+
onEffect: (inferred, manual, manualMemoLoc) => {
|
|
53568
|
+
if (env.config.validateExhaustiveEffectDependencies === false) {
|
|
53569
|
+
return;
|
|
53570
|
+
}
|
|
53571
|
+
const manualDeps = [];
|
|
53572
|
+
for (const dep of manual) {
|
|
53573
|
+
if (dep.kind === 'Local') {
|
|
53574
|
+
manualDeps.push({
|
|
53575
|
+
root: {
|
|
53576
|
+
kind: 'NamedLocal',
|
|
53577
|
+
constant: false,
|
|
53578
|
+
value: {
|
|
53579
|
+
effect: Effect.Read,
|
|
53580
|
+
identifier: dep.identifier,
|
|
53581
|
+
kind: 'Identifier',
|
|
53582
|
+
loc: dep.loc,
|
|
53583
|
+
reactive: reactive.has(dep.identifier.id),
|
|
53584
|
+
},
|
|
53585
|
+
},
|
|
53586
|
+
path: dep.path,
|
|
53587
|
+
loc: dep.loc,
|
|
53588
|
+
});
|
|
53535
53589
|
}
|
|
53536
|
-
|
|
53537
|
-
|
|
53538
|
-
|
|
53539
|
-
|
|
53540
|
-
|
|
53541
|
-
|
|
53542
|
-
|
|
53543
|
-
|
|
53544
|
-
|
|
53545
|
-
return String(aProperty.property).localeCompare(String(bProperty.property));
|
|
53546
|
-
}
|
|
53590
|
+
else {
|
|
53591
|
+
manualDeps.push({
|
|
53592
|
+
root: {
|
|
53593
|
+
kind: 'Global',
|
|
53594
|
+
identifierName: dep.binding.name,
|
|
53595
|
+
},
|
|
53596
|
+
path: [],
|
|
53597
|
+
loc: GeneratedSource,
|
|
53598
|
+
});
|
|
53547
53599
|
}
|
|
53548
|
-
return 0;
|
|
53549
53600
|
}
|
|
53550
|
-
|
|
53551
|
-
|
|
53552
|
-
|
|
53553
|
-
if (aName != null && bName != null) {
|
|
53554
|
-
return aName.localeCompare(bName);
|
|
53555
|
-
}
|
|
53556
|
-
return 0;
|
|
53601
|
+
const diagnostic = validateDependencies(Array.from(inferred), manualDeps, reactive, manualMemoLoc, ErrorCategory.EffectExhaustiveDependencies);
|
|
53602
|
+
if (diagnostic != null) {
|
|
53603
|
+
error.pushDiagnostic(diagnostic);
|
|
53557
53604
|
}
|
|
53558
|
-
}
|
|
53559
|
-
|
|
53560
|
-
|
|
53561
|
-
|
|
53562
|
-
|
|
53563
|
-
|
|
53564
|
-
|
|
53565
|
-
|
|
53605
|
+
},
|
|
53606
|
+
}, false);
|
|
53607
|
+
return error.asResult();
|
|
53608
|
+
}
|
|
53609
|
+
function validateDependencies(inferred, manualDependencies, reactive, manualMemoLoc, category) {
|
|
53610
|
+
var _a, _b, _c, _d;
|
|
53611
|
+
inferred.sort((a, b) => {
|
|
53612
|
+
var _a, _b;
|
|
53613
|
+
if (a.kind === 'Global' && b.kind == 'Global') {
|
|
53614
|
+
return a.binding.name.localeCompare(b.binding.name);
|
|
53615
|
+
}
|
|
53616
|
+
else if (a.kind == 'Local' && b.kind == 'Local') {
|
|
53617
|
+
CompilerError.simpleInvariant(a.identifier.name != null &&
|
|
53618
|
+
a.identifier.name.kind === 'named' &&
|
|
53619
|
+
b.identifier.name != null &&
|
|
53620
|
+
b.identifier.name.kind === 'named', {
|
|
53621
|
+
reason: 'Expected dependencies to be named variables',
|
|
53622
|
+
loc: a.loc,
|
|
53566
53623
|
});
|
|
53567
|
-
|
|
53568
|
-
|
|
53569
|
-
|
|
53570
|
-
|
|
53571
|
-
|
|
53572
|
-
|
|
53573
|
-
|
|
53574
|
-
|
|
53575
|
-
|
|
53576
|
-
|
|
53577
|
-
|
|
53578
|
-
|
|
53579
|
-
|
|
53580
|
-
|
|
53581
|
-
|
|
53624
|
+
if (a.identifier.id !== b.identifier.id) {
|
|
53625
|
+
return a.identifier.name.value.localeCompare(b.identifier.name.value);
|
|
53626
|
+
}
|
|
53627
|
+
if (a.path.length !== b.path.length) {
|
|
53628
|
+
return a.path.length - b.path.length;
|
|
53629
|
+
}
|
|
53630
|
+
for (let i = 0; i < a.path.length; i++) {
|
|
53631
|
+
const aProperty = a.path[i];
|
|
53632
|
+
const bProperty = b.path[i];
|
|
53633
|
+
const aOptional = aProperty.optional ? 0 : 1;
|
|
53634
|
+
const bOptional = bProperty.optional ? 0 : 1;
|
|
53635
|
+
if (aOptional !== bOptional) {
|
|
53636
|
+
return aOptional - bOptional;
|
|
53637
|
+
}
|
|
53638
|
+
else if (aProperty.property !== bProperty.property) {
|
|
53639
|
+
return String(aProperty.property).localeCompare(String(bProperty.property));
|
|
53582
53640
|
}
|
|
53583
|
-
continue;
|
|
53584
53641
|
}
|
|
53585
|
-
|
|
53586
|
-
|
|
53587
|
-
|
|
53588
|
-
|
|
53589
|
-
|
|
53642
|
+
return 0;
|
|
53643
|
+
}
|
|
53644
|
+
else {
|
|
53645
|
+
const aName = a.kind === 'Global' ? a.binding.name : (_a = a.identifier.name) === null || _a === void 0 ? void 0 : _a.value;
|
|
53646
|
+
const bName = b.kind === 'Global' ? b.binding.name : (_b = b.identifier.name) === null || _b === void 0 ? void 0 : _b.value;
|
|
53647
|
+
if (aName != null && bName != null) {
|
|
53648
|
+
return aName.localeCompare(bName);
|
|
53649
|
+
}
|
|
53650
|
+
return 0;
|
|
53651
|
+
}
|
|
53652
|
+
});
|
|
53653
|
+
retainWhere(inferred, (dep, ix) => {
|
|
53654
|
+
const match = inferred.findIndex(prevDep => {
|
|
53655
|
+
return (isEqualTemporary(prevDep, dep) ||
|
|
53656
|
+
(prevDep.kind === 'Local' &&
|
|
53657
|
+
dep.kind === 'Local' &&
|
|
53658
|
+
prevDep.identifier.id === dep.identifier.id &&
|
|
53659
|
+
isSubPath(prevDep.path, dep.path)));
|
|
53660
|
+
});
|
|
53661
|
+
return match === -1 || match >= ix;
|
|
53662
|
+
});
|
|
53663
|
+
const matched = new Set();
|
|
53664
|
+
const missing = [];
|
|
53665
|
+
const extra = [];
|
|
53666
|
+
for (const inferredDependency of inferred) {
|
|
53667
|
+
if (inferredDependency.kind === 'Global') {
|
|
53590
53668
|
for (const manualDependency of manualDependencies) {
|
|
53591
|
-
if (manualDependency.root.kind === '
|
|
53592
|
-
manualDependency.root.
|
|
53593
|
-
inferredDependency.
|
|
53594
|
-
(areEqualPaths(manualDependency.path, inferredDependency.path) ||
|
|
53595
|
-
isSubPathIgnoringOptionals(manualDependency.path, inferredDependency.path))) {
|
|
53596
|
-
hasMatchingManualDependency = true;
|
|
53669
|
+
if (manualDependency.root.kind === 'Global' &&
|
|
53670
|
+
manualDependency.root.identifierName ===
|
|
53671
|
+
inferredDependency.binding.name) {
|
|
53597
53672
|
matched.add(manualDependency);
|
|
53673
|
+
extra.push(manualDependency);
|
|
53598
53674
|
}
|
|
53599
53675
|
}
|
|
53600
|
-
|
|
53601
|
-
isOptionalDependency(inferredDependency, reactive)) {
|
|
53602
|
-
continue;
|
|
53603
|
-
}
|
|
53604
|
-
missing.push(inferredDependency);
|
|
53676
|
+
continue;
|
|
53605
53677
|
}
|
|
53606
|
-
|
|
53607
|
-
|
|
53608
|
-
|
|
53609
|
-
|
|
53610
|
-
|
|
53611
|
-
|
|
53612
|
-
|
|
53613
|
-
|
|
53614
|
-
|
|
53615
|
-
|
|
53616
|
-
|
|
53678
|
+
CompilerError.simpleInvariant(inferredDependency.kind === 'Local', {
|
|
53679
|
+
reason: 'Unexpected function dependency',
|
|
53680
|
+
loc: inferredDependency.loc,
|
|
53681
|
+
});
|
|
53682
|
+
if (isEffectEventFunctionType(inferredDependency.identifier)) {
|
|
53683
|
+
continue;
|
|
53684
|
+
}
|
|
53685
|
+
let hasMatchingManualDependency = false;
|
|
53686
|
+
for (const manualDependency of manualDependencies) {
|
|
53687
|
+
if (manualDependency.root.kind === 'NamedLocal' &&
|
|
53688
|
+
manualDependency.root.value.identifier.id ===
|
|
53689
|
+
inferredDependency.identifier.id &&
|
|
53690
|
+
(areEqualPaths(manualDependency.path, inferredDependency.path) ||
|
|
53691
|
+
isSubPathIgnoringOptionals(manualDependency.path, inferredDependency.path))) {
|
|
53692
|
+
hasMatchingManualDependency = true;
|
|
53693
|
+
matched.add(manualDependency);
|
|
53617
53694
|
}
|
|
53618
|
-
|
|
53619
|
-
|
|
53620
|
-
|
|
53621
|
-
|
|
53622
|
-
|
|
53623
|
-
|
|
53624
|
-
|
|
53625
|
-
|
|
53626
|
-
|
|
53627
|
-
|
|
53628
|
-
|
|
53629
|
-
|
|
53630
|
-
|
|
53631
|
-
|
|
53695
|
+
}
|
|
53696
|
+
if (hasMatchingManualDependency ||
|
|
53697
|
+
isOptionalDependency(inferredDependency, reactive)) {
|
|
53698
|
+
continue;
|
|
53699
|
+
}
|
|
53700
|
+
missing.push(inferredDependency);
|
|
53701
|
+
}
|
|
53702
|
+
for (const dep of manualDependencies) {
|
|
53703
|
+
if (matched.has(dep)) {
|
|
53704
|
+
continue;
|
|
53705
|
+
}
|
|
53706
|
+
if (dep.root.kind === 'NamedLocal' && dep.root.constant) {
|
|
53707
|
+
CompilerError.simpleInvariant(!dep.root.value.reactive && isPrimitiveType(dep.root.value.identifier), {
|
|
53708
|
+
reason: 'Expected constant-folded dependency to be non-reactive',
|
|
53709
|
+
loc: dep.root.value.loc,
|
|
53710
|
+
});
|
|
53711
|
+
continue;
|
|
53712
|
+
}
|
|
53713
|
+
extra.push(dep);
|
|
53714
|
+
}
|
|
53715
|
+
if (missing.length !== 0 || extra.length !== 0) {
|
|
53716
|
+
let suggestion = null;
|
|
53717
|
+
if (manualMemoLoc != null && typeof manualMemoLoc !== 'symbol') {
|
|
53718
|
+
suggestion = {
|
|
53719
|
+
description: 'Update dependencies',
|
|
53720
|
+
range: [manualMemoLoc.start.index, manualMemoLoc.end.index],
|
|
53721
|
+
op: CompilerSuggestionOperation.Replace,
|
|
53722
|
+
text: `[${inferred
|
|
53723
|
+
.filter(dep => dep.kind === 'Local' &&
|
|
53724
|
+
!isOptionalDependency(dep, reactive) &&
|
|
53725
|
+
!isEffectEventFunctionType(dep.identifier))
|
|
53726
|
+
.map(printInferredDependency)
|
|
53727
|
+
.join(', ')}]`,
|
|
53728
|
+
};
|
|
53729
|
+
}
|
|
53730
|
+
const diagnostic = createDiagnostic(category, missing, extra, suggestion);
|
|
53731
|
+
for (const dep of missing) {
|
|
53732
|
+
let reactiveStableValueHint = '';
|
|
53733
|
+
if (isStableType(dep.identifier)) {
|
|
53734
|
+
reactiveStableValueHint =
|
|
53735
|
+
'. Refs, setState functions, and other "stable" values generally do not need to be added ' +
|
|
53736
|
+
'as dependencies, but this variable may change over time to point to different values';
|
|
53632
53737
|
}
|
|
53633
|
-
|
|
53634
|
-
|
|
53635
|
-
|
|
53636
|
-
|
|
53637
|
-
missing.length !== 0
|
|
53638
|
-
? 'Missing dependencies can cause a value to update less often than it should, ' +
|
|
53639
|
-
'resulting in stale UI'
|
|
53640
|
-
: null,
|
|
53641
|
-
extra.length !== 0
|
|
53642
|
-
? 'Extra dependencies can cause a value to update more often than it should, ' +
|
|
53643
|
-
'resulting in performance problems such as excessive renders or effects firing too often'
|
|
53644
|
-
: null,
|
|
53645
|
-
]
|
|
53646
|
-
.filter(Boolean)
|
|
53647
|
-
.join('. '),
|
|
53648
|
-
suggestions: suggestion != null ? [suggestion] : null,
|
|
53738
|
+
diagnostic.withDetails({
|
|
53739
|
+
kind: 'error',
|
|
53740
|
+
message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
|
|
53741
|
+
loc: dep.loc,
|
|
53649
53742
|
});
|
|
53650
|
-
|
|
53651
|
-
|
|
53652
|
-
|
|
53653
|
-
reactiveStableValueHint =
|
|
53654
|
-
'. Refs, setState functions, and other "stable" values generally do not need to be added ' +
|
|
53655
|
-
'as dependencies, but this variable may change over time to point to different values';
|
|
53656
|
-
}
|
|
53743
|
+
}
|
|
53744
|
+
for (const dep of extra) {
|
|
53745
|
+
if (dep.root.kind === 'Global') {
|
|
53657
53746
|
diagnostic.withDetails({
|
|
53658
53747
|
kind: 'error',
|
|
53659
|
-
message: `
|
|
53660
|
-
|
|
53748
|
+
message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\`. ` +
|
|
53749
|
+
'Values declared outside of a component/hook should not be listed as ' +
|
|
53750
|
+
'dependencies as the component will not re-render if they change',
|
|
53751
|
+
loc: (_a = dep.loc) !== null && _a !== void 0 ? _a : manualMemoLoc,
|
|
53661
53752
|
});
|
|
53662
53753
|
}
|
|
53663
|
-
|
|
53664
|
-
|
|
53754
|
+
else {
|
|
53755
|
+
const root = dep.root.value;
|
|
53756
|
+
const matchingInferred = inferred.find((inferredDep) => {
|
|
53757
|
+
return (inferredDep.kind === 'Local' &&
|
|
53758
|
+
inferredDep.identifier.id === root.identifier.id &&
|
|
53759
|
+
isSubPathIgnoringOptionals(inferredDep.path, dep.path));
|
|
53760
|
+
});
|
|
53761
|
+
if (matchingInferred != null &&
|
|
53762
|
+
isEffectEventFunctionType(matchingInferred.identifier)) {
|
|
53763
|
+
diagnostic.withDetails({
|
|
53764
|
+
kind: 'error',
|
|
53765
|
+
message: `Functions returned from \`useEffectEvent\` must not be included in the dependency array. ` +
|
|
53766
|
+
`Remove \`${printManualMemoDependency(dep)}\` from the dependencies.`,
|
|
53767
|
+
loc: (_b = dep.loc) !== null && _b !== void 0 ? _b : manualMemoLoc,
|
|
53768
|
+
});
|
|
53769
|
+
}
|
|
53770
|
+
else if (matchingInferred != null &&
|
|
53771
|
+
!isOptionalDependency(matchingInferred, reactive)) {
|
|
53665
53772
|
diagnostic.withDetails({
|
|
53666
53773
|
kind: 'error',
|
|
53667
|
-
message: `
|
|
53668
|
-
|
|
53669
|
-
|
|
53670
|
-
loc: (_e = (_d = dep.loc) !== null && _d !== void 0 ? _d : startMemo.depsLoc) !== null && _e !== void 0 ? _e : value.loc,
|
|
53774
|
+
message: `Overly precise dependency \`${printManualMemoDependency(dep)}\`, ` +
|
|
53775
|
+
`use \`${printInferredDependency(matchingInferred)}\` instead`,
|
|
53776
|
+
loc: (_c = dep.loc) !== null && _c !== void 0 ? _c : manualMemoLoc,
|
|
53671
53777
|
});
|
|
53672
|
-
error.pushDiagnostic(diagnostic);
|
|
53673
53778
|
}
|
|
53674
53779
|
else {
|
|
53675
|
-
|
|
53676
|
-
|
|
53677
|
-
|
|
53678
|
-
|
|
53679
|
-
isSubPathIgnoringOptionals(inferredDep.path, dep.path));
|
|
53780
|
+
diagnostic.withDetails({
|
|
53781
|
+
kind: 'error',
|
|
53782
|
+
message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\``,
|
|
53783
|
+
loc: (_d = dep.loc) !== null && _d !== void 0 ? _d : manualMemoLoc,
|
|
53680
53784
|
});
|
|
53681
|
-
if (matchingInferred != null &&
|
|
53682
|
-
!isOptionalDependency(matchingInferred, reactive)) {
|
|
53683
|
-
diagnostic.withDetails({
|
|
53684
|
-
kind: 'error',
|
|
53685
|
-
message: `Overly precise dependency \`${printManualMemoDependency(dep)}\`, ` +
|
|
53686
|
-
`use \`${printInferredDependency(matchingInferred)}\` instead`,
|
|
53687
|
-
loc: (_g = (_f = dep.loc) !== null && _f !== void 0 ? _f : startMemo.depsLoc) !== null && _g !== void 0 ? _g : value.loc,
|
|
53688
|
-
});
|
|
53689
|
-
}
|
|
53690
|
-
else {
|
|
53691
|
-
diagnostic.withDetails({
|
|
53692
|
-
kind: 'error',
|
|
53693
|
-
message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\``,
|
|
53694
|
-
loc: (_j = (_h = dep.loc) !== null && _h !== void 0 ? _h : startMemo.depsLoc) !== null && _j !== void 0 ? _j : value.loc,
|
|
53695
|
-
});
|
|
53696
|
-
}
|
|
53697
53785
|
}
|
|
53698
53786
|
}
|
|
53699
|
-
if (suggestion != null) {
|
|
53700
|
-
diagnostic.withDetails({
|
|
53701
|
-
kind: 'hint',
|
|
53702
|
-
message: `Inferred dependencies: \`${suggestion.text}\``,
|
|
53703
|
-
});
|
|
53704
|
-
}
|
|
53705
|
-
error.pushDiagnostic(diagnostic);
|
|
53706
53787
|
}
|
|
53707
|
-
|
|
53708
|
-
|
|
53709
|
-
|
|
53788
|
+
if (suggestion != null) {
|
|
53789
|
+
diagnostic.withDetails({
|
|
53790
|
+
kind: 'hint',
|
|
53791
|
+
message: `Inferred dependencies: \`${suggestion.text}\``,
|
|
53792
|
+
});
|
|
53793
|
+
}
|
|
53794
|
+
return diagnostic;
|
|
53710
53795
|
}
|
|
53711
|
-
|
|
53712
|
-
onStartMemoize,
|
|
53713
|
-
onFinishMemoize,
|
|
53714
|
-
}, false);
|
|
53715
|
-
return error.asResult();
|
|
53796
|
+
return null;
|
|
53716
53797
|
}
|
|
53717
53798
|
function addDependency(dep, dependencies, locals) {
|
|
53718
|
-
if (dep.kind === '
|
|
53799
|
+
if (dep.kind === 'Aggregate') {
|
|
53719
53800
|
for (const x of dep.dependencies) {
|
|
53720
53801
|
addDependency(x, dependencies, locals);
|
|
53721
53802
|
}
|
|
@@ -53734,7 +53815,7 @@ function visitCandidateDependency(place, temporaries, dependencies, locals) {
|
|
|
53734
53815
|
}
|
|
53735
53816
|
}
|
|
53736
53817
|
function collectDependencies(fn, temporaries, callbacks, isFunctionExpression) {
|
|
53737
|
-
var _a;
|
|
53818
|
+
var _a, _b;
|
|
53738
53819
|
const optionals = findOptionalPlaces(fn);
|
|
53739
53820
|
const locals = new Set();
|
|
53740
53821
|
if (isFunctionExpression) {
|
|
@@ -53749,20 +53830,20 @@ function collectDependencies(fn, temporaries, callbacks, isFunctionExpression) {
|
|
|
53749
53830
|
}
|
|
53750
53831
|
for (const block of fn.body.blocks.values()) {
|
|
53751
53832
|
for (const phi of block.phis) {
|
|
53752
|
-
|
|
53833
|
+
const deps = [];
|
|
53753
53834
|
for (const operand of phi.operands.values()) {
|
|
53754
53835
|
const dep = temporaries.get(operand.identifier.id);
|
|
53755
53836
|
if (dep == null) {
|
|
53756
53837
|
continue;
|
|
53757
53838
|
}
|
|
53758
|
-
if (
|
|
53759
|
-
deps
|
|
53839
|
+
if (dep.kind === 'Aggregate') {
|
|
53840
|
+
deps.push(...dep.dependencies);
|
|
53760
53841
|
}
|
|
53761
53842
|
else {
|
|
53762
53843
|
deps.push(dep);
|
|
53763
53844
|
}
|
|
53764
53845
|
}
|
|
53765
|
-
if (deps
|
|
53846
|
+
if (deps.length === 0) {
|
|
53766
53847
|
continue;
|
|
53767
53848
|
}
|
|
53768
53849
|
else if (deps.length === 1) {
|
|
@@ -53770,7 +53851,7 @@ function collectDependencies(fn, temporaries, callbacks, isFunctionExpression) {
|
|
|
53770
53851
|
}
|
|
53771
53852
|
else {
|
|
53772
53853
|
temporaries.set(phi.place.identifier.id, {
|
|
53773
|
-
kind: '
|
|
53854
|
+
kind: 'Aggregate',
|
|
53774
53855
|
dependencies: new Set(deps),
|
|
53775
53856
|
});
|
|
53776
53857
|
}
|
|
@@ -53787,9 +53868,6 @@ function collectDependencies(fn, temporaries, callbacks, isFunctionExpression) {
|
|
|
53787
53868
|
}
|
|
53788
53869
|
case 'LoadContext':
|
|
53789
53870
|
case 'LoadLocal': {
|
|
53790
|
-
if (locals.has(value.place.identifier.id)) {
|
|
53791
|
-
break;
|
|
53792
|
-
}
|
|
53793
53871
|
const temp = temporaries.get(value.place.identifier.id);
|
|
53794
53872
|
if (temp != null) {
|
|
53795
53873
|
if (temp.kind === 'Local') {
|
|
@@ -53799,6 +53877,9 @@ function collectDependencies(fn, temporaries, callbacks, isFunctionExpression) {
|
|
|
53799
53877
|
else {
|
|
53800
53878
|
temporaries.set(lvalue.identifier.id, temp);
|
|
53801
53879
|
}
|
|
53880
|
+
if (locals.has(value.place.identifier.id)) {
|
|
53881
|
+
locals.add(lvalue.identifier.id);
|
|
53882
|
+
}
|
|
53802
53883
|
}
|
|
53803
53884
|
break;
|
|
53804
53885
|
}
|
|
@@ -53927,9 +54008,41 @@ function collectDependencies(fn, temporaries, callbacks, isFunctionExpression) {
|
|
|
53927
54008
|
}
|
|
53928
54009
|
break;
|
|
53929
54010
|
}
|
|
54011
|
+
case 'ArrayExpression': {
|
|
54012
|
+
const arrayDeps = new Set();
|
|
54013
|
+
for (const item of value.elements) {
|
|
54014
|
+
if (item.kind === 'Hole') {
|
|
54015
|
+
continue;
|
|
54016
|
+
}
|
|
54017
|
+
const place = item.kind === 'Identifier' ? item : item.place;
|
|
54018
|
+
visitCandidateDependency(place, temporaries, arrayDeps, new Set());
|
|
54019
|
+
visit(place);
|
|
54020
|
+
}
|
|
54021
|
+
temporaries.set(lvalue.identifier.id, {
|
|
54022
|
+
kind: 'Aggregate',
|
|
54023
|
+
dependencies: arrayDeps,
|
|
54024
|
+
loc: value.loc,
|
|
54025
|
+
});
|
|
54026
|
+
break;
|
|
54027
|
+
}
|
|
54028
|
+
case 'CallExpression':
|
|
53930
54029
|
case 'MethodCall': {
|
|
54030
|
+
const receiver = value.kind === 'CallExpression' ? value.callee : value.property;
|
|
54031
|
+
const onEffect = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onEffect;
|
|
54032
|
+
if (onEffect != null && isEffectHook(receiver.identifier)) {
|
|
54033
|
+
const [fn, deps] = value.args;
|
|
54034
|
+
if ((fn === null || fn === void 0 ? void 0 : fn.kind) === 'Identifier' && (deps === null || deps === void 0 ? void 0 : deps.kind) === 'Identifier') {
|
|
54035
|
+
const fnDeps = temporaries.get(fn.identifier.id);
|
|
54036
|
+
const manualDeps = temporaries.get(deps.identifier.id);
|
|
54037
|
+
if ((fnDeps === null || fnDeps === void 0 ? void 0 : fnDeps.kind) === 'Aggregate' &&
|
|
54038
|
+
(manualDeps === null || manualDeps === void 0 ? void 0 : manualDeps.kind) === 'Aggregate') {
|
|
54039
|
+
onEffect(fnDeps.dependencies, manualDeps.dependencies, (_b = manualDeps.loc) !== null && _b !== void 0 ? _b : null);
|
|
54040
|
+
}
|
|
54041
|
+
}
|
|
54042
|
+
}
|
|
53931
54043
|
for (const operand of eachInstructionValueOperand(value)) {
|
|
53932
|
-
if (
|
|
54044
|
+
if (value.kind === 'MethodCall' &&
|
|
54045
|
+
operand.identifier.id === value.property.identifier.id) {
|
|
53933
54046
|
continue;
|
|
53934
54047
|
}
|
|
53935
54048
|
visit(operand);
|
|
@@ -53953,7 +54066,7 @@ function collectDependencies(fn, temporaries, callbacks, isFunctionExpression) {
|
|
|
53953
54066
|
visit(operand);
|
|
53954
54067
|
}
|
|
53955
54068
|
}
|
|
53956
|
-
return { kind: '
|
|
54069
|
+
return { kind: 'Aggregate', dependencies };
|
|
53957
54070
|
}
|
|
53958
54071
|
function printInferredDependency(dep) {
|
|
53959
54072
|
switch (dep.kind) {
|
|
@@ -53986,7 +54099,7 @@ function printManualMemoDependency(dep) {
|
|
|
53986
54099
|
}
|
|
53987
54100
|
function isEqualTemporary(a, b) {
|
|
53988
54101
|
switch (a.kind) {
|
|
53989
|
-
case '
|
|
54102
|
+
case 'Aggregate': {
|
|
53990
54103
|
return false;
|
|
53991
54104
|
}
|
|
53992
54105
|
case 'Global': {
|
|
@@ -54098,6 +54211,44 @@ function isOptionalDependency(inferredDependency, reactive) {
|
|
|
54098
54211
|
(isStableType(inferredDependency.identifier) ||
|
|
54099
54212
|
isPrimitiveType(inferredDependency.identifier)));
|
|
54100
54213
|
}
|
|
54214
|
+
function createDiagnostic(category, missing, extra, suggestion) {
|
|
54215
|
+
let reason;
|
|
54216
|
+
let description;
|
|
54217
|
+
function joinMissingExtraDetail(missingString, extraString, joinStr) {
|
|
54218
|
+
return [
|
|
54219
|
+
missing.length !== 0 ? missingString : null,
|
|
54220
|
+
extra.length !== 0 ? extraString : null,
|
|
54221
|
+
]
|
|
54222
|
+
.filter(Boolean)
|
|
54223
|
+
.join(joinStr);
|
|
54224
|
+
}
|
|
54225
|
+
switch (category) {
|
|
54226
|
+
case ErrorCategory.MemoDependencies: {
|
|
54227
|
+
reason = `Found ${joinMissingExtraDetail('missing', 'extra', '/')} memoization dependencies`;
|
|
54228
|
+
description = joinMissingExtraDetail('Missing dependencies can cause a value to update less often than it should, resulting in stale UI', 'Extra dependencies can cause a value to update more often than it should, resulting in performance' +
|
|
54229
|
+
' problems such as excessive renders or effects firing too often', '. ');
|
|
54230
|
+
break;
|
|
54231
|
+
}
|
|
54232
|
+
case ErrorCategory.EffectExhaustiveDependencies: {
|
|
54233
|
+
reason = `Found ${joinMissingExtraDetail('missing', 'extra', '/')} effect dependencies`;
|
|
54234
|
+
description = joinMissingExtraDetail('Missing dependencies can cause an effect to fire less often than it should', 'Extra dependencies can cause an effect to fire more often than it should, resulting' +
|
|
54235
|
+
' in performance problems such as excessive renders and side effects', '. ');
|
|
54236
|
+
break;
|
|
54237
|
+
}
|
|
54238
|
+
default: {
|
|
54239
|
+
CompilerError.simpleInvariant(false, {
|
|
54240
|
+
reason: `Unexpected error category: ${category}`,
|
|
54241
|
+
loc: GeneratedSource,
|
|
54242
|
+
});
|
|
54243
|
+
}
|
|
54244
|
+
}
|
|
54245
|
+
return CompilerDiagnostic.create({
|
|
54246
|
+
category,
|
|
54247
|
+
reason,
|
|
54248
|
+
description,
|
|
54249
|
+
suggestions: suggestion != null ? [suggestion] : null,
|
|
54250
|
+
});
|
|
54251
|
+
}
|
|
54101
54252
|
|
|
54102
54253
|
function run(func, config, fnType, mode, programContext, logger, filename, code) {
|
|
54103
54254
|
var _a, _b;
|
|
@@ -54225,7 +54376,8 @@ function runWithEnvironment(func, env) {
|
|
|
54225
54376
|
inferReactivePlaces(hir);
|
|
54226
54377
|
log({ kind: 'hir', name: 'InferReactivePlaces', value: hir });
|
|
54227
54378
|
if (env.enableValidations) {
|
|
54228
|
-
if (env.config.validateExhaustiveMemoizationDependencies
|
|
54379
|
+
if (env.config.validateExhaustiveMemoizationDependencies ||
|
|
54380
|
+
env.config.validateExhaustiveEffectDependencies) {
|
|
54229
54381
|
validateExhaustiveDependencies(hir).unwrap();
|
|
54230
54382
|
}
|
|
54231
54383
|
}
|