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.
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
53546
|
-
|
|
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
|
|
53601
|
+
let suggestion = null;
|
|
53557
53602
|
if (startMemo.depsLoc != null && typeof startMemo.depsLoc !== 'symbol') {
|
|
53558
|
-
|
|
53559
|
-
|
|
53560
|
-
|
|
53561
|
-
|
|
53562
|
-
|
|
53563
|
-
|
|
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
|
-
|
|
53568
|
-
|
|
53569
|
-
|
|
53570
|
-
|
|
53571
|
-
|
|
53572
|
-
'
|
|
53573
|
-
|
|
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
|
-
|
|
53576
|
-
|
|
53577
|
-
|
|
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: `
|
|
53584
|
-
|
|
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
|
-
|
|
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: '
|
|
53598
|
-
message: `
|
|
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.
|
|
54120
|
-
|
|
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,
|
|
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.
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
53373
|
-
|
|
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
|
|
53428
|
+
let suggestion = null;
|
|
53384
53429
|
if (startMemo.depsLoc != null && typeof startMemo.depsLoc !== 'symbol') {
|
|
53385
|
-
|
|
53386
|
-
|
|
53387
|
-
|
|
53388
|
-
|
|
53389
|
-
|
|
53390
|
-
|
|
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
|
-
|
|
53395
|
-
|
|
53396
|
-
|
|
53397
|
-
|
|
53398
|
-
|
|
53399
|
-
'
|
|
53400
|
-
|
|
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
|
-
|
|
53403
|
-
|
|
53404
|
-
|
|
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: `
|
|
53411
|
-
|
|
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
|
-
|
|
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: '
|
|
53425
|
-
message: `
|
|
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.
|
|
53947
|
-
|
|
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,
|
|
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-
|
|
4
|
+
"version": "7.1.0-canary-7dc903cd-20251203",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/facebook/react.git",
|