Compare commits

..

2 Commits

Author SHA1 Message Date
Joe Savona
2c4a3b9587 [compiler] Validate ref reads in render via improved impure value validation
Updates to guard against *reading* refs during render via the improved validateNoImpureValuesInRender() pass. InferMutationAliasingEffects generates `Impure` effects for ref reads, and then this pass validates that those accesses don't flow into `Render` effects. We now call the impure value validation first so that it takes priority over validateNoRefAccessInRender - the latter still has all the existing logic for now, but we can dramatically simplify it in a follow-up PR so that it only has to validate against ref writes.
2026-01-12 16:38:51 -08:00
Joe Savona
8428a7ec5f [compiler] Improve impurity/ref tracking
note: This implements the idea discussed in https://github.com/reactwg/react/discussions/389#discussioncomment-14252280

Currently we create `Impure` effects for impure functions like `Date.now()` or `Math.random()`, and then throw if the effect is reachable during render. However, impurity is a property of the resulting value: if the value isn't accessed during render then it's okay: maybe you're console-logging the time while debugging (fine), or storing the impure value into a ref and only accessing it in an effect or event handler (totally ok).

This PR updates to validate that impure values are not transitively consumed during render, building on the `Render` effect. The validation currently uses an algorithm very similar to that of InferReactivePlaces - building a set of known impure values, starting with `Impure` effects as the sources and then flowing outward via data flow and mutations. If a transitively impure value reaches a `Render` effect, it's an error. We record both the source of the impure value and where it gets rendered to make it easier to understand and fix 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-functions-in-render-via-render-helper.ts:10:25
   8 |     const array = makeArray(now);
   9 |     const hasDate = identity(array);
> 10 |     return <Bar hasDate={hasDate} />;
     |                          ^^^^^^^ Cannot access impure value during render
  11 |   };
  12 |   return <Foo renderItem={renderItem} />;
  13 | }

