Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
165cf8e429 | ||
|
|
f85772c9ef | ||
|
|
91a6cc3288 | ||
|
|
5aec1b2a8d | ||
|
|
d6cae440e3 | ||
|
|
00908be9ff | ||
|
|
0e180141bf | ||
|
|
65eec428c4 | ||
|
|
454fc41fc7 | ||
|
|
f93b9fd44b | ||
|
|
b731fe28cc | ||
|
|
88ee1f5955 | ||
|
|
bcf97c7564 | ||
|
|
ba5b843692 |
@@ -635,6 +635,7 @@ module.exports = {
|
||||
FocusOptions: 'readonly',
|
||||
OptionalEffectTiming: 'readonly',
|
||||
|
||||
__REACT_ROOT_PATH_TEST__: 'readonly',
|
||||
spyOnDev: 'readonly',
|
||||
spyOnDevAndProd: 'readonly',
|
||||
spyOnProd: 'readonly',
|
||||
|
||||
47
.github/workflows/runtime_build_and_test.yml
vendored
47
.github/workflows/runtime_build_and_test.yml
vendored
@@ -382,9 +382,6 @@ jobs:
|
||||
-r=experimental --env=development,
|
||||
-r=experimental --env=production,
|
||||
|
||||
# Dev Tools
|
||||
--project=devtools -r=experimental,
|
||||
|
||||
# TODO: Update test config to support www build tests
|
||||
# - "-r=www-classic --env=development --variant=false"
|
||||
# - "-r=www-classic --env=production --variant=false"
|
||||
@@ -450,6 +447,50 @@ jobs:
|
||||
run: ls -R build
|
||||
- run: yarn test --build ${{ matrix.test_params }} --shard=${{ matrix.shard }} --ci
|
||||
|
||||
test_build_devtools:
|
||||
name: yarn test-build (devtools)
|
||||
needs: [build_and_lint, runtime_node_modules_cache]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard:
|
||||
- 1/5
|
||||
- 2/5
|
||||
- 3/5
|
||||
- 4/5
|
||||
- 5/5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: yarn test --build --project=devtools -r=experimental --shard=${{ matrix.shard }} --ci
|
||||
|
||||
process_artifacts_combined:
|
||||
name: Process artifacts combined
|
||||
needs: [build_and_lint, runtime_node_modules_cache]
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(node scripts/enable-feature-flag.js:*)"
|
||||
"Bash(node scripts/enable-feature-flag.js:*)",
|
||||
"Bash(yarn snap:*)",
|
||||
"Bash(for test in \"error.invalid-access-ref-during-render\" \"error.invalid-ref-in-callback-invoked-during-render\" \"error.invalid-impure-functions-in-render-via-render-helper\")",
|
||||
"Bash(do)",
|
||||
"Bash(echo:*)",
|
||||
"Bash(done)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(sl revert:*)",
|
||||
"Bash(yarn workspace snap run build:*)",
|
||||
"Bash(yarn tsc:*)",
|
||||
"Bash(yarn snap:build)",
|
||||
"Bash(timeout 30 yarn snap:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## Pending
|
||||
|
||||
* Improve impurity and ref validation, reducing false positives [#35298](https://github.com/facebook/react/pull/35298) by [@josephsavona](https://github.com/josephsavona)
|
||||
|
||||
## 19.1.0-rc.2 (May 14, 2025)
|
||||
|
||||
## babel-plugin-react-compiler
|
||||
|
||||
251
compiler/CLAUDE.md
Normal file
251
compiler/CLAUDE.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# React Compiler Knowledge Base
|
||||
|
||||
This document contains knowledge about the React Compiler gathered during development sessions. It serves as a reference for understanding the codebase architecture and key concepts.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `packages/babel-plugin-react-compiler/` - Main compiler package
|
||||
- `src/HIR/` - High-level Intermediate Representation types and utilities
|
||||
- `src/Inference/` - Effect inference passes (aliasing, mutation, etc.)
|
||||
- `src/Validation/` - Validation passes that check for errors
|
||||
- `src/Entrypoint/Pipeline.ts` - Main compilation pipeline with pass ordering
|
||||
- `src/__tests__/fixtures/compiler/` - Test fixtures
|
||||
- `error.*.js` - Fixtures that should produce compilation errors
|
||||
- `*.expect.md` - Expected output for each fixture
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
yarn snap
|
||||
|
||||
# Run tests matching a pattern
|
||||
# Example: yarn snap -p 'error.*'
|
||||
yarn snap -p <pattern>
|
||||
|
||||
# Run a single fixture in debug mode. Use the path relative to the __tests__/fixtures/compiler directory
|
||||
# For each step of compilation, outputs the step name and state of the compiled program
|
||||
# Example: yarn snap -p simple.js -d
|
||||
yarn snap -p <file-basename> -d
|
||||
|
||||
# Update fixture outputs (also works with -p)
|
||||
yarn snap -u
|
||||
```
|
||||
|
||||
## Version Control
|
||||
|
||||
This repository uses Sapling (`sl`) for version control. Unlike git, Sapling does not require explicitly adding files to the staging area.
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
sl status
|
||||
|
||||
# Commit all changes
|
||||
sl commit -m "Your commit message"
|
||||
|
||||
# Commit with multi-line message using heredoc
|
||||
sl commit -m "$(cat <<'EOF'
|
||||
Summary line
|
||||
|
||||
Detailed description here
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### HIR (High-level Intermediate Representation)
|
||||
|
||||
The compiler converts source code to HIR for analysis. Key types in `src/HIR/HIR.ts`:
|
||||
|
||||
- **HIRFunction** - A function being compiled
|
||||
- `body.blocks` - Map of BasicBlocks
|
||||
- `context` - Captured variables from outer scope
|
||||
- `params` - Function parameters
|
||||
- `returns` - The function's return place
|
||||
- `aliasingEffects` - Effects that describe the function's behavior when called
|
||||
|
||||
- **Instruction** - A single operation
|
||||
- `lvalue` - The place being assigned to
|
||||
- `value` - The instruction kind (CallExpression, FunctionExpression, LoadLocal, etc.)
|
||||
- `effects` - Array of AliasingEffects for this instruction
|
||||
|
||||
- **Terminal** - Block terminators (return, branch, etc.)
|
||||
- `effects` - Array of AliasingEffects
|
||||
|
||||
- **Place** - A reference to a value
|
||||
- `identifier.id` - Unique IdentifierId
|
||||
|
||||
- **Phi nodes** - Join points for values from different control flow paths
|
||||
- Located at `block.phis`
|
||||
- `phi.place` - The result place
|
||||
- `phi.operands` - Map of predecessor block to source place
|
||||
|
||||
### AliasingEffects System
|
||||
|
||||
Effects describe data flow and operations. Defined in `src/Inference/AliasingEffects.ts`:
|
||||
|
||||
**Data Flow Effects:**
|
||||
- `Impure` - Marks a place as containing an impure value (e.g., Date.now() result, ref.current)
|
||||
- `Capture a -> b` - Value from `a` is captured into `b` (mutable capture)
|
||||
- `Alias a -> b` - `b` aliases `a`
|
||||
- `ImmutableCapture a -> b` - Immutable capture (like Capture but read-only)
|
||||
- `Assign a -> b` - Direct assignment
|
||||
- `MaybeAlias a -> b` - Possible aliasing
|
||||
- `CreateFrom a -> b` - Created from source
|
||||
|
||||
**Mutation Effects:**
|
||||
- `Mutate value` - Value is mutated
|
||||
- `MutateTransitive value` - Value and transitive captures are mutated
|
||||
- `MutateConditionally value` - May mutate
|
||||
- `MutateTransitiveConditionally value` - May mutate transitively
|
||||
|
||||
**Other Effects:**
|
||||
- `Render place` - Place is used in render context (JSX props, component return)
|
||||
- `Freeze place` - Place is frozen (made immutable)
|
||||
- `Create place` - New value created
|
||||
- `CreateFunction` - Function expression created, includes `captures` array
|
||||
- `Apply` - Function application with receiver, function, args, and result
|
||||
|
||||
### Hook Aliasing Signatures
|
||||
|
||||
Located in `src/HIR/Globals.ts`, hooks can define custom aliasing signatures to control how data flows through them.
|
||||
|
||||
**Structure:**
|
||||
```typescript
|
||||
aliasing: {
|
||||
receiver: '@receiver', // The hook function itself
|
||||
params: ['@param0'], // Named positional parameters
|
||||
rest: '@rest', // Rest parameters (or null)
|
||||
returns: '@returns', // Return value
|
||||
temporaries: [], // Temporary values during execution
|
||||
effects: [ // Array of effects to apply when hook is called
|
||||
{kind: 'Freeze', value: '@param0', reason: ValueReason.HookCaptured},
|
||||
{kind: 'Assign', from: '@param0', into: '@returns'},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
**Common patterns:**
|
||||
|
||||
1. **RenderHookAliasing** (useState, useContext, useMemo, useCallback):
|
||||
- Freezes arguments (`Freeze @rest`)
|
||||
- Marks arguments as render-time (`Render @rest`)
|
||||
- Creates frozen return value
|
||||
- Aliases arguments to return
|
||||
|
||||
2. **EffectHookAliasing** (useEffect, useLayoutEffect, useInsertionEffect):
|
||||
- Freezes function and deps
|
||||
- Creates internal effect object
|
||||
- Captures function and deps into effect
|
||||
- Returns undefined
|
||||
|
||||
3. **Event handler hooks** (useEffectEvent):
|
||||
- Freezes callback (`Freeze @fn`)
|
||||
- Aliases input to return (`Assign @fn -> @returns`)
|
||||
- NO Render effect (callback not called during render)
|
||||
|
||||
**Example: useEffectEvent**
|
||||
```typescript
|
||||
const UseEffectEventHook = addHook(
|
||||
DEFAULT_SHAPES,
|
||||
{
|
||||
positionalParams: [Effect.Freeze], // Takes one positional param
|
||||
restParam: null,
|
||||
returnType: {kind: 'Function', ...},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useEffectEvent',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: {
|
||||
receiver: '@receiver',
|
||||
params: ['@fn'], // Name for the callback parameter
|
||||
rest: null,
|
||||
returns: '@returns',
|
||||
temporaries: [],
|
||||
effects: [
|
||||
{kind: 'Freeze', value: '@fn', reason: ValueReason.HookCaptured},
|
||||
{kind: 'Assign', from: '@fn', into: '@returns'},
|
||||
// Note: NO Render effect - callback is not called during render
|
||||
],
|
||||
},
|
||||
},
|
||||
BuiltInUseEffectEventId,
|
||||
);
|
||||
|
||||
// Add as both names for compatibility
|
||||
['useEffectEvent', UseEffectEventHook],
|
||||
['experimental_useEffectEvent', UseEffectEventHook],
|
||||
```
|
||||
|
||||
**Key insight:** If a hook is missing an `aliasing` config, it falls back to `DefaultNonmutatingHook` which includes a `Render` effect on all arguments. This can cause false positives for hooks like `useEffectEvent` whose callbacks are not called during render.
|
||||
|
||||
### Effect Inference Pipeline
|
||||
|
||||
Effects are populated by `InferMutationAliasingEffects` (runs before validation):
|
||||
|
||||
1. For `Date.now()`, `Math.random()` etc. - adds `Impure` effect (controlled by `validateNoImpureFunctionsInRender` config)
|
||||
2. For `ref.current` access - adds `Impure` effect (controlled by `validateRefAccessDuringRender` config)
|
||||
3. For return terminals - adds `Alias` from return value to `fn.returns`
|
||||
4. For component/JSX returns - adds `Render` effect
|
||||
5. For function expressions - adds `CreateFunction` effect with captures
|
||||
|
||||
### Validation: validateNoImpureValuesInRender
|
||||
|
||||
Located at `src/Validation/ValidateNoImpureValuesInRender.ts`
|
||||
|
||||
**Purpose:** Detect when impure values (refs, Date.now results, etc.) flow into render context.
|
||||
|
||||
**Algorithm:**
|
||||
1. Track impure values in a Map<IdentifierId, ImpureEffect>
|
||||
2. Track functions with impure returns separately (they're not impure values themselves)
|
||||
3. Fixed-point iteration over all blocks:
|
||||
- Process phi nodes (any impure operand makes result impure)
|
||||
- Process instruction effects
|
||||
- Process terminal effects
|
||||
- Backwards propagation for mutated LoadLocal values
|
||||
4. Validate: check all Render effects against impure values
|
||||
|
||||
**Key patterns:**
|
||||
- `Impure` effect marks the target as impure
|
||||
- `Capture/Alias/etc` propagates impurity from source to target
|
||||
- `Apply` propagates impurity from args/receiver to result
|
||||
- `CreateFunction` propagates impurity from captured values (but NOT from body effects)
|
||||
- If a value has both `Render` and `Capture` in same instruction, only error on Render (don't cascade)
|
||||
|
||||
**Tracking functions with impure returns:**
|
||||
- Separate from the `impure` map (function values aren't impure, just their returns)
|
||||
- Populated when analyzing FunctionExpression bodies
|
||||
- Used when:
|
||||
1. Calling the function - mark call result as impure
|
||||
2. Capturing the function - mark target as impure (for object.foo = impureFunc cases)
|
||||
|
||||
**Backwards propagation:**
|
||||
- When `$x = LoadLocal y` and `$x` is mutated with impure content, mark `y` as impure
|
||||
- This handles: `const arr = []; arr.push(impure); render(arr)`
|
||||
|
||||
## Known Issues / Edge Cases
|
||||
|
||||
### Function Outlining
|
||||
After `OutlineFunctions` pass, inner functions are replaced with `LoadGlobal(_temp)`. The validation runs BEFORE outlining, so it sees the original FunctionExpression. But be aware that test output shows post-outlining HIR.
|
||||
|
||||
### SSA and LoadLocal
|
||||
In SSA form, each `LoadLocal` creates a new identifier. When a loaded value is mutated:
|
||||
- `$x = LoadLocal y`
|
||||
- `mutate($x, impure)`
|
||||
- `$z = LoadLocal y` (different from $x!)
|
||||
- `render($z)`
|
||||
|
||||
The impurity in $x must propagate back to y, then forward to $z. This requires backwards propagation in the fixed-point loop.
|
||||
|
||||
## Configuration Flags
|
||||
|
||||
In `Environment.ts` / test directives:
|
||||
- `validateNoImpureFunctionsInRender` - Enable impure function validation (Date.now, Math.random, etc.)
|
||||
- `validateRefAccessDuringRender` - Enable ref access validation
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
1. Run `yarn snap -p <fixture>` to see full HIR output with effects
|
||||
2. Look for `@aliasingEffects=` on FunctionExpressions
|
||||
3. Look for `Impure`, `Render`, `Capture` effects on instructions
|
||||
4. Check the pass ordering in Pipeline.ts to understand when effects are populated vs validated
|
||||
@@ -96,7 +96,6 @@ import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHI
|
||||
import {outlineJSX} from '../Optimization/OutlineJsx';
|
||||
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
|
||||
import {transformFire} from '../Transform';
|
||||
import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender';
|
||||
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
|
||||
import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
|
||||
import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
|
||||
@@ -107,6 +106,7 @@ import {nameAnonymousFunctions} from '../Transform/NameAnonymousFunctions';
|
||||
import {optimizeForSSR} from '../Optimization/OptimizeForSSR';
|
||||
import {validateExhaustiveDependencies} from '../Validation/ValidateExhaustiveDependencies';
|
||||
import {validateSourceLocations} from '../Validation/ValidateSourceLocations';
|
||||
import {validateNoImpureValuesInRender} from '../Validation/ValidateNoImpureValuesInRender';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
@@ -271,10 +271,6 @@ function runWithEnvironment(
|
||||
assertValidMutableRanges(hir);
|
||||
}
|
||||
|
||||
if (env.config.validateRefAccessDuringRender) {
|
||||
validateNoRefAccessInRender(hir).unwrap();
|
||||
}
|
||||
|
||||
if (env.config.validateNoSetStateInRender) {
|
||||
validateNoSetStateInRender(hir).unwrap();
|
||||
}
|
||||
@@ -296,8 +292,15 @@ function runWithEnvironment(
|
||||
env.logErrors(validateNoJSXInTryStatement(hir));
|
||||
}
|
||||
|
||||
if (env.config.validateNoImpureFunctionsInRender) {
|
||||
validateNoImpureFunctionsInRender(hir).unwrap();
|
||||
if (
|
||||
env.config.validateNoImpureFunctionsInRender ||
|
||||
env.config.validateRefAccessDuringRender
|
||||
) {
|
||||
validateNoImpureValuesInRender(hir).unwrap();
|
||||
}
|
||||
|
||||
if (env.config.validateRefAccessDuringRender) {
|
||||
validateNoRefAccessInRender(hir).unwrap();
|
||||
}
|
||||
|
||||
validateNoFreezingKnownMutableFunctions(hir).unwrap();
|
||||
|
||||
@@ -225,8 +225,15 @@ export const EnvironmentConfigSchema = z.object({
|
||||
|
||||
/**
|
||||
* Validate that dependencies supplied to effect hooks are exhaustive.
|
||||
* Can be:
|
||||
* - 'off': No validation (default)
|
||||
* - 'all': Validate and report both missing and extra dependencies
|
||||
* - 'missing-only': Only report missing dependencies
|
||||
* - 'extra-only': Only report extra/unnecessary dependencies
|
||||
*/
|
||||
validateExhaustiveEffectDependencies: z.boolean().default(false),
|
||||
validateExhaustiveEffectDependencies: z
|
||||
.enum(['off', 'all', 'missing-only', 'extra-only'])
|
||||
.default('off'),
|
||||
|
||||
/**
|
||||
* When this is true, rather than pruning existing manual memoization but ensuring or validating
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
addObject,
|
||||
} from './ObjectShape';
|
||||
import {BuiltInType, ObjectType, PolyType} from './Types';
|
||||
import {TypeConfig} from './TypeSchema';
|
||||
import {AliasingSignatureConfig, TypeConfig} from './TypeSchema';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {isHookName} from './Environment';
|
||||
import {CompilerError, SourceLocation} from '..';
|
||||
@@ -626,11 +626,136 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
// TODO: rest of Global objects
|
||||
];
|
||||
|
||||
const createRenderHookAliasing: (
|
||||
reason: ValueReason,
|
||||
) => AliasingSignatureConfig = reason => ({
|
||||
receiver: '@receiver',
|
||||
params: [],
|
||||
rest: '@rest',
|
||||
returns: '@returns',
|
||||
temporaries: [],
|
||||
effects: [
|
||||
// Freeze the arguments
|
||||
{
|
||||
kind: 'Freeze',
|
||||
value: '@rest',
|
||||
reason: ValueReason.HookCaptured,
|
||||
},
|
||||
// Render the arguments
|
||||
{
|
||||
kind: 'Render',
|
||||
place: '@rest',
|
||||
},
|
||||
// Returns a frozen value
|
||||
{
|
||||
kind: 'Create',
|
||||
into: '@returns',
|
||||
value: ValueKind.Frozen,
|
||||
reason,
|
||||
},
|
||||
// May alias any arguments into the return
|
||||
{
|
||||
kind: 'Alias',
|
||||
from: '@rest',
|
||||
into: '@returns',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const EffectHookAliasing: AliasingSignatureConfig = {
|
||||
receiver: '@receiver',
|
||||
params: ['@fn', '@deps'],
|
||||
rest: '@rest',
|
||||
returns: '@returns',
|
||||
temporaries: ['@effect'],
|
||||
effects: [
|
||||
// Freezes the function and deps
|
||||
{
|
||||
kind: 'Freeze',
|
||||
value: '@rest',
|
||||
reason: ValueReason.Effect,
|
||||
},
|
||||
{
|
||||
kind: 'Freeze',
|
||||
value: '@fn',
|
||||
reason: ValueReason.Effect,
|
||||
},
|
||||
{
|
||||
kind: 'Freeze',
|
||||
value: '@deps',
|
||||
reason: ValueReason.Effect,
|
||||
},
|
||||
// Deps are accessed during render
|
||||
{
|
||||
kind: 'Render',
|
||||
place: '@deps',
|
||||
},
|
||||
// Internally creates an effect object that captures the function and deps
|
||||
{
|
||||
kind: 'Create',
|
||||
into: '@effect',
|
||||
value: ValueKind.Frozen,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
},
|
||||
// The effect stores the function and dependencies
|
||||
{
|
||||
kind: 'Capture',
|
||||
from: '@rest',
|
||||
into: '@effect',
|
||||
},
|
||||
{
|
||||
kind: 'Capture',
|
||||
from: '@fn',
|
||||
into: '@effect',
|
||||
},
|
||||
// Returns undefined
|
||||
{
|
||||
kind: 'Create',
|
||||
into: '@returns',
|
||||
value: ValueKind.Primitive,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/*
|
||||
* TODO(mofeiZ): We currently only store rest param effects for hooks.
|
||||
* now that FeatureFlag `enableTreatHooksAsFunctions` is removed we can
|
||||
* use positional params too (?)
|
||||
*/
|
||||
const useEffectEvent = addHook(
|
||||
DEFAULT_SHAPES,
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {
|
||||
kind: 'Function',
|
||||
return: {kind: 'Poly'},
|
||||
shapeId: BuiltInEffectEventId,
|
||||
isConstructor: false,
|
||||
},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useEffectEvent',
|
||||
// Frozen because it should not mutate any locally-bound values
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: {
|
||||
receiver: '@receiver',
|
||||
params: ['@value'],
|
||||
rest: null,
|
||||
returns: '@return',
|
||||
temporaries: [],
|
||||
effects: [
|
||||
{
|
||||
kind: 'Freeze',
|
||||
value: '@value',
|
||||
reason: ValueReason.HookCaptured,
|
||||
},
|
||||
{kind: 'Assign', from: '@value', into: '@return'},
|
||||
],
|
||||
},
|
||||
},
|
||||
BuiltInUseEffectEventId,
|
||||
);
|
||||
const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
[
|
||||
'useContext',
|
||||
@@ -644,6 +769,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useContext',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.Context,
|
||||
aliasing: createRenderHookAliasing(ValueReason.Context),
|
||||
},
|
||||
BuiltInUseContextHookId,
|
||||
),
|
||||
@@ -658,6 +784,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useState',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.State,
|
||||
aliasing: createRenderHookAliasing(ValueReason.State),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -670,6 +797,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useActionState',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.State,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -682,6 +810,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useReducer',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.ReducerState,
|
||||
aliasing: createRenderHookAliasing(ValueReason.ReducerState),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -693,6 +822,22 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useRef',
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
aliasing: {
|
||||
receiver: '@receiver',
|
||||
params: [],
|
||||
rest: '@rest',
|
||||
returns: '@return',
|
||||
temporaries: [],
|
||||
effects: [
|
||||
{
|
||||
kind: 'Create',
|
||||
into: '@return',
|
||||
value: ValueKind.Mutable,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
},
|
||||
{kind: 'Capture', from: '@rest', into: '@return'},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -715,6 +860,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useMemo',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -722,10 +868,16 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: {
|
||||
kind: 'Function',
|
||||
isConstructor: false,
|
||||
return: {kind: 'Poly'},
|
||||
shapeId: null,
|
||||
},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useCallback',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -739,41 +891,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useEffect',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: {
|
||||
receiver: '@receiver',
|
||||
params: [],
|
||||
rest: '@rest',
|
||||
returns: '@returns',
|
||||
temporaries: ['@effect'],
|
||||
effects: [
|
||||
// Freezes the function and deps
|
||||
{
|
||||
kind: 'Freeze',
|
||||
value: '@rest',
|
||||
reason: ValueReason.Effect,
|
||||
},
|
||||
// Internally creates an effect object that captures the function and deps
|
||||
{
|
||||
kind: 'Create',
|
||||
into: '@effect',
|
||||
value: ValueKind.Frozen,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
},
|
||||
// The effect stores the function and dependencies
|
||||
{
|
||||
kind: 'Capture',
|
||||
from: '@rest',
|
||||
into: '@effect',
|
||||
},
|
||||
// Returns undefined
|
||||
{
|
||||
kind: 'Create',
|
||||
into: '@returns',
|
||||
value: ValueKind.Primitive,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
},
|
||||
],
|
||||
},
|
||||
aliasing: EffectHookAliasing,
|
||||
},
|
||||
BuiltInUseEffectHookId,
|
||||
),
|
||||
@@ -789,6 +907,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useLayoutEffect',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: EffectHookAliasing,
|
||||
},
|
||||
BuiltInUseLayoutEffectHookId,
|
||||
),
|
||||
@@ -804,6 +923,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useInsertionEffect',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: EffectHookAliasing,
|
||||
},
|
||||
BuiltInUseInsertionEffectHookId,
|
||||
),
|
||||
@@ -817,6 +937,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useTransition',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -829,6 +950,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useOptimistic',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.State,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
}),
|
||||
],
|
||||
[
|
||||
@@ -842,6 +964,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
returnType: {kind: 'Poly'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: createRenderHookAliasing(ValueReason.HookCaptured),
|
||||
},
|
||||
BuiltInUseOperatorId,
|
||||
),
|
||||
@@ -866,27 +989,8 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
BuiltInFireId,
|
||||
),
|
||||
],
|
||||
[
|
||||
'useEffectEvent',
|
||||
addHook(
|
||||
DEFAULT_SHAPES,
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {
|
||||
kind: 'Function',
|
||||
return: {kind: 'Poly'},
|
||||
shapeId: BuiltInEffectEventId,
|
||||
isConstructor: false,
|
||||
},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useEffectEvent',
|
||||
// Frozen because it should not mutate any locally-bound values
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
BuiltInUseEffectEventId,
|
||||
),
|
||||
],
|
||||
['useEffectEvent', useEffectEvent],
|
||||
['experimental_useEffectEvent', useEffectEvent],
|
||||
['AUTODEPS', addObject(DEFAULT_SHAPES, BuiltInAutodepsId, [])],
|
||||
];
|
||||
|
||||
|
||||
@@ -1879,7 +1879,15 @@ export function isRefValueType(id: Identifier): boolean {
|
||||
}
|
||||
|
||||
export function isUseRefType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseRefId';
|
||||
return isUseRefType_(id.type);
|
||||
}
|
||||
|
||||
export function isUseRefType_(type: Type): boolean {
|
||||
return (
|
||||
(type.kind === 'Object' && type.shapeId === 'BuiltInUseRefId') ||
|
||||
(type.kind === 'Phi' &&
|
||||
type.operands.some(operand => isUseRefType_(operand)))
|
||||
);
|
||||
}
|
||||
|
||||
export function isUseStateType(id: Identifier): boolean {
|
||||
@@ -1890,6 +1898,13 @@ export function isJsxType(type: Type): boolean {
|
||||
return type.kind === 'Object' && type.shapeId === 'BuiltInJsx';
|
||||
}
|
||||
|
||||
export function isJsxOrJsxUnionType(type: Type): boolean {
|
||||
return (
|
||||
(type.kind === 'Object' && type.shapeId === 'BuiltInJsx') ||
|
||||
(type.kind === 'Phi' && type.operands.some(op => isJsxOrJsxUnionType(op)))
|
||||
);
|
||||
}
|
||||
|
||||
export function isRefOrRefValue(id: Identifier): boolean {
|
||||
return isUseRefType(id) || isRefValueType(id);
|
||||
}
|
||||
@@ -2058,4 +2073,23 @@ export function getHookKindForType(
|
||||
return null;
|
||||
}
|
||||
|
||||
export function areEqualSourceLocations(
|
||||
loc1: SourceLocation,
|
||||
loc2: SourceLocation,
|
||||
): boolean {
|
||||
if (typeof loc1 === 'symbol' || typeof loc2 === 'symbol') {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
loc1.filename === loc2.filename &&
|
||||
loc1.identifierName === loc2.identifierName &&
|
||||
loc1.start.line === loc2.start.line &&
|
||||
loc1.start.column === loc2.start.column &&
|
||||
loc1.start.index === loc2.start.index &&
|
||||
loc1.end.line === loc2.end.line &&
|
||||
loc1.end.column === loc2.end.column &&
|
||||
loc1.end.index === loc2.end.index
|
||||
);
|
||||
}
|
||||
|
||||
export * from './Types';
|
||||
|
||||
@@ -988,7 +988,7 @@ export function createTemporaryPlace(
|
||||
identifier: makeTemporaryIdentifier(env.nextIdentifierId, loc),
|
||||
reactive: false,
|
||||
effect: Effect.Unknown,
|
||||
loc: GeneratedSource,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {CompilerError, ErrorCategory} from '../CompilerError';
|
||||
import {AliasingEffect, AliasingSignature} from '../Inference/AliasingEffects';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {
|
||||
@@ -190,14 +190,22 @@ function parseAliasingSignatureConfig(
|
||||
};
|
||||
}
|
||||
case 'Impure': {
|
||||
const place = lookup(effect.place);
|
||||
const into = lookup(effect.into);
|
||||
return {
|
||||
kind: 'Impure',
|
||||
into,
|
||||
category: ErrorCategory.Purity,
|
||||
description: effect.description,
|
||||
reason: effect.reason,
|
||||
sourceMessage: effect.sourceMessage,
|
||||
usageMessage: effect.usageMessage,
|
||||
};
|
||||
}
|
||||
case 'Render': {
|
||||
const place = lookup(effect.place);
|
||||
return {
|
||||
kind: 'Render',
|
||||
place,
|
||||
error: CompilerError.throwTodo({
|
||||
reason: 'Support impure effect declarations',
|
||||
loc: GeneratedSource,
|
||||
}),
|
||||
};
|
||||
}
|
||||
case 'Apply': {
|
||||
@@ -1513,6 +1521,11 @@ export const DefaultNonmutatingHook = addHook(
|
||||
value: '@rest',
|
||||
reason: ValueReason.HookCaptured,
|
||||
},
|
||||
// Render the arguments
|
||||
{
|
||||
kind: 'Render',
|
||||
place: '@rest',
|
||||
},
|
||||
// Returns a frozen value
|
||||
{
|
||||
kind: 'Create',
|
||||
|
||||
@@ -983,15 +983,7 @@ export function printAliasingEffect(effect: AliasingEffect): string {
|
||||
return `...${printPlaceForAliasEffect(arg.place)}`;
|
||||
})
|
||||
.join(', ');
|
||||
let signature = '';
|
||||
if (effect.signature != null) {
|
||||
if (effect.signature.aliasing != null) {
|
||||
signature = printAliasingSignature(effect.signature.aliasing);
|
||||
} else {
|
||||
signature = JSON.stringify(effect.signature, null, 2);
|
||||
}
|
||||
}
|
||||
return `Apply ${printPlaceForAliasEffect(effect.into)} = ${receiverCallee}(${args})${signature != '' ? '\n ' : ''}${signature}`;
|
||||
return `Apply ${printPlaceForAliasEffect(effect.into)} = ${receiverCallee}(${args})`;
|
||||
}
|
||||
case 'Freeze': {
|
||||
return `Freeze ${printPlaceForAliasEffect(effect.value)} ${effect.reason}`;
|
||||
@@ -1009,7 +1001,7 @@ export function printAliasingEffect(effect: AliasingEffect): string {
|
||||
return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;
|
||||
}
|
||||
case 'Impure': {
|
||||
return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;
|
||||
return `Impure ${printPlaceForAliasEffect(effect.into)} reason=${effect.reason} description=${effect.description}`;
|
||||
}
|
||||
case 'Render': {
|
||||
return `Render ${printPlaceForAliasEffect(effect.place)}`;
|
||||
|
||||
@@ -185,11 +185,29 @@ export const ApplyEffectSchema: z.ZodType<ApplyEffectConfig> = z.object({
|
||||
|
||||
export type ImpureEffectConfig = {
|
||||
kind: 'Impure';
|
||||
place: string;
|
||||
into: string;
|
||||
reason: string;
|
||||
description: string;
|
||||
sourceMessage: string;
|
||||
usageMessage: string;
|
||||
};
|
||||
|
||||
export const ImpureEffectSchema: z.ZodType<ImpureEffectConfig> = z.object({
|
||||
kind: z.literal('Impure'),
|
||||
into: LifetimeIdSchema,
|
||||
reason: z.string(),
|
||||
description: z.string(),
|
||||
sourceMessage: z.string(),
|
||||
usageMessage: z.string(),
|
||||
});
|
||||
|
||||
export type RenderEffectConfig = {
|
||||
kind: 'Render';
|
||||
place: string;
|
||||
};
|
||||
|
||||
export const RenderEffectSchema: z.ZodType<RenderEffectConfig> = z.object({
|
||||
kind: z.literal('Render'),
|
||||
place: LifetimeIdSchema,
|
||||
});
|
||||
|
||||
@@ -204,7 +222,8 @@ export type AliasingEffectConfig =
|
||||
| ImpureEffectConfig
|
||||
| MutateEffectConfig
|
||||
| MutateTransitiveConditionallyConfig
|
||||
| ApplyEffectConfig;
|
||||
| ApplyEffectConfig
|
||||
| RenderEffectConfig;
|
||||
|
||||
export const AliasingEffectSchema: z.ZodType<AliasingEffectConfig> = z.union([
|
||||
FreezeEffectSchema,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerDiagnostic} from '../CompilerError';
|
||||
import {CompilerDiagnostic, ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
FunctionExpression,
|
||||
GeneratedSource,
|
||||
@@ -162,7 +162,15 @@ export type AliasingEffect =
|
||||
/**
|
||||
* Indicates a side-effect that is not safe during render
|
||||
*/
|
||||
| {kind: 'Impure'; place: Place; error: CompilerDiagnostic}
|
||||
| {
|
||||
kind: 'Impure';
|
||||
into: Place;
|
||||
category: ErrorCategory;
|
||||
reason: string;
|
||||
description: string;
|
||||
usageMessage: string;
|
||||
sourceMessage: string;
|
||||
}
|
||||
/**
|
||||
* Indicates that a given place is accessed during render. Used to distingush
|
||||
* hook arguments that are known to be called immediately vs those used for
|
||||
@@ -222,6 +230,14 @@ export function hashEffect(effect: AliasingEffect): string {
|
||||
return [effect.kind, effect.value.identifier.id, effect.reason].join(':');
|
||||
}
|
||||
case 'Impure':
|
||||
return [
|
||||
effect.kind,
|
||||
effect.into.identifier.id,
|
||||
effect.reason,
|
||||
effect.description,
|
||||
effect.usageMessage,
|
||||
effect.sourceMessage,
|
||||
].join(':');
|
||||
case 'Render': {
|
||||
return [effect.kind, effect.place.identifier.id].join(':');
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import {BlockId, computePostDominatorTree, HIRFunction, Place} from '../HIR';
|
||||
import {PostDominator} from '../HIR/Dominator';
|
||||
|
||||
export type ControlDominators = (id: BlockId) => boolean;
|
||||
export type ControlDominators = (id: BlockId) => Place | null;
|
||||
|
||||
/**
|
||||
* Returns an object that lazily calculates whether particular blocks are controlled
|
||||
@@ -23,7 +23,7 @@ export function createControlDominators(
|
||||
});
|
||||
const postDominatorFrontierCache = new Map<BlockId, Set<BlockId>>();
|
||||
|
||||
function isControlledBlock(id: BlockId): boolean {
|
||||
function isControlledBlock(id: BlockId): Place | null {
|
||||
let controlBlocks = postDominatorFrontierCache.get(id);
|
||||
if (controlBlocks === undefined) {
|
||||
controlBlocks = postDominatorFrontier(fn, postDominators, id);
|
||||
@@ -35,24 +35,24 @@ export function createControlDominators(
|
||||
case 'if':
|
||||
case 'branch': {
|
||||
if (isControlVariable(controlBlock.terminal.test)) {
|
||||
return true;
|
||||
return controlBlock.terminal.test;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'switch': {
|
||||
if (isControlVariable(controlBlock.terminal.test)) {
|
||||
return true;
|
||||
return controlBlock.terminal.test;
|
||||
}
|
||||
for (const case_ of controlBlock.terminal.cases) {
|
||||
if (case_.test !== null && isControlVariable(case_.test)) {
|
||||
return true;
|
||||
return case_.test;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
return isControlledBlock;
|
||||
|
||||
@@ -27,11 +27,12 @@ import {
|
||||
InstructionKind,
|
||||
InstructionValue,
|
||||
isArrayType,
|
||||
isJsxType,
|
||||
isJsxOrJsxUnionType,
|
||||
isMapType,
|
||||
isPrimitiveType,
|
||||
isRefOrRefValue,
|
||||
isSetType,
|
||||
isUseRefType,
|
||||
makeIdentifierId,
|
||||
Phi,
|
||||
Place,
|
||||
@@ -70,6 +71,7 @@ import {
|
||||
MutationReason,
|
||||
} from './AliasingEffects';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {REF_ERROR_DESCRIPTION} from '../Validation/ValidateNoRefAccessInRender';
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
@@ -569,14 +571,32 @@ function inferBlock(
|
||||
terminal.effects = effects.length !== 0 ? effects : null;
|
||||
}
|
||||
} else if (terminal.kind === 'return') {
|
||||
terminal.effects = [
|
||||
context.internEffect({
|
||||
kind: 'Alias',
|
||||
from: terminal.value,
|
||||
into: context.fn.returns,
|
||||
}),
|
||||
];
|
||||
if (!context.isFuctionExpression) {
|
||||
terminal.effects = [
|
||||
terminal.effects.push(
|
||||
context.internEffect({
|
||||
kind: 'Freeze',
|
||||
value: terminal.value,
|
||||
reason: ValueReason.JsxCaptured,
|
||||
}),
|
||||
];
|
||||
);
|
||||
}
|
||||
if (
|
||||
context.fn.fnType === 'Component' ||
|
||||
isJsxOrJsxUnionType(context.fn.returns.identifier.type)
|
||||
) {
|
||||
terminal.effects.push(
|
||||
context.internEffect({
|
||||
kind: 'Render',
|
||||
place: terminal.value,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -749,17 +769,7 @@ function applyEffect(
|
||||
break;
|
||||
}
|
||||
case 'ImmutableCapture': {
|
||||
const kind = state.kind(effect.from).kind;
|
||||
switch (kind) {
|
||||
case ValueKind.Global:
|
||||
case ValueKind.Primitive: {
|
||||
// no-op: we don't need to track data flow for copy types
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
effects.push(effect);
|
||||
}
|
||||
}
|
||||
effects.push(effect);
|
||||
break;
|
||||
}
|
||||
case 'CreateFrom': {
|
||||
@@ -1061,6 +1071,17 @@ function applyEffect(
|
||||
reason: new Set(fromValue.reason),
|
||||
});
|
||||
state.define(effect.into, value);
|
||||
applyEffect(
|
||||
context,
|
||||
state,
|
||||
{
|
||||
kind: 'ImmutableCapture',
|
||||
from: effect.from,
|
||||
into: effect.into,
|
||||
},
|
||||
initialized,
|
||||
effects,
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -1072,6 +1093,8 @@ function applyEffect(
|
||||
break;
|
||||
}
|
||||
case 'Apply': {
|
||||
effects.push(effect);
|
||||
|
||||
const functionValues = state.values(effect.function);
|
||||
if (
|
||||
functionValues.length === 1 &&
|
||||
@@ -1966,6 +1989,11 @@ function computeSignatureForInstruction(
|
||||
value: ValueKind.Primitive,
|
||||
reason: ValueReason.Other,
|
||||
});
|
||||
effects.push({
|
||||
kind: 'ImmutableCapture',
|
||||
from: value.object,
|
||||
into: lvalue,
|
||||
});
|
||||
} else {
|
||||
effects.push({
|
||||
kind: 'CreateFrom',
|
||||
@@ -1973,6 +2001,20 @@ function computeSignatureForInstruction(
|
||||
into: lvalue,
|
||||
});
|
||||
}
|
||||
if (
|
||||
env.config.validateRefAccessDuringRender &&
|
||||
isUseRefType(value.object.identifier)
|
||||
) {
|
||||
effects.push({
|
||||
kind: 'Impure',
|
||||
into: lvalue,
|
||||
category: ErrorCategory.Refs,
|
||||
reason: `Cannot access ref value during render`,
|
||||
description: REF_ERROR_DESCRIPTION,
|
||||
sourceMessage: `Ref is initially accessed`,
|
||||
usageMessage: `Ref value is used during render`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'PropertyStore':
|
||||
@@ -2137,6 +2179,15 @@ function computeSignatureForInstruction(
|
||||
into: lvalue,
|
||||
});
|
||||
}
|
||||
if (value.children != null) {
|
||||
// Children are typically called during render, not used as an event/effect callback
|
||||
for (const child of value.children) {
|
||||
effects.push({
|
||||
kind: 'Render',
|
||||
place: child,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (value.kind === 'JsxExpression') {
|
||||
if (value.tag.kind === 'Identifier') {
|
||||
// Tags are render function, by definition they're called during render
|
||||
@@ -2145,29 +2196,17 @@ function computeSignatureForInstruction(
|
||||
place: value.tag,
|
||||
});
|
||||
}
|
||||
if (value.children != null) {
|
||||
// Children are typically called during render, not used as an event/effect callback
|
||||
for (const child of value.children) {
|
||||
effects.push({
|
||||
kind: 'Render',
|
||||
place: child,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const prop of value.props) {
|
||||
if (
|
||||
prop.kind === 'JsxAttribute' &&
|
||||
prop.place.identifier.type.kind === 'Function' &&
|
||||
(isJsxType(prop.place.identifier.type.return) ||
|
||||
(prop.place.identifier.type.return.kind === 'Phi' &&
|
||||
prop.place.identifier.type.return.operands.some(operand =>
|
||||
isJsxType(operand),
|
||||
)))
|
||||
) {
|
||||
// Any props which return jsx are assumed to be called during render
|
||||
const place =
|
||||
prop.kind === 'JsxAttribute' ? prop.place : prop.argument;
|
||||
if (isUseRefType(place.identifier)) {
|
||||
continue;
|
||||
}
|
||||
if (place.identifier.type.kind !== 'Function') {
|
||||
// Functions are checked independently
|
||||
effects.push({
|
||||
kind: 'Render',
|
||||
place: prop.place,
|
||||
place,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2203,6 +2242,11 @@ function computeSignatureForInstruction(
|
||||
value: ValueKind.Primitive,
|
||||
reason: ValueReason.Other,
|
||||
});
|
||||
effects.push({
|
||||
kind: 'ImmutableCapture',
|
||||
from: value.value,
|
||||
into: place,
|
||||
});
|
||||
} else if (patternItem.kind === 'Identifier') {
|
||||
effects.push({
|
||||
kind: 'CreateFrom',
|
||||
@@ -2384,15 +2428,46 @@ function computeSignatureForInstruction(
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'BinaryExpression': {
|
||||
effects.push({
|
||||
kind: 'Create',
|
||||
into: lvalue,
|
||||
value: ValueKind.Primitive,
|
||||
reason: ValueReason.Other,
|
||||
});
|
||||
effects.push({
|
||||
kind: 'ImmutableCapture',
|
||||
into: lvalue,
|
||||
from: value.left,
|
||||
});
|
||||
effects.push({
|
||||
kind: 'ImmutableCapture',
|
||||
into: lvalue,
|
||||
from: value.right,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'UnaryExpression': {
|
||||
effects.push({
|
||||
kind: 'Create',
|
||||
into: lvalue,
|
||||
value: ValueKind.Primitive,
|
||||
reason: ValueReason.Other,
|
||||
});
|
||||
effects.push({
|
||||
kind: 'ImmutableCapture',
|
||||
into: lvalue,
|
||||
from: value.value,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'TaggedTemplateExpression':
|
||||
case 'BinaryExpression':
|
||||
case 'Debugger':
|
||||
case 'JSXText':
|
||||
case 'MetaProperty':
|
||||
case 'Primitive':
|
||||
case 'RegExpLiteral':
|
||||
case 'TemplateLiteral':
|
||||
case 'UnaryExpression':
|
||||
case 'UnsupportedNode': {
|
||||
effects.push({
|
||||
kind: 'Create',
|
||||
@@ -2423,7 +2498,7 @@ function computeEffectsForLegacySignature(
|
||||
lvalue: Place,
|
||||
receiver: Place,
|
||||
args: Array<Place | SpreadPattern | Hole>,
|
||||
loc: SourceLocation,
|
||||
_loc: SourceLocation,
|
||||
): Array<AliasingEffect> {
|
||||
const returnValueReason = signature.returnValueReason ?? ValueReason.Other;
|
||||
const effects: Array<AliasingEffect> = [];
|
||||
@@ -2436,20 +2511,18 @@ function computeEffectsForLegacySignature(
|
||||
if (signature.impure && state.env.config.validateNoImpureFunctionsInRender) {
|
||||
effects.push({
|
||||
kind: 'Impure',
|
||||
place: receiver,
|
||||
error: CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Purity,
|
||||
reason: 'Cannot call impure function during render',
|
||||
description:
|
||||
(signature.canonicalName != null
|
||||
? `\`${signature.canonicalName}\` is an impure function. `
|
||||
: '') +
|
||||
'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: 'Cannot call impure function',
|
||||
}),
|
||||
into: lvalue,
|
||||
category: ErrorCategory.Purity,
|
||||
reason: 'Cannot access impure value during render',
|
||||
description:
|
||||
'Calling an impure function can produce unstable results that update ' +
|
||||
'unpredictably when the component happens to re-render. ' +
|
||||
'(https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
|
||||
sourceMessage:
|
||||
signature.canonicalName != null
|
||||
? `\`${signature.canonicalName}\` is an impure function.`
|
||||
: 'This function is impure',
|
||||
usageMessage: 'Cannot access impure value during render',
|
||||
});
|
||||
}
|
||||
if (signature.knownIncompatible != null && state.env.enableValidations) {
|
||||
@@ -2748,7 +2821,23 @@ function computeEffectsForSignature(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Impure':
|
||||
case 'Impure': {
|
||||
if (env.config.validateNoImpureFunctionsInRender) {
|
||||
const values = substitutions.get(effect.into.identifier.id) ?? [];
|
||||
for (const value of values) {
|
||||
effects.push({
|
||||
kind: effect.kind,
|
||||
into: value,
|
||||
category: effect.category,
|
||||
reason: effect.reason,
|
||||
description: effect.description,
|
||||
sourceMessage: effect.sourceMessage,
|
||||
usageMessage: effect.usageMessage,
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'MutateFrozen':
|
||||
case 'MutateGlobal': {
|
||||
const values = substitutions.get(effect.place.identifier.id) ?? [];
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
ValueReason,
|
||||
Place,
|
||||
isPrimitiveType,
|
||||
isUseRefType,
|
||||
isJsxOrJsxUnionType,
|
||||
} from '../HIR/HIR';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
@@ -28,6 +30,9 @@ import {
|
||||
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {AliasingEffect, MutationReason} from './AliasingEffects';
|
||||
import {printIdentifier, printType} from '../HIR/PrintHIR';
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
/**
|
||||
* This pass builds an abstract model of the heap and interprets the effects of the
|
||||
@@ -104,7 +109,6 @@ export function inferMutationAliasingRanges(
|
||||
reason: MutationReason | null;
|
||||
}> = [];
|
||||
const renders: Array<{index: number; place: Place}> = [];
|
||||
|
||||
let index = 0;
|
||||
|
||||
const errors = new CompilerError();
|
||||
@@ -197,14 +201,12 @@ export function inferMutationAliasingRanges(
|
||||
});
|
||||
} else if (
|
||||
effect.kind === 'MutateFrozen' ||
|
||||
effect.kind === 'MutateGlobal' ||
|
||||
effect.kind === 'Impure'
|
||||
effect.kind === 'MutateGlobal'
|
||||
) {
|
||||
errors.pushDiagnostic(effect.error);
|
||||
functionEffects.push(effect);
|
||||
} else if (effect.kind === 'Render') {
|
||||
renders.push({index: index++, place: effect.place});
|
||||
functionEffects.push(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,10 +216,6 @@ export function inferMutationAliasingRanges(
|
||||
state.assign(index, from, into);
|
||||
}
|
||||
}
|
||||
if (block.terminal.kind === 'return') {
|
||||
state.assign(index++, block.terminal.value, fn.returns);
|
||||
}
|
||||
|
||||
if (
|
||||
(block.terminal.kind === 'maybe-throw' ||
|
||||
block.terminal.kind === 'return') &&
|
||||
@@ -227,23 +225,31 @@ export function inferMutationAliasingRanges(
|
||||
if (effect.kind === 'Alias') {
|
||||
state.assign(index++, effect.from, effect.into);
|
||||
} else {
|
||||
CompilerError.invariant(effect.kind === 'Freeze', {
|
||||
reason: `Unexpected '${effect.kind}' effect for MaybeThrow terminal`,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: block.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
CompilerError.invariant(
|
||||
effect.kind === 'Freeze' || effect.kind === 'Render',
|
||||
{
|
||||
reason: `Unexpected '${effect.kind}' effect for MaybeThrow terminal`,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: block.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const mutation of mutations) {
|
||||
if (DEBUG) {
|
||||
console.log(
|
||||
`[${mutation.index}] mutate ${printIdentifier(mutation.place.identifier)}`,
|
||||
);
|
||||
}
|
||||
state.mutate(
|
||||
mutation.index,
|
||||
mutation.place.identifier,
|
||||
@@ -255,8 +261,16 @@ export function inferMutationAliasingRanges(
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if (DEBUG) {
|
||||
console.log(state.debug());
|
||||
}
|
||||
for (const render of renders) {
|
||||
state.render(render.index, render.place.identifier, errors);
|
||||
if (DEBUG) {
|
||||
console.log(
|
||||
`[${render.index}] render ${printIdentifier(render.place.identifier)}`,
|
||||
);
|
||||
}
|
||||
state.render(render.index, render.place, errors);
|
||||
}
|
||||
for (const param of [...fn.context, ...fn.params]) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
@@ -383,17 +397,7 @@ export function inferMutationAliasingRanges(
|
||||
break;
|
||||
}
|
||||
case 'Apply': {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects`,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: effect.function.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'MutateTransitive':
|
||||
case 'MutateConditionally':
|
||||
@@ -515,6 +519,13 @@ export function inferMutationAliasingRanges(
|
||||
const ignoredErrors = new CompilerError();
|
||||
for (const param of [...fn.params, ...fn.context, fn.returns]) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
const node = state.nodes.get(place.identifier);
|
||||
if (node != null && node.render != null) {
|
||||
functionEffects.push({
|
||||
kind: 'Render',
|
||||
place: place,
|
||||
});
|
||||
}
|
||||
tracked.push(place);
|
||||
}
|
||||
for (const into of tracked) {
|
||||
@@ -568,7 +579,10 @@ export function inferMutationAliasingRanges(
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.hasAnyErrors() && !isFunctionExpression) {
|
||||
if (
|
||||
errors.hasAnyErrors() &&
|
||||
(!isFunctionExpression || isJsxOrJsxUnionType(fn.returns.identifier.type))
|
||||
) {
|
||||
return Err(errors);
|
||||
}
|
||||
return Ok(functionEffects);
|
||||
@@ -577,7 +591,6 @@ export function inferMutationAliasingRanges(
|
||||
function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
|
||||
for (const effect of fn.aliasingEffects ?? []) {
|
||||
switch (effect.kind) {
|
||||
case 'Impure':
|
||||
case 'MutateFrozen':
|
||||
case 'MutateGlobal': {
|
||||
errors.pushDiagnostic(effect.error);
|
||||
@@ -612,10 +625,74 @@ type Node = {
|
||||
| {kind: 'Object'}
|
||||
| {kind: 'Phi'}
|
||||
| {kind: 'Function'; function: HIRFunction};
|
||||
render: Place | null;
|
||||
};
|
||||
|
||||
function _printNode(node: Node): string {
|
||||
const out: Array<string> = [];
|
||||
debugNode(out, node);
|
||||
return out.join('\n');
|
||||
}
|
||||
function debugNode(out: Array<string>, node: Node): void {
|
||||
out.push(
|
||||
printIdentifier(node.id) +
|
||||
printType(node.id.type) +
|
||||
` lastMutated=[${node.lastMutated}]`,
|
||||
);
|
||||
if (node.transitive != null) {
|
||||
out.push(` transitive=${node.transitive.kind}`);
|
||||
}
|
||||
if (node.local != null) {
|
||||
out.push(` local=${node.local.kind}`);
|
||||
}
|
||||
if (node.mutationReason != null) {
|
||||
out.push(` mutationReason=${node.mutationReason?.kind}`);
|
||||
}
|
||||
const edges: Array<{
|
||||
index: number;
|
||||
direction: '<=' | '=>';
|
||||
kind: string;
|
||||
id: Identifier;
|
||||
}> = [];
|
||||
for (const [alias, index] of node.createdFrom) {
|
||||
edges.push({index, direction: '<=', kind: 'createFrom', id: alias});
|
||||
}
|
||||
for (const [alias, index] of node.aliases) {
|
||||
edges.push({index, direction: '<=', kind: 'alias', id: alias});
|
||||
}
|
||||
for (const [alias, index] of node.maybeAliases) {
|
||||
edges.push({index, direction: '<=', kind: 'alias?', id: alias});
|
||||
}
|
||||
for (const [alias, index] of node.captures) {
|
||||
edges.push({index, direction: '<=', kind: 'capture', id: alias});
|
||||
}
|
||||
for (const edge of node.edges) {
|
||||
edges.push({
|
||||
index: edge.index,
|
||||
direction: '=>',
|
||||
kind: edge.kind,
|
||||
id: edge.node,
|
||||
});
|
||||
}
|
||||
edges.sort((a, b) => a.index - b.index);
|
||||
for (const edge of edges) {
|
||||
out.push(
|
||||
` [${edge.index}] ${edge.direction} ${edge.kind} ${printIdentifier(edge.id)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AliasingState {
|
||||
nodes: Map<Identifier, Node> = new Map();
|
||||
|
||||
debug(): string {
|
||||
const items: Array<string> = [];
|
||||
for (const [_id, node] of this.nodes) {
|
||||
debugNode(items, node);
|
||||
}
|
||||
return items.join('\n');
|
||||
}
|
||||
|
||||
create(place: Place, value: Node['value']): void {
|
||||
this.nodes.set(place.identifier, {
|
||||
id: place.identifier,
|
||||
@@ -629,6 +706,7 @@ class AliasingState {
|
||||
lastMutated: 0,
|
||||
mutationReason: null,
|
||||
value,
|
||||
render: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -681,9 +759,9 @@ class AliasingState {
|
||||
}
|
||||
}
|
||||
|
||||
render(index: number, start: Identifier, errors: CompilerError): void {
|
||||
render(index: number, start: Place, errors: CompilerError): void {
|
||||
const seen = new Set<Identifier>();
|
||||
const queue: Array<Identifier> = [start];
|
||||
const queue: Array<Identifier> = [start.identifier];
|
||||
while (queue.length !== 0) {
|
||||
const current = queue.pop()!;
|
||||
if (seen.has(current)) {
|
||||
@@ -691,11 +769,34 @@ class AliasingState {
|
||||
}
|
||||
seen.add(current);
|
||||
const node = this.nodes.get(current);
|
||||
if (node == null || node.transitive != null || node.local != null) {
|
||||
if (node == null || isUseRefType(node.id)) {
|
||||
if (DEBUG) {
|
||||
console.log(` render ${printIdentifier(current)}: skip mutated/ref`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (node.value.kind === 'Function') {
|
||||
appendFunctionErrors(errors, node.value.function);
|
||||
if (
|
||||
node.local == null &&
|
||||
node.transitive == null &&
|
||||
node.value.kind === 'Function'
|
||||
) {
|
||||
const returns = node.value.function.returns;
|
||||
if (
|
||||
isJsxType(returns.identifier.type) ||
|
||||
(returns.identifier.type.kind === 'Phi' &&
|
||||
returns.identifier.type.operands.some(operand =>
|
||||
isJsxType(operand),
|
||||
))
|
||||
) {
|
||||
appendFunctionErrors(errors, node.value.function);
|
||||
}
|
||||
if (DEBUG) {
|
||||
console.log(` render ${printIdentifier(current)}: skip function`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (node.render == null) {
|
||||
node.render = start;
|
||||
}
|
||||
for (const [alias, when] of node.createdFrom) {
|
||||
if (when >= index) {
|
||||
@@ -709,6 +810,12 @@ class AliasingState {
|
||||
}
|
||||
queue.push(alias);
|
||||
}
|
||||
for (const [alias, when] of node.maybeAliases) {
|
||||
if (when >= index) {
|
||||
continue;
|
||||
}
|
||||
queue.push(alias);
|
||||
}
|
||||
for (const [capture, when] of node.captures) {
|
||||
if (when >= index) {
|
||||
continue;
|
||||
|
||||
@@ -167,6 +167,14 @@ export function Set_filter<T>(
|
||||
return result;
|
||||
}
|
||||
|
||||
export function Set_subtract<T>(
|
||||
source: ReadonlySet<T>,
|
||||
other: Iterable<T>,
|
||||
): Set<T> {
|
||||
const otherSet = other instanceof Set ? other : new Set(other);
|
||||
return Set_filter(source, item => !otherSet.has(item));
|
||||
}
|
||||
|
||||
export function hasNode<T>(
|
||||
input: NodePath<T | null | undefined>,
|
||||
): input is NodePath<NonNullable<T>> {
|
||||
|
||||
@@ -141,6 +141,7 @@ export function validateExhaustiveDependencies(
|
||||
reactive,
|
||||
startMemo.depsLoc,
|
||||
ErrorCategory.MemoDependencies,
|
||||
'all',
|
||||
);
|
||||
if (diagnostic != null) {
|
||||
error.pushDiagnostic(diagnostic);
|
||||
@@ -159,7 +160,7 @@ export function validateExhaustiveDependencies(
|
||||
onStartMemoize,
|
||||
onFinishMemoize,
|
||||
onEffect: (inferred, manual, manualMemoLoc) => {
|
||||
if (env.config.validateExhaustiveEffectDependencies === false) {
|
||||
if (env.config.validateExhaustiveEffectDependencies === 'off') {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
@@ -195,12 +196,17 @@ export function validateExhaustiveDependencies(
|
||||
});
|
||||
}
|
||||
}
|
||||
const effectReportMode =
|
||||
typeof env.config.validateExhaustiveEffectDependencies === 'string'
|
||||
? env.config.validateExhaustiveEffectDependencies
|
||||
: 'all';
|
||||
const diagnostic = validateDependencies(
|
||||
Array.from(inferred),
|
||||
manualDeps,
|
||||
reactive,
|
||||
manualMemoLoc,
|
||||
ErrorCategory.EffectExhaustiveDependencies,
|
||||
effectReportMode,
|
||||
);
|
||||
if (diagnostic != null) {
|
||||
error.pushDiagnostic(diagnostic);
|
||||
@@ -220,6 +226,7 @@ function validateDependencies(
|
||||
category:
|
||||
| ErrorCategory.MemoDependencies
|
||||
| ErrorCategory.EffectExhaustiveDependencies,
|
||||
exhaustiveDepsReportMode: 'all' | 'missing-only' | 'extra-only',
|
||||
): CompilerDiagnostic | null {
|
||||
// Sort dependencies by name and path, with shorter/non-optional paths first
|
||||
inferred.sort((a, b) => {
|
||||
@@ -370,9 +377,20 @@ function validateDependencies(
|
||||
extra.push(dep);
|
||||
}
|
||||
|
||||
if (missing.length !== 0 || extra.length !== 0) {
|
||||
// Filter based on report mode
|
||||
const filteredMissing =
|
||||
exhaustiveDepsReportMode === 'extra-only' ? [] : missing;
|
||||
const filteredExtra =
|
||||
exhaustiveDepsReportMode === 'missing-only' ? [] : extra;
|
||||
|
||||
if (filteredMissing.length !== 0 || filteredExtra.length !== 0) {
|
||||
let suggestion: CompilerSuggestion | null = null;
|
||||
if (manualMemoLoc != null && typeof manualMemoLoc !== 'symbol') {
|
||||
if (
|
||||
manualMemoLoc != null &&
|
||||
typeof manualMemoLoc !== 'symbol' &&
|
||||
manualMemoLoc.start.index != null &&
|
||||
manualMemoLoc.end.index != null
|
||||
) {
|
||||
suggestion = {
|
||||
description: 'Update dependencies',
|
||||
range: [manualMemoLoc.start.index, manualMemoLoc.end.index],
|
||||
@@ -388,8 +406,13 @@ function validateDependencies(
|
||||
.join(', ')}]`,
|
||||
};
|
||||
}
|
||||
const diagnostic = createDiagnostic(category, missing, extra, suggestion);
|
||||
for (const dep of missing) {
|
||||
const diagnostic = createDiagnostic(
|
||||
category,
|
||||
filteredMissing,
|
||||
filteredExtra,
|
||||
suggestion,
|
||||
);
|
||||
for (const dep of filteredMissing) {
|
||||
let reactiveStableValueHint = '';
|
||||
if (isStableType(dep.identifier)) {
|
||||
reactiveStableValueHint =
|
||||
@@ -402,7 +425,7 @@ function validateDependencies(
|
||||
loc: dep.loc,
|
||||
});
|
||||
}
|
||||
for (const dep of extra) {
|
||||
for (const dep of filteredExtra) {
|
||||
if (dep.root.kind === 'Global') {
|
||||
diagnostic.withDetails({
|
||||
kind: 'error',
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerDiagnostic, CompilerError} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {HIRFunction} from '../HIR';
|
||||
import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffects';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
/**
|
||||
* Checks that known-impure functions are not called during render. Examples of invalid functions to
|
||||
* call during render are `Math.random()` and `Date.now()`. Users may extend this set of
|
||||
* impure functions via a module type provider and specifying functions with `impure: true`.
|
||||
*
|
||||
* TODO: add best-effort analysis of functions which are called during render. We have variations of
|
||||
* this in several of our validation passes and should unify those analyses into a reusable helper
|
||||
* and use it here.
|
||||
*/
|
||||
export function validateNoImpureFunctionsInRender(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
const errors = new CompilerError();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
const value = instr.value;
|
||||
if (value.kind === 'MethodCall' || value.kind == 'CallExpression') {
|
||||
const callee =
|
||||
value.kind === 'MethodCall' ? value.property : value.callee;
|
||||
const signature = getFunctionCallSignature(
|
||||
fn.env,
|
||||
callee.identifier.type,
|
||||
);
|
||||
if (signature != null && signature.impure === true) {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Purity,
|
||||
reason: 'Cannot call impure function during render',
|
||||
description:
|
||||
(signature.canonicalName != null
|
||||
? `\`${signature.canonicalName}\` is an impure function. `
|
||||
: '') +
|
||||
'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: callee.loc,
|
||||
message: 'Cannot call impure function',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.asResult();
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerDiagnostic, CompilerError} from '..';
|
||||
import {
|
||||
areEqualSourceLocations,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
InstructionId,
|
||||
isJsxType,
|
||||
isUseRefType,
|
||||
} from '../HIR';
|
||||
import {AliasingEffect, hashEffect} from '../Inference/AliasingEffects';
|
||||
import {createControlDominators} from '../Inference/ControlDominators';
|
||||
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
|
||||
type ImpureEffect = Extract<AliasingEffect, {kind: 'Impure'}>;
|
||||
type RenderEffect = Extract<AliasingEffect, {kind: 'Render'}>;
|
||||
type FunctionCache = Map<HIRFunction, Map<string, ImpuritySignature>>;
|
||||
type ImpuritySignature = {
|
||||
effects: Map<IdentifierId, ImpureEffect>;
|
||||
error: CompilerError;
|
||||
returns: IdentifierId;
|
||||
};
|
||||
|
||||
export function validateNoImpureValuesInRender(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
const impure = new Map<IdentifierId, ImpureEffect>();
|
||||
const impureFunctions = new Map<IdentifierId, ImpuritySignature>();
|
||||
const result = inferImpureValues(fn, impure, impureFunctions, new Map());
|
||||
|
||||
if (result.error.hasAnyErrors()) {
|
||||
return Err(result.error);
|
||||
}
|
||||
return Ok(undefined);
|
||||
}
|
||||
|
||||
function inferFunctionExpressionMemo(
|
||||
fn: HIRFunction,
|
||||
impure: Map<IdentifierId, ImpureEffect>,
|
||||
impureFunctions: Map<IdentifierId, ImpuritySignature>,
|
||||
cache: FunctionCache,
|
||||
): ImpuritySignature {
|
||||
const key = fn.context
|
||||
.map(
|
||||
place =>
|
||||
`${place.identifier.id}:${impure.has(place.identifier.id)}:${Array.from(
|
||||
impureFunctions.get(place.identifier.id)?.effects ?? new Map(),
|
||||
)
|
||||
.map(([id, effect]) => `${id}=>${effect.into.identifier.id}`)
|
||||
.join(',')}`,
|
||||
)
|
||||
.join(',');
|
||||
return getOrInsertWith(
|
||||
getOrInsertWith(cache, fn, () => new Map()),
|
||||
key,
|
||||
() => inferImpureValues(fn, impure, impureFunctions, cache),
|
||||
);
|
||||
}
|
||||
|
||||
function processEffects(
|
||||
id: InstructionId,
|
||||
effects: Array<AliasingEffect>,
|
||||
impure: Map<IdentifierId, ImpureEffect>,
|
||||
impureFunctions: Map<IdentifierId, ImpuritySignature>,
|
||||
cache: FunctionCache,
|
||||
): boolean {
|
||||
let hasChanges = false;
|
||||
const rendered: Set<IdentifierId> = new Set();
|
||||
for (const effect of effects) {
|
||||
if (effect.kind === 'Render') {
|
||||
rendered.add(effect.place.identifier.id);
|
||||
}
|
||||
}
|
||||
for (const effect of effects) {
|
||||
switch (effect.kind) {
|
||||
case 'Alias':
|
||||
case 'Assign':
|
||||
case 'Capture':
|
||||
case 'CreateFrom':
|
||||
case 'ImmutableCapture': {
|
||||
const sourceEffect = impure.get(effect.from.identifier.id);
|
||||
if (
|
||||
sourceEffect != null &&
|
||||
!impure.has(effect.into.identifier.id) &&
|
||||
!rendered.has(effect.from.identifier.id) &&
|
||||
!isUseRefType(effect.into.identifier) &&
|
||||
!isJsxType(effect.into.identifier.type)
|
||||
) {
|
||||
impure.set(effect.into.identifier.id, sourceEffect);
|
||||
hasChanges = true;
|
||||
}
|
||||
if (
|
||||
sourceEffect == null &&
|
||||
(effect.kind === 'Assign' || effect.kind === 'Capture') &&
|
||||
!impure.has(effect.from.identifier.id) &&
|
||||
!rendered.has(effect.from.identifier.id) &&
|
||||
!isUseRefType(effect.from.identifier) &&
|
||||
isMutable({id}, effect.into)
|
||||
) {
|
||||
const destinationEffect = impure.get(effect.into.identifier.id);
|
||||
if (destinationEffect != null) {
|
||||
impure.set(effect.from.identifier.id, destinationEffect);
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
if (
|
||||
(effect.kind === 'Alias' ||
|
||||
effect.kind === 'Assign' ||
|
||||
effect.kind === 'ImmutableCapture') &&
|
||||
!rendered.has(effect.into.identifier.id) &&
|
||||
!isJsxType(effect.into.identifier.type)
|
||||
) {
|
||||
const functionEffect = impureFunctions.get(effect.from.identifier.id);
|
||||
if (
|
||||
functionEffect != null &&
|
||||
!impureFunctions.has(effect.into.identifier.id)
|
||||
/*
|
||||
* TODO: check if the function signature has changed (should be rare)
|
||||
* ||
|
||||
* !areEqualFunctionSignatures(
|
||||
* impureFunctions.get(effect.into.identifier.id)!.effects,
|
||||
* functionEffect.effects,
|
||||
* )
|
||||
*/
|
||||
) {
|
||||
impureFunctions.set(effect.into.identifier.id, functionEffect);
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Impure': {
|
||||
if (!impure.has(effect.into.identifier.id)) {
|
||||
impure.set(effect.into.identifier.id, effect);
|
||||
hasChanges = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Render': {
|
||||
break;
|
||||
}
|
||||
case 'CreateFunction': {
|
||||
const result = inferFunctionExpressionMemo(
|
||||
effect.function.loweredFunc.func,
|
||||
impure,
|
||||
impureFunctions,
|
||||
cache,
|
||||
);
|
||||
if (result.error.hasAnyErrors()) {
|
||||
break;
|
||||
}
|
||||
const previousResult = impureFunctions.get(effect.into.identifier.id);
|
||||
if (
|
||||
previousResult == null ||
|
||||
!areEqualFunctionSignatures(result.effects, previousResult.effects)
|
||||
) {
|
||||
impureFunctions.set(effect.into.identifier.id, result);
|
||||
hasChanges = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Apply': {
|
||||
const functionSignature = impureFunctions.get(
|
||||
effect.function.identifier.id,
|
||||
);
|
||||
if (functionSignature != null) {
|
||||
for (const [id, functionEffect] of functionSignature.effects) {
|
||||
if (!impure.has(id)) {
|
||||
impure.set(id, functionEffect);
|
||||
hasChanges = true;
|
||||
}
|
||||
if (
|
||||
id === functionSignature.returns &&
|
||||
!impure.has(effect.into.identifier.id)
|
||||
) {
|
||||
impure.set(effect.into.identifier.id, functionEffect);
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'MaybeAlias':
|
||||
case 'Create':
|
||||
case 'Freeze':
|
||||
case 'Mutate':
|
||||
case 'MutateConditionally':
|
||||
case 'MutateFrozen':
|
||||
case 'MutateGlobal':
|
||||
case 'MutateTransitive':
|
||||
case 'MutateTransitiveConditionally': {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
function inferImpureValues(
|
||||
fn: HIRFunction,
|
||||
impure: Map<IdentifierId, ImpureEffect>,
|
||||
impureFunctions: Map<IdentifierId, ImpuritySignature>,
|
||||
cache: FunctionCache,
|
||||
): ImpuritySignature {
|
||||
const getBlockControl = createControlDominators(fn, place => {
|
||||
return impure.has(place.identifier.id);
|
||||
});
|
||||
|
||||
let hasChanges = false;
|
||||
let iterations = 0;
|
||||
do {
|
||||
hasChanges = false;
|
||||
|
||||
if (iterations++ > 100) {
|
||||
throw new Error('too many iterations');
|
||||
}
|
||||
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const phi of block.phis) {
|
||||
if (impure.has(phi.place.identifier.id)) {
|
||||
// Already marked impure on a previous pass
|
||||
continue;
|
||||
}
|
||||
let impureEffect = null;
|
||||
for (const [, operand] of phi.operands) {
|
||||
const operandEffect = impure.get(operand.identifier.id);
|
||||
if (operandEffect != null) {
|
||||
impureEffect = operandEffect;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (impureEffect != null) {
|
||||
impure.set(phi.place.identifier.id, impureEffect);
|
||||
hasChanges = true;
|
||||
} else {
|
||||
for (const [pred] of phi.operands) {
|
||||
const predControl = getBlockControl(pred);
|
||||
if (predControl != null) {
|
||||
const predEffect = impure.get(predControl.identifier.id);
|
||||
if (predEffect != null) {
|
||||
impure.set(phi.place.identifier.id, predEffect);
|
||||
hasChanges = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: consider propagating impurity for assignments/mutations that
|
||||
* are controlled by an impure value.
|
||||
*
|
||||
* ```
|
||||
* const controlPlace = getBlockControl(block.id);
|
||||
* const controlImpureEffect =
|
||||
* controlPlace != null ? impure.get(controlPlace.identifier.id) : null;
|
||||
* ```
|
||||
*
|
||||
* Example
|
||||
*
|
||||
* This should error since we know the semantics of array.push, it's a definite
|
||||
* Mutate and definite Capture, not maybemutate+maybecapture:
|
||||
*
|
||||
* ```
|
||||
* let x = [];
|
||||
* if (Date.now() < START_DATE) {
|
||||
* x.push(1);
|
||||
* }
|
||||
* return <Foo x={x} />
|
||||
* ```
|
||||
*/
|
||||
for (const instr of block.instructions) {
|
||||
hasChanges =
|
||||
processEffects(
|
||||
instr.id,
|
||||
instr.effects ?? [],
|
||||
impure,
|
||||
impureFunctions,
|
||||
cache,
|
||||
) || hasChanges;
|
||||
}
|
||||
if (block.terminal.kind === 'return' && block.terminal.effects != null) {
|
||||
hasChanges =
|
||||
processEffects(
|
||||
block.terminal.id,
|
||||
block.terminal.effects,
|
||||
impure,
|
||||
impureFunctions,
|
||||
cache,
|
||||
) || hasChanges;
|
||||
}
|
||||
}
|
||||
} while (hasChanges);
|
||||
|
||||
fn.env.logger?.debugLogIRs?.({
|
||||
kind: 'debug',
|
||||
name: 'ValidateNoImpureValuesInRender',
|
||||
value: JSON.stringify(Array.from(impure.keys()).sort(), null, 2),
|
||||
});
|
||||
fn.env.logger?.debugLogIRs?.({
|
||||
kind: 'debug',
|
||||
name: 'ValidateNoImpureValuesInRender (function)',
|
||||
value: JSON.stringify(Array.from(impureFunctions.keys()).sort(), null, 2),
|
||||
});
|
||||
|
||||
const error = new CompilerError();
|
||||
function validateRenderEffect(effect: RenderEffect): void {
|
||||
let impureEffect = impure.get(effect.place.identifier.id);
|
||||
if (impureEffect == null) {
|
||||
const functionSignature = impureFunctions.get(effect.place.identifier.id);
|
||||
impureEffect = functionSignature?.effects.get(functionSignature.returns);
|
||||
}
|
||||
if (impureEffect == null) {
|
||||
return;
|
||||
}
|
||||
const diagnostic = CompilerDiagnostic.create({
|
||||
category: impureEffect.category,
|
||||
reason: impureEffect.reason,
|
||||
description: impureEffect.description,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: effect.place.loc,
|
||||
message: impureEffect.usageMessage,
|
||||
});
|
||||
if (!areEqualSourceLocations(effect.place.loc, impureEffect.into.loc)) {
|
||||
diagnostic.withDetails({
|
||||
kind: 'error',
|
||||
loc: impureEffect.into.loc,
|
||||
message: impureEffect.sourceMessage,
|
||||
});
|
||||
}
|
||||
error.pushDiagnostic(diagnostic);
|
||||
}
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const instr of block.instructions) {
|
||||
const value = instr.value;
|
||||
if (
|
||||
value.kind === 'FunctionExpression' ||
|
||||
value.kind === 'ObjectMethod'
|
||||
) {
|
||||
const result = inferFunctionExpressionMemo(
|
||||
value.loweredFunc.func,
|
||||
impure,
|
||||
impureFunctions,
|
||||
cache,
|
||||
);
|
||||
if (result.error.hasAnyErrors()) {
|
||||
error.merge(result.error);
|
||||
}
|
||||
}
|
||||
for (const effect of instr.effects ?? []) {
|
||||
if (effect.kind === 'Render') {
|
||||
validateRenderEffect(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (block.terminal.kind === 'return' && block.terminal.effects != null) {
|
||||
for (const effect of block.terminal.effects) {
|
||||
if (effect.kind === 'Render') {
|
||||
validateRenderEffect(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const impureEffects: Map<IdentifierId, ImpureEffect> = new Map();
|
||||
for (const param of [...fn.context, ...fn.params, fn.returns]) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
const impureEffect = impure.get(place.identifier.id);
|
||||
if (impureEffect != null) {
|
||||
impureEffects.set(place.identifier.id, impureEffect);
|
||||
}
|
||||
}
|
||||
return {effects: impureEffects, error, returns: fn.returns.identifier.id};
|
||||
}
|
||||
|
||||
function areEqualFunctionSignatures(
|
||||
sig1: Map<IdentifierId, ImpureEffect>,
|
||||
sig2: Map<IdentifierId, ImpureEffect>,
|
||||
): boolean {
|
||||
return (
|
||||
sig1.size === sig2.size &&
|
||||
Array.from(sig1).every(
|
||||
([id, effect]) =>
|
||||
sig2.has(id) && hashEffect(effect) === hashEffect(sig2.get(id)!),
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
# ValidateNoRefAccessInRender
|
||||
|
||||
This document summarizes the design and key learnings for the ref mutation validation pass.
|
||||
|
||||
## Purpose
|
||||
|
||||
Validates that a function does not mutate a ref value during render. This ensures React components follow the rules of React by not writing to `ref.current` during the render phase.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Ref vs RefValue
|
||||
|
||||
- **Ref**: The ref object itself (e.g., `useRef()` return value). Has type `React.RefObject<T>`.
|
||||
- **RefValue**: The `.current` property of a ref. This is the mutable value that should not be accessed during render.
|
||||
|
||||
The validation tracks both using a `RefInfo` type with a `refId` that correlates refs with their `.current` values.
|
||||
|
||||
### What Constitutes a Mutation
|
||||
|
||||
A mutation is any `PropertyStore` or `ComputedStore` instruction where:
|
||||
1. The target object is a known ref (tracked in the `refs` map)
|
||||
2. OR the target object has a ref type (`isUseRefType`)
|
||||
|
||||
### Allowed Patterns
|
||||
|
||||
1. **Event handlers and effect callbacks**: Functions that are not called at the top level during render can mutate refs freely.
|
||||
|
||||
2. **Null-guard initialization**: The pattern `if (ref.current == null) { ref.current = value; }` is allowed because it's a common lazy initialization pattern that only runs once.
|
||||
|
||||
## Algorithm: Single Forward Data-Flow Pass
|
||||
|
||||
The validation uses a single forward pass over all blocks:
|
||||
|
||||
### Phase 1: Track Refs
|
||||
- Initialize refs from function params and context (captured variables)
|
||||
- Process phi nodes to propagate ref info through control flow joins
|
||||
- Track refs through LoadLocal, StoreLocal, PropertyLoad operations
|
||||
|
||||
### Phase 2: Detect Null Guards
|
||||
- Track nullable values (null literals, undefined)
|
||||
- Track binary comparisons of `ref.current` to null (`==`, `===`, `!=`, `!==`)
|
||||
- Mark blocks as "safe" for specific refs when inside null-guard branches
|
||||
- Propagate safety through control flow until fallthrough
|
||||
|
||||
### Phase 3: Validate Mutations
|
||||
- For PropertyStore/ComputedStore on refs:
|
||||
- If inside a null-guard for this ref: allow (but track for duplicate detection)
|
||||
- If at top level: error immediately
|
||||
- If in nested function: track for later (error if function is called)
|
||||
|
||||
### Phase 4: Track Ref-Mutating Functions
|
||||
- When a FunctionExpression mutates a ref, track it in `refMutatingFunctions`
|
||||
- When such a function is called at top level, report the error at the mutation site
|
||||
|
||||
## Key Data Structures
|
||||
|
||||
```typescript
|
||||
// Correlates refs with their .current values
|
||||
type RefInfo = {
|
||||
kind: 'Ref' | 'RefValue';
|
||||
refId: number;
|
||||
};
|
||||
|
||||
// Tracks null-guard conditions
|
||||
type GuardInfo = {
|
||||
refId: number;
|
||||
isEquality: boolean; // true for ==, ===; false for !=, !==
|
||||
};
|
||||
|
||||
// Information about a mutation (for error reporting)
|
||||
type MutationInfo = {
|
||||
loc: SourceLocation;
|
||||
isCurrentProperty: boolean;
|
||||
};
|
||||
```
|
||||
|
||||
## Error Reporting
|
||||
|
||||
### Error Location
|
||||
|
||||
Errors highlight the **entire instruction** (e.g., `ref.current = value`), not just the ref identifier. This is achieved by using `instr.loc` instead of `value.object.loc`.
|
||||
|
||||
### Duplicate Initialization
|
||||
|
||||
When a ref is initialized more than once inside a null-guard:
|
||||
1. Primary error: Points to the second initialization
|
||||
2. Secondary error: Points to the first initialization with "Ref was first initialized here"
|
||||
|
||||
### Transitive Mutations
|
||||
|
||||
When a function that mutates refs is called during render:
|
||||
- The error points to the mutation site inside the function
|
||||
- Not the call site (the call site is what triggers the check)
|
||||
|
||||
## Edge Cases and Patterns
|
||||
|
||||
### Unary NOT on Guards
|
||||
|
||||
The `!` operator inverts guard polarity:
|
||||
```javascript
|
||||
if (!ref.current) { ... } // Same as: if (ref.current == null)
|
||||
```
|
||||
|
||||
### Nested Functions
|
||||
|
||||
Functions defined during render but not called are allowed to mutate refs:
|
||||
```javascript
|
||||
// OK - onClick is not called during render
|
||||
const onClick = () => { ref.current = value; };
|
||||
return <button onClick={onClick} />;
|
||||
```
|
||||
|
||||
### Props with Ref Type
|
||||
|
||||
Refs can come from props. The validation handles `props.ref` by checking type information.
|
||||
|
||||
## Limitations / Known Gaps
|
||||
|
||||
The following patterns are NOT currently validated by this pass:
|
||||
|
||||
1. **Impure values in render**: `Date.now()`, `Math.random()` flowing into render context (handled by `ValidateNoImpureValuesInRender`)
|
||||
|
||||
2. **useState/useReducer callbacks**: These hooks call their initializer functions during render, so ref access inside them should error. This requires special hook semantics.
|
||||
|
||||
3. **Ref reads during render**: This pass focuses on mutations. Ref reads are handled separately.
|
||||
|
||||
## Testing
|
||||
|
||||
Test fixtures use naming conventions:
|
||||
- `error.*.ts` - Fixtures expected to produce compilation errors
|
||||
- Regular names - Fixtures expected to compile successfully
|
||||
|
||||
Run tests with:
|
||||
```bash
|
||||
yarn snap -p <pattern> --nodebug # Run specific tests
|
||||
yarn snap -p <pattern> --nodebug -u # Update expected output
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -202,10 +202,10 @@ function getSetStateCall(
|
||||
);
|
||||
};
|
||||
|
||||
const isRefControlledBlock: (id: BlockId) => boolean =
|
||||
const isRefControlledBlock: (id: BlockId) => Place | null =
|
||||
enableAllowSetStateFromRefsInEffects
|
||||
? createControlDominators(fn, place => isDerivedFromRef(place))
|
||||
: (): boolean => false;
|
||||
: (): Place | null => null;
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
if (enableAllowSetStateFromRefsInEffects) {
|
||||
@@ -224,7 +224,7 @@ function getSetStateCall(
|
||||
refDerivedValues.add(phi.place.identifier.id);
|
||||
} else {
|
||||
for (const [pred] of phi.operands) {
|
||||
if (isRefControlledBlock(pred)) {
|
||||
if (isRefControlledBlock(pred) != null) {
|
||||
refDerivedValues.add(phi.place.identifier.id);
|
||||
break;
|
||||
}
|
||||
@@ -337,7 +337,7 @@ function getSetStateCall(
|
||||
* be needed when initial layout measurements from refs need to be stored in state.
|
||||
*/
|
||||
return null;
|
||||
} else if (isRefControlledBlock(block.id)) {
|
||||
} else if (isRefControlledBlock(block.id) != null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useRef} from 'react';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render.
|
||||
*/
|
||||
function Component() {
|
||||
const ref = useRef(null);
|
||||
const object = {};
|
||||
object.foo = () => ref.current;
|
||||
const refValue = object.foo();
|
||||
return <div>{refValue}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useRef } from "react";
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render.
|
||||
*/
|
||||
function Component() {
|
||||
const $ = _c(2);
|
||||
const ref = useRef(null);
|
||||
const object = {};
|
||||
object.foo = () => ref.current;
|
||||
const refValue = object.foo();
|
||||
let t0;
|
||||
if ($[0] !== refValue) {
|
||||
t0 = <div>{refValue}</div>;
|
||||
$[0] = refValue;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -1,5 +1,9 @@
|
||||
import {useRef} from 'react';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render.
|
||||
*/
|
||||
function Component() {
|
||||
const ref = useRef(null);
|
||||
const object = {};
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useRef} from 'react';
|
||||
import {addOne} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render.
|
||||
*/
|
||||
function useKeyCommand() {
|
||||
const currentPosition = useRef(0);
|
||||
const handleKey = direction => () => {
|
||||
const position = currentPosition.current;
|
||||
const nextPosition = direction === 'left' ? addOne(position) : position;
|
||||
currentPosition.current = nextPosition;
|
||||
};
|
||||
const moveLeft = {
|
||||
handler: handleKey('left')(),
|
||||
};
|
||||
const moveRight = {
|
||||
handler: handleKey('right')(),
|
||||
};
|
||||
return [moveLeft, moveRight];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useKeyCommand,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useRef } from "react";
|
||||
import { addOne } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render.
|
||||
*/
|
||||
function useKeyCommand() {
|
||||
const $ = _c(1);
|
||||
const currentPosition = useRef(0);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const handleKey = (direction) => () => {
|
||||
const position = currentPosition.current;
|
||||
const nextPosition = direction === "left" ? addOne(position) : position;
|
||||
currentPosition.current = nextPosition;
|
||||
};
|
||||
const moveLeft = { handler: handleKey("left")() };
|
||||
const moveRight = { handler: handleKey("right")() };
|
||||
t0 = [moveLeft, moveRight];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useKeyCommand,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [{},{}]
|
||||
@@ -1,6 +1,10 @@
|
||||
import {useRef} from 'react';
|
||||
import {addOne} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render.
|
||||
*/
|
||||
function useKeyCommand() {
|
||||
const currentPosition = useRef(0);
|
||||
const handleKey = direction => () => {
|
||||
@@ -5,6 +5,10 @@
|
||||
import {useRef} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render.
|
||||
*/
|
||||
function Component(props) {
|
||||
const ref = useRef(props.value);
|
||||
const object = {};
|
||||
@@ -26,6 +30,10 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { useRef } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render.
|
||||
*/
|
||||
function Component(props) {
|
||||
const $ = _c(1);
|
||||
const ref = useRef(props.value);
|
||||
@@ -1,6 +1,10 @@
|
||||
import {useRef} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render.
|
||||
*/
|
||||
function Component(props) {
|
||||
const ref = useRef(props.value);
|
||||
const object = {};
|
||||
@@ -5,6 +5,7 @@
|
||||
function Component() {
|
||||
const Foo = () => {
|
||||
someGlobal = true;
|
||||
return <div />;
|
||||
};
|
||||
return <Foo />;
|
||||
}
|
||||
@@ -26,9 +27,9 @@ error.assign-global-in-component-tag-function.ts:3:4
|
||||
2 | const Foo = () => {
|
||||
> 3 | someGlobal = true;
|
||||
| ^^^^^^^^^^ `someGlobal` cannot be reassigned
|
||||
4 | };
|
||||
5 | return <Foo />;
|
||||
6 | }
|
||||
4 | return <div />;
|
||||
5 | };
|
||||
6 | return <Foo />;
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
function Component() {
|
||||
const Foo = () => {
|
||||
someGlobal = true;
|
||||
return <div />;
|
||||
};
|
||||
return <Foo />;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
function Component() {
|
||||
const foo = () => {
|
||||
someGlobal = true;
|
||||
return <div />;
|
||||
};
|
||||
// Children are generally access/called during render, so
|
||||
// modifying a global in a children function is almost
|
||||
@@ -29,9 +30,9 @@ error.assign-global-in-jsx-children.ts:3:4
|
||||
2 | const foo = () => {
|
||||
> 3 | someGlobal = true;
|
||||
| ^^^^^^^^^^ `someGlobal` cannot be reassigned
|
||||
4 | };
|
||||
5 | // Children are generally access/called during render, so
|
||||
6 | // modifying a global in a children function is almost
|
||||
4 | return <div />;
|
||||
5 | };
|
||||
6 | // Children are generally access/called during render, so
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
function Component() {
|
||||
const foo = () => {
|
||||
someGlobal = true;
|
||||
return <div />;
|
||||
};
|
||||
// Children are generally access/called during render, so
|
||||
// modifying a global in a children function is almost
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useRef} from 'react';
|
||||
import {addOne} from 'shared-runtime';
|
||||
|
||||
function useKeyCommand() {
|
||||
const currentPosition = useRef(0);
|
||||
const handleKey = direction => () => {
|
||||
const position = currentPosition.current;
|
||||
const nextPosition = direction === 'left' ? addOne(position) : position;
|
||||
currentPosition.current = nextPosition;
|
||||
};
|
||||
const moveLeft = {
|
||||
handler: handleKey('left')(),
|
||||
};
|
||||
const moveRight = {
|
||||
handler: handleKey('right')(),
|
||||
};
|
||||
return [moveLeft, moveRight];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useKeyCommand,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.capture-ref-for-mutation.ts:12:13
|
||||
10 | };
|
||||
11 | const moveLeft = {
|
||||
> 12 | handler: handleKey('left')(),
|
||||
| ^^^^^^^^^^^^^^^^^ This function accesses a ref value
|
||||
13 | };
|
||||
14 | const moveRight = {
|
||||
15 | handler: handleKey('right')(),
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.capture-ref-for-mutation.ts:15:13
|
||||
13 | };
|
||||
14 | const moveRight = {
|
||||
> 15 | handler: handleKey('right')(),
|
||||
| ^^^^^^^^^^^^^^^^^^ This function accesses a ref value
|
||||
16 | };
|
||||
17 | return [moveLeft, moveRight];
|
||||
18 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -20,30 +20,26 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.hook-ref-value.ts:5:23
|
||||
error.hook-ref-value.ts:5:22
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef();
|
||||
> 5 | useEffect(() => {}, [ref.current]);
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
| ^^^^^^^^^^^^^ Ref value is used during render
|
||||
6 | }
|
||||
7 |
|
||||
8 | export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.hook-ref-value.ts:5:23
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef();
|
||||
> 5 | useEffect(() => {}, [ref.current]);
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
| ^^^^^^^^^^^ Ref is initially accessed
|
||||
6 | }
|
||||
7 |
|
||||
8 | export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -17,15 +17,23 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-access-ref-during-render.ts:5:9
|
||||
3 | const ref = useRef(null);
|
||||
4 | const value = ref.current;
|
||||
> 5 | return value;
|
||||
| ^^^^^ Ref value is used during render
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
error.invalid-access-ref-during-render.ts:4:16
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef(null);
|
||||
> 4 | const value = ref.current;
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
| ^^^^^^^^^^^ Ref is initially accessed
|
||||
5 | return value;
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
@@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
@@ -36,7 +36,16 @@ error.invalid-access-ref-in-reducer-init.ts:8:4
|
||||
6 | (state, action) => state + action,
|
||||
7 | 0,
|
||||
> 8 | init => ref.current
|
||||
| ^^^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
|
||||
| ^^^^^^^^^^^^^^^^^^^ Ref value is used during render
|
||||
9 | );
|
||||
10 |
|
||||
11 | return <Stringify state={state} />;
|
||||
|
||||
error.invalid-access-ref-in-reducer-init.ts:8:12
|
||||
6 | (state, action) => state + action,
|
||||
7 | 0,
|
||||
> 8 | init => ref.current
|
||||
| ^^^^^^^^^^^ Ref is initially accessed
|
||||
9 | );
|
||||
10 |
|
||||
11 | return <Stringify state={state} />;
|
||||
|
||||
@@ -22,9 +22,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
@@ -32,7 +32,38 @@ error.invalid-access-ref-in-reducer.ts:5:29
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef(props.value);
|
||||
> 5 | const [state] = useReducer(() => ref.current, null);
|
||||
| ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
|
||||
| ^^^^^^^^^^^^^^^^^ Ref value is used during render
|
||||
6 |
|
||||
7 | return <Stringify state={state} />;
|
||||
8 | }
|
||||
|
||||
error.invalid-access-ref-in-reducer.ts:5:35
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef(props.value);
|
||||
> 5 | const [state] = useReducer(() => ref.current, null);
|
||||
| ^^^^^^^^^^^ Ref is initially accessed
|
||||
6 |
|
||||
7 | return <Stringify state={state} />;
|
||||
8 | }
|
||||
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-access-ref-in-reducer.ts:7:27
|
||||
5 | const [state] = useReducer(() => ref.current, null);
|
||||
6 |
|
||||
> 7 | return <Stringify state={state} />;
|
||||
| ^^^^^ Ref value is used during render
|
||||
8 | }
|
||||
9 |
|
||||
10 | export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
error.invalid-access-ref-in-reducer.ts:5:35
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef(props.value);
|
||||
> 5 | const [state] = useReducer(() => ref.current, null);
|
||||
| ^^^^^^^^^^^ Ref is initially accessed
|
||||
6 |
|
||||
7 | return <Stringify state={state} />;
|
||||
8 | }
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useRef} from 'react';
|
||||
|
||||
function Component() {
|
||||
const ref = useRef(null);
|
||||
const object = {};
|
||||
object.foo = () => ref.current;
|
||||
const refValue = object.foo();
|
||||
return <div>{refValue}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-access-ref-in-render-mutate-object-with-ref-function.ts:7:19
|
||||
5 | const object = {};
|
||||
6 | object.foo = () => ref.current;
|
||||
> 7 | const refValue = object.foo();
|
||||
| ^^^^^^^^^^ This function accesses a ref value
|
||||
8 | return <div>{refValue}</div>;
|
||||
9 | }
|
||||
10 |
|
||||
```
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
@@ -32,7 +32,38 @@ error.invalid-access-ref-in-state-initializer.ts:5:27
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef(props.value);
|
||||
> 5 | const [state] = useState(() => ref.current);
|
||||
| ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
|
||||
| ^^^^^^^^^^^^^^^^^ Ref value is used during render
|
||||
6 |
|
||||
7 | return <Stringify state={state} />;
|
||||
8 | }
|
||||
|
||||
error.invalid-access-ref-in-state-initializer.ts:5:33
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef(props.value);
|
||||
> 5 | const [state] = useState(() => ref.current);
|
||||
| ^^^^^^^^^^^ Ref is initially accessed
|
||||
6 |
|
||||
7 | return <Stringify state={state} />;
|
||||
8 | }
|
||||
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-access-ref-in-state-initializer.ts:7:27
|
||||
5 | const [state] = useState(() => ref.current);
|
||||
6 |
|
||||
> 7 | return <Stringify state={state} />;
|
||||
| ^^^^^ Ref value is used during render
|
||||
8 | }
|
||||
9 |
|
||||
10 | export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
error.invalid-access-ref-in-state-initializer.ts:5:33
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef(props.value);
|
||||
> 5 | const [state] = useState(() => ref.current);
|
||||
| ^^^^^^^^^^^ Ref is initially accessed
|
||||
6 |
|
||||
7 | return <Stringify state={state} />;
|
||||
8 | }
|
||||
|
||||
@@ -21,17 +21,27 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:9:33
|
||||
7 | return <Foo item={item} current={current} />;
|
||||
error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:7:37
|
||||
5 | const aliasedRef = ref;
|
||||
6 | const current = aliasedRef.current;
|
||||
> 7 | return <Foo item={item} current={current} />;
|
||||
| ^^^^^^^ Ref value is used during render
|
||||
8 | };
|
||||
> 9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ Cannot access ref value during render
|
||||
9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
|
||||
10 | }
|
||||
11 |
|
||||
|
||||
error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:6:20
|
||||
4 | const renderItem = item => {
|
||||
5 | const aliasedRef = ref;
|
||||
> 6 | const current = aliasedRef.current;
|
||||
| ^^^^^^^^^^^^^^^^^^ Ref is initially accessed
|
||||
7 | return <Foo item={item} current={current} />;
|
||||
8 | };
|
||||
9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
|
||||
```
|
||||
|
||||
|
||||
@@ -27,14 +27,14 @@ Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-disallow-mutating-refs-in-render-transitive.ts:9:2
|
||||
7 | };
|
||||
8 | const changeRef = setRef;
|
||||
> 9 | changeRef();
|
||||
| ^^^^^^^^^ This function accesses a ref value
|
||||
10 |
|
||||
11 | return <button ref={ref} />;
|
||||
12 | }
|
||||
error.invalid-disallow-mutating-refs-in-render-transitive.ts:6:4
|
||||
4 |
|
||||
5 | const setRef = () => {
|
||||
> 6 | ref.current = false;
|
||||
| ^^^^^^^^^^^ Cannot update ref during render
|
||||
7 | };
|
||||
8 | const changeRef = setRef;
|
||||
9 | changeRef();
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoImpureFunctionsInRender
|
||||
|
||||
import {arrayPush, identity, makeArray} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render. The impurity is lost
|
||||
* when passed through external function calls.
|
||||
*/
|
||||
function Component() {
|
||||
const getDate = () => Date.now();
|
||||
const now = getDate();
|
||||
const array = [];
|
||||
arrayPush(array, now);
|
||||
return <Foo hasDate={array} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-functions-in-render-indirect-via-mutation.ts:15:23
|
||||
13 | const array = [];
|
||||
14 | arrayPush(array, now);
|
||||
> 15 | return <Foo hasDate={array} />;
|
||||
| ^^^^^ Cannot access impure value during render
|
||||
16 | }
|
||||
17 |
|
||||
|
||||
error.invalid-impure-functions-in-render-indirect-via-mutation.ts:11:24
|
||||
9 | */
|
||||
10 | function Component() {
|
||||
> 11 | const getDate = () => Date.now();
|
||||
| ^^^^^^^^^^ `Date.now` is an impure function.
|
||||
12 | const now = getDate();
|
||||
13 | const array = [];
|
||||
14 | arrayPush(array, now);
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// @validateNoImpureFunctionsInRender
|
||||
|
||||
import {arrayPush, identity, makeArray} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render. The impurity is lost
|
||||
* when passed through external function calls.
|
||||
*/
|
||||
function Component() {
|
||||
const getDate = () => Date.now();
|
||||
const now = getDate();
|
||||
const array = [];
|
||||
arrayPush(array, now);
|
||||
return <Foo hasDate={array} />;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoImpureFunctionsInRender
|
||||
|
||||
import {identity, makeArray} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
const now = Date.now();
|
||||
const f = () => {
|
||||
const array = makeArray(now);
|
||||
const hasDate = identity(array);
|
||||
return hasDate;
|
||||
};
|
||||
const hasDate = f();
|
||||
return <Foo hasDate={hasDate} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-functions-in-render-via-function-call.ts:13:23
|
||||
11 | };
|
||||
12 | const hasDate = f();
|
||||
> 13 | return <Foo hasDate={hasDate} />;
|
||||
| ^^^^^^^ Cannot access impure value during render
|
||||
14 | }
|
||||
15 |
|
||||
|
||||
error.invalid-impure-functions-in-render-via-function-call.ts:6:14
|
||||
4 |
|
||||
5 | function Component() {
|
||||
> 6 | const now = Date.now();
|
||||
| ^^^^^^^^^^ `Date.now` is an impure function.
|
||||
7 | const f = () => {
|
||||
8 | const array = makeArray(now);
|
||||
9 | const hasDate = identity(array);
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// @validateNoImpureFunctionsInRender
|
||||
|
||||
import {identity, makeArray} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
const now = Date.now();
|
||||
const f = () => {
|
||||
const array = makeArray(now);
|
||||
const hasDate = identity(array);
|
||||
return hasDate;
|
||||
};
|
||||
const hasDate = f();
|
||||
return <Foo hasDate={hasDate} />;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoImpureFunctionsInRender
|
||||
|
||||
import {typedArrayPush, typedIdentity} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
const now = () => Date.now();
|
||||
const renderItem = () => {
|
||||
const array = [];
|
||||
typedArrayPush(array, now());
|
||||
const hasDate = typedIdentity(array);
|
||||
return <Bar hasDate={hasDate} />;
|
||||
};
|
||||
return <Foo renderItem={renderItem} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-functions-in-render-via-render-helper-typed.ts:11:25
|
||||
9 | typedArrayPush(array, now());
|
||||
10 | const hasDate = typedIdentity(array);
|
||||
> 11 | return <Bar hasDate={hasDate} />;
|
||||
| ^^^^^^^ Cannot access impure value during render
|
||||
12 | };
|
||||
13 | return <Foo renderItem={renderItem} />;
|
||||
14 | }
|
||||
|
||||
error.invalid-impure-functions-in-render-via-render-helper-typed.ts:6:20
|
||||
4 |
|
||||
5 | function Component() {
|
||||
> 6 | const now = () => Date.now();
|
||||
| ^^^^^^^^^^ `Date.now` is an impure function.
|
||||
7 | const renderItem = () => {
|
||||
8 | const array = [];
|
||||
9 | typedArrayPush(array, now());
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// @validateNoImpureFunctionsInRender
|
||||
|
||||
import {typedArrayPush, typedIdentity} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
const now = () => Date.now();
|
||||
const renderItem = () => {
|
||||
const array = [];
|
||||
typedArrayPush(array, now());
|
||||
const hasDate = typedIdentity(array);
|
||||
return <Bar hasDate={hasDate} />;
|
||||
};
|
||||
return <Foo renderItem={renderItem} />;
|
||||
}
|
||||
@@ -19,41 +19,65 @@ function Component() {
|
||||
```
|
||||
Found 3 errors:
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:7:20
|
||||
5 | const now = performance.now();
|
||||
6 | const rand = Math.random();
|
||||
> 7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
| ^^^^ Cannot access impure value during render
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:4:15
|
||||
2 |
|
||||
3 | function Component() {
|
||||
> 4 | const date = Date.now();
|
||||
| ^^^^^^^^^^ Cannot call impure function
|
||||
| ^^^^^^^^^^ `Date.now` is an impure function.
|
||||
5 | const now = performance.now();
|
||||
6 | const rand = Math.random();
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:7:31
|
||||
5 | const now = performance.now();
|
||||
6 | const rand = Math.random();
|
||||
> 7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
| ^^^ Cannot access impure value during render
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:5:14
|
||||
3 | function Component() {
|
||||
4 | const date = Date.now();
|
||||
> 5 | const now = performance.now();
|
||||
| ^^^^^^^^^^^^^^^^^ Cannot call impure function
|
||||
| ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function.
|
||||
6 | const rand = Math.random();
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
8 | }
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:7:42
|
||||
5 | const now = performance.now();
|
||||
6 | const rand = Math.random();
|
||||
> 7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
| ^^^^ Cannot access impure value during render
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:6:15
|
||||
4 | const date = Date.now();
|
||||
5 | const now = performance.now();
|
||||
> 6 | const rand = Math.random();
|
||||
| ^^^^^^^^^^^^^ Cannot call impure function
|
||||
| ^^^^^^^^^^^^^ `Math.random` is an impure function.
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoImpureFunctionsInRender
|
||||
function Component() {
|
||||
const now = () => Date.now();
|
||||
const render = () => {
|
||||
return <div>{now()}</div>;
|
||||
};
|
||||
return <div>{render()}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-value-in-render-helper.ts:5:17
|
||||
3 | const now = () => Date.now();
|
||||
4 | const render = () => {
|
||||
> 5 | return <div>{now()}</div>;
|
||||
| ^^^^^ Cannot access impure value during render
|
||||
6 | };
|
||||
7 | return <div>{render()}</div>;
|
||||
8 | }
|
||||
|
||||
error.invalid-impure-value-in-render-helper.ts:3:20
|
||||
1 | // @validateNoImpureFunctionsInRender
|
||||
2 | function Component() {
|
||||
> 3 | const now = () => Date.now();
|
||||
| ^^^^^^^^^^ `Date.now` is an impure function.
|
||||
4 | const render = () => {
|
||||
5 | return <div>{now()}</div>;
|
||||
6 | };
|
||||
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-value-in-render-helper.ts:7:15
|
||||
5 | return <div>{now()}</div>;
|
||||
6 | };
|
||||
> 7 | return <div>{render()}</div>;
|
||||
| ^^^^^^^^ Cannot access impure value during render
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
error.invalid-impure-value-in-render-helper.ts:3:20
|
||||
1 | // @validateNoImpureFunctionsInRender
|
||||
2 | function Component() {
|
||||
> 3 | const now = () => Date.now();
|
||||
| ^^^^^^^^^^ `Date.now` is an impure function.
|
||||
4 | const render = () => {
|
||||
5 | return <div>{now()}</div>;
|
||||
6 | };
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// @validateNoImpureFunctionsInRender
|
||||
function Component() {
|
||||
const now = () => Date.now();
|
||||
const render = () => {
|
||||
return <div>{now()}</div>;
|
||||
};
|
||||
return <div>{render()}</div>;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender
|
||||
function Component(props) {
|
||||
const ref = useRef(null);
|
||||
const x = foo(ref);
|
||||
return x.current;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-pass-ref-to-function.ts:4:16
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef(null);
|
||||
> 4 | const x = foo(ref);
|
||||
| ^^^ Passing a ref to a function may read its value during render
|
||||
5 | return x.current;
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
// @validateRefAccessDuringRender
|
||||
function Component(props) {
|
||||
const ref = useRef(null);
|
||||
const x = foo(ref);
|
||||
return x.current;
|
||||
}
|
||||
@@ -16,15 +16,23 @@ function Component({ref}) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-read-ref-prop-in-render-destructure.ts:4:15
|
||||
2 | function Component({ref}) {
|
||||
3 | const value = ref.current;
|
||||
> 4 | return <div>{value}</div>;
|
||||
| ^^^^^ Ref value is used during render
|
||||
5 | }
|
||||
6 |
|
||||
|
||||
error.invalid-read-ref-prop-in-render-destructure.ts:3:16
|
||||
1 | // @validateRefAccessDuringRender @compilationMode:"infer"
|
||||
2 | function Component({ref}) {
|
||||
> 3 | const value = ref.current;
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
| ^^^^^^^^^^^ Ref is initially accessed
|
||||
4 | return <div>{value}</div>;
|
||||
5 | }
|
||||
6 |
|
||||
|
||||
@@ -16,15 +16,23 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-read-ref-prop-in-render-property-load.ts:4:15
|
||||
2 | function Component(props) {
|
||||
3 | const value = props.ref.current;
|
||||
> 4 | return <div>{value}</div>;
|
||||
| ^^^^^ Ref value is used during render
|
||||
5 | }
|
||||
6 |
|
||||
|
||||
error.invalid-read-ref-prop-in-render-property-load.ts:3:16
|
||||
1 | // @validateRefAccessDuringRender @compilationMode:"infer"
|
||||
2 | function Component(props) {
|
||||
> 3 | const value = props.ref.current;
|
||||
| ^^^^^^^^^^^^^^^^^ Cannot access ref value during render
|
||||
| ^^^^^^^^^^^^^^^^^ Ref is initially accessed
|
||||
4 | return <div>{value}</div>;
|
||||
5 | }
|
||||
6 |
|
||||
|
||||
@@ -22,57 +22,27 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 4 errors:
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
4 | component C() {
|
||||
5 | const r = useRef(null);
|
||||
> 6 | const current = !r.current;
|
||||
| ^^^^^^^^^ Cannot access ref value during render
|
||||
7 | return <div>{current}</div>;
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
4 | component C() {
|
||||
5 | const r = useRef(null);
|
||||
> 6 | const current = !r.current;
|
||||
| ^^^^^^^^^^ Cannot access ref value during render
|
||||
7 | return <div>{current}</div>;
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
5 | const r = useRef(null);
|
||||
6 | const current = !r.current;
|
||||
> 7 | return <div>{current}</div>;
|
||||
| ^^^^^^^ Cannot access ref value during render
|
||||
| ^^^^^^^ Ref value is used during render
|
||||
8 | }
|
||||
9 |
|
||||
10 | export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
5 | const r = useRef(null);
|
||||
6 | const current = !r.current;
|
||||
> 7 | return <div>{current}</div>;
|
||||
| ^^^^^^^ Cannot access ref value during render
|
||||
8 | }
|
||||
9 |
|
||||
10 | export const FIXTURE_ENTRYPOINT = {
|
||||
4 | component C() {
|
||||
5 | const r = useRef(null);
|
||||
> 6 | const current = !r.current;
|
||||
| ^^^^^^^^^ Ref is initially accessed
|
||||
7 | return <div>{current}</div>;
|
||||
8 | }
|
||||
9 |
|
||||
```
|
||||
|
||||
|
||||
@@ -20,17 +20,27 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-ref-in-callback-invoked-during-render.ts:8:33
|
||||
6 | return <Foo item={item} current={current} />;
|
||||
7 | };
|
||||
> 8 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ Cannot access ref value during render
|
||||
9 | }
|
||||
10 |
|
||||
error.invalid-ref-in-callback-invoked-during-render.ts:6:37
|
||||
4 | const renderItem = item => {
|
||||
5 | const current = ref.current;
|
||||
> 6 | return <Foo item={item} current={current} />;
|
||||
| ^^^^^^^ Ref value is used during render
|
||||
7 | };
|
||||
8 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
|
||||
9 | }
|
||||
|
||||
error.invalid-ref-in-callback-invoked-during-render.ts:5:20
|
||||
3 | const ref = useRef(null);
|
||||
4 | const renderItem = item => {
|
||||
> 5 | const current = ref.current;
|
||||
| ^^^^^^^^^^^ Ref is initially accessed
|
||||
6 | return <Foo item={item} current={current} />;
|
||||
7 | };
|
||||
8 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
|
||||
```
|
||||
|
||||
|
||||
@@ -29,15 +29,13 @@ Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
4 | component C() {
|
||||
5 | const r = useRef(null);
|
||||
> 6 | if (!r.current) {
|
||||
| ^^^^^^^^^ Cannot access ref value during render
|
||||
7 | r.current = 1;
|
||||
8 | }
|
||||
9 | }
|
||||
|
||||
To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`
|
||||
5 | const r = useRef(null);
|
||||
6 | if (!r.current) {
|
||||
> 7 | r.current = 1;
|
||||
| ^^^^^^^^^ Cannot update ref during render
|
||||
8 | }
|
||||
9 | }
|
||||
10 |
|
||||
```
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
@@ -24,7 +24,7 @@ error.invalid-ref-value-as-props.ts:4:19
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef(null);
|
||||
> 4 | return <Foo ref={ref.current} />;
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
| ^^^^^^^^^^^ Ref value is used during render
|
||||
5 | }
|
||||
6 |
|
||||
```
|
||||
|
||||
@@ -15,22 +15,9 @@ function Component(props) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-set-and-read-ref-during-render.ts:4:2
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef(null);
|
||||
> 4 | ref.current = props.value;
|
||||
| ^^^^^^^^^^^ Cannot update ref during render
|
||||
5 | return ref.current;
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
@@ -38,7 +25,7 @@ error.invalid-set-and-read-ref-during-render.ts:5:9
|
||||
3 | const ref = useRef(null);
|
||||
4 | ref.current = props.value;
|
||||
> 5 | return ref.current;
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
| ^^^^^^^^^^^ Ref value is used during render
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
@@ -15,22 +15,9 @@ function Component(props) {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-set-and-read-ref-nested-property-during-render.ts:4:2
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef({inner: null});
|
||||
> 4 | ref.current.inner = props.value;
|
||||
| ^^^^^^^^^^^ Cannot update ref during render
|
||||
5 | return ref.current.inner;
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
@@ -38,7 +25,15 @@ error.invalid-set-and-read-ref-nested-property-during-render.ts:5:9
|
||||
3 | const ref = useRef({inner: null});
|
||||
4 | ref.current.inner = props.value;
|
||||
> 5 | return ref.current.inner;
|
||||
| ^^^^^^^^^^^^^^^^^ Cannot access ref value during render
|
||||
| ^^^^^^^^^^^^^^^^^ Ref value is used during render
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
error.invalid-set-and-read-ref-nested-property-during-render.ts:5:9
|
||||
3 | const ref = useRef({inner: null});
|
||||
4 | ref.current.inner = props.value;
|
||||
> 5 | return ref.current.inner;
|
||||
| ^^^^^^^^^^^ Ref is initially accessed
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender
|
||||
function Foo({a}) {
|
||||
const ref = useRef();
|
||||
// type information is lost here as we don't track types of fields
|
||||
const val = {ref};
|
||||
// without type info, we don't know that val.ref.current is a ref value so we
|
||||
// *would* end up depending on val.ref.current
|
||||
// however, this is an instance of accessing a ref during render and is disallowed
|
||||
// under React's rules, so we reject this input
|
||||
const x = {a, val: val.ref.current};
|
||||
|
||||
return <VideoList videos={x} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-use-ref-added-to-dep-without-type-info.ts:10:21
|
||||
8 | // however, this is an instance of accessing a ref during render and is disallowed
|
||||
9 | // under React's rules, so we reject this input
|
||||
> 10 | const x = {a, val: val.ref.current};
|
||||
| ^^^^^^^^^^^^^^^ Cannot access ref value during render
|
||||
11 |
|
||||
12 | return <VideoList videos={x} />;
|
||||
13 | }
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.invalid-use-ref-added-to-dep-without-type-info.ts:12:28
|
||||
10 | const x = {a, val: val.ref.current};
|
||||
11 |
|
||||
> 12 | return <VideoList videos={x} />;
|
||||
| ^ Cannot access ref value during render
|
||||
13 | }
|
||||
14 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// @validateRefAccessDuringRender
|
||||
function Foo({a}) {
|
||||
const ref = useRef();
|
||||
// type information is lost here as we don't track types of fields
|
||||
const val = {ref};
|
||||
// without type info, we don't know that val.ref.current is a ref value so we
|
||||
// *would* end up depending on val.ref.current
|
||||
// however, this is an instance of accessing a ref during render and is disallowed
|
||||
// under React's rules, so we reject this input
|
||||
const x = {a, val: val.ref.current};
|
||||
|
||||
return <VideoList videos={x} />;
|
||||
}
|
||||
@@ -25,19 +25,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
6 | component C() {
|
||||
7 | const r = useRef(DEFAULT_VALUE);
|
||||
> 8 | if (r.current == DEFAULT_VALUE) {
|
||||
| ^^^^^^^^^ Cannot access ref value during render
|
||||
9 | r.current = 1;
|
||||
10 | }
|
||||
11 | }
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
//@flow
|
||||
import {useRef} from 'react';
|
||||
|
||||
component C() {
|
||||
const r = useRef(null);
|
||||
if (r.current == null) {
|
||||
f(r);
|
||||
}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: C,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
5 | const r = useRef(null);
|
||||
6 | if (r.current == null) {
|
||||
> 7 | f(r);
|
||||
| ^ Passing a ref to a function may read its value during render
|
||||
8 | }
|
||||
9 | }
|
||||
10 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
//@flow
|
||||
import {useRef} from 'react';
|
||||
|
||||
component C() {
|
||||
const r = useRef(null);
|
||||
if (r.current == null) {
|
||||
f(r.current);
|
||||
}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: C,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
5 | const r = useRef(null);
|
||||
6 | if (r.current == null) {
|
||||
> 7 | f(r.current);
|
||||
| ^^^^^^^^^ Passing a ref to a function may read its value during render
|
||||
8 | }
|
||||
9 | }
|
||||
10 |
|
||||
```
|
||||
|
||||
|
||||
@@ -33,10 +33,18 @@ React refs are values that are not needed for rendering. Refs should only be acc
|
||||
6 | if (r.current == null) {
|
||||
7 | r.current = 42;
|
||||
> 8 | r.current = 42;
|
||||
| ^^^^^^^^^ Cannot update ref during render
|
||||
| ^^^^^^^^^ Ref is initialized more than once during render
|
||||
9 | }
|
||||
10 | }
|
||||
11 |
|
||||
|
||||
5 | const r = useRef(null);
|
||||
6 | if (r.current == null) {
|
||||
> 7 | r.current = 42;
|
||||
| ^^^^^^^^^ Ref was first initialized here
|
||||
8 | r.current = 42;
|
||||
9 | }
|
||||
10 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -24,31 +24,19 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
4 | component C() {
|
||||
5 | const r = useRef(null);
|
||||
> 6 | const guard = r.current == null;
|
||||
| ^^^^^^^^^^^^^^^^^ Cannot access ref value during render
|
||||
7 | if (guard) {
|
||||
8 | r.current = 1;
|
||||
9 | }
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
5 | const r = useRef(null);
|
||||
6 | const guard = r.current == null;
|
||||
> 7 | if (guard) {
|
||||
| ^^^^^ Cannot access ref value during render
|
||||
8 | r.current = 1;
|
||||
7 | if (guard) {
|
||||
> 8 | r.current = 1;
|
||||
| ^^^^^^^^^ Cannot update ref during render
|
||||
9 | }
|
||||
10 | }
|
||||
11 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
//@flow
|
||||
import {useRef} from 'react';
|
||||
|
||||
component C() {
|
||||
const r = useRef(null);
|
||||
if (r.current == null) {
|
||||
r.current = 1;
|
||||
}
|
||||
f(r.current);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: C,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
7 | r.current = 1;
|
||||
8 | }
|
||||
> 9 | f(r.current);
|
||||
| ^^^^^^^^^ Passing a ref to a function may read its value during render
|
||||
10 | }
|
||||
11 |
|
||||
12 | export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
Error: Cannot access ref value during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
@@ -30,7 +30,7 @@ error.ref-optional.ts:5:9
|
||||
3 | function Component(props) {
|
||||
4 | const ref = useRef();
|
||||
> 5 | return ref?.current;
|
||||
| ^^^^^^^^^^^^ Cannot access ref value during render
|
||||
| ^^^^^^^^^^^^ Ref value is used during render
|
||||
6 | }
|
||||
7 |
|
||||
8 | export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableInferEventHandlers
|
||||
import {useRef} from 'react';
|
||||
|
||||
// Simulates a custom component wrapper
|
||||
function CustomForm({onSubmit, children}: any) {
|
||||
return <form onSubmit={onSubmit}>{children}</form>;
|
||||
}
|
||||
|
||||
// Simulates react-hook-form's handleSubmit
|
||||
function handleSubmit<T>(callback: (data: T) => void) {
|
||||
return (event: any) => {
|
||||
event.preventDefault();
|
||||
callback({} as T);
|
||||
};
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
// This should error: passing function with ref access to custom component
|
||||
// event handler, even though it would be safe on a native <form>
|
||||
if (ref.current !== null) {
|
||||
console.log(ref.current.value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<input ref={ref} />
|
||||
<CustomForm onSubmit={handleSubmit(onSubmit)}>
|
||||
<button type="submit">Submit</button>
|
||||
</CustomForm>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.ref-value-in-custom-component-event-handler-wrapper.ts:31:41
|
||||
29 | <>
|
||||
30 | <input ref={ref} />
|
||||
> 31 | <CustomForm onSubmit={handleSubmit(onSubmit)}>
|
||||
| ^^^^^^^^ Passing a ref to a function may read its value during render
|
||||
32 | <button type="submit">Submit</button>
|
||||
33 | </CustomForm>
|
||||
34 | </>
|
||||
```
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableInferEventHandlers
|
||||
import {useRef} from 'react';
|
||||
|
||||
// Simulates a handler wrapper
|
||||
function handleClick(value: any) {
|
||||
return () => {
|
||||
console.log(value);
|
||||
};
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const ref = useRef(null);
|
||||
|
||||
// This should still error: passing ref.current directly to a wrapper
|
||||
// The ref value is accessed during render, not in the event handler
|
||||
return (
|
||||
<>
|
||||
<input ref={ref} />
|
||||
<button onClick={handleClick(ref.current)}>Click</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.ref-value-in-event-handler-wrapper.ts:19:35
|
||||
17 | <>
|
||||
18 | <input ref={ref} />
|
||||
> 19 | <button onClick={handleClick(ref.current)}>Click</button>
|
||||
| ^^^^^^^^^^^ Cannot access ref value during render
|
||||
20 | </>
|
||||
21 | );
|
||||
22 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ error.todo-useCallback-set-ref-nested-property-ref-modified-later-preserve-memoi
|
||||
12 |
|
||||
13 | // The ref is modified later, extending its range and preventing memoization of onChange
|
||||
> 14 | ref.current.inner = null;
|
||||
| ^^^^^^^^^^^ Cannot update ref during render
|
||||
| ^^^^^^^^^^^^^^^^^ Cannot update ref during render
|
||||
15 |
|
||||
16 | return <input onChange={onChange} />;
|
||||
17 | }
|
||||
|
||||
@@ -40,14 +40,14 @@ Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.useCallback-accesses-ref-mutated-later-via-function-preserve-memoization.ts:17:2
|
||||
15 | ref.current.inner = null;
|
||||
error.useCallback-accesses-ref-mutated-later-via-function-preserve-memoization.ts:15:4
|
||||
13 | // The ref is modified later, extending its range and preventing memoization of onChange
|
||||
14 | const reset = () => {
|
||||
> 15 | ref.current.inner = null;
|
||||
| ^^^^^^^^^^^^^^^^^ Cannot update ref during render
|
||||
16 | };
|
||||
> 17 | reset();
|
||||
| ^^^^^ This function accesses a ref value
|
||||
17 | reset();
|
||||
18 |
|
||||
19 | return <input onChange={onChange} />;
|
||||
20 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ error.useCallback-set-ref-nested-property-dont-preserve-memoization.ts:13:2
|
||||
11 | });
|
||||
12 |
|
||||
> 13 | ref.current.inner = null;
|
||||
| ^^^^^^^^^^^ Cannot update ref during render
|
||||
| ^^^^^^^^^^^^^^^^^ Cannot update ref during render
|
||||
14 |
|
||||
15 | return <input onChange={onChange} />;
|
||||
16 | }
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender:true
|
||||
function Foo(props, ref) {
|
||||
console.log(ref.current);
|
||||
return <div>{props.bar}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{bar: 'foo'}, {ref: {cuurrent: 1}}],
|
||||
isComponent: true,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Cannot access refs during render
|
||||
|
||||
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
|
||||
|
||||
error.validate-mutate-ref-arg-in-render.ts:3:14
|
||||
1 | // @validateRefAccessDuringRender:true
|
||||
2 | function Foo(props, ref) {
|
||||
> 3 | console.log(ref.current);
|
||||
| ^^^^^^^^^^^ Passing a ref to a function may read its value during render
|
||||
4 | return <div>{props.bar}</div>;
|
||||
5 | }
|
||||
6 |
|
||||
```
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveEffectDependencies
|
||||
// @validateExhaustiveEffectDependencies:"all"
|
||||
import {useEffect, useEffectEvent} from 'react';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateExhaustiveEffectDependencies
|
||||
// @validateExhaustiveEffectDependencies:"all"
|
||||
import {useEffect, useEffectEvent} from 'react';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
// @validateExhaustiveMemoizationDependencies @validateRefAccessDuringRender:false
|
||||
import {useMemo} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
// @validateExhaustiveMemoizationDependencies @validateRefAccessDuringRender:false
|
||||
import {useMemo} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveEffectDependencies:"extra-only"
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
// no error: missing dep not reported in extra-only mode
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, []);
|
||||
|
||||
// error: extra dep - y
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, [x, y]);
|
||||
|
||||
// error: extra dep - y (missing dep - z not reported)
|
||||
useEffect(() => {
|
||||
log(x, z);
|
||||
}, [x, y]);
|
||||
|
||||
// error: extra dep - x.y
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, [x.y]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## 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.invalid-exhaustive-effect-deps-extra-only.ts:13:9
|
||||
11 | useEffect(() => {
|
||||
12 | log(x);
|
||||
> 13 | }, [x, y]);
|
||||
| ^ Unnecessary dependency `y`
|
||||
14 |
|
||||
15 | // error: extra dep - y (missing dep - z not reported)
|
||||
16 | useEffect(() => {
|
||||
|
||||
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-extra-only.ts:18:9
|
||||
16 | useEffect(() => {
|
||||
17 | log(x, z);
|
||||
> 18 | }, [x, y]);
|
||||
| ^ Unnecessary dependency `y`
|
||||
19 |
|
||||
20 | // error: extra dep - x.y
|
||||
21 | useEffect(() => {
|
||||
|
||||
Inferred dependencies: `[x, 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.invalid-exhaustive-effect-deps-extra-only.ts:23:6
|
||||
21 | useEffect(() => {
|
||||
22 | log(x);
|
||||
> 23 | }, [x.y]);
|
||||
| ^^^ Overly precise dependency `x.y`, use `x` instead
|
||||
24 | }
|
||||
25 |
|
||||
|
||||
Inferred dependencies: `[x]`
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// @validateExhaustiveEffectDependencies:"extra-only"
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
// no error: missing dep not reported in extra-only mode
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, []);
|
||||
|
||||
// error: extra dep - y
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, [x, y]);
|
||||
|
||||
// error: extra dep - y (missing dep - z not reported)
|
||||
useEffect(() => {
|
||||
log(x, z);
|
||||
}, [x, y]);
|
||||
|
||||
// error: extra dep - x.y
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, [x.y]);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveEffectDependencies:"missing-only"
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
// error: missing dep - x
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, []);
|
||||
|
||||
// no error: extra dep not reported in missing-only mode
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, [x, y]);
|
||||
|
||||
// error: missing dep - z (extra dep - y not reported)
|
||||
useEffect(() => {
|
||||
log(x, z);
|
||||
}, [x, y]);
|
||||
|
||||
// error: missing dep x
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, [x.y]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 3 errors:
|
||||
|
||||
Error: Found missing effect dependencies
|
||||
|
||||
Missing dependencies can cause an effect to fire less often than it should.
|
||||
|
||||
error.invalid-exhaustive-effect-deps-missing-only.ts:7:8
|
||||
5 | // error: missing dep - x
|
||||
6 | useEffect(() => {
|
||||
> 7 | log(x);
|
||||
| ^ Missing dependency `x`
|
||||
8 | }, []);
|
||||
9 |
|
||||
10 | // no error: extra dep not reported in missing-only mode
|
||||
|
||||
Inferred dependencies: `[x]`
|
||||
|
||||
Error: Found missing effect dependencies
|
||||
|
||||
Missing dependencies can cause an effect to fire less often than it should.
|
||||
|
||||
error.invalid-exhaustive-effect-deps-missing-only.ts:17:11
|
||||
15 | // error: missing dep - z (extra dep - y not reported)
|
||||
16 | useEffect(() => {
|
||||
> 17 | log(x, z);
|
||||
| ^ Missing dependency `z`
|
||||
18 | }, [x, y]);
|
||||
19 |
|
||||
20 | // error: missing dep x
|
||||
|
||||
Inferred dependencies: `[x, z]`
|
||||
|
||||
Error: Found missing effect dependencies
|
||||
|
||||
Missing dependencies can cause an effect to fire less often than it should.
|
||||
|
||||
error.invalid-exhaustive-effect-deps-missing-only.ts:22:8
|
||||
20 | // error: missing dep x
|
||||
21 | useEffect(() => {
|
||||
> 22 | log(x);
|
||||
| ^ Missing dependency `x`
|
||||
23 | }, [x.y]);
|
||||
24 | }
|
||||
25 |
|
||||
|
||||
Inferred dependencies: `[x]`
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// @validateExhaustiveEffectDependencies:"missing-only"
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
// error: missing dep - x
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, []);
|
||||
|
||||
// no error: extra dep not reported in missing-only mode
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, [x, y]);
|
||||
|
||||
// error: missing dep - z (extra dep - y not reported)
|
||||
useEffect(() => {
|
||||
log(x, z);
|
||||
}, [x, y]);
|
||||
|
||||
// error: missing dep x
|
||||
useEffect(() => {
|
||||
log(x);
|
||||
}, [x.y]);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveEffectDependencies
|
||||
// @validateExhaustiveEffectDependencies:"all"
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateExhaustiveEffectDependencies
|
||||
// @validateExhaustiveEffectDependencies:"all"
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
|
||||
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies:"all"
|
||||
import {
|
||||
useCallback,
|
||||
useTransition,
|
||||
@@ -69,7 +69,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies:"all"
|
||||
import {
|
||||
useCallback,
|
||||
useTransition,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
|
||||
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies:"all"
|
||||
import {
|
||||
useCallback,
|
||||
useTransition,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveEffectDependencies
|
||||
// @validateExhaustiveEffectDependencies:"all"
|
||||
import {useEffect, useEffectEvent} from 'react';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
@@ -30,7 +30,7 @@ function Component({x, y, z}) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveEffectDependencies
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveEffectDependencies:"all"
|
||||
import { useEffect, useEffectEvent } from "react";
|
||||
|
||||
function Component(t0) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateExhaustiveEffectDependencies
|
||||
// @validateExhaustiveEffectDependencies:"all"
|
||||
import {useEffect, useEffectEvent} from 'react';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoImpureFunctionsInRender
|
||||
|
||||
import {identity, makeArray} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render. The impurity is lost
|
||||
* when passed through external function calls.
|
||||
*/
|
||||
function Component() {
|
||||
const getDate = () => Date.now();
|
||||
const array = makeArray(getDate());
|
||||
const hasDate = identity(array);
|
||||
return <Foo hasDate={hasDate} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoImpureFunctionsInRender
|
||||
|
||||
import { identity, makeArray } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render. The impurity is lost
|
||||
* when passed through external function calls.
|
||||
*/
|
||||
function Component() {
|
||||
const $ = _c(1);
|
||||
const getDate = _temp;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const array = makeArray(getDate());
|
||||
const hasDate = identity(array);
|
||||
t0 = <Foo hasDate={hasDate} />;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp() {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,15 @@
|
||||
// @validateNoImpureFunctionsInRender
|
||||
|
||||
import {identity, makeArray} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render. The impurity is lost
|
||||
* when passed through external function calls.
|
||||
*/
|
||||
function Component() {
|
||||
const getDate = () => Date.now();
|
||||
const array = makeArray(getDate());
|
||||
const hasDate = identity(array);
|
||||
return <Foo hasDate={hasDate} />;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoImpureFunctionsInRender
|
||||
|
||||
import {identity, makeArray} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render. The impurity is lost
|
||||
* when passed through external function calls.
|
||||
*/
|
||||
function Component() {
|
||||
const now = () => Date.now();
|
||||
const f = () => {
|
||||
const array = makeArray(now());
|
||||
const hasDate = identity(array);
|
||||
return hasDate;
|
||||
};
|
||||
const hasDate = f();
|
||||
return <Foo hasDate={hasDate} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoImpureFunctionsInRender
|
||||
|
||||
import { identity, makeArray } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render. The impurity is lost
|
||||
* when passed through external function calls.
|
||||
*/
|
||||
function Component() {
|
||||
const $ = _c(1);
|
||||
const now = _temp;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const f = () => {
|
||||
const array = makeArray(now());
|
||||
const hasDate = identity(array);
|
||||
return hasDate;
|
||||
};
|
||||
const hasDate_0 = f();
|
||||
t0 = <Foo hasDate={hasDate_0} />;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp() {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,19 @@
|
||||
// @validateNoImpureFunctionsInRender
|
||||
|
||||
import {identity, makeArray} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses an impure value during render. The impurity is lost
|
||||
* when passed through external function calls.
|
||||
*/
|
||||
function Component() {
|
||||
const now = () => Date.now();
|
||||
const f = () => {
|
||||
const array = makeArray(now());
|
||||
const hasDate = identity(array);
|
||||
return hasDate;
|
||||
};
|
||||
const hasDate = f();
|
||||
return <Foo hasDate={hasDate} />;
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}}
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":20,"index":307},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}}
|
||||
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":237},"end":{"line":8,"column":50,"index":285},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":259},"end":{"line":8,"column":30,"index":265},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
@@ -19,41 +19,65 @@ function Component() {
|
||||
```
|
||||
Found 3 errors:
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:7:20
|
||||
5 | const now = performance.now();
|
||||
6 | const rand = Math.random();
|
||||
> 7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
| ^^^^ Cannot access impure value during render
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:4:15
|
||||
2 |
|
||||
3 | function Component() {
|
||||
> 4 | const date = Date.now();
|
||||
| ^^^^^^^^^^ Cannot call impure function
|
||||
| ^^^^^^^^^^ `Date.now` is an impure function.
|
||||
5 | const now = performance.now();
|
||||
6 | const rand = Math.random();
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:7:31
|
||||
5 | const now = performance.now();
|
||||
6 | const rand = Math.random();
|
||||
> 7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
| ^^^ Cannot access impure value during render
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:5:14
|
||||
3 | function Component() {
|
||||
4 | const date = Date.now();
|
||||
> 5 | const now = performance.now();
|
||||
| ^^^^^^^^^^^^^^^^^ Cannot call impure function
|
||||
| ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function.
|
||||
6 | const rand = Math.random();
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
8 | }
|
||||
|
||||
Error: Cannot call impure function during render
|
||||
Error: Cannot access impure value during render
|
||||
|
||||
`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:7:42
|
||||
5 | const now = performance.now();
|
||||
6 | const rand = Math.random();
|
||||
> 7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
| ^^^^ Cannot access impure value during render
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
error.invalid-impure-functions-in-render.ts:6:15
|
||||
4 | const date = Date.now();
|
||||
5 | const now = performance.now();
|
||||
> 6 | const rand = Math.random();
|
||||
| ^^^^^^^^^^^^^ Cannot call impure function
|
||||
| ^^^^^^^^^^^^^ `Math.random` is an impure function.
|
||||
7 | return <Foo date={date} now={now} rand={rand} />;
|
||||
8 | }
|
||||
9 |
|
||||
|
||||
@@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}}
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":20,"index":339},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}}
|
||||
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":269},"end":{"line":8,"column":50,"index":317},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":291},"end":{"line":8,"column":30,"index":297},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses a ref during render. The return type of foo() is unknown.
|
||||
*/
|
||||
function Component(props) {
|
||||
const ref = useRef(null);
|
||||
const x = foo(ref);
|
||||
return x.current;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRender
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses a ref during render. The return type of foo() is unknown.
|
||||
*/
|
||||
function Component(props) {
|
||||
const $ = _c(1);
|
||||
const ref = useRef(null);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = foo(ref);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const x = t0;
|
||||
return x.current;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,11 @@
|
||||
// @validateRefAccessDuringRender
|
||||
|
||||
/**
|
||||
* Allowed: we don't have sufficient type information to be sure that
|
||||
* this accesses a ref during render. The return type of foo() is unknown.
|
||||
*/
|
||||
function Component(props) {
|
||||
const ref = useRef(null);
|
||||
const x = foo(ref);
|
||||
return x.current;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user