Compare commits

..

27 Commits

Author SHA1 Message Date
Jack Pope
6da57774f0 Support suggestions for effect deps 2025-12-05 18:07:10 -05:00
Jack Pope
e1872717ef Don't pass env to validator 2025-12-05 10:36:57 -05:00
Jack Pope
2ce8e3a452 Handle useEffectEvent - should never be included in effect deps 2025-12-05 10:33:47 -05:00
Jack Pope
0a93bf88e0 Add new ErrorCategory.EffectExhaustiveDependencies and update reason/description for effect dep errors 2025-12-04 15:29:28 -05:00
Jack Pope
5c7e374d73 Add config option validateExhaustiveEffectDependencies 2025-12-04 14:36:16 -05:00
Joe Savona
a6e489bba3 [compiler][poc] Reuse ValidateExhaustiveDeps for effect dep validation
Alternative approach to #35282 for validating effect deps in the compiler that builds on the machinery in ValidateExhaustiveDependencies. Key changes to that pass:

* Refactor to track the dependencies of array expressions as temporaries so we can look them up later if they appear as effect deps.
* Instead of not storing temporaries for LoadLocals of locally created variables, we store the temporary but also propagate the local-ness through. This allows us to record deps at the top level, necessary for effect deps. Previously the pass was only ever concerned with tracking deps within function expressions.
* Refactor the bulk of the dependency-checking logic from `onFinishMemoize()` into a standalone helper to use it for the new `onEffect()` helper as well.

A few important remaining todos:
* Add a new ErrorCategory for effect deps, use it for errors on effects
* Put the effect dep validation behind a feature flag
* Adjust the error reason for effect errors