error.invalid-impure-functions-in-render-via-render-helper.ts:6:14
  4 |
  5 | function Component() {
> 6 |   const now = Date.now();
    |               ^^^^^^^^^^ `Date.now` is an impure function.
  7 |   const renderItem = () => {
  8 |     const array = makeArray(now);
  9 |     const hasDate = identity(array);
```

Impure values are allowed to flow into refs, meaning that we now allow `useRef(Date.now())` or `useRef(localFunctionThatReturnsMathDotRandom())` which would have errored previously. The next PR reuses this improved impurity tracking to validate ref access in render as well.
2026-01-12 16:38:51 -08:00
92 changed files with 2182 additions and 2631 deletions

View File

@@ -1,18 +1,7 @@
{
"permissions": {
"allow": [
"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:*)"
"Bash(node scripts/enable-feature-flag.js:*)"
],
"deny": [],
"ask": []

View File

@@ -1,245 +0,0 @@
# 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 specific test by pattern
yarn snap -p <pattern>
# Update fixture outputs
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

View File

@@ -664,7 +664,7 @@ const RenderHookAliasing: (
const EffectHookAliasing: AliasingSignatureConfig = {
receiver: '@receiver',
params: ['@fn', '@deps'],
params: [],
rest: '@rest',
returns: '@returns',
temporaries: ['@effect'],
@@ -675,21 +675,6 @@ const EffectHookAliasing: AliasingSignatureConfig = {
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',
@@ -703,11 +688,6 @@ const EffectHookAliasing: AliasingSignatureConfig = {
from: '@rest',
into: '@effect',
},
{
kind: 'Capture',
from: '@fn',
into: '@effect',
},
// Returns undefined
{
kind: 'Create',
@@ -723,39 +703,6 @@ const EffectHookAliasing: AliasingSignatureConfig = {
* 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: 'Assign', from: '@value', into: '@return'},
{
kind: 'Freeze',
value: '@value',
reason: ValueReason.HookCaptured,
},
],
},
},
BuiltInUseEffectEventId,
);
const REACT_APIS: Array<[string, BuiltInType]> = [
[
'useContext',
@@ -822,22 +769,6 @@ 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'},
],
},
}),
],
[
@@ -868,12 +799,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
addHook(DEFAULT_SHAPES, {
positionalParams: [],
restParam: Effect.Freeze,
returnType: {
kind: 'Function',
isConstructor: false,
return: {kind: 'Poly'},
shapeId: null,
},
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
hookKind: 'useCallback',
returnValueKind: ValueKind.Frozen,
@@ -989,8 +915,27 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
BuiltInFireId,
),
],
['useEffectEvent', useEffectEvent],
['experimental_useEffectEvent', useEffectEvent],
[
'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,
),
],
['AUTODEPS', addObject(DEFAULT_SHAPES, BuiltInAutodepsId, [])],
];

View File

@@ -1879,15 +1879,7 @@ export function isRefValueType(id: Identifier): boolean {
}
export function isUseRefType(id: Identifier): boolean {
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)))
);
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseRefId';
}
export function isUseStateType(id: Identifier): boolean {

View File

@@ -983,15 +983,15 @@ 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})`;
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}`;
}
case 'Freeze': {
return `Freeze ${printPlaceForAliasEffect(effect.value)} ${effect.reason}`;

View File

@@ -29,7 +29,6 @@ import {
isArrayType,
isJsxOrJsxUnionType,
isMapType,
isMutableEffect,
isPrimitiveType,
isRefOrRefValue,
isSetType,
@@ -588,17 +587,6 @@ function inferBlock(
}),
);
}
if (
context.fn.fnType === 'Component' ||
isJsxOrJsxUnionType(context.fn.returns.identifier.type)
) {
terminal.effects.push(
context.internEffect({
kind: 'Render',
place: terminal.value,
}),
);
}
}
}
@@ -770,7 +758,17 @@ function applyEffect(
break;
}
case 'ImmutableCapture': {
effects.push(effect);
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);
}
}
break;
}
case 'CreateFrom': {
@@ -1072,17 +1070,6 @@ 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: {
@@ -1094,8 +1081,6 @@ function applyEffect(
break;
}
case 'Apply': {
effects.push(effect);
const functionValues = state.values(effect.function);
if (
functionValues.length === 1 &&
@@ -1990,11 +1975,6 @@ function computeSignatureForInstruction(
value: ValueKind.Primitive,
reason: ValueReason.Other,
});
effects.push({
kind: 'ImmutableCapture',
from: value.object,
into: lvalue,
});
} else {
effects.push({
kind: 'CreateFrom',
@@ -2200,11 +2180,14 @@ function computeSignatureForInstruction(
for (const prop of value.props) {
const place =
prop.kind === 'JsxAttribute' ? prop.place : prop.argument;
if (isUseRefType(place.identifier)) {
continue;
}
if (place.identifier.type.kind !== 'Function') {
// Functions are checked independently
if (place.identifier.type.kind === 'Function') {
if (isJsxOrJsxUnionType(place.identifier.type.return)) {
effects.push({
kind: 'Render',
place,
});
}
} else {
effects.push({
kind: 'Render',
place,
@@ -2243,11 +2226,6 @@ 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',
@@ -2429,46 +2407,15 @@ 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',

View File

@@ -20,7 +20,6 @@ import {
Place,
isPrimitiveType,
isUseRefType,
isJsxOrJsxUnionType,
} from '../HIR/HIR';
import {
eachInstructionLValue,
@@ -225,20 +224,17 @@ export function inferMutationAliasingRanges(
if (effect.kind === 'Alias') {
state.assign(index++, effect.from, effect.into);
} else {
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,
},
],
},
);
CompilerError.invariant(effect.kind === 'Freeze', {
reason: `Unexpected '${effect.kind}' effect for MaybeThrow terminal`,
description: null,
details: [
{
kind: 'error',
loc: block.terminal.loc,
message: null,
},
],
});
}
}
}
@@ -397,7 +393,17 @@ export function inferMutationAliasingRanges(
break;
}
case 'Apply': {
break;
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,
},
],
});
}
case 'MutateTransitive':
case 'MutateConditionally':
@@ -579,12 +585,7 @@ export function inferMutationAliasingRanges(
}
}
if (
errors.hasAnyErrors() &&
(fn.fnType === 'Component' ||
isJsxOrJsxUnionType(fn.returns.identifier.type) ||
!isFunctionExpression)
) {
if (errors.hasAnyErrors() && !isFunctionExpression) {
return Err(errors);
}
return Ok(functionEffects);

View File

@@ -167,14 +167,6 @@ 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>> {

View File

@@ -10,32 +10,27 @@ import {
areEqualSourceLocations,
HIRFunction,
IdentifierId,
InstructionId,
isJsxType,
isUseRefType,
} from '../HIR';
import {AliasingEffect, hashEffect} from '../Inference/AliasingEffects';
import {
eachInstructionLValue,
eachInstructionValueOperand,
} from '../HIR/visitors';
import {AliasingEffect} 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';
import {printFunction} from '../HIR/PrintHIR';
import {assertExhaustive, 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;
};
type ImpuritySignature = {effects: Array<ImpureEffect>; error: CompilerError};
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());
const result = inferImpureValues(fn, impure, new Map());
if (result.error.hasAnyErrors()) {
return Err(result.error);
@@ -46,177 +41,23 @@ export function validateNoImpureValuesInRender(
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(',')}`,
)
.map(place => `${place.identifier.id}:${impure.has(place.identifier.id)}`)
.join(',');
return getOrInsertWith(
getOrInsertWith(cache, fn, () => new Map()),
key,
() => inferImpureValues(fn, impure, impureFunctions, cache),
() => {
return inferImpureValues(fn, impure, 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)
) {
// console.log(
// `${effect.kind} $${effect.into.identifier.id} <= $${effect.from.identifier.id} ($${sourceEffect.into.identifier.id} forward)`,
// );
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) {
// console.log(
// `${effect.kind} $${effect.into.identifier.id} => $${effect.from.identifier.id} ($${destinationEffect.into.identifier.id} backward)`,
// );
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)
// ||
// !areEqualFunctionSignatures(
// impureFunctions.get(effect.into.identifier.id)!.effects,
// functionEffect.effects,
// )
) {
// console.log(
// `${effect.kind} $${effect.into.identifier.id} <= $${effect.from.identifier.id} (function)`,
// );
impureFunctions.set(effect.into.identifier.id, functionEffect);
hasChanges = true;
}
}
break;
}
case 'Impure': {
if (!impure.has(effect.into.identifier.id)) {
// console.log(`Impure $${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)
) {
// console.log(`Function $${effect.into.identifier.id}`);
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 => {
@@ -224,14 +65,9 @@ function inferImpureValues(
});
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()) {
const controlPlace = getBlockControl(block.id);
const controlImpureEffect =
@@ -269,68 +105,128 @@ function inferImpureValues(
}
for (const instr of block.instructions) {
const _impure = new Set(impure.keys());
hasChanges =
processEffects(
instr.id,
instr.effects ?? [],
impure,
impureFunctions,
cache,
) || hasChanges;
let impureEffect: ImpureEffect | undefined = instr.effects?.find(
(effect: AliasingEffect): effect is ImpureEffect =>
effect.kind === 'Impure',
);
if (
impureEffect == null &&
(instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod')
) {
impureEffect = instr.value.loweredFunc.func.aliasingEffects?.find(
(effect: AliasingEffect): effect is ImpureEffect =>
effect.kind === 'Impure',
);
if (impureEffect == null) {
const result = inferFunctionExpressionMemo(
instr.value.loweredFunc.func,
impure,
cache,
);
if (!result.error.hasAnyErrors()) {
impureEffect = result.effects[0];
}
}
}
if (impureEffect == null) {
for (const operand of eachInstructionValueOperand(instr.value)) {
const operandEffect = impure.get(operand.identifier.id);
if (operandEffect != null) {
impureEffect = operandEffect;
break;
}
}
}
if (impureEffect != null) {
for (const lvalue of eachInstructionLValue(instr)) {
if (isUseRefType(lvalue.identifier)) {
continue;
}
if (!impure.has(lvalue.identifier.id)) {
impure.set(lvalue.identifier.id, impureEffect);
hasChanges = true;
}
}
}
if (impureEffect != null || controlImpureEffect != null) {
for (const operand of eachInstructionValueOperand(instr.value)) {
switch (operand.effect) {
case Effect.Capture:
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
if (
!impure.has(operand.identifier.id) &&
isMutable(instr, operand)
) {
impure.set(
operand.identifier.id,
(impureEffect ?? controlImpureEffect)!,
);
hasChanges = true;
}
break;
}
case Effect.Freeze:
case Effect.Read: {
// no-op
break;
}
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
description: null,
details: [
{
kind: 'error',
loc: operand.loc,
message: null,
},
],
suggestions: null,
});
}
default: {
assertExhaustive(
operand.effect,
`Unexpected effect kind \`${operand.effect}\``,
);
}
}
}
}
if (impureEffect == null) {
const lvalueEffect = impure.get(instr.lvalue.identifier.id)!;
if (lvalueEffect != null) {
for (const operand of eachInstructionValueOperand(instr.value)) {
if (
isMutable(instr, operand) &&
!impure.has(operand.identifier.id)
) {
impure.set(operand.identifier.id, lvalueEffect);
hasChanges = true;
}
}
}
}
}
if (block.terminal.kind === 'return' && block.terminal.effects != null) {
hasChanges =
processEffects(
block.terminal.id,
block.terminal.effects,
impure,
impureFunctions,
cache,
) || hasChanges;
if (block.terminal.kind === 'return') {
const terminalEffect = impure.get(block.terminal.value.identifier.id);
if (terminalEffect != null && !impure.has(fn.returns.identifier.id)) {
impure.set(fn.returns.identifier.id, terminalEffect);
hasChanges = true;
}
}
}
} 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;
@@ -341,7 +237,6 @@ function inferImpureValues(
const result = inferFunctionExpressionMemo(
value.loweredFunc.func,
impure,
impureFunctions,
cache,
);
if (result.error.hasAnyErrors()) {
@@ -349,39 +244,48 @@ function inferImpureValues(
}
}
for (const effect of instr.effects ?? []) {
if (effect.kind === 'Render') {
validateRenderEffect(effect);
if (
effect.kind !== 'Render' ||
!impure.has(effect.place.identifier.id)
) {
continue;
}
}
}
if (block.terminal.kind === 'return' && block.terminal.effects != null) {
for (const effect of block.terminal.effects) {
if (effect.kind === 'Render') {
validateRenderEffect(effect);
const impureEffect = impure.get(effect.place.identifier.id)!;
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);
}
}
}
const impureEffects: Map<IdentifierId, ImpureEffect> = new Map();
const impureEffects: Array<ImpureEffect> = [];
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);
impureEffects.push({
kind: 'Impure',
into: {...place},
category: impureEffect.category,
reason: impureEffect.reason,
description: impureEffect.description,
sourceMessage: impureEffect.sourceMessage,
usageMessage: impureEffect.usageMessage,
});
}
}
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)!),
)
);
return {effects: impureEffects, error};
}

View File

@@ -1,137 +0,0 @@
# 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
```

View File

@@ -1,51 +0,0 @@
## 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

View File

@@ -1,74 +0,0 @@
## 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) [{},{}]

View File

@@ -1,60 +0,0 @@
## Input
```javascript
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 = {};
object.foo = () => ref.current;
return <Stringify object={object} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
};
```
## Code
```javascript
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);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const object = {};
object.foo = () => ref.current;
t0 = <Stringify object={object} shouldInvokeFns={true} />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
};
```
### Eval output
(kind: ok) <div>{"object":{"foo":{"kind":"Function","result":42}},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,64 @@
## 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 | }
```

View File

@@ -1,10 +1,6 @@
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 => () => {

View File

@@ -20,26 +20,30 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot access ref value during render
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:22
3 | function Component(props) {
4 | const ref = useRef();
> 5 | useEffect(() => {}, [ref.current]);
| ^^^^^^^^^^^^^ Ref value is used during render
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {
error.hook-ref-value.ts:5:23
3 | function Component(props) {
4 | const ref = useRef();
> 5 | useEffect(() => {}, [ref.current]);
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^ Cannot access ref value 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
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {

View File

@@ -17,23 +17,15 @@ function Component(props) {
```
Found 1 error:
Error: Cannot access ref value during render
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-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;
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^ Cannot access ref value during render
5 | return value;
6 | }
7 |

View File

@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot access ref value during render
@@ -41,11 +41,24 @@ error.invalid-access-ref-in-reducer-init.ts:8:4
10 |
11 | return <Stringify state={state} />;
error.invalid-access-ref-in-reducer-init.ts:8:12
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-init.ts:11:27
9 | );
10 |
> 11 | return <Stringify state={state} />;
| ^^^^^ Ref value is used during render
12 | }
13 |
14 | export const FIXTURE_ENTRYPOINT = {
error.invalid-access-ref-in-reducer-init.ts:8:4
6 | (state, action) => state + action,
7 | 0,
> 8 | init => ref.current
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^^^^^^^^^ Ref is initially accessed
9 | );
10 |
11 | return <Stringify state={state} />;

View File

@@ -37,15 +37,6 @@ error.invalid-access-ref-in-reducer.ts:5:29
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).
@@ -59,11 +50,11 @@ error.invalid-access-ref-in-reducer.ts:7:27
9 |
10 | export const FIXTURE_ENTRYPOINT = {
error.invalid-access-ref-in-reducer.ts:5:35
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);
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^^^^^^^ Ref is initially accessed
6 |
7 | return <Stringify state={state} />;
8 | }

View File

@@ -0,0 +1,45 @@
## 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 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-render-mutate-object-with-ref-function.ts:8:15
6 | object.foo = () => ref.current;
7 | const refValue = object.foo();
> 8 | return <div>{refValue}</div>;
| ^^^^^^^^ Ref value is used during render
9 | }
10 |
error.invalid-access-ref-in-render-mutate-object-with-ref-function.ts:6:15
4 | const ref = useRef(null);
5 | const object = {};
> 6 | object.foo = () => ref.current;
| ^^^^^^^^^^^^^^^^^ Ref is initially accessed
7 | const refValue = object.foo();
8 | return <div>{refValue}</div>;
9 | }
```

View File

@@ -1,9 +1,5 @@
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 = {};

View File

@@ -37,15 +37,6 @@ error.invalid-access-ref-in-state-initializer.ts:5:27
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).
@@ -59,11 +50,11 @@ error.invalid-access-ref-in-state-initializer.ts:7:27
9 |
10 | export const FIXTURE_ENTRYPOINT = {
error.invalid-access-ref-in-state-initializer.ts:5:33
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);
| ^^^^^^^^^^^ Ref is initially accessed
| ^^^^^^^^^^^^^^^^^ Ref is initially accessed
6 |
7 | return <Stringify state={state} />;
8 | }

View File

@@ -0,0 +1,51 @@
## Input
```javascript
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
function Component(props) {
const ref = useRef(props.value);
const object = {};
object.foo = () => ref.current;
return <Stringify object={object} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
};
```
## Error
```
Found 1 error:
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-capturing-ref-returning-function-in-rendered-object.ts:8:28
6 | const object = {};
7 | object.foo = () => ref.current;
> 8 | return <Stringify object={object} shouldInvokeFns={true} />;
| ^^^^^^ Ref value is used during render
9 | }
10 |
11 | export const FIXTURE_ENTRYPOINT = {
error.invalid-capturing-ref-returning-function-in-rendered-object.ts:7:15
5 | const ref = useRef(props.value);
6 | const object = {};
> 7 | object.foo = () => ref.current;
| ^^^^^^^^^^^^^^^^^ Ref is initially accessed
8 | return <Stringify object={object} shouldInvokeFns={true} />;
9 | }
10 |
```

View File

@@ -1,10 +1,6 @@
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 = {};

View File

@@ -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:6:4
4 |
5 | const setRef = () => {
> 6 | ref.current = false;
| ^^^^^^^^^^^ Cannot update ref during render
7 | };
8 | const changeRef = setRef;
9 | changeRef();
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 | }
```

View File

@@ -6,11 +6,6 @@
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();
@@ -31,22 +26,22 @@ 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} />;
error.invalid-impure-functions-in-render-indirect-via-mutation.ts:10:23
8 | const array = [];
9 | arrayPush(array, now);
> 10 | return <Foo hasDate={array} />;
| ^^^^^ Cannot access impure value during render
16 | }
17 |
11 | }
12 |
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);
error.invalid-impure-functions-in-render-indirect-via-mutation.ts:6:18
4 |
5 | function Component() {
> 6 | const getDate = () => Date.now();
| ^^^^^^^^^^^^^^^^ `Date.now` is an impure function.
7 | const now = getDate();
8 | const array = [];
9 | arrayPush(array, now);
```

View File

@@ -2,11 +2,6 @@
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();

View File

@@ -0,0 +1,46 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
import {identity, makeArray} from 'shared-runtime';
function Component() {
const getDate = () => Date.now();
const array = makeArray(getDate());
const hasDate = identity(array);
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-indirect.ts:9:23
7 | const array = makeArray(getDate());
8 | const hasDate = identity(array);
> 9 | return <Foo hasDate={hasDate} />;
| ^^^^^^^ Cannot access impure value during render
10 | }
11 |
error.invalid-impure-functions-in-render-indirect.ts:6:18
4 |
5 | function Component() {
> 6 | const getDate = () => Date.now();
| ^^^^^^^^^^^^^^^^ `Date.now` is an impure function.
7 | const array = makeArray(getDate());
8 | const hasDate = identity(array);
9 | return <Foo hasDate={hasDate} />;
```

View File

@@ -2,11 +2,6 @@
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());

View File

@@ -0,0 +1,52 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
import {identity, makeArray} from 'shared-runtime';
function Component() {
const now = () => Date.now();
const f = () => {
// this should error but we currently lose track of the impurity bc
// the impure value comes from behind a call
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-2.ts:15:23
13 | };
14 | const hasDate = f();
> 15 | return <Foo hasDate={hasDate} />;
| ^^^^^^^ Cannot access impure value during render
16 | }
17 |
error.invalid-impure-functions-in-render-via-function-call-2.ts:10:28
8 | // this should error but we currently lose track of the impurity bc
9 | // the impure value comes from behind a call
> 10 | const array = makeArray(now());
| ^^^ `Date.now` is an impure function.
11 | const hasDate = identity(array);
12 | return hasDate;
13 | };
```

View File

@@ -2,14 +2,11 @@
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 = () => {
// this should error but we currently lose track of the impurity bc
// the impure value comes from behind a call
const array = makeArray(now());
const hasDate = identity(array);
return hasDate;

View File

@@ -37,14 +37,14 @@ error.invalid-impure-functions-in-render-via-function-call.ts:13:23
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);
error.invalid-impure-functions-in-render-via-function-call.ts:8:28
6 | const now = Date.now();
7 | const f = () => {
> 8 | const array = makeArray(now);
| ^^^ `Date.now` is an impure function.
9 | const hasDate = identity(array);
10 | return hasDate;
11 | };
```

View File

@@ -1,51 +0,0 @@
## 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());
```

View File

@@ -1,14 +0,0 @@
// @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} />;
}

View File

@@ -0,0 +1,71 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
import {identity, makeArray} from 'shared-runtime';
function Component() {
const now = Date.now();
const renderItem = () => {
const array = makeArray(now);
const hasDate = identity(array);
return <Bar hasDate={hasDate} />;
};
return <Foo renderItem={renderItem} />;
}
```
## 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-functions-in-render-via-render-helper.ts:10:25
8 | const array = makeArray(now);
9 | const hasDate = identity(array);
> 10 | return <Bar hasDate={hasDate} />;
| ^^^^^^^ Cannot access impure value during render
11 | };
12 | return <Foo renderItem={renderItem} />;
13 | }
error.invalid-impure-functions-in-render-via-render-helper.ts:6:14
4 |
5 | function Component() {
> 6 | const now = Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
7 | const renderItem = () => {
8 | const array = makeArray(now);
9 | const hasDate = identity(array);
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.ts:12:26
10 | return <Bar hasDate={hasDate} />;
11 | };
> 12 | return <Foo renderItem={renderItem} />;
| ^^^^^^^^^^ Cannot access impure value during render
13 | }
14 |
error.invalid-impure-functions-in-render-via-render-helper.ts:6:14
4 |
5 | function Component() {
> 6 | const now = Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
7 | const renderItem = () => {
8 | const array = makeArray(now);
9 | const hasDate = identity(array);
```

View File

@@ -6,8 +6,6 @@ function Component() {
const now = Date.now();
const renderItem = () => {
const array = makeArray(now);
// we don't have an alias signature for identity(), so we optimistically
// assume this doesn't propagate the impurity
const hasDate = identity(array);
return <Bar hasDate={hasDate} />;
};

View File

@@ -32,11 +32,11 @@ error.invalid-impure-value-in-render-helper.ts:5:17
7 | return <div>{render()}</div>;
8 | }
error.invalid-impure-value-in-render-helper.ts:3:20
error.invalid-impure-value-in-render-helper.ts:3:14
1 | // @validateNoImpureFunctionsInRender
2 | function Component() {
> 3 | const now = () => Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
| ^^^^^^^^^^^^^^^^ `Date.now` is an impure function.
4 | const render = () => {
5 | return <div>{now()}</div>;
6 | };
@@ -53,11 +53,11 @@ error.invalid-impure-value-in-render-helper.ts:7:15
8 | }
9 |
error.invalid-impure-value-in-render-helper.ts:3:20
error.invalid-impure-value-in-render-helper.ts:3:14
1 | // @validateNoImpureFunctionsInRender
2 | function Component() {
> 3 | const now = () => Date.now();
| ^^^^^^^^^^ `Date.now` is an impure function.
| ^^^^^^^^^^^^^^^^ `Date.now` is an impure function.
4 | const render = () => {
5 | return <div>{now()}</div>;
6 | };

View File

@@ -0,0 +1,34 @@
## 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 |
```

View File

@@ -0,0 +1,6 @@
// @validateRefAccessDuringRender
function Component(props) {
const ref = useRef(null);
const x = foo(ref);
return x.current;
}

View File

@@ -29,13 +29,15 @@ 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) {
> 7 | r.current = 1;
| ^^^^^^^^^ Cannot update ref during render
8 | }
9 | }
10 |
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 = ... }`
```

View File

@@ -15,9 +15,22 @@ function Component(props) {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot access ref value during render
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
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).
@@ -25,7 +38,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;
| ^^^^^^^^^^^ Ref value is used during render
| ^^^^^^^^^^^ Cannot access ref value during render
6 | }
7 |
```

View File

@@ -15,9 +15,22 @@ function Component(props) {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot access ref value during render
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
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).
@@ -25,15 +38,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 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
| ^^^^^^^^^^^^^^^^^ Cannot access ref value during render
6 | }
7 |
```

View File

@@ -0,0 +1,53 @@
## 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 |
```

View File

@@ -0,0 +1,13 @@
// @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} />;
}

View File

@@ -25,7 +25,19 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 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 | }
Error: Cannot access refs during render

View File

@@ -0,0 +1,41 @@
## 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 |
```

View File

@@ -1,10 +1,6 @@
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {

View File

@@ -0,0 +1,41 @@
## 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 |
```

View File

@@ -1,10 +1,6 @@
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {

View File

@@ -33,18 +33,10 @@ 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;
| ^^^^^^^^^ Ref is initialized more than once during render
| ^^^^^^^^^ Cannot update ref 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 | }
```

View File

@@ -24,19 +24,31 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 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).
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) {
> 8 | r.current = 1;
| ^^^^^^^^^ Cannot update ref during render
> 7 | if (guard) {
| ^^^^^ Cannot access ref value during render
8 | r.current = 1;
9 | }
10 | }
11 |
```

View File

@@ -0,0 +1,42 @@
## 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 = {
```

View File

@@ -1,10 +1,6 @@
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {

View File

@@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Error: Cannot access ref value during render
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).
@@ -30,7 +30,7 @@ error.ref-optional.ts:5:9
3 | function Component(props) {
4 | const ref = useRef();
> 5 | return ref?.current;
| ^^^^^^^^^^^^ Ref value is used during render
| ^^^^^^^^^^^^ Cannot access ref value during render
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {

View File

@@ -0,0 +1,69 @@
## 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 | </>
```

View File

@@ -18,8 +18,8 @@ function Component() {
const ref = useRef<HTMLInputElement>(null);
const onSubmit = (data: any) => {
// Allowed: we aren't sure that the ref.current value flows into the render
// output, so we optimistically assume it's safe
// 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);
}

View File

@@ -0,0 +1,64 @@
## 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 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.ref-value-in-event-handler-wrapper.ts:19:6
17 | <>
18 | <input ref={ref} />
> 19 | <button onClick={handleClick(ref.current)}>Click</button>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ref value is used during render
20 | </>
21 | );
22 | }
error.ref-value-in-event-handler-wrapper.ts:19:35
17 | <>
18 | <input ref={ref} />
> 19 | <button onClick={handleClick(ref.current)}>Click</button>
| ^^^^^^^^^^^ Ref is initially accessed
20 | </>
21 | );
22 | }
```

View File

@@ -11,8 +11,8 @@ function handleClick(value: any) {
function Component() {
const ref = useRef(null);
// Allowed: we aren't sure that the ref.current value flows into the render
// output, so we optimistically assume it's safe
// 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} />

View File

@@ -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 | }

View File

@@ -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: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
error.useCallback-accesses-ref-mutated-later-via-function-preserve-memoization.ts:17:2
15 | ref.current.inner = null;
16 | };
17 | reset();
> 17 | reset();
| ^^^^^ This function accesses a ref value
18 |
19 | return <input onChange={onChange} />;
20 | }
```