Testing it locally on fixtures this looks ballpark right but i didn't do an exhaustive test.
2025-12-04 00:12:35 -08:00
Sebastian "Sebbie" Silbermann
0526c799d4 Update changelog with latest releases (#35279) 2025-12-03 18:13:43 +01:00
Sebastian Markbåge
7dc903cd29 Patch FlightReplyServer with fixes from ReactFlightClient (#35277)
FlightReplyServer are for client->server and ReactFlightClient is for
server->client. They're not 100% symmetrical.

We did a number of refactors to ReactFlightClient in PRs like #29823 and
#33664 to change the structure of the resolution. This PR brings those
changes to synchronize the two approaches. Which addresses deep
resolution of cycles and deferred error handling.

This also fixes a critical security vulnerability.
2025-12-03 10:41:19 -05:00
Sebastian "Sebbie" Silbermann
36df5e8b42 [release] Allow building single release channel with processed versions (#35270) 2025-12-02 22:05:10 +01:00
Jack Pope
09f05694a2 [compiler] Extend setState in effect validation to useEffectEvent (#35214)
ValidateNoSetStateInEffects already supports transitive setter
functions. This PR marks any synchonous state setter useEffectEvent
function so we can validate that uEE isn't being used only as
misdirection to avoid the validation within an effect body.

The error points to the call of the effect event.

Example:

```js
export default function MyApp() {
  const [count, setCount] = useState(0)
  const effectEvent = useEffectEvent(() => {
    setCount(10)
  })
  useEffect(() => {
    effectEvent()
  }, [])
  return <div>{count}</div>;
```

```
Found 1 error:

Error: Calling setState synchronously within an effect can trigger cascading renders

Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.

Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).

   5 |   })
   6 |   useEffect(() => {
>  7 |     effectEvent()
     |     ^^^^^^^^^^^ Avoid calling setState() directly within an effect
   8 |   }, [])
   9 |   return <div>{count}</div>;
  10 | }
```
2025-12-01 14:55:42 -05:00
Sebastian "Sebbie" Silbermann
0af4fd80ed [test] Update ESLint e2e tests (#35233) 2025-12-01 19:03:59 +01:00
Sebastian "Sebbie" Silbermann
1721e73e14 [test] Fix Error Proxy in Node.js 21+ (#35227) 2025-11-29 16:52:39 +01:00
Sebastian "Sebbie" Silbermann
6875c3eab4 [test] Only run tests overriding fallback and error states in supported versions (#35234) 2025-11-27 16:15:14 +01:00
Sebastian "Sebbie" Silbermann
74fa1667a7 [DevTools] Move "Back to full tree view" into Activity list item (#35164) 2025-11-26 10:42:18 +01:00
Joseph Savona
627b583650 [compiler][snap] Fix for filter mode with nested files, 'error.' prefix (#35215)
Fixes some issues i ran into w my recent snap changes:
* Correctly match against patterns that contain subdirectories, eg
`fbt/fbt-call`
* When checking if the input pattern has an extension, only prune known
supported extensions. Our convention of `error.<name>` for fixtures that
error makes the rest of the test name look like an extension to
`path.extname()`.

Tested with lots of different patterns including `error.` examples at
the top level and in nested directories, etc.
2025-11-25 15:39:07 -08:00
Joseph Savona
fb18ad3fd3 [compiler] Exhaustive deps: extra tests, improve diagnostic (#35213)
First, this adds some more tests and organizes them into an
`exhaustive-deps/` subdirectory.

Second, the diagnostics are overhauled. For each memo block we now
report a single diagnostic which summarizes the issue, plus individual
errors for each missing/extra dependency. Within the extra deps, we
distinguish whether it's truly extra vs whether its just a more (too)
precise version of an inferred dep. For example, if you depend on
`x.y.z` but the inferred dep was `x.y`. Finally, we print the full
inferred deps at the end as a hint (it's also a suggestion, but this
makes it more clear what would be suggested).
2025-11-25 12:09:09 -08:00
Joseph Savona
ddff35441a [compiler] Enable validateExhaustiveMemoizationDependencies by default (#35201)
Enables `@validateExhaustiveMemoizationDependencies` feature flag by
default, and disables it in select tests that failed due to the change.
Some of our tests intentionally use incorrect memo dependencies in order
to test edge cases.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35201).
* #35213
* __->__ #35201
2025-11-25 12:07:55 -08:00
Joseph Savona
d39a1d6b63 [compiler] Distingush optional/extraneous deps (#35204)
In ValidateExhaustiveDependencies, I previously changed to allow
extraneous dependencies as long as they were non-reactive. Here we make
that more precise, and distinguish between values that are definitely
referenced in the memo function but optional as dependencies vs values
that are not even referenced in the memo function. The latter now error
as extraneous even if they're non-reactive. This also turned up a case
where constant-folded primitives could show up as false positives of the
latter category, so now we track manual deps which quality for constant
folding and don't error on them.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35204).
* #35213
* #35201
* __->__ #35204
2025-11-25 12:06:25 -08:00
Joseph Savona
16e16ec6ff [compiler] Script to enable a feature by default and update tests (#35202)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35202).
* #35201
* __->__ #35202
2025-11-24 12:21:35 -08:00
Joseph Savona
9599e7a787 [compiler] Adjustments to exhaustive deps messages, disable the lint rule (#35192)
Similar to ValidateHookUsage, we implement this check in the compiler
for safety but (for now) continue to rely on the existing rule for
actually reporting errors to users.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35192).
* #35201
* #35202
* __->__ #35192
2025-11-24 12:20:12 -08:00
Joseph Savona
67c1487ffd [compiler] Allow extraneous non-reactive locals (#35190)
The existing exhaustive-deps rule allows omitting non-reactive
dependencies, even if they're not memoized. Conceptually, if a value is
non-reactive then it cannot semantically change. Even if the value is a
new object, that object represents the exact same value and doesn't
necessitate redoing downstream computation. Thus its fine to exclude
nonreactive dependencies, whether they're a stable type or not.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35190).
* #35201
* #35202
* #35192
* __->__ #35190
2025-11-24 12:18:49 -08:00
Joseph Savona
454e01e603 [compiler] Allow manual dependencies to have different optionality than inferred deps (#35186)
Since adding this validation we've already changed our inference to use
knowledge from manual memoization to inform when values are frozen and
which values are non-nullable. To align with that, if the user chooses
to use different optionality btw the deps and the memo block/callback,
that's fine. The key is that eg `x?.y` will invalidate whenever `x.y`
would, so from a memoization correctness perspective its fine. It's not
our job to be a type checker: if a value is potentially nullable, it
should likely use a nullable property access in both places but
TypeScript/Flow can check that.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35186).
* #35201
* #35202
* #35192
* #35190
* __->__ #35186
2025-11-24 12:17:03 -08:00
Joseph Savona
c9a8cf3411 [compiler] Allow nonreactive stable types as extraneous deps (#35185)
When checking ValidateExhaustiveDeps internally, this seems to be the
most common case that it flags. The current exhaustive-deps rule allows
extraneous deps if they are a set of stable types. So here we reuse our
existing isStableType() util in the compiler to allow this case.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35185).
* #35201
* #35202
* #35192
* #35190
* #35186
* __->__ #35185
2025-11-24 12:15:06 -08:00
Joseph Savona
fca172e3f3 [compiler] Ignore ESLint suppressions when ValidateMemoDeps enabled (#35184)
With `ValidateExhaustiveMemoDependencies` we can now check exhaustive
dependencies for useMemo and useCallback within the compiler, without
relying on the separate exhaustive-deps rule. Until now we've bailed out
of any component/hook that suppresses this rule, since the suppression
_might_ affect a memoization value. Compiling code with incorrect memo
deps can change behavior so this wasn't safe. The downside was that a
suppression within a useEffect could prevent memoization, even though
non-exhaustive deps for effects do not cause problems for memoization
specifically.

So here, we change to ignore ESLint suppressions if we have both the
compiler's hooks validation and memo deps validations enabled.

Now we just have to test out the new validation and refine before we can
enable this by default.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35184).
* #35201
* #35202
* #35192
* #35190
* #35186
* #35185
* __->__ #35184
2025-11-24 12:12:49 -08:00
emily8rown
fd524fe02a [DevTools] hotkey to start/stop profiling (#35160)
## Summary

The built-in browser profiler supports starting/stopping with Cmd+E. For
Symmetry this adds the same hotkey for react devtools profiler.

## How did you test this change?
yarn build:\<browser name\> 
yarn run test:\<browser name\>

<img width="483" height="135" alt="Screenshot 2025-11-17 at 14 30 34"
src="https://github.com/user-attachments/assets/426939aa-15da-4c21-87a4-e949e6949482"
/>

firefox:

https://github.com/user-attachments/assets/6f225b90-828f-4e79-a364-59d6bc942f83

edge:

https://github.com/user-attachments/assets/5b2e9242-f0e8-481b-99a2-2dd78099f3ac

chrome:

https://github.com/user-attachments/assets/790aab02-2867-4499-aec1-e32e38c763f9

---------

Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
2025-11-21 11:37:10 -05:00
Joseph Savona
40b4a5bf71 [compiler] ValidateExhaustiveDeps disallows unnecessary non-reactive deps (#34472)
Just to be consistent, we disallow unnecessary deps even if they're
known to be non-reactive.
2025-11-20 19:30:35 -08:00
Joseph Savona
df75af4edc [compiler] Auto-fix for non-exhaustive deps (#34471)
Records more information in DropManualMemoization so that we know the
full span of the manual dependencies array (if present). This allows
ValidateExhaustiveDeps to include a suggestion with the correct deps.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34471).
* #34472
* __->__ #34471
2025-11-20 19:28:08 -08:00
204 changed files with 3944 additions and 908 deletions

View File

@@ -1,3 +1,9 @@
## 19.2.1 (Dec 3, 2025)
### React Server Components
- Bring React Server Component fixes to Server Actions (@sebmarkbage [#35277](https://github.com/facebook/react/pull/35277))
## 19.2.0 (October 1st, 2025)
Below is a list of all new features, APIs, and bug fixes.
@@ -71,6 +77,12 @@ Read the [React 19.2 release post](https://react.dev/blog/2025/10/01/react-19-2)
- [createContainer](https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L255-L261) and [createHydrationContainer](https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L305-L312) had their parameter order adjusted after `on*` handlers to account for upcoming experimental APIs
## 19.1.2 (Dec 3, 2025)
### React Server Components
- Bring React Server Component fixes to Server Actions (@sebmarkbage [#35277](https://github.com/facebook/react/pull/35277))
## 19.1.1 (July 28, 2025)
### React
@@ -123,6 +135,12 @@ An Owner Stack is a string representing the components that are directly respons
* Exposed `registerServerReference` in client builds to handle server references in different environments. [#32534](https://github.com/facebook/react/pull/32534)
* Added react-server-dom-parcel package which integrates Server Components with the [Parcel bundler](https://parceljs.org/) [#31725](https://github.com/facebook/react/pull/31725), [#32132](https://github.com/facebook/react/pull/32132), [#31799](https://github.com/facebook/react/pull/31799), [#32294](https://github.com/facebook/react/pull/32294), [#31741](https://github.com/facebook/react/pull/31741)
## 19.0.1 (Dec 3, 2025)
### React Server Components
- Bring React Server Component fixes to Server Actions (@sebmarkbage [#35277](https://github.com/facebook/react/pull/35277))
## 19.0.0 (December 5, 2024)
Below is a list of all new features, APIs, deprecations, and breaking changes. Read [React 19 release post](https://react.dev/blog/2024/04/25/react-19) and [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) for more information.

View File

@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(node scripts/enable-feature-flag.js:*)"
],
"deny": [],
"ask": []
}
}

View File

@@ -600,7 +600,9 @@ function printErrorSummary(category: ErrorCategory, message: string): string {
case ErrorCategory.Suppression:
case ErrorCategory.Syntax:
case ErrorCategory.UseMemo:
case ErrorCategory.VoidUseMemo: {
case ErrorCategory.VoidUseMemo:
case ErrorCategory.MemoDependencies:
case ErrorCategory.EffectExhaustiveDependencies: {
heading = 'Error';
break;
}
@@ -658,6 +660,10 @@ export enum ErrorCategory {
* Checks that manual memoization is preserved
*/
PreserveManualMemo = 'PreserveManualMemo',
/**
* Checks for exhaustive useMemo/useCallback dependencies without extraneous values
*/
MemoDependencies = 'MemoDependencies',
/**
* Checks for known incompatible libraries
*/
@@ -678,6 +684,10 @@ export enum ErrorCategory {
* Checks for memoized effect deps
*/
EffectDependencies = 'EffectDependencies',
/**
* Checks for exhaustive and extraneous effect dependencies
*/
EffectExhaustiveDependencies = 'EffectExhaustiveDependencies',
/**
* Checks for no setState in effect bodies
*/
@@ -833,6 +843,16 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
preset: LintRulePreset.Off,
};
}
case ErrorCategory.EffectExhaustiveDependencies: {
return {
category,
severity: ErrorSeverity.Error,
name: 'exhaustive-effect-dependencies',
description:
'Validates that effect dependencies are exhaustive and without extraneous values',
preset: LintRulePreset.Off,
};
}
case ErrorCategory.EffectDerivationsOfState: {
return {
category,
@@ -1055,6 +1075,24 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
preset: LintRulePreset.RecommendedLatest,
};
}
case ErrorCategory.MemoDependencies: {
return {
category,
severity: ErrorSeverity.Error,
name: 'memo-dependencies',
description:
'Validates that useMemo() and useCallback() specify comprehensive dependencies without extraneous values. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
/**
* TODO: the "MemoDependencies" rule largely reimplements the "exhaustive-deps" non-compiler rule,
* allowing the compiler to ensure it does not regress change behavior due to different dependencies.
* We previously relied on the source having ESLint suppressions for any exhaustive-deps violations,
* but it's more reliable to verify it within the compiler.
*
* Long-term we should de-duplicate these implementations.
*/
preset: LintRulePreset.Off,
};
}
case ErrorCategory.IncompatibleLibrary: {
return {
category,

View File

@@ -303,9 +303,14 @@ function runWithEnvironment(
inferReactivePlaces(hir);
log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
if (env.config.validateExhaustiveMemoizationDependencies) {
// NOTE: this relies on reactivity inference running first
validateExhaustiveDependencies(hir).unwrap();
if (env.enableValidations) {
if (
env.config.validateExhaustiveMemoizationDependencies ||
env.config.validateExhaustiveEffectDependencies
) {
// NOTE: this relies on reactivity inference running first
validateExhaustiveDependencies(hir).unwrap();
}
}
rewriteInstructionKindsBasedOnReassignment(hir);

View File

@@ -400,7 +400,15 @@ export function compileProgram(
*/
const suppressions = findProgramSuppressions(
pass.comments,
pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS,
/*
* If the compiler is validating hooks rules and exhaustive memo dependencies, we don't need to check
* for React ESLint suppressions
*/
pass.opts.environment.validateExhaustiveMemoizationDependencies &&
pass.opts.environment.validateHooksUsage
? null
: (pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS),
// Always bail on Flow suppressions
pass.opts.flowSuppressions,
);

View File

@@ -78,7 +78,7 @@ export function filterSuppressionsThatAffectFunction(
export function findProgramSuppressions(
programComments: Array<t.Comment>,
ruleNames: Array<string>,
ruleNames: Array<string> | null,
flowSuppressions: boolean,
): Array<SuppressionRange> {
const suppressionRanges: Array<SuppressionRange> = [];
@@ -89,7 +89,7 @@ export function findProgramSuppressions(
let disableNextLinePattern: RegExp | null = null;
let disablePattern: RegExp | null = null;
let enablePattern: RegExp | null = null;
if (ruleNames.length !== 0) {
if (ruleNames != null && ruleNames.length !== 0) {
const rulePattern = `(${ruleNames.join('|')})`;
disableNextLinePattern = new RegExp(
`eslint-disable-next-line ${rulePattern}`,

View File

@@ -221,7 +221,12 @@ export const EnvironmentConfigSchema = z.object({
/**
* Validate that dependencies supplied to manual memoization calls are exhaustive.
*/
validateExhaustiveMemoizationDependencies: z.boolean().default(false),
validateExhaustiveMemoizationDependencies: z.boolean().default(true),
/**
* Validate that dependencies supplied to effect hooks are exhaustive.
*/
validateExhaustiveEffectDependencies: z.boolean().default(false),
/**
* When this is true, rather than pruning existing manual memoization but ensuring or validating

View File

@@ -803,9 +803,11 @@ export type ManualMemoDependency = {
| {
kind: 'NamedLocal';
value: Place;
constant: boolean;
}
| {kind: 'Global'; identifierName: string};
path: DependencyPath;
loc: SourceLocation;
};
export type StartMemoize = {
@@ -2021,6 +2023,11 @@ export function isUseInsertionEffectHookType(id: Identifier): boolean {
id.type.shapeId === 'BuiltInUseInsertionEffectHook'
);
}
export function isUseEffectEventType(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseEffectEvent'
);
}
export function isUseContextHookType(id: Identifier): boolean {
return (

View File

@@ -65,6 +65,7 @@ export function collectMaybeMemoDependencies(
identifierName: value.binding.name,
},
path: [],
loc: value.loc,
};
}
case 'PropertyLoad': {
@@ -74,6 +75,7 @@ export function collectMaybeMemoDependencies(
root: object.root,
// TODO: determine if the access is optional
path: [...object.path, {property: value.property, optional}],
loc: value.loc,
};
}
break;
@@ -92,8 +94,10 @@ export function collectMaybeMemoDependencies(
root: {
kind: 'NamedLocal',
value: {...value.place},
constant: false,
},
path: [],
loc: value.place.loc,
};
}
break;

View File

@@ -609,6 +609,19 @@ function evaluateInstruction(
constantPropagationImpl(value.loweredFunc.func, constants);
return null;
}
case 'StartMemoize': {
if (value.deps != null) {
for (const dep of value.deps) {
if (dep.root.kind === 'NamedLocal') {
const placeValue = read(constants, dep.root.value);
if (placeValue != null && placeValue.kind === 'Primitive') {
dep.root.constant = true;
}
}
}
}
return null;
}
default: {
// TODO: handle more cases
return null;

View File

@@ -10,6 +10,7 @@ import {
CompilerDiagnostic,
CompilerError,
CompilerSuggestionOperation,
Effect,
SourceLocation,
} from '..';
import {CompilerSuggestion, ErrorCategory} from '../CompilerError';
@@ -18,11 +19,17 @@ import {
BlockId,
DependencyPath,
FinishMemoize,
GeneratedSource,
HIRFunction,
Identifier,
IdentifierId,
InstructionKind,
isEffectEventFunctionType,
isPrimitiveType,
isStableType,
isSubPath,
isSubPathIgnoringOptionals,
isUseRefType,
LoadGlobal,
ManualMemoDependency,
Place,
@@ -36,23 +43,40 @@ import {
} from '../HIR/visitors';
import {Result} from '../Utils/Result';
import {retainWhere} from '../Utils/utils';
import {isEffectHook} from './ValidateMemoizedEffectDependencies';
const DEBUG = false;
/**
* Validates that existing manual memoization had exhaustive dependencies.
* Memoization with missing or extra reactive dependencies is invalid React
* and compilation can change behavior, causing a value to be computed more
* or less times.
* Validates that existing manual memoization is exhaustive and does not
* have extraneous dependencies. The goal of the validation is to ensure
* that auto-memoization will not substantially change the behavior of
* the program:
* - If the manual dependencies were non-exhaustive (missing important deps)
* then auto-memoization will include those dependencies, and cause the
* value to update *more* frequently.
* - If the manual dependencies had extraneous deps, then auto memoization
* will remove them and cause the value to update *less* frequently.
*
* TODOs:
* - Better handling of cases where we infer multiple dependencies related to a single
* variable. Eg if the user has dep `x` and we inferred `x.y, x.z`, the user's dep
* is sufficient.
* - Handle cases where the user deps were not simple identifiers + property chains.
* We try to detect this in ValidateUseMemo but we miss some cases. The problem
* is that invalid forms can be value blocks or function calls that don't get
* removed by DCE, leaving a structure like:
* The implementation compares the manual dependencies against the values
* actually used within the memoization function
* - For each value V referenced in the memo function, either:
* - If the value is non-reactive *and* a known stable type, then the
* value may optionally be specified as an exact dependency.
* - Otherwise, report an error unless there is a manual dependency that will
* invalidate whenever V invalidates. If `x.y.z` is referenced, there must
* be a manual dependency for `x.y.z`, `x.y`, or `x`. Note that we assume
* no interior mutability, ie we assume that any changes to inner paths must
* always cause the other path to change as well.
* - Any dependencies that do not correspond to a value referenced in the memo
* function are considered extraneous and throw an error
*
* ## TODO: Invalid, Complex Deps
*
* Handle cases where the user deps were not simple identifiers + property chains.
* We try to detect this in ValidateUseMemo but we miss some cases. The problem
* is that invalid forms can be value blocks or function calls that don't get
* removed by DCE, leaving a structure like:
*
* StartMemoize
* t0 = <value to memoize>
@@ -65,6 +89,7 @@ const DEBUG = false;
export function validateExhaustiveDependencies(
fn: HIRFunction,
): Result<void, CompilerError> {
const env = fn.env;
const reactive = collectReactiveIdentifiersHIR(fn);
const temporaries: Map<IdentifierId, Temporary> = new Map();
@@ -106,178 +131,18 @@ export function validateExhaustiveDependencies(
loc: value.loc,
},
);
visitCandidateDependency(value.decl, temporaries, dependencies, locals);
const inferred: Array<InferredDependency> = Array.from(dependencies);
// Sort dependencies by name, and path, with shorter/non-optional paths first
inferred.sort((a, b) => {
if (a.kind === 'Global' && b.kind == 'Global') {
return a.binding.name.localeCompare(b.binding.name);
} else if (a.kind == 'Local' && b.kind == 'Local') {
CompilerError.simpleInvariant(
a.identifier.name != null &&
a.identifier.name.kind === 'named' &&
b.identifier.name != null &&
b.identifier.name.kind === 'named',
{
reason: 'Expected dependencies to be named variables',
loc: a.loc,
},
);
if (a.identifier.id !== b.identifier.id) {
return a.identifier.name.value.localeCompare(b.identifier.name.value);
}
if (a.path.length !== b.path.length) {
// if a's path is shorter this returns a negative, sorting a first
return a.path.length - b.path.length;
}
for (let i = 0; i < a.path.length; i++) {
const aProperty = a.path[i];
const bProperty = b.path[i];
const aOptional = aProperty.optional ? 0 : 1;
const bOptional = bProperty.optional ? 0 : 1;
if (aOptional !== bOptional) {
// sort non-optionals first
return aOptional - bOptional;
} else if (aProperty.property !== bProperty.property) {
return String(aProperty.property).localeCompare(
String(bProperty.property),
);
}
}
return 0;
} else {
const aName =
a.kind === 'Global' ? a.binding.name : a.identifier.name?.value;
const bName =
b.kind === 'Global' ? b.binding.name : b.identifier.name?.value;
if (aName != null && bName != null) {
return aName.localeCompare(bName);
}
return 0;
}
});
// remove redundant inferred dependencies
retainWhere(inferred, (dep, ix) => {
const match = inferred.findIndex(prevDep => {
return (
isEqualTemporary(prevDep, dep) ||
(prevDep.kind === 'Local' &&
dep.kind === 'Local' &&
prevDep.identifier.id === dep.identifier.id &&
isSubPath(prevDep.path, dep.path))
);
});
// only retain entries that don't have a prior match
return match === -1 || match >= ix;
});
// Validate that all manual dependencies belong there
if (DEBUG) {
console.log('manual');
console.log(
(startMemo.deps ?? [])
.map(x => ' ' + printManualMemoDependency(x))
.join('\n'),
);
console.log('inferred');
console.log(
inferred.map(x => ' ' + printInferredDependency(x)).join('\n'),
);
}
const manualDependencies = startMemo.deps ?? [];
const matched: Set<ManualMemoDependency> = new Set();
const missing: Array<Extract<InferredDependency, {kind: 'Local'}>> = [];
const extra: Array<ManualMemoDependency> = [];
for (const inferredDependency of inferred) {
if (inferredDependency.kind === 'Global') {
for (const manualDependency of manualDependencies) {
if (
manualDependency.root.kind === 'Global' &&
manualDependency.root.identifierName ===
inferredDependency.binding.name
) {
matched.add(manualDependency);
extra.push(manualDependency);
}
}
continue;
}
CompilerError.simpleInvariant(inferredDependency.kind === 'Local', {
reason: 'Unexpected function dependency',
loc: value.loc,
});
let hasMatchingManualDependency = false;
for (const manualDependency of manualDependencies) {
if (
manualDependency.root.kind === 'NamedLocal' &&
manualDependency.root.value.identifier.id ===
inferredDependency.identifier.id &&
(areEqualPaths(manualDependency.path, inferredDependency.path) ||
isSubPath(manualDependency.path, inferredDependency.path))
) {
hasMatchingManualDependency = true;
matched.add(manualDependency);
}
}
if (!hasMatchingManualDependency) {
missing.push(inferredDependency);
}
}
if (env.config.validateExhaustiveMemoizationDependencies) {
visitCandidateDependency(value.decl, temporaries, dependencies, locals);
const inferred: Array<InferredDependency> = Array.from(dependencies);
for (const dep of startMemo.deps ?? []) {
if (
matched.has(dep) ||
(dep.root.kind === 'NamedLocal' &&
!reactive.has(dep.root.value.identifier.id))
) {
continue;
}
extra.push(dep);
}
if (missing.length !== 0 || extra.length !== 0) {
let suggestions: Array<CompilerSuggestion> | null = null;
if (startMemo.depsLoc != null && typeof startMemo.depsLoc !== 'symbol') {
suggestions = [
{
description: 'Update dependencies',
range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
op: CompilerSuggestionOperation.Replace,
text: `[${inferred.map(printInferredDependency).join(', ')}]`,
},
];
}
if (missing.length !== 0) {
// Error
const diagnostic = CompilerDiagnostic.create({
category: ErrorCategory.PreserveManualMemo,
reason: 'Found non-exhaustive dependencies',
description:
'Missing dependencies can cause a value not to update when those inputs change, ' +
'resulting in stale UI. This memoization cannot be safely rewritten by the compiler.',
suggestions,
});
for (const dep of missing) {
diagnostic.withDetails({
kind: 'error',
message: `Missing dependency \`${printInferredDependency(dep)}\``,
loc: dep.loc,
});
}
error.pushDiagnostic(diagnostic);
} else if (extra.length !== 0) {
const diagnostic = CompilerDiagnostic.create({
category: ErrorCategory.PreserveManualMemo,
reason: 'Found unnecessary memoization dependencies',
description:
'Unnecessary dependencies can cause a value to update more often than necessary, ' +
'which can cause effects to run more than expected. This memoization cannot be safely ' +
'rewritten by the compiler',
});
diagnostic.withDetails({
kind: 'error',
message: `Unnecessary dependencies ${extra.map(dep => `\`${printManualMemoDependency(dep)}\``).join(', ')}`,
loc: value.loc,
});
const diagnostic = validateDependencies(
inferred,
startMemo.deps ?? [],
reactive,
startMemo.depsLoc,
ErrorCategory.MemoDependencies,
);
if (diagnostic != null) {
error.pushDiagnostic(diagnostic);
}
}
@@ -287,19 +152,331 @@ export function validateExhaustiveDependencies(
startMemo = null;
}
collectDependencies(fn, temporaries, {
onStartMemoize,
onFinishMemoize,
});
collectDependencies(
fn,
temporaries,
{
onStartMemoize,
onFinishMemoize,
onEffect: (inferred, manual, manualMemoLoc) => {
if (env.config.validateExhaustiveEffectDependencies === false) {
return;
}
if (DEBUG) {
console.log(Array.from(inferred, printInferredDependency));
console.log(Array.from(manual, printInferredDependency));
}
const manualDeps: Array<ManualMemoDependency> = [];
for (const dep of manual) {
if (dep.kind === 'Local') {
manualDeps.push({
root: {
kind: 'NamedLocal',
constant: false,
value: {
effect: Effect.Read,
identifier: dep.identifier,
kind: 'Identifier',
loc: dep.loc,
reactive: reactive.has(dep.identifier.id),
},
},
path: dep.path,
loc: dep.loc,
});
} else {
manualDeps.push({
root: {
kind: 'Global',
identifierName: dep.binding.name,
},
path: [],
loc: GeneratedSource,
});
}
}
const diagnostic = validateDependencies(
Array.from(inferred),
manualDeps,
reactive,
manualMemoLoc,
ErrorCategory.EffectExhaustiveDependencies,
);
if (diagnostic != null) {
error.pushDiagnostic(diagnostic);
}
},
},
false, // isFunctionExpression
);
return error.asResult();
}
function validateDependencies(
inferred: Array<InferredDependency>,
manualDependencies: Array<ManualMemoDependency>,
reactive: Set<IdentifierId>,
manualMemoLoc: SourceLocation | null,
category:
| ErrorCategory.MemoDependencies
| ErrorCategory.EffectExhaustiveDependencies,
): CompilerDiagnostic | null {
// Sort dependencies by name and path, with shorter/non-optional paths first
inferred.sort((a, b) => {
if (a.kind === 'Global' && b.kind == 'Global') {
return a.binding.name.localeCompare(b.binding.name);
} else if (a.kind == 'Local' && b.kind == 'Local') {
CompilerError.simpleInvariant(
a.identifier.name != null &&
a.identifier.name.kind === 'named' &&
b.identifier.name != null &&
b.identifier.name.kind === 'named',
{
reason: 'Expected dependencies to be named variables',
loc: a.loc,
},
);
if (a.identifier.id !== b.identifier.id) {
return a.identifier.name.value.localeCompare(b.identifier.name.value);
}
if (a.path.length !== b.path.length) {
// if a's path is shorter this returns a negative, sorting a first
return a.path.length - b.path.length;
}
for (let i = 0; i < a.path.length; i++) {
const aProperty = a.path[i];
const bProperty = b.path[i];
const aOptional = aProperty.optional ? 0 : 1;
const bOptional = bProperty.optional ? 0 : 1;
if (aOptional !== bOptional) {
// sort non-optionals first
return aOptional - bOptional;
} else if (aProperty.property !== bProperty.property) {
return String(aProperty.property).localeCompare(
String(bProperty.property),
);
}
}
return 0;
} else {
const aName =
a.kind === 'Global' ? a.binding.name : a.identifier.name?.value;
const bName =
b.kind === 'Global' ? b.binding.name : b.identifier.name?.value;
if (aName != null && bName != null) {
return aName.localeCompare(bName);
}
return 0;
}
});
// remove redundant inferred dependencies
retainWhere(inferred, (dep, ix) => {
const match = inferred.findIndex(prevDep => {
return (
isEqualTemporary(prevDep, dep) ||
(prevDep.kind === 'Local' &&
dep.kind === 'Local' &&
prevDep.identifier.id === dep.identifier.id &&
isSubPath(prevDep.path, dep.path))
);
});
// only retain entries that don't have a prior match
return match === -1 || match >= ix;
});
// Validate that all manual dependencies belong there
if (DEBUG) {
console.log('manual');
console.log(
manualDependencies
.map(x => ' ' + printManualMemoDependency(x))
.join('\n'),
);
console.log('inferred');
console.log(
inferred.map(x => ' ' + printInferredDependency(x)).join('\n'),
);
}
const matched: Set<ManualMemoDependency> = new Set();
const missing: Array<Extract<InferredDependency, {kind: 'Local'}>> = [];
const extra: Array<ManualMemoDependency> = [];
for (const inferredDependency of inferred) {
if (inferredDependency.kind === 'Global') {
for (const manualDependency of manualDependencies) {
if (
manualDependency.root.kind === 'Global' &&
manualDependency.root.identifierName ===
inferredDependency.binding.name
) {
matched.add(manualDependency);
extra.push(manualDependency);
}
}
continue;
}
CompilerError.simpleInvariant(inferredDependency.kind === 'Local', {
reason: 'Unexpected function dependency',
loc: inferredDependency.loc,
});
/**
* Skip effect event functions as they are not valid dependencies
*/
if (isEffectEventFunctionType(inferredDependency.identifier)) {
continue;
}
let hasMatchingManualDependency = false;
for (const manualDependency of manualDependencies) {
if (
manualDependency.root.kind === 'NamedLocal' &&
manualDependency.root.value.identifier.id ===
inferredDependency.identifier.id &&
(areEqualPaths(manualDependency.path, inferredDependency.path) ||
isSubPathIgnoringOptionals(
manualDependency.path,
inferredDependency.path,
))
) {
hasMatchingManualDependency = true;
matched.add(manualDependency);
}
}
if (
hasMatchingManualDependency ||
isOptionalDependency(inferredDependency, reactive)
) {
continue;
}
missing.push(inferredDependency);
}
for (const dep of manualDependencies) {
if (matched.has(dep)) {
continue;
}
if (dep.root.kind === 'NamedLocal' && dep.root.constant) {
CompilerError.simpleInvariant(
!dep.root.value.reactive && isPrimitiveType(dep.root.value.identifier),
{
reason: 'Expected constant-folded dependency to be non-reactive',
loc: dep.root.value.loc,
},
);
/*
* Constant primitives can get constant-folded, which means we won't
* see a LoadLocal for the value within the memo function.
*/
continue;
}
extra.push(dep);
}
if (missing.length !== 0 || extra.length !== 0) {
let suggestion: CompilerSuggestion | null = null;
if (manualMemoLoc != null && typeof manualMemoLoc !== 'symbol') {
suggestion = {
description: 'Update dependencies',
range: [manualMemoLoc.start.index, manualMemoLoc.end.index],
op: CompilerSuggestionOperation.Replace,
text: `[${inferred
.filter(
dep =>
dep.kind === 'Local' &&
!isOptionalDependency(dep, reactive) &&
!isEffectEventFunctionType(dep.identifier),
)
.map(printInferredDependency)
.join(', ')}]`,
};
}
const diagnostic = createDiagnostic(category, missing, extra, suggestion);
for (const dep of missing) {
let reactiveStableValueHint = '';
if (isStableType(dep.identifier)) {
reactiveStableValueHint =
'. 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';
}
diagnostic.withDetails({
kind: 'error',
message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
loc: dep.loc,
});
}
for (const dep of extra) {
if (dep.root.kind === 'Global') {
diagnostic.withDetails({
kind: 'error',
message:
`Unnecessary dependency \`${printManualMemoDependency(dep)}\`. ` +
'Values declared outside of a component/hook should not be listed as ' +
'dependencies as the component will not re-render if they change',
loc: dep.loc ?? manualMemoLoc,
});
} else {
const root = dep.root.value;
const matchingInferred = inferred.find(
(
inferredDep,
): inferredDep is Extract<InferredDependency, {kind: 'Local'}> => {
return (
inferredDep.kind === 'Local' &&
inferredDep.identifier.id === root.identifier.id &&
isSubPathIgnoringOptionals(inferredDep.path, dep.path)
);
},
);
if (
matchingInferred != null &&
isEffectEventFunctionType(matchingInferred.identifier)
) {
diagnostic.withDetails({
kind: 'error',
message:
`Functions returned from \`useEffectEvent\` must not be included in the dependency array. ` +
`Remove \`${printManualMemoDependency(dep)}\` from the dependencies.`,
loc: dep.loc ?? manualMemoLoc,
});
} else if (
matchingInferred != null &&
!isOptionalDependency(matchingInferred, reactive)
) {
diagnostic.withDetails({
kind: 'error',
message:
`Overly precise dependency \`${printManualMemoDependency(dep)}\`, ` +
`use \`${printInferredDependency(matchingInferred)}\` instead`,
loc: dep.loc ?? manualMemoLoc,
});
} else {
/**
* Else this dependency doesn't correspond to anything referenced in the memo function,
* or is an optional dependency so we don't want to suggest adding it
*/
diagnostic.withDetails({
kind: 'error',
message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\``,
loc: dep.loc ?? manualMemoLoc,
});
}
}
}
if (suggestion != null) {
diagnostic.withDetails({
kind: 'hint',
message: `Inferred dependencies: \`${suggestion.text}\``,
});
}
return diagnostic;
}
return null;
}
function addDependency(
dep: Temporary,
dependencies: Set<InferredDependency>,
locals: Set<IdentifierId>,
): void {
if (dep.kind === 'Function') {
if (dep.kind === 'Aggregate') {
for (const x of dep.dependencies) {
addDependency(x, dependencies, locals);
}
@@ -382,38 +559,51 @@ function collectDependencies(
dependencies: Set<InferredDependency>,
locals: Set<IdentifierId>,
) => void;
onEffect: (
inferred: Set<InferredDependency>,
manual: Set<InferredDependency>,
manualMemoLoc: SourceLocation | null,
) => void;
} | null,
): Extract<Temporary, {kind: 'Function'}> {
isFunctionExpression: boolean,
): Extract<Temporary, {kind: 'Aggregate'}> {
const optionals = findOptionalPlaces(fn);
if (DEBUG) {
console.log(prettyFormat(optionals));
}
const locals: Set<IdentifierId> = new Set();
if (isFunctionExpression) {
for (const param of fn.params) {
const place = param.kind === 'Identifier' ? param : param.place;
locals.add(place.identifier.id);
}
}
const dependencies: Set<InferredDependency> = new Set();
function visit(place: Place): void {
visitCandidateDependency(place, temporaries, dependencies, locals);
}
for (const block of fn.body.blocks.values()) {
for (const phi of block.phis) {
let deps: Array<Temporary> | null = null;
const deps: Array<InferredDependency> = [];
for (const operand of phi.operands.values()) {
const dep = temporaries.get(operand.identifier.id);
if (dep == null) {
continue;
}
if (deps == null) {
deps = [dep];
if (dep.kind === 'Aggregate') {
deps.push(...dep.dependencies);
} else {
deps.push(dep);
}
}
if (deps == null) {
if (deps.length === 0) {
continue;
} else if (deps.length === 1) {
temporaries.set(phi.place.identifier.id, deps[0]!);
} else {
temporaries.set(phi.place.identifier.id, {
kind: 'Function',
kind: 'Aggregate',
dependencies: new Set(deps),
});
}
@@ -431,9 +621,6 @@ function collectDependencies(
}
case 'LoadContext':
case 'LoadLocal': {
if (locals.has(value.place.identifier.id)) {
break;
}
const temp = temporaries.get(value.place.identifier.id);
if (temp != null) {
if (temp.kind === 'Local') {
@@ -442,6 +629,9 @@ function collectDependencies(
} else {
temporaries.set(lvalue.identifier.id, temp);
}
if (locals.has(value.place.identifier.id)) {
locals.add(lvalue.identifier.id);
}
}
break;
}
@@ -523,7 +713,11 @@ function collectDependencies(
break;
}
case 'PropertyLoad': {
if (typeof value.property === 'number') {
if (
typeof value.property === 'number' ||
(isUseRefType(value.object.identifier) &&
value.property === 'current')
) {
visit(value.object);
break;
}
@@ -553,6 +747,7 @@ function collectDependencies(
value.loweredFunc.func,
temporaries,
null,
true, // isFunctionExpression
);
temporaries.set(lvalue.identifier.id, functionDeps);
addDependency(functionDeps, dependencies, locals);
@@ -572,10 +767,55 @@ function collectDependencies(
}
break;
}
case 'ArrayExpression': {
const arrayDeps: Set<InferredDependency> = new Set();
for (const item of value.elements) {
if (item.kind === 'Hole') {
continue;
}
const place = item.kind === 'Identifier' ? item : item.place;
// Visit with alternative deps/locals to record manual dependencies
visitCandidateDependency(place, temporaries, arrayDeps, new Set());
// Visit normally to propagate inferred dependencies upward
visit(place);
}
temporaries.set(lvalue.identifier.id, {
kind: 'Aggregate',
dependencies: arrayDeps,
loc: value.loc,
});
break;
}
case 'CallExpression':
case 'MethodCall': {
const receiver =
value.kind === 'CallExpression' ? value.callee : value.property;
const onEffect = callbacks?.onEffect;
if (onEffect != null && isEffectHook(receiver.identifier)) {
const [fn, deps] = value.args;
if (fn?.kind === 'Identifier' && deps?.kind === 'Identifier') {
const fnDeps = temporaries.get(fn.identifier.id);
const manualDeps = temporaries.get(deps.identifier.id);
if (
fnDeps?.kind === 'Aggregate' &&
manualDeps?.kind === 'Aggregate'
) {
onEffect(
fnDeps.dependencies,
manualDeps.dependencies,
manualDeps.loc ?? null,
);
}
}
}
// Ignore the method itself
for (const operand of eachInstructionValueOperand(value)) {
if (operand.identifier.id === value.property.identifier.id) {
if (
value.kind === 'MethodCall' &&
operand.identifier.id === value.property.identifier.id
) {
continue;
}
visit(operand);
@@ -599,7 +839,7 @@ function collectDependencies(
visit(operand);
}
}
return {kind: 'Function', dependencies};
return {kind: 'Aggregate', dependencies};
}
function printInferredDependency(dep: InferredDependency): string {
@@ -637,7 +877,7 @@ function printManualMemoDependency(dep: ManualMemoDependency): string {
function isEqualTemporary(a: Temporary, b: Temporary): boolean {
switch (a.kind) {
case 'Function': {
case 'Aggregate': {
return false;
}
case 'Global': {
@@ -662,7 +902,11 @@ type Temporary =
context: boolean;
loc: SourceLocation;
}
| {kind: 'Function'; dependencies: Set<Temporary>};
| {
kind: 'Aggregate';
dependencies: Set<InferredDependency>;
loc?: SourceLocation;
};
type InferredDependency = Extract<Temporary, {kind: 'Local' | 'Global'}>;
function collectReactiveIdentifiersHIR(fn: HIRFunction): Set<IdentifierId> {
@@ -766,3 +1010,75 @@ export function findOptionalPlaces(
}
return optionals;
}
function isOptionalDependency(
inferredDependency: Extract<InferredDependency, {kind: 'Local'}>,
reactive: Set<IdentifierId>,
): boolean {
return (
!reactive.has(inferredDependency.identifier.id) &&
(isStableType(inferredDependency.identifier) ||
isPrimitiveType(inferredDependency.identifier))
);
}
function createDiagnostic(
category:
| ErrorCategory.MemoDependencies
| ErrorCategory.EffectExhaustiveDependencies,
missing: Array<InferredDependency>,
extra: Array<ManualMemoDependency>,
suggestion: CompilerSuggestion | null,
): CompilerDiagnostic {
let reason: string;
let description: string;
function joinMissingExtraDetail(
missingString: string,
extraString: string,
joinStr: string,
): string {
return [
missing.length !== 0 ? missingString : null,
extra.length !== 0 ? extraString : null,
]
.filter(Boolean)
.join(joinStr);
}
switch (category) {
case ErrorCategory.MemoDependencies: {
reason = `Found ${joinMissingExtraDetail('missing', 'extra', '/')} memoization dependencies`;
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' +
' problems such as excessive renders or effects firing too often',
'. ',
);
break;
}
case ErrorCategory.EffectExhaustiveDependencies: {
reason = `Found ${joinMissingExtraDetail('missing', 'extra', '/')} effect dependencies`;
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' +
' in performance problems such as excessive renders and side effects',
'. ',
);
break;
}
default: {
CompilerError.simpleInvariant(false, {
reason: `Unexpected error category: ${category}`,
loc: GeneratedSource,
});
}
}
return CompilerDiagnostic.create({
category,
reason,
description,
suggestions: suggestion != null ? [suggestion] : null,
});
}

View File

@@ -16,6 +16,7 @@ import {
IdentifierId,
isSetStateType,
isUseEffectHookType,
isUseEffectEventType,
isUseInsertionEffectHookType,
isUseLayoutEffectHookType,
isUseRefType,
@@ -98,7 +99,20 @@ export function validateNoSetStateInEffects(
instr.value.kind === 'MethodCall'
? instr.value.receiver
: instr.value.callee;
if (
if (isUseEffectEventType(callee.identifier)) {
const arg = instr.value.args[0];
if (arg !== undefined && arg.kind === 'Identifier') {
const setState = setStateFunctions.get(arg.identifier.id);
if (setState !== undefined) {
/**
* This effect event function calls setState synchonously,
* treat it as a setState function for transitive tracking
*/
setStateFunctions.set(instr.lvalue.identifier.id, setState);
}
}
} else if (
isUseEffectHookType(callee.identifier) ||
isUseLayoutEffectHookType(callee.identifier) ||
isUseInsertionEffectHookType(callee.identifier)

View File

@@ -242,6 +242,7 @@ function validateInferredDep(
normalizedDep = {
root: maybeNormalizedRoot.root,
path: [...maybeNormalizedRoot.path, ...dep.path],
loc: maybeNormalizedRoot.loc,
};
} else {
CompilerError.invariant(dep.identifier.name?.kind === 'named', {
@@ -267,8 +268,10 @@ function validateInferredDep(
effect: Effect.Read,
reactive: false,
},
constant: false,
},
path: [...dep.path],
loc: GeneratedSource,
};
}
for (const decl of declsWithinMemoBlock) {
@@ -379,8 +382,10 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
root: {
kind: 'NamedLocal',
value: storeTarget,
constant: false,
},
path: [],
loc: storeTarget.loc,
});
}
}
@@ -408,8 +413,10 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
root: {
kind: 'NamedLocal',
value: {...lvalue},
constant: false,
},
path: [],
loc: lvalue.loc,
});
}
}

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
const someGlobal = {value: 0};
@@ -33,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
const someGlobal = { value: 0 };

View File

@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
const someGlobal = {value: 0};

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
// @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
@@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";

View File

@@ -1,4 +1,4 @@
// @validatePreserveExistingMemoizationGuarantees
// @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
function Component(props) {
@@ -30,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
function Component(props) {

View File

@@ -1,3 +1,4 @@
// @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
function Component(props) {

View File

@@ -11,7 +11,7 @@ function Component(props) {
Component = useMemo(() => {
return Component;
});
}, [Component]);
return <Component {...props} />;
}
@@ -36,6 +36,7 @@ function Component(props) {
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
Component = Stringify;
Component;
Component = Component;
$[0] = Component;
} else {

View File

@@ -7,7 +7,7 @@ function Component(props) {
Component = useMemo(() => {
return Component;
});
}, [Component]);
return <Component {...props} />;
}

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @eslintSuppressionRules:["my-app","react-rule"]
// @eslintSuppressionRules:["my-app","react-rule"] @validateExhaustiveMemoizationDependencies:false
/* eslint-disable my-app/react-rule */
function lowercasecomponent() {
@@ -26,7 +26,7 @@ Error: React Compiler has skipped optimizing this component because one or more
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable my-app/react-rule`.
error.bailout-on-suppression-of-custom-rule.ts:3:0
1 | // @eslintSuppressionRules:["my-app","react-rule"]
1 | // @eslintSuppressionRules:["my-app","react-rule"] @validateExhaustiveMemoizationDependencies:false
2 |
> 3 | /* eslint-disable my-app/react-rule */
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression

View File

@@ -1,4 +1,4 @@
// @eslintSuppressionRules:["my-app","react-rule"]
// @eslintSuppressionRules:["my-app","react-rule"] @validateExhaustiveMemoizationDependencies:false
/* eslint-disable my-app/react-rule */
function lowercasecomponent() {

View File

@@ -1,98 +0,0 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {Stringify} from 'shared-runtime';
function Component({x, y, z}) {
const a = useMemo(() => {
return x?.y.z?.a;
}, [x?.y.z?.a.b]);
const b = useMemo(() => {
return x.y.z?.a;
}, [x.y.z.a]);
const c = useMemo(() => {
return x?.y.z.a?.b;
}, [x?.y.z.a?.b.z]);
const d = useMemo(() => {
return x?.y?.[(console.log(y), z?.b)];
}, [x?.y, y, z?.b]);
const e = useMemo(() => {
const e = [];
e.push(x);
return e;
}, [x]);
const f = useMemo(() => {
return [];
}, [x, y.z, z?.y?.a]);
return <Stringify results={[a, b, c, d, e, f]} />;
}
```
## Error
```
Found 4 errors:
Compilation Skipped: Found non-exhaustive dependencies
Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI. This memoization cannot be safely rewritten by the compiler..
error.invalid-exhaustive-deps.ts:7:11
5 | function Component({x, y, z}) {
6 | const a = useMemo(() => {
> 7 | return x?.y.z?.a;
| ^^^^^^^^^ Missing dependency `x?.y.z?.a`
8 | }, [x?.y.z?.a.b]);
9 | const b = useMemo(() => {
10 | return x.y.z?.a;
Compilation Skipped: Found non-exhaustive dependencies
Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI. This memoization cannot be safely rewritten by the compiler..
error.invalid-exhaustive-deps.ts:10:11
8 | }, [x?.y.z?.a.b]);
9 | const b = useMemo(() => {
> 10 | return x.y.z?.a;
| ^^^^^^^^ Missing dependency `x.y.z?.a`
11 | }, [x.y.z.a]);
12 | const c = useMemo(() => {
13 | return x?.y.z.a?.b;
Compilation Skipped: Found non-exhaustive dependencies
Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI. This memoization cannot be safely rewritten by the compiler..
error.invalid-exhaustive-deps.ts:13:11
11 | }, [x.y.z.a]);
12 | const c = useMemo(() => {
> 13 | return x?.y.z.a?.b;
| ^^^^^^^^^^^ Missing dependency `x?.y.z.a?.b`
14 | }, [x?.y.z.a?.b.z]);
15 | const d = useMemo(() => {
16 | return x?.y?.[(console.log(y), z?.b)];
Compilation Skipped: Found unnecessary memoization dependencies
Unnecessary dependencies can cause a value to update more often than necessary, which can cause effects to run more than expected. This memoization cannot be safely rewritten by the compiler.
error.invalid-exhaustive-deps.ts:23:20
21 | return e;
22 | }, [x]);
> 23 | const f = useMemo(() => {
| ^^^^^^^
> 24 | return [];
| ^^^^^^^^^^^^^^
> 25 | }, [x, y.z, z?.y?.a]);
| ^^^^ Unnecessary dependencies `x`, `y.z`, `z?.y?.a`
26 | return <Stringify results={[a, b, c, d, e, f]} />;
27 | }
28 |
```

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies:false
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
'use forget';
@@ -23,25 +24,26 @@ Error: React Compiler has skipped optimizing this component because one or more
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`.
error.invalid-sketchy-code-use-forget.ts:1:0
> 1 | /* eslint-disable react-hooks/rules-of-hooks */
error.invalid-sketchy-code-use-forget.ts:2:0
1 | // @validateExhaustiveMemoizationDependencies:false
> 2 | /* eslint-disable react-hooks/rules-of-hooks */
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
2 | function lowercasecomponent() {
3 | 'use forget';
4 | const x = [];
3 | function lowercasecomponent() {
4 | 'use forget';
5 | const x = [];
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks`.
error.invalid-sketchy-code-use-forget.ts:5:2
3 | 'use forget';
4 | const x = [];
> 5 | // eslint-disable-next-line react-hooks/rules-of-hooks
error.invalid-sketchy-code-use-forget.ts:6:2
4 | 'use forget';
5 | const x = [];
> 6 | // eslint-disable-next-line react-hooks/rules-of-hooks
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
6 | return <div>{x}</div>;
7 | }
8 | /* eslint-enable react-hooks/rules-of-hooks */
7 | return <div>{x}</div>;
8 | }
9 | /* eslint-enable react-hooks/rules-of-hooks */
```

View File

@@ -1,3 +1,4 @@
// @validateExhaustiveMemoizationDependencies:false
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
'use forget';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// Note: Everything below this is sketchy
// Note: Everything below this is sketchy @validateExhaustiveMemoizationDependencies:false
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
'use forget';
@@ -43,7 +43,7 @@ Error: React Compiler has skipped optimizing this component because one or more
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`.
error.invalid-unclosed-eslint-suppression.ts:2:0
1 | // Note: Everything below this is sketchy
1 | // Note: Everything below this is sketchy @validateExhaustiveMemoizationDependencies:false
> 2 | /* eslint-disable react-hooks/rules-of-hooks */
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
3 | function lowercasecomponent() {

View File

@@ -1,4 +1,4 @@
// Note: Everything below this is sketchy
// Note: Everything below this is sketchy @validateExhaustiveMemoizationDependencies:false
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
'use forget';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
// @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
import {useCallback, useRef} from 'react';
function useCustomRef() {

View File

@@ -1,4 +1,4 @@
// @validatePreserveExistingMemoizationGuarantees
// @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
import {useCallback, useRef} from 'react';
function useCustomRef() {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
// @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
import {useCallback, useRef} from 'react';
function useCustomRef() {

View File

@@ -1,4 +1,4 @@
// @validatePreserveExistingMemoizationGuarantees
// @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
import {useCallback, useRef} from 'react';
function useCustomRef() {

View File

@@ -1,39 +0,0 @@
## Input
```javascript
function Component() {
const item = [];
const foo = useCallback(
() => {
item.push(1);
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return <Button foo={foo} />;
}
```
## Error
```
Found 1 error:
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps`.
error.sketchy-code-exhaustive-deps.ts:6:7
4 | () => {
5 | item.push(1);
> 6 | }, // eslint-disable-next-line react-hooks/exhaustive-deps
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
7 | []
8 | );
9 |
```

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies:false
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
const x = [];
@@ -27,12 +28,13 @@ Error: React Compiler has skipped optimizing this component because one or more
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`.
error.sketchy-code-rules-of-hooks.ts:1:0
> 1 | /* eslint-disable react-hooks/rules-of-hooks */
error.sketchy-code-rules-of-hooks.ts:2:0
1 | // @validateExhaustiveMemoizationDependencies:false
> 2 | /* eslint-disable react-hooks/rules-of-hooks */
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
2 | function lowercasecomponent() {
3 | const x = [];
4 | return <div>{x}</div>;
3 | function lowercasecomponent() {
4 | const x = [];
5 | return <div>{x}</div>;
```

View File

@@ -1,3 +1,4 @@
// @validateExhaustiveMemoizationDependencies:false
/* eslint-disable react-hooks/rules-of-hooks */
function lowercasecomponent() {
const x = [];

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useFragment} from 'react-relay';
import LogEvent from 'LogEvent';
import {useCallback, useMemo} from 'react';

View File

@@ -1,4 +1,4 @@
// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useFragment} from 'react-relay';
import LogEvent from 'LogEvent';
import {useCallback, useMemo} from 'react';

View File

@@ -0,0 +1,91 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {ValidateMemoization} from 'shared-runtime';
function Component({x}) {
useEffect(
() => {
console.log(x);
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[
/* intentionally missing deps */
]
);
const memo = useMemo(() => {
return [x];
}, [x]);
return <ValidateMemoization inputs={[x]} output={memo} />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies
import { useMemo } from "react";
import { ValidateMemoization } from "shared-runtime";
function Component(t0) {
const $ = _c(10);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = () => {
console.log(x);
};
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [];
$[2] = t2;
} else {
t2 = $[2];
}
useEffect(t1, t2);
let t3;
if ($[3] !== x) {
t3 = [x];
$[3] = x;
$[4] = t3;
} else {
t3 = $[4];
}
const memo = t3;
let t4;
if ($[5] !== x) {
t4 = [x];
$[5] = x;
$[6] = t4;
} else {
t4 = $[6];
}
let t5;
if ($[7] !== memo || $[8] !== t4) {
t5 = <ValidateMemoization inputs={t4} output={memo} />;
$[7] = memo;
$[8] = t4;
$[9] = t5;
} else {
t5 = $[9];
}
return t5;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,22 @@
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {ValidateMemoization} from 'shared-runtime';
function Component({x}) {
useEffect(
() => {
console.log(x);
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[
/* intentionally missing deps */
]
);
const memo = useMemo(() => {
return [x];
}, [x]);
return <ValidateMemoization inputs={[x]} output={memo} />;
}

View File

@@ -0,0 +1,86 @@
## Input
```javascript
// @validateExhaustiveEffectDependencies
import {useEffect, useEffectEvent} from 'react';
function Component({x, y, z}) {
const effectEvent = useEffectEvent(() => {
log(x);
});
const effectEvent2 = useEffectEvent(z => {
log(y, z);
});
// error - do not include effect event in deps
useEffect(() => {
effectEvent();
}, [effectEvent]);
// error - do not include effect event in deps
useEffect(() => {
effectEvent2(z);
}, [effectEvent2, z]);
// error - do not include effect event captured values in deps
useEffect(() => {
effectEvent2(z);
}, [y, z]);
}
```
## Error
```
Found 3 errors:
Error: Found extra effect dependencies
Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.
error.exhaustive-deps-effect-events.ts:16:6
14 | useEffect(() => {
15 | effectEvent();
> 16 | }, [effectEvent]);
| ^^^^^^^^^^^ Functions returned from `useEffectEvent` must not be included in the dependency array. Remove `effectEvent` from the dependencies.
17 |
18 | // error - do not include effect event in deps
19 | useEffect(() => {
Inferred dependencies: `[]`
Error: Found extra effect dependencies
Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.
error.exhaustive-deps-effect-events.ts:21:6
19 | useEffect(() => {
20 | effectEvent2(z);
> 21 | }, [effectEvent2, z]);
| ^^^^^^^^^^^^ Functions returned from `useEffectEvent` must not be included in the dependency array. Remove `effectEvent2` from the dependencies.
22 |
23 | // error - do not include effect event captured values in deps
24 | useEffect(() => {
Inferred dependencies: `[z]`
Error: Found extra effect dependencies
Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.
error.exhaustive-deps-effect-events.ts:26:6
24 | useEffect(() => {
25 | effectEvent2(z);
> 26 | }, [y, z]);
| ^ Unnecessary dependency `y`
27 | }
28 |
Inferred dependencies: `[z]`
```

View File

@@ -0,0 +1,27 @@
// @validateExhaustiveEffectDependencies
import {useEffect, useEffectEvent} from 'react';
function Component({x, y, z}) {
const effectEvent = useEffectEvent(() => {
log(x);
});
const effectEvent2 = useEffectEvent(z => {
log(y, z);
});
// error - do not include effect event in deps
useEffect(() => {
effectEvent();
}, [effectEvent]);
// error - do not include effect event in deps
useEffect(() => {
effectEvent2(z);
}, [effectEvent2, z]);
// error - do not include effect event captured values in deps
useEffect(() => {
effectEvent2(z);
}, [y, z]);
}

View File

@@ -0,0 +1,40 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies
function Component() {
const ref = useRef(null);
const onChange = useCallback(() => {
return ref.current.value;
}, [ref.current.value]);
return <input ref={ref} onChange={onChange} />;
}
```
## Error
```
Found 1 error:
Error: Found extra memoization dependencies
Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
error.invalid-dep-on-ref-current-value.ts:7:6
5 | const onChange = useCallback(() => {
6 | return ref.current.value;
> 7 | }, [ref.current.value]);
| ^^^^^^^^^^^^^^^^^ Unnecessary dependency `ref.current.value`
8 |
9 | return <input ref={ref} onChange={onChange} />;
10 | }
Inferred dependencies: `[]`
```

View File

@@ -0,0 +1,10 @@
// @validateExhaustiveMemoizationDependencies
function Component() {
const ref = useRef(null);
const onChange = useCallback(() => {
return ref.current.value;
}, [ref.current.value]);
return <input ref={ref} onChange={onChange} />;
}

View File

@@ -0,0 +1,44 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies
import {useState} from 'react';
import {Stringify} from 'shared-runtime';
function Component() {
const [state, setState] = useState(0);
const x = useMemo(() => {
return [state];
// error: `setState` is a stable type, but not actually referenced
}, [state, setState]);
return 'oops';
}
```
## Error
```
Found 1 error:
Error: Found extra memoization dependencies
Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
error.invalid-exhaustive-deps-disallow-unused-stable-types.ts:11:13
9 | return [state];
10 | // error: `setState` is a stable type, but not actually referenced
> 11 | }, [state, setState]);
| ^^^^^^^^ Unnecessary dependency `setState`
12 |
13 | return 'oops';
14 | }
Inferred dependencies: `[state]`
```

View File

@@ -0,0 +1,14 @@
// @validateExhaustiveMemoizationDependencies
import {useState} from 'react';
import {Stringify} from 'shared-runtime';
function Component() {
const [state, setState] = useState(0);
const x = useMemo(() => {
return [state];
// error: `setState` is a stable type, but not actually referenced
}, [state, setState]);
return 'oops';
}

View File

@@ -0,0 +1,162 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {Stringify} from 'shared-runtime';
function Component({x, y, z}) {
const a = useMemo(() => {
return x?.y.z?.a;
// error: too precise
}, [x?.y.z?.a.b]);
const b = useMemo(() => {
return x.y.z?.a;
// ok, not our job to type check nullability
}, [x.y.z.a]);
const c = useMemo(() => {
return x?.y.z.a?.b;
// error: too precise
}, [x?.y.z.a?.b.z]);
const d = useMemo(() => {
return x?.y?.[(console.log(y), z?.b)];
// ok
}, [x?.y, y, z?.b]);
const e = useMemo(() => {
const e = [];
e.push(x);
return e;
// ok
}, [x]);
const f = useMemo(() => {
return [];
// error: unnecessary
}, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
const ref1 = useRef(null);
const ref2 = useRef(null);
const ref = z ? ref1 : ref2;
const cb = useMemo(() => {
return () => {
return ref.current;
};
// error: ref is a stable type but reactive
}, []);
return <Stringify results={[a, b, c, d, e, f, cb]} />;
}
```
## Error
```
Found 4 errors:
Error: Found missing/extra memoization dependencies
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 problems such as excessive renders or effects firing too often.
error.invalid-exhaustive-deps.ts:7:11
5 | function Component({x, y, z}) {
6 | const a = useMemo(() => {
> 7 | return x?.y.z?.a;
| ^^^^^^^^^ Missing dependency `x?.y.z?.a`
8 | // error: too precise
9 | }, [x?.y.z?.a.b]);
10 | const b = useMemo(() => {
error.invalid-exhaustive-deps.ts:9:6
7 | return x?.y.z?.a;
8 | // error: too precise
> 9 | }, [x?.y.z?.a.b]);
| ^^^^^^^^^^^ Overly precise dependency `x?.y.z?.a.b`, use `x?.y.z?.a` instead
10 | const b = useMemo(() => {
11 | return x.y.z?.a;
12 | // ok, not our job to type check nullability
Inferred dependencies: `[x?.y.z?.a]`
Error: Found missing/extra memoization dependencies
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 problems such as excessive renders or effects firing too often.
error.invalid-exhaustive-deps.ts:15:11
13 | }, [x.y.z.a]);
14 | const c = useMemo(() => {
> 15 | return x?.y.z.a?.b;
| ^^^^^^^^^^^ Missing dependency `x?.y.z.a?.b`
16 | // error: too precise
17 | }, [x?.y.z.a?.b.z]);
18 | const d = useMemo(() => {
error.invalid-exhaustive-deps.ts:17:6
15 | return x?.y.z.a?.b;
16 | // error: too precise
> 17 | }, [x?.y.z.a?.b.z]);
| ^^^^^^^^^^^^^ Overly precise dependency `x?.y.z.a?.b.z`, use `x?.y.z.a?.b` instead
18 | const d = useMemo(() => {
19 | return x?.y?.[(console.log(y), z?.b)];
20 | // ok
Inferred dependencies: `[x?.y.z.a?.b]`
Error: Found extra memoization dependencies
Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
error.invalid-exhaustive-deps.ts:31:6
29 | return [];
30 | // error: unnecessary
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
| ^ Unnecessary dependency `x`
32 | const ref1 = useRef(null);
33 | const ref2 = useRef(null);
34 | const ref = z ? ref1 : ref2;
error.invalid-exhaustive-deps.ts:31:9
29 | return [];
30 | // error: unnecessary
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
| ^^^ Unnecessary dependency `y.z`
32 | const ref1 = useRef(null);
33 | const ref2 = useRef(null);
34 | const ref = z ? ref1 : ref2;
error.invalid-exhaustive-deps.ts:31:14
29 | return [];
30 | // error: unnecessary
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
| ^^^^^^^ Unnecessary dependency `z?.y?.a`
32 | const ref1 = useRef(null);
33 | const ref2 = useRef(null);
34 | const ref = z ? ref1 : ref2;
error.invalid-exhaustive-deps.ts:31:23
29 | return [];
30 | // error: unnecessary
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
| ^^^^^^^^^^^^^ Unnecessary dependency `UNUSED_GLOBAL`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change
32 | const ref1 = useRef(null);
33 | const ref2 = useRef(null);
34 | const ref = z ? ref1 : ref2;
Inferred dependencies: `[]`
Error: Found missing memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
error.invalid-exhaustive-deps.ts:37:13
35 | const cb = useMemo(() => {
36 | return () => {
> 37 | return ref.current;
| ^^^ Missing dependency `ref`. 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
38 | };
39 | // error: ref is a stable type but reactive
40 | }, []);
Inferred dependencies: `[ref]`
```

View File

@@ -5,23 +5,38 @@ import {Stringify} from 'shared-runtime';
function Component({x, y, z}) {
const a = useMemo(() => {
return x?.y.z?.a;
// error: too precise
}, [x?.y.z?.a.b]);
const b = useMemo(() => {
return x.y.z?.a;
// ok, not our job to type check nullability
}, [x.y.z.a]);
const c = useMemo(() => {
return x?.y.z.a?.b;
// error: too precise
}, [x?.y.z.a?.b.z]);
const d = useMemo(() => {
return x?.y?.[(console.log(y), z?.b)];
// ok
}, [x?.y, y, z?.b]);
const e = useMemo(() => {
const e = [];
e.push(x);
return e;
// ok
}, [x]);
const f = useMemo(() => {
return [];
}, [x, y.z, z?.y?.a]);
return <Stringify results={[a, b, c, d, e, f]} />;
// error: unnecessary
}, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
const ref1 = useRef(null);
const ref2 = useRef(null);
const ref = z ? ref1 : ref2;
const cb = useMemo(() => {
return () => {
return ref.current;
};
// error: ref is a stable type but reactive
}, []);
return <Stringify results={[a, b, c, d, e, f, cb]} />;
}

View File

@@ -0,0 +1,116 @@
## Input
```javascript
// @validateExhaustiveEffectDependencies
import {useEffect} from 'react';
function Component({x, y, z}) {
// error: missing dep - x
useEffect(() => {
log(x);
}, []);
// error: extra dep - y
useEffect(() => {
log(x);
}, [x, y]);
// error: missing dep - z; extra dep - y
useEffect(() => {
log(x, z);
}, [x, y]);
// error: missing dep x
useEffect(() => {
log(x);
}, [x.y]);
}
```
## Error
```
Found 4 errors:
Error: Found missing effect dependencies
Missing dependencies can cause an effect to fire less often than it should.
error.invalid-exhaustive-effect-deps.ts:7:8
5 | // error: missing dep - x
6 | useEffect(() => {
> 7 | log(x);
| ^ Missing dependency `x`
8 | }, []);
9 |
10 | // error: extra dep - y
Inferred dependencies: `[x]`
Error: Found extra effect dependencies
Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.
error.invalid-exhaustive-effect-deps.ts:13:9
11 | useEffect(() => {
12 | log(x);
> 13 | }, [x, y]);
| ^ Unnecessary dependency `y`
14 |
15 | // error: missing dep - z; extra dep - y
16 | useEffect(() => {
Inferred dependencies: `[x]`
Error: Found missing/extra effect dependencies
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 in performance problems such as excessive renders and side effects.
error.invalid-exhaustive-effect-deps.ts:17:11
15 | // error: missing dep - z; extra dep - y
16 | useEffect(() => {
> 17 | log(x, z);
| ^ Missing dependency `z`
18 | }, [x, y]);
19 |
20 | // error: missing dep x
error.invalid-exhaustive-effect-deps.ts:18:9
16 | useEffect(() => {
17 | log(x, z);
> 18 | }, [x, y]);
| ^ Unnecessary dependency `y`
19 |
20 | // error: missing dep x
21 | useEffect(() => {
Inferred dependencies: `[x, z]`
Error: Found missing/extra effect dependencies
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 in performance problems such as excessive renders and side effects.
error.invalid-exhaustive-effect-deps.ts:22:8
20 | // error: missing dep x
21 | useEffect(() => {
> 22 | log(x);
| ^ Missing dependency `x`
23 | }, [x.y]);
24 | }
25 |
error.invalid-exhaustive-effect-deps.ts:23:6
21 | useEffect(() => {
22 | log(x);
> 23 | }, [x.y]);
| ^^^ Overly precise dependency `x.y`, use `x` instead
24 | }
25 |
Inferred dependencies: `[x]`
```

View File

@@ -0,0 +1,24 @@
// @validateExhaustiveEffectDependencies
import {useEffect} from 'react';
function Component({x, y, z}) {
// error: missing dep - x
useEffect(() => {
log(x);
}, []);
// error: extra dep - y
useEffect(() => {
log(x);
}, [x, y]);
// error: missing dep - z; extra dep - y
useEffect(() => {
log(x, z);
}, [x, y]);
// error: missing dep x
useEffect(() => {
log(x);
}, [x.y]);
}

View File

@@ -0,0 +1,43 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {makeObject_Primitives} from 'shared-runtime';
function useHook() {
const object = makeObject_Primitives();
const fn = useCallback(() => {
const g = () => {
return [object];
};
return g;
});
return fn;
}
```
## Error
```
Found 1 error:
Error: Found missing memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
error.invalid-missing-nonreactive-dep-inner-function.ts:10:14
8 | const fn = useCallback(() => {
9 | const g = () => {
> 10 | return [object];
| ^^^^^^ Missing dependency `object`
11 | };
12 | return g;
13 | });
```

View File

@@ -0,0 +1,15 @@
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {makeObject_Primitives} from 'shared-runtime';
function useHook() {
const object = makeObject_Primitives();
const fn = useCallback(() => {
const g = () => {
return [object];
};
return g;
});
return fn;
}

View File

@@ -0,0 +1,43 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {makeObject_Primitives, useIdentity} from 'shared-runtime';
function useHook() {
// object is non-reactive but not memoized bc the mutation surrounds a hook
const object = makeObject_Primitives();
useIdentity();
object.x = 0;
const array = useMemo(() => [object], []);
return array;
}
```
## Error
```
Found 1 error:
Error: Found missing memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
error.invalid-missing-nonreactive-dep-unmemoized.ts:11:31
9 | useIdentity();
10 | object.x = 0;
> 11 | const array = useMemo(() => [object], []);
| ^^^^^^ Missing dependency `object`
12 | return array;
13 | }
14 |
Inferred dependencies: `[object]`
```

View File

@@ -0,0 +1,13 @@
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {makeObject_Primitives, useIdentity} from 'shared-runtime';
function useHook() {
// object is non-reactive but not memoized bc the mutation surrounds a hook
const object = makeObject_Primitives();
useIdentity();
object.x = 0;
const array = useMemo(() => [object], []);
return array;
}

View File

@@ -0,0 +1,40 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {makeObject_Primitives} from 'shared-runtime';
function useHook() {
const object = makeObject_Primitives();
const array = useMemo(() => [object], []);
return array;
}
```
## Error
```
Found 1 error:
Error: Found missing memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
error.invalid-missing-nonreactive-dep.ts:8:31
6 | function useHook() {
7 | const object = makeObject_Primitives();
> 8 | const array = useMemo(() => [object], []);
| ^^^^^^ Missing dependency `object`
9 | return array;
10 | }
11 |
Inferred dependencies: `[object]`
```

View File

@@ -0,0 +1,10 @@
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {makeObject_Primitives} from 'shared-runtime';
function useHook() {
const object = makeObject_Primitives();
const array = useMemo(() => [object], []);
return array;
}

View File

@@ -0,0 +1,44 @@
## Input
```javascript
import {arrayPush} from 'shared-runtime';
// @validateExhaustiveMemoizationDependencies
function Component() {
const item = [];
const foo = useCallback(
() => {
arrayPush(item, 1);
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return <Button foo={foo} />;
}
```
## Error
```
Found 1 error:
Error: Found missing memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
error.sketchy-code-exhaustive-deps.ts:8:16
6 | const foo = useCallback(
7 | () => {
> 8 | arrayPush(item, 1);
| ^^^^ Missing dependency `item`
9 | }, // eslint-disable-next-line react-hooks/exhaustive-deps
10 | []
11 | );
Inferred dependencies: `[item]`
```

View File

@@ -1,8 +1,11 @@
import {arrayPush} from 'shared-runtime';
// @validateExhaustiveMemoizationDependencies
function Component() {
const item = [];
const foo = useCallback(
() => {
item.push(1);
arrayPush(item, 1);
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[]
);

View File

@@ -0,0 +1,41 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies
function Component() {
const x = 0;
const y = useMemo(() => {
return [x];
// x gets constant-folded but shouldn't count as extraneous,
// it was referenced in the memo block
}, [x]);
return y;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies
function Component() {
const $ = _c(1);
const x = 0;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [0];
$[0] = t0;
} else {
t0 = $[0];
}
const y = t0;
return y;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,11 @@
// @validateExhaustiveMemoizationDependencies
function Component() {
const x = 0;
const y = useMemo(() => {
return [x];
// x gets constant-folded but shouldn't count as extraneous,
// it was referenced in the memo block
}, [x]);
return y;
}

View File

@@ -0,0 +1,148 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
import {
useCallback,
useTransition,
useState,
useOptimistic,
useActionState,
useRef,
useReducer,
useEffect,
} from 'react';
function useFoo() {
const [s, setState] = useState();
const ref = useRef(null);
const [t, startTransition] = useTransition();
const [u, addOptimistic] = useOptimistic();
const [v, dispatch] = useReducer(() => {}, null);
const [isPending, dispatchAction] = useActionState(() => {}, null);
useEffect(() => {
dispatch();
startTransition(() => {});
addOptimistic();
setState(null);
dispatchAction();
ref.current = true;
}, [
// intentionally adding unnecessary deps on nonreactive stable values
// to check that they're allowed
dispatch,
startTransition,
addOptimistic,
setState,
dispatchAction,
ref,
]);
return useCallback(() => {
dispatch();
startTransition(() => {});
addOptimistic();
setState(null);
dispatchAction();
ref.current = true;
}, [
// intentionally adding unnecessary deps on nonreactive stable values
// to check that they're allowed
dispatch,
startTransition,
addOptimistic,
setState,
dispatchAction,
ref,
]);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
import {
useCallback,
useTransition,
useState,
useOptimistic,
useActionState,
useRef,
useReducer,
useEffect,
} from "react";
function useFoo() {
const $ = _c(3);
const [, setState] = useState();
const ref = useRef(null);
const [, startTransition] = useTransition();
const [, addOptimistic] = useOptimistic();
const [, dispatch] = useReducer(_temp, null);
const [, dispatchAction] = useActionState(_temp2, null);
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
dispatch();
startTransition(_temp3);
addOptimistic();
setState(null);
dispatchAction();
ref.current = true;
};
t1 = [
dispatch,
startTransition,
addOptimistic,
setState,
dispatchAction,
ref,
];
$[0] = t0;
$[1] = t1;
} else {
t0 = $[0];
t1 = $[1];
}
useEffect(t0, t1);
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
dispatch();
startTransition(_temp4);
addOptimistic();
setState(null);
dispatchAction();
ref.current = true;
};
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
}
function _temp4() {}
function _temp3() {}
function _temp2() {}
function _temp() {}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
### Eval output
(kind: ok) "[[ function params=0 ]]"
logs: ['An optimistic state update occurred outside a transition or action. To fix, move the update to an action, or wrap with startTransition.']

View File

@@ -0,0 +1,61 @@
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
import {
useCallback,
useTransition,
useState,
useOptimistic,
useActionState,
useRef,
useReducer,
useEffect,
} from 'react';
function useFoo() {
const [s, setState] = useState();
const ref = useRef(null);
const [t, startTransition] = useTransition();
const [u, addOptimistic] = useOptimistic();
const [v, dispatch] = useReducer(() => {}, null);
const [isPending, dispatchAction] = useActionState(() => {}, null);
useEffect(() => {
dispatch();
startTransition(() => {});
addOptimistic();
setState(null);
dispatchAction();
ref.current = true;
}, [
// intentionally adding unnecessary deps on nonreactive stable values
// to check that they're allowed
dispatch,
startTransition,
addOptimistic,
setState,
dispatchAction,
ref,
]);
return useCallback(() => {
dispatch();
startTransition(() => {});
addOptimistic();
setState(null);
dispatchAction();
ref.current = true;
}, [
// intentionally adding unnecessary deps on nonreactive stable values
// to check that they're allowed
dispatch,
startTransition,
addOptimistic,
setState,
dispatchAction,
ref,
]);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};

View File

@@ -0,0 +1,104 @@
## Input
```javascript
// @validateExhaustiveEffectDependencies
import {useEffect, useEffectEvent} from 'react';
function Component({x, y, z}) {
const effectEvent = useEffectEvent(() => {
log(x);
});
const effectEvent2 = useEffectEvent(z => {
log(y, z);
});
// ok - effectEvent not included in deps
useEffect(() => {
effectEvent();
}, []);
// ok - effectEvent2 not included in deps, z included
useEffect(() => {
effectEvent2(z);
}, [z]);
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveEffectDependencies
import { useEffect, useEffectEvent } from "react";
function Component(t0) {
const $ = _c(12);
const { x, y, z } = t0;
let t1;
if ($[0] !== x) {
t1 = () => {
log(x);
};
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
const effectEvent = useEffectEvent(t1);
let t2;
if ($[2] !== y) {
t2 = (z_0) => {
log(y, z_0);
};
$[2] = y;
$[3] = t2;
} else {
t2 = $[3];
}
const effectEvent2 = useEffectEvent(t2);
let t3;
if ($[4] !== effectEvent) {
t3 = () => {
effectEvent();
};
$[4] = effectEvent;
$[5] = t3;
} else {
t3 = $[5];
}
let t4;
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
t4 = [];
$[6] = t4;
} else {
t4 = $[6];
}
useEffect(t3, t4);
let t5;
if ($[7] !== effectEvent2 || $[8] !== z) {
t5 = () => {
effectEvent2(z);
};
$[7] = effectEvent2;
$[8] = z;
$[9] = t5;
} else {
t5 = $[9];
}
let t6;
if ($[10] !== z) {
t6 = [z];
$[10] = z;
$[11] = t6;
} else {
t6 = $[11];
}
useEffect(t5, t6);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,22 @@
// @validateExhaustiveEffectDependencies
import {useEffect, useEffectEvent} from 'react';
function Component({x, y, z}) {
const effectEvent = useEffectEvent(() => {
log(x);
});
const effectEvent2 = useEffectEvent(z => {
log(y, z);
});
// ok - effectEvent not included in deps
useEffect(() => {
effectEvent();
}, []);
// ok - effectEvent2 not included in deps, z included
useEffect(() => {
effectEvent2(z);
}, [z]);
}

View File

@@ -3,7 +3,7 @@
```javascript
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {useCallback, useMemo} from 'react';
import {makeObject_Primitives, Stringify} from 'shared-runtime';
function useHook1(x) {
@@ -47,6 +47,16 @@ function useHook6(x) {
}, [x]);
}
function useHook7(x) {
const [state, setState] = useState(true);
const f = () => {
setState(x => !x);
};
return useCallback(() => {
f();
}, [f]);
}
function Component({x, y, z}) {
const a = useHook1(x);
const b = useHook2(x);
@@ -54,7 +64,8 @@ function Component({x, y, z}) {
const d = useHook4(x, y, z);
const e = useHook5(x);
const f = useHook6(x);
return <Stringify results={[a, b, c, d, e, f]} />;
const g = useHook7(x);
return <Stringify results={[a, b, c, d, e, f, g]} />;
}
```
@@ -63,7 +74,7 @@ function Component({x, y, z}) {
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies
import { useMemo } from "react";
import { useCallback, useMemo } from "react";
import { makeObject_Primitives, Stringify } from "shared-runtime";
function useHook1(x) {
@@ -121,8 +132,36 @@ function useHook6(x) {
return f;
}
function useHook7(x) {
const $ = _c(2);
const [, setState] = useState(true);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
setState(_temp);
};
$[0] = t0;
} else {
t0 = $[0];
}
const f = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
f();
};
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
function _temp(x_0) {
return !x_0;
}
function Component(t0) {
const $ = _c(7);
const $ = _c(8);
const { x, y, z } = t0;
const a = useHook1(x);
const b = useHook2(x);
@@ -130,6 +169,7 @@ function Component(t0) {
const d = useHook4(x, y, z);
const e = useHook5(x);
const f = useHook6(x);
const g = useHook7(x);
let t1;
if (
$[0] !== a ||
@@ -137,18 +177,20 @@ function Component(t0) {
$[2] !== c ||
$[3] !== d ||
$[4] !== e ||
$[5] !== f
$[5] !== f ||
$[6] !== g
) {
t1 = <Stringify results={[a, b, c, d, e, f]} />;
t1 = <Stringify results={[a, b, c, d, e, f, g]} />;
$[0] = a;
$[1] = b;
$[2] = c;
$[3] = d;
$[4] = e;
$[5] = f;
$[6] = t1;
$[6] = g;
$[7] = t1;
} else {
t1 = $[6];
t1 = $[7];
}
return t1;
}

View File

@@ -1,5 +1,5 @@
// @validateExhaustiveMemoizationDependencies
import {useMemo} from 'react';
import {useCallback, useMemo} from 'react';
import {makeObject_Primitives, Stringify} from 'shared-runtime';
function useHook1(x) {
@@ -43,6 +43,16 @@ function useHook6(x) {
}, [x]);
}
function useHook7(x) {
const [state, setState] = useState(true);
const f = () => {
setState(x => !x);
};
return useCallback(() => {
f();
}, [f]);
}
function Component({x, y, z}) {
const a = useHook1(x);
const b = useHook2(x);
@@ -50,5 +60,6 @@ function Component({x, y, z}) {
const d = useHook4(x, y, z);
const e = useHook5(x);
const f = useHook6(x);
return <Stringify results={[a, b, c, d, e, f]} />;
const g = useHook7(x);
return <Stringify results={[a, b, c, d, e, f, g]} />;
}

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo, useState} from 'react';
import {ValidateMemoization} from 'shared-runtime';
@@ -29,7 +29,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c2 } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
import { c as _c2 } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useMemo, useState } from "react";
import { ValidateMemoization } from "shared-runtime";

View File

@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo, useState} from 'react';
import {ValidateMemoization} from 'shared-runtime';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges
// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges @validateExhaustiveMemoizationDependencies:false
import {useEffect, useMemo, useState} from 'react';
import {ValidateMemoization} from 'shared-runtime';
@@ -46,7 +46,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableResetCacheOnSourceFileChanges
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableResetCacheOnSourceFileChanges @validateExhaustiveMemoizationDependencies:false
import { useEffect, useMemo, useState } from "react";
import { ValidateMemoization } from "shared-runtime";
@@ -63,12 +63,12 @@ function unsafeUpdateConst() {
function Component() {
const $ = _c(3);
if (
$[0] !== "a585d27423c1181e7b6305ff909458183d284658c3c3d2e3764e1128be302fd7"
$[0] !== "36c02976ff5bc474b7510128ea8220ffe31d92cd5d245148ed0a43146d18ded4"
) {
for (let $i = 0; $i < 3; $i += 1) {
$[$i] = Symbol.for("react.memo_cache_sentinel");
}
$[0] = "a585d27423c1181e7b6305ff909458183d284658c3c3d2e3764e1128be302fd7";
$[0] = "36c02976ff5bc474b7510128ea8220ffe31d92cd5d245148ed0a43146d18ded4";
}
useState(_temp);

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges
// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges @validateExhaustiveMemoizationDependencies:false
import {useEffect, useMemo, useState} from 'react';
import {ValidateMemoization} from 'shared-runtime';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly
// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {identity} from 'shared-runtime';
@@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly
// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
import { identity } from "shared-runtime";
@@ -58,7 +58,7 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"category":"PreserveManualMemo","reason":"Existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}}
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":255},"end":{"line":16,"column":1,"index":482},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"category":"PreserveManualMemo","reason":"Existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":337},"end":{"line":9,"column":52,"index":358},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}}
```
### Eval output

View File

@@ -1,4 +1,4 @@
// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly
// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {identity} from 'shared-runtime';

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies:false
import {useState, useMemo} from 'react';
import {useIdentity} from 'shared-runtime';
@@ -36,7 +37,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies:false
import { useState, useMemo } from "react";
import { useIdentity } from "shared-runtime";

View File

@@ -1,3 +1,4 @@
// @validateExhaustiveMemoizationDependencies:false
import {useState, useMemo} from 'react';
import {useIdentity} from 'shared-runtime';

View File

@@ -0,0 +1,71 @@
## Input
```javascript
// @loggerTestOnly @validateNoSetStateInEffects
import {useEffect, useEffectEvent, useState} from 'react';
function Component() {
const [state, setState] = useState(0);
const effectEvent = useEffectEvent(() => {
setState(true);
});
useEffect(() => {
effectEvent();
}, []);
return state;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInEffects
import { useEffect, useEffectEvent, useState } from "react";
function Component() {
const $ = _c(4);
const [state, setState] = useState(0);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
setState(true);
};
$[0] = t0;
} else {
t0 = $[0];
}
const effectEvent = useEffectEvent(t0);
let t1;
if ($[1] !== effectEvent) {
t1 = () => {
effectEvent();
};
$[1] = effectEvent;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [];
$[3] = t2;
} else {
t2 = $[3];
}
useEffect(t1, t2);
return state;
}
```
## Logs
```
{"kind":"CompileError","detail":{"options":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","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. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":10,"column":4,"index":267},"end":{"line":10,"column":15,"index":278},"filename":"invalid-setState-in-useEffect-via-useEffectEvent.ts","identifierName":"effectEvent"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":108},"end":{"line":13,"column":1,"index":309},"filename":"invalid-setState-in-useEffect-via-useEffectEvent.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":3,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,13 @@
// @loggerTestOnly @validateNoSetStateInEffects
import {useEffect, useEffectEvent, useState} from 'react';
function Component() {
const [state, setState] = useState(0);
const effectEvent = useEffectEvent(() => {
setState(true);
});
useEffect(() => {
effectEvent();
}, []);
return state;
}

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @loggerTestOnly
// @loggerTestOnly @validateExhaustiveMemoizationDependencies:false
function component(a) {
let x = useMemo(() => {
mutate(a);
@@ -15,7 +15,7 @@ function component(a) {
## Code
```javascript
// @loggerTestOnly
// @loggerTestOnly @validateExhaustiveMemoizationDependencies:false
function component(a) {
mutate(a);
}
@@ -25,8 +25,8 @@ function component(a) {
## Logs
```
{"kind":"CompileError","detail":{"options":{"category":"VoidUseMemo","reason":"useMemo() callbacks must return a value","description":"This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":3,"column":18,"index":61},"end":{"line":5,"column":3,"index":87},"filename":"invalid-useMemo-return-empty.ts"},"message":"useMemo() callbacks must return a value"}]}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":19},"end":{"line":7,"column":1,"index":107},"filename":"invalid-useMemo-return-empty.ts"},"fnName":"component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":1,"prunedMemoValues":0}
{"kind":"CompileError","detail":{"options":{"category":"VoidUseMemo","reason":"useMemo() callbacks must return a value","description":"This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":3,"column":18,"index":110},"end":{"line":5,"column":3,"index":136},"filename":"invalid-useMemo-return-empty.ts"},"message":"useMemo() callbacks must return a value"}]}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":68},"end":{"line":7,"column":1,"index":156},"filename":"invalid-useMemo-return-empty.ts"},"fnName":"component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":1,"prunedMemoValues":0}
```
### Eval output

View File

@@ -1,4 +1,4 @@
// @loggerTestOnly
// @loggerTestOnly @validateExhaustiveMemoizationDependencies:false
function component(a) {
let x = useMemo(() => {
mutate(a);

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {identity, ValidateMemoization} from 'shared-runtime';
@@ -33,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
import { identity, ValidateMemoization } from "shared-runtime";

View File

@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {identity, ValidateMemoization} from 'shared-runtime';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {identity, ValidateMemoization} from 'shared-runtime';
@@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
import { identity, ValidateMemoization } from "shared-runtime";

View File

@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {identity, ValidateMemoization} from 'shared-runtime';

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies:false
export function useFormatRelativeTime(opts = {}) {
const {timeZone, minimal} = opts;
const format = useCallback(function formatWithUnit() {}, [minimal]);
@@ -21,7 +22,7 @@ export function useFormatRelativeTime(opts = {}) {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies:false
export function useFormatRelativeTime(t0) {
const $ = _c(1);
const opts = t0 === undefined ? {} : t0;

View File

@@ -1,3 +1,4 @@
// @validateExhaustiveMemoizationDependencies:false
export function useFormatRelativeTime(opts = {}) {
const {timeZone, minimal} = opts;
const format = useCallback(function formatWithUnit() {}, [minimal]);

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {
mutate,
@@ -50,7 +50,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
import {
mutate,

View File

@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {
mutate,

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {
typedCapture,
@@ -43,7 +43,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
import {
typedCapture,

View File

@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {
typedCapture,

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {
typedCapture,
@@ -41,7 +41,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
import {
typedCapture,

View File

@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {
typedCapture,

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {
typedCapture,
@@ -37,7 +37,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
import {
typedCapture,

View File

@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {
typedCapture,

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {
typedCapture,
@@ -41,7 +41,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
import {
typedCapture,

View File

@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees:false
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
import {
typedCapture,

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
@@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useCallback } from "react";
import { Stringify } from "shared-runtime";

View File

@@ -1,4 +1,4 @@
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
@@ -31,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useCallback } from "react";
import { Stringify } from "shared-runtime";

View File

@@ -1,4 +1,4 @@
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
function useFoo(arr1, arr2) {
@@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
function useFoo(arr1, arr2) {

View File

@@ -1,4 +1,4 @@
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
import {useMemo} from 'react';
function useFoo(arr1, arr2) {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
// @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
import {useCallback, useRef} from 'react';
function useFoo({cond}) {

Some files were not shown because too many files have changed in this diff Show More