View File

@@ -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 | }

View File

@@ -0,0 +1,39 @@
## 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 |
```

View File

@@ -1,7 +1,5 @@
// @validateRefAccessDuringRender:true
function Foo(props, ref) {
// Allowed: the value is not guaranteed to flow into something that's rendered
console.log(ref.current);
return <div>{props.bar}</div>;
}

View File

@@ -1,56 +0,0 @@
## 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

View File

@@ -1,64 +0,0 @@
## 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

View File

@@ -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":20,"index":307},"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":16,"index":303},"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}
```

View File

@@ -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":20,"index":339},"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":16,"index":335},"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}
```

View File

@@ -1,45 +0,0 @@
## 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

View File

@@ -1,11 +0,0 @@
// @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;
}

View File

@@ -0,0 +1,42 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees:true
import {useRef, useMemo} from 'react';
import {makeArray} from 'shared-runtime';
function useFoo() {
const r = useRef();
return useMemo(() => makeArray(r), []);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
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.maybe-mutable-ref-not-preserved.ts:8:33
6 | function useFoo() {
7 | const r = useRef();
> 8 | return useMemo(() => makeArray(r), []);
| ^ Passing a ref to a function may read its value during render
9 | }
10 |
11 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -3,10 +3,6 @@
import {useRef, useMemo} from 'react';
import {makeArray} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function useFoo() {
const r = useRef();
return useMemo(() => makeArray(r), []);

View File

@@ -0,0 +1,37 @@
## Input
```javascript
// @flow @validatePreserveExistingMemoizationGuarantees
import {identity} from 'shared-runtime';
component Component(disableLocalRef, ref) {
const localRef = useFooRef();
const mergedRef = useMemo(() => {
return disableLocalRef ? ref : identity(ref, localRef);
}, [disableLocalRef, ref, localRef]);
return <div ref={mergedRef} />;
}
```
## 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 localRef = useFooRef();
6 | const mergedRef = useMemo(() => {
> 7 | return disableLocalRef ? ref : identity(ref, localRef);
| ^^^ Passing a ref to a function may read its value during render
8 | }, [disableLocalRef, ref, localRef]);
9 | return <div ref={mergedRef} />;
10 | }
```

View File

@@ -1,59 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees:true
import {useRef, useMemo} from 'react';
import {makeArray} from 'shared-runtime';
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function useFoo() {
const r = useRef();
return useMemo(() => makeArray(r), []);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees:true
import { useRef, useMemo } from "react";
import { makeArray } from "shared-runtime";
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses an impure value during render.
*/
function useFoo() {
const $ = _c(1);
const r = useRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = makeArray(r);
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
### Eval output
(kind: ok) [{}]

View File

@@ -1,52 +0,0 @@
## Input
```javascript
// @flow @validatePreserveExistingMemoizationGuarantees
import {identity} from 'shared-runtime';
component Component(disableLocalRef, ref) {
const localRef = useFooRef();
const mergedRef = useMemo(() => {
return disableLocalRef ? ref : identity(ref, localRef);
}, [disableLocalRef, ref, localRef]);
return <div ref={mergedRef} />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { identity } from "shared-runtime";
const Component = React.forwardRef(Component_withRef);
function Component_withRef(t0, ref) {
const $ = _c(6);
const { disableLocalRef } = t0;
const localRef = useFooRef();
let t1;
if ($[0] !== disableLocalRef || $[1] !== localRef || $[2] !== ref) {
t1 = disableLocalRef ? ref : identity(ref, localRef);
$[0] = disableLocalRef;
$[1] = localRef;
$[2] = ref;
$[3] = t1;
} else {
t1 = $[3];
}
const mergedRef = t1;
let t2;
if ($[4] !== mergedRef) {
t2 = <div ref={mergedRef} />;
$[4] = mergedRef;
$[5] = t2;
} else {
t2 = $[5];
}
return t2;
}
```

View File

@@ -1,46 +0,0 @@
## Input
```javascript
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {
f(r);
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
## Code
```javascript
import { useRef } from "react";
function C() {
const r = useRef(null);
if (r.current == null) {
f(r);
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
### Eval output
(kind: exception) f is not defined

View File

@@ -1,46 +0,0 @@
## Input
```javascript
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {
f(r.current);
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
## Code
```javascript
import { useRef } from "react";
function C() {
const r = useRef(null);
if (r.current == null) {
f(r.current);
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
### Eval output
(kind: exception) f is not defined

View File

@@ -1,49 +0,0 @@
## Input
```javascript
//@flow
import {useRef} from 'react';
/**
* Allowed: we aren't sure that the ref.current value flows into the render
* output, so we optimistically assume it's safe
*/
component C() {
const r = useRef(null);
if (r.current == null) {
r.current = 1;
}
f(r.current);
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
## Code
```javascript
import { useRef } from "react";
function C() {
const r = useRef(null);
if (r.current == null) {
r.current = 1;
}
f(r.current);
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
### Eval output
(kind: exception) f is not defined

View File

@@ -1,121 +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) => {
// Allowed: we aren't sure that the ref.current value flows into the render
// output, so we optimistically assume it's safe
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: [{}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableInferEventHandlers
import { useRef } from "react";
// Simulates a custom component wrapper
function CustomForm(t0) {
const $ = _c(3);
const { onSubmit, children } = t0;
let t1;
if ($[0] !== children || $[1] !== onSubmit) {
t1 = <form onSubmit={onSubmit}>{children}</form>;
$[0] = children;
$[1] = onSubmit;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
// Simulates react-hook-form's handleSubmit
function handleSubmit(callback) {
const $ = _c(2);
let t0;
if ($[0] !== callback) {
t0 = (event) => {
event.preventDefault();
callback({} as T);
};
$[0] = callback;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
function Component() {
const $ = _c(1);
const ref = useRef(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const onSubmit = (data) => {
if (ref.current !== null) {
console.log(ref.current.value);
}
};
t0 = (
<>
<input ref={ref} />
<CustomForm onSubmit={handleSubmit(onSubmit)}>
<button type="submit">Submit</button>
</CustomForm>
</>
);
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
### Eval output
(kind: ok) <input><form><button type="submit">Submit</button></form>

View File

@@ -1,83 +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);
// Allowed: we aren't sure that the ref.current value flows into the render
// output, so we optimistically assume it's safe
return (
<>
<input ref={ref} />
<button onClick={handleClick(ref.current)}>Click</button>
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableInferEventHandlers
import { useRef } from "react";
// Simulates a handler wrapper
function handleClick(value) {
const $ = _c(2);
let t0;
if ($[0] !== value) {
t0 = () => {
console.log(value);
};
$[0] = value;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
function Component() {
const $ = _c(1);
const ref = useRef(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (
<>
<input ref={ref} />
<button onClick={handleClick(ref.current)}>Click</button>
</>
);
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
### Eval output
(kind: ok) <input><button>Click</button>

View File

@@ -6,7 +6,7 @@
import {useRef} from 'react';
hook useFoo() {
component Foo() {
const ref = useRef();
const s = () => {
@@ -16,10 +16,6 @@ hook useFoo() {
return s;
}
component Foo() {
useFoo();
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
@@ -34,7 +30,7 @@ import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
function useFoo() {
function Foo() {
const $ = _c(1);
const ref = useRef();
let t0;
@@ -48,10 +44,6 @@ function useFoo() {
return s;
}
function Foo() {
useFoo();
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
@@ -60,4 +52,4 @@ export const FIXTURE_ENTRYPOINT = {
```
### Eval output
(kind: ok)
(kind: ok) "[[ function params=0 ]]"

View File

@@ -2,7 +2,7 @@
import {useRef} from 'react';
hook useFoo() {
component Foo() {
const ref = useRef();
const s = () => {
@@ -12,10 +12,6 @@ hook useFoo() {
return s;
}
component Foo() {
useFoo();
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],

View File

@@ -25,40 +25,22 @@ component Component(prop1, ref) {
## Code
```javascript
import { c as _c, useFire } from "react/compiler-runtime";
import { useFire } from "react/compiler-runtime";
import { fire } from "react";
import { print } from "shared-runtime";
const Component = React.forwardRef(Component_withRef);
function Component_withRef(t0, ref) {
const $ = _c(5);
const { prop1 } = t0;
let t1;
if ($[0] !== prop1) {
t1 = () => {
console.log(prop1);
};
$[0] = prop1;
$[1] = t1;
} else {
t1 = $[1];
}
const foo = t1;
const t2 = useFire(foo);
let t3;
if ($[2] !== prop1 || $[3] !== t2) {
t3 = () => {
t2(prop1);
bar();
t2();
};
$[2] = prop1;
$[3] = t2;
$[4] = t3;
} else {
t3 = $[4];
}
useEffect(t3);
const foo = () => {
console.log(prop1);
};
const t1 = useFire(foo);
useEffect(() => {
t1(prop1);
bar();
t1();
});
print(ref.current);
return null;
}

View File

@@ -1,59 +0,0 @@
## Input
```javascript
// @validateRefAccessDuringRender
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses a ref during render. Type info is lost when ref is
* stored in an object field.
*/
function Foo({a}) {
const ref = useRef();
const val = {ref};
const x = {a, val: val.ref.current};
return <VideoList videos={x} />;
}
```
## 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. Type info is lost when ref is
* stored in an object field.
*/
function Foo(t0) {
const $ = _c(3);
const { a } = t0;
const ref = useRef();
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { ref };
$[0] = t1;
} else {
t1 = $[0];
}
const val = t1;
let t2;
if ($[1] !== a) {
const x = { a, val: val.ref.current };
t2 = <VideoList videos={x} />;
$[1] = a;
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,14 +0,0 @@
// @validateRefAccessDuringRender
/**
* Allowed: we don't have sufficient type information to be sure that
* this accesses a ref during render. Type info is lost when ref is
* stored in an object field.
*/
function Foo({a}) {
const ref = useRef();
const val = {ref};
const x = {a, val: val.ref.current};
return <VideoList videos={x} />;
}

View File

@@ -1,51 +0,0 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
import {identity, makeArray} from 'shared-runtime';
function Component() {
const now = Date.now();
const renderItem = () => {
const array = makeArray(now);
// we don't have an alias signature for identity(), so we optimistically
// assume this doesn't propagate the impurity
const hasDate = identity(array);
return <Bar hasDate={hasDate} />;
};
return <Foo renderItem={renderItem} />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateNoImpureFunctionsInRender
import { identity, makeArray } from "shared-runtime";
function Component() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const now = Date.now();
const renderItem = () => {
const array = makeArray(now);
const hasDate = identity(array);
return <Bar hasDate={hasDate} />;
};
t0 = <Foo renderItem={renderItem} />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,51 +0,0 @@
## Input
```javascript
// @validateRefAccessDuringRender:true
function Foo(props, ref) {
// Allowed: the value is not guaranteed to flow into something that's rendered
console.log(ref.current);
return <div>{props.bar}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{bar: 'foo'}, {ref: {cuurrent: 1}}],
isComponent: true,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRender:true
function Foo(props, ref) {
const $ = _c(2);
console.log(ref.current);
let t0;
if ($[0] !== props.bar) {
t0 = <div>{props.bar}</div>;
$[0] = props.bar;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }],
isComponent: true,
};
```
### Eval output
(kind: ok) <div>foo</div>
logs: [undefined]

View File

@@ -26,3 +26,5 @@ export const FIXTURES_PATH = path.join(
'compiler',
);
export const SNAPSHOT_EXTENSION = '.expect.md';
export const FILTER_FILENAME = 'testfilter.txt';
export const FILTER_PATH = path.join(PROJECT_ROOT, FILTER_FILENAME);

View File

@@ -8,7 +8,7 @@
import fs from 'fs/promises';
import * as glob from 'glob';
import path from 'path';
import {FIXTURES_PATH, SNAPSHOT_EXTENSION} from './constants';
import {FILTER_PATH, FIXTURES_PATH, SNAPSHOT_EXTENSION} from './constants';
const INPUT_EXTENSIONS = [
'.js',
@@ -22,9 +22,19 @@ const INPUT_EXTENSIONS = [
];
export type TestFilter = {
debug: boolean;
paths: Array<string>;
};
async function exists(file: string): Promise<boolean> {
try {
await fs.access(file);
return true;
} catch {
return false;
}
}
function stripExtension(filename: string, extensions: Array<string>): string {
for (const ext of extensions) {
if (filename.endsWith(ext)) {
@@ -34,6 +44,37 @@ function stripExtension(filename: string, extensions: Array<string>): string {
return filename;
}
export async function readTestFilter(): Promise<TestFilter | null> {
if (!(await exists(FILTER_PATH))) {
throw new Error(`testfilter file not found at \`${FILTER_PATH}\``);
}
const input = await fs.readFile(FILTER_PATH, 'utf8');
const lines = input.trim().split('\n');
let debug: boolean = false;
const line0 = lines[0];
if (line0 != null) {
// Try to parse pragmas
let consumedLine0 = false;
if (line0.indexOf('@only') !== -1) {
consumedLine0 = true;
}
if (line0.indexOf('@debug') !== -1) {
debug = true;
consumedLine0 = true;
}
if (consumedLine0) {
lines.shift();
}
}
return {
debug,
paths: lines.filter(line => !line.trimStart().startsWith('//')),
};
}
export function getBasename(fixture: TestFixture): string {
return stripExtension(path.basename(fixture.inputPath), INPUT_EXTENSIONS);
}

View File

@@ -8,8 +8,8 @@
import watcher from '@parcel/watcher';
import path from 'path';
import ts from 'typescript';
import {FIXTURES_PATH, PROJECT_ROOT} from './constants';
import {TestFilter} from './fixture-utils';
import {FILTER_FILENAME, FIXTURES_PATH, PROJECT_ROOT} from './constants';
import {TestFilter, readTestFilter} from './fixture-utils';
import {execSync} from 'child_process';
export function watchSrc(
@@ -117,10 +117,6 @@ export type RunnerState = {
lastUpdate: number;
mode: RunnerMode;
filter: TestFilter | null;
debug: boolean;
// Input mode for interactive pattern entry
inputMode: 'none' | 'pattern';
inputBuffer: string;
};
function subscribeFixtures(
@@ -146,6 +142,26 @@ function subscribeFixtures(
});
}
function subscribeFilterFile(
state: RunnerState,
onChange: (state: RunnerState) => void,
) {
watcher.subscribe(PROJECT_ROOT, async (err, events) => {
if (err) {
console.error(err);
process.exit(1);
} else if (
events.findIndex(event => event.path.includes(FILTER_FILENAME)) !== -1
) {
if (state.mode.filter) {
state.filter = await readTestFilter();
state.mode.action = RunnerAction.Test;
onChange(state);
}
}
});
}
function subscribeTsc(
state: RunnerState,
onChange: (state: RunnerState) => void,
@@ -184,67 +200,15 @@ function subscribeKeyEvents(
onChange: (state: RunnerState) => void,
) {
process.stdin.on('keypress', async (str, key) => {
// Handle input mode (pattern entry)
if (state.inputMode !== 'none') {
if (key.name === 'return') {
// Enter pressed - process input
const pattern = state.inputBuffer.trim();
state.inputMode = 'none';
state.inputBuffer = '';
process.stdout.write('\n');
if (pattern !== '') {
// Set the pattern as filter
state.filter = {paths: [pattern]};
state.mode.filter = true;
state.mode.action = RunnerAction.Test;
onChange(state);
}
// If empty, just exit input mode without changes
return;
} else if (key.name === 'escape') {
// Cancel input mode
state.inputMode = 'none';
state.inputBuffer = '';
process.stdout.write(' (cancelled)\n');
return;
} else if (key.name === 'backspace') {
if (state.inputBuffer.length > 0) {
state.inputBuffer = state.inputBuffer.slice(0, -1);
// Erase character: backspace, space, backspace
process.stdout.write('\b \b');
}
return;
} else if (str && !key.ctrl && !key.meta) {
// Regular character - accumulate and echo
state.inputBuffer += str;
process.stdout.write(str);
return;
}
return; // Ignore other keys in input mode
}
// Normal mode keypress handling
if (key.name === 'u') {
// u => update fixtures
state.mode.action = RunnerAction.Update;
} else if (key.name === 'q') {
process.exit(0);
} else if (key.name === 'a') {
// a => exit filter mode and run all tests
state.mode.filter = false;
state.filter = null;
} else if (key.name === 'f') {
state.mode.filter = !state.mode.filter;
state.filter = state.mode.filter ? await readTestFilter() : null;
state.mode.action = RunnerAction.Test;
} else if (key.name === 'd') {
// d => toggle debug logging
state.debug = !state.debug;
state.mode.action = RunnerAction.Test;
} else if (key.name === 'p') {
// p => enter pattern input mode
state.inputMode = 'pattern';
state.inputBuffer = '';
process.stdout.write('Pattern: ');
return; // Don't trigger onChange yet
} else {
// any other key re-runs tests
state.mode.action = RunnerAction.Test;
@@ -255,33 +219,21 @@ function subscribeKeyEvents(
export async function makeWatchRunner(
onChange: (state: RunnerState) => void,
debugMode: boolean,
initialPattern?: string,
filterMode: boolean,
): Promise<void> {
// Determine initial filter state
let filter: TestFilter | null = null;
let filterEnabled = false;
if (initialPattern) {
filter = {paths: [initialPattern]};
filterEnabled = true;
}
const state: RunnerState = {
const state = {
compilerVersion: 0,
isCompilerBuildValid: false,
lastUpdate: -1,
mode: {
action: RunnerAction.Test,
filter: filterEnabled,
filter: filterMode,
},
filter,
debug: debugMode,
inputMode: 'none',
inputBuffer: '',
filter: filterMode ? await readTestFilter() : null,
};
subscribeTsc(state, onChange);
subscribeFixtures(state, onChange);
subscribeKeyEvents(state, onChange);
subscribeFilterFile(state, onChange);
}

View File

@@ -12,8 +12,8 @@ import * as readline from 'readline';
import ts from 'typescript';
import yargs from 'yargs';
import {hideBin} from 'yargs/helpers';
import {PROJECT_ROOT} from './constants';
import {TestFilter, getFixtures} from './fixture-utils';
import {FILTER_PATH, PROJECT_ROOT} from './constants';
import {TestFilter, getFixtures, readTestFilter} from './fixture-utils';
import {TestResult, TestResults, report, update} from './reporter';
import {
RunnerAction,
@@ -33,9 +33,9 @@ type RunnerOptions = {
sync: boolean;
workerThreads: boolean;
watch: boolean;
filter: boolean;
update: boolean;
pattern?: string;
debug: boolean;
};
const opts: RunnerOptions = yargs
@@ -59,16 +59,18 @@ const opts: RunnerOptions = yargs
.alias('u', 'update')
.describe('update', 'Update fixtures')
.default('update', false)
.boolean('filter')
.describe(
'filter',
'Only run fixtures which match the contents of testfilter.txt',
)
.default('filter', false)
.string('pattern')
.alias('p', 'pattern')
.describe(
'pattern',
'Optional glob pattern to filter fixtures (e.g., "error.*", "use-memo")',
)
.boolean('debug')
.alias('d', 'debug')
.describe('debug', 'Enable debug logging to print HIR for each pass')
.default('debug', false)
.help('help')
.strict()
.parseSync(hideBin(process.argv)) as RunnerOptions;
@@ -80,15 +82,12 @@ async function runFixtures(
worker: Worker & typeof runnerWorker,
filter: TestFilter | null,
compilerVersion: number,
debug: boolean,
requireSingleFixture: boolean,
): Promise<TestResults> {
// We could in theory be fancy about tracking the contents of the fixtures
// directory via our file subscription, but it's simpler to just re-read
// the directory each time.
const fixtures = await getFixtures(filter);
const isOnlyFixture = filter !== null && fixtures.size === 1;
const shouldLog = debug && (!requireSingleFixture || isOnlyFixture);
let entries: Array<[string, TestResult]>;
if (!opts.sync) {
@@ -97,7 +96,12 @@ async function runFixtures(
for (const [fixtureName, fixture] of fixtures) {
work.push(
worker
.transformFixture(fixture, compilerVersion, shouldLog, true)
.transformFixture(
fixture,
compilerVersion,
(filter?.debug ?? false) && isOnlyFixture,
true,
)
.then(result => [fixtureName, result]),
);
}
@@ -109,7 +113,7 @@ async function runFixtures(
let output = await runnerWorker.transformFixture(
fixture,
compilerVersion,
shouldLog,
(filter?.debug ?? false) && isOnlyFixture,
true,
);
entries.push([fixtureName, output]);
@@ -124,7 +128,7 @@ async function onChange(
worker: Worker & typeof runnerWorker,
state: RunnerState,
) {
const {compilerVersion, isCompilerBuildValid, mode, filter, debug} = state;
const {compilerVersion, isCompilerBuildValid, mode, filter} = state;
if (isCompilerBuildValid) {
const start = performance.now();
@@ -138,8 +142,6 @@ async function onChange(
worker,
mode.filter ? filter : null,
compilerVersion,
debug,
true, // requireSingleFixture in watch mode
);
const end = performance.now();
if (mode.action === RunnerAction.Update) {
@@ -157,13 +159,11 @@ async function onChange(
console.log(
'\n' +
(mode.filter
? `Current mode = FILTER, pattern = "${filter?.paths[0] ?? ''}".`
? `Current mode = FILTER, filter test fixtures by "${FILTER_PATH}".`
: 'Current mode = NORMAL, run all test fixtures.') +
'\nWaiting for input or file changes...\n' +
'u - update all fixtures\n' +
`d - toggle (turn ${debug ? 'off' : 'on'}) debug logging\n` +
'p - enter pattern to filter fixtures\n' +
(mode.filter ? 'a - run all tests (exit filter mode)\n' : '') +
`f - toggle (turn ${mode.filter ? 'off' : 'on'}) filter mode\n` +
'q - quit\n' +
'[any] - rerun tests\n',
);
@@ -180,16 +180,15 @@ export async function main(opts: RunnerOptions): Promise<void> {
worker.getStderr().pipe(process.stderr);
worker.getStdout().pipe(process.stdout);
// Check if watch mode should be enabled
const shouldWatch = opts.watch;
// If pattern is provided, force watch mode off and use pattern filter
const shouldWatch = opts.watch && opts.pattern == null;
if (opts.watch && opts.pattern != null) {
console.warn('NOTE: --watch is ignored when a --pattern is supplied');
}
if (shouldWatch) {
makeWatchRunner(
state => onChange(worker, state),
opts.debug,
opts.pattern,
);
if (opts.pattern) {
makeWatchRunner(state => onChange(worker, state), opts.filter);
if (opts.filter) {
/**
* Warm up wormers when in watch mode. Loading the Forget babel plugin
* and all of its transitive dependencies takes 1-3s (per worker) on a M1.
@@ -237,17 +236,14 @@ export async function main(opts: RunnerOptions): Promise<void> {
let testFilter: TestFilter | null = null;
if (opts.pattern) {
testFilter = {
debug: true,
paths: [opts.pattern],
};
} else if (opts.filter) {
testFilter = await readTestFilter();
}
const results = await runFixtures(
worker,
testFilter,
0,
opts.debug,
false, // no requireSingleFixture in non-watch mode
);
const results = await runFixtures(worker, testFilter, 0);
if (opts.update) {
update(results);
isSuccess = true;