Compare commits
10 Commits
eslint-plu
...
gh/mvitous
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d2236196a | ||
|
|
5fb783ce58 | ||
|
|
ef8833306c | ||
|
|
076abcf35a | ||
|
|
4052937195 | ||
|
|
d4ce56ffc6 | ||
|
|
2e114b0b2e | ||
|
|
3affe0339f | ||
|
|
129abed386 | ||
|
|
22b97005ee |
@@ -294,6 +294,13 @@ export type HIRFunction = {
|
||||
};
|
||||
|
||||
export type FunctionEffect =
|
||||
| {
|
||||
kind: 'ImmutableFunctionCall';
|
||||
loc: SourceLocation;
|
||||
lvalue: IdentifierId;
|
||||
callee: IdentifierId;
|
||||
global: boolean;
|
||||
}
|
||||
| {
|
||||
kind: 'GlobalMutation';
|
||||
error: CompilerErrorDetailOptions;
|
||||
|
||||
@@ -26,10 +26,12 @@ import {
|
||||
Type,
|
||||
ValueKind,
|
||||
ValueReason,
|
||||
getHookKind,
|
||||
isArrayType,
|
||||
isMutableEffect,
|
||||
isObjectType,
|
||||
isRefValueType,
|
||||
isSetStateType,
|
||||
isUseRefType,
|
||||
} from '../HIR/HIR';
|
||||
import {FunctionSignature} from '../HIR/ObjectShape';
|
||||
@@ -48,7 +50,6 @@ import {
|
||||
eachTerminalSuccessor,
|
||||
} from '../HIR/visitors';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {isEffectHook} from '../Validation/ValidateMemoizedEffectDependencies';
|
||||
|
||||
const UndefinedValue: InstructionValue = {
|
||||
kind: 'Primitive',
|
||||
@@ -251,6 +252,10 @@ export default function inferReferenceEffects(
|
||||
loc: eff.loc,
|
||||
});
|
||||
}
|
||||
case 'ImmutableFunctionCall': {
|
||||
// Handled below
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assertExhaustive(
|
||||
eff,
|
||||
@@ -258,9 +263,122 @@ export default function inferReferenceEffects(
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fn.effects = functionEffects;
|
||||
|
||||
if (functionEffects.length > 0) {
|
||||
const error = new CompilerError();
|
||||
let usedIdentifiers = new Set<IdentifierId>();
|
||||
let names = new Map<IdentifierId, string | undefined>();
|
||||
|
||||
function visitFunction(fn: HIRFunction): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
switch (instr.value.kind) {
|
||||
case 'FunctionExpression':
|
||||
case 'ObjectMethod':
|
||||
names.set(
|
||||
instr.lvalue.identifier.id,
|
||||
instr.value.loweredFunc.func.id ?? '(anonymous function)',
|
||||
);
|
||||
visitFunction(instr.value.loweredFunc.func);
|
||||
break;
|
||||
case 'LoadGlobal':
|
||||
names.set(instr.lvalue.identifier.id, instr.value.binding.name);
|
||||
break;
|
||||
case 'LoadLocal':
|
||||
case 'LoadContext':
|
||||
names.set(
|
||||
instr.lvalue.identifier.id,
|
||||
instr.value.place.identifier.name?.value ??
|
||||
names.get(instr.value.place.identifier.id),
|
||||
);
|
||||
break;
|
||||
case 'StoreContext':
|
||||
case 'StoreLocal':
|
||||
names.set(
|
||||
instr.lvalue.identifier.id,
|
||||
instr.value.value.identifier.name?.value ??
|
||||
names.get(instr.value.value.identifier.id),
|
||||
);
|
||||
names.set(
|
||||
instr.value.lvalue.place.identifier.id,
|
||||
instr.value.value.identifier.name?.value ??
|
||||
names.get(instr.value.value.identifier.id),
|
||||
);
|
||||
break;
|
||||
case 'PropertyLoad':
|
||||
names.set(
|
||||
instr.lvalue.identifier.id,
|
||||
`${instr.value.object.identifier.name?.value ?? names.get(instr.value.object.identifier.id) ?? '(unknown)'}.${instr.value.property}`,
|
||||
);
|
||||
break;
|
||||
case 'ComputedLoad':
|
||||
names.set(
|
||||
instr.lvalue.identifier.id,
|
||||
`${instr.value.object.identifier.name?.value ?? names.get(instr.value.object.identifier.id) ?? '(unknown)'}[...]`,
|
||||
);
|
||||
break;
|
||||
case 'Destructure': {
|
||||
const destructuredName =
|
||||
instr.value.value.identifier.name?.value ??
|
||||
names.get(instr.value.value.identifier.id);
|
||||
const destructuredMsg = destructuredName
|
||||
? `(destructured from \`${destructuredName}\`)`
|
||||
: '(destructured)';
|
||||
Array.from(
|
||||
eachPatternOperand(instr.value.lvalue.pattern),
|
||||
).forEach(place =>
|
||||
names.set(
|
||||
place.identifier.id,
|
||||
`${place.identifier.name?.value ?? 'value'} ${destructuredMsg}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Array.from(eachInstructionOperand(instr)).forEach(operand =>
|
||||
usedIdentifiers.add(operand.identifier.id),
|
||||
);
|
||||
}
|
||||
for (const phi of block.phis) {
|
||||
Array.from(phi.operands.values()).forEach(operand =>
|
||||
usedIdentifiers.add(operand.id),
|
||||
);
|
||||
}
|
||||
Array.from(eachTerminalOperand(block.terminal)).forEach(operand =>
|
||||
usedIdentifiers.add(operand.identifier.id),
|
||||
);
|
||||
}
|
||||
}
|
||||
visitFunction(fn);
|
||||
|
||||
const allowedNames = new Set(['invariant', 'recoverableViolation']);
|
||||
|
||||
for (const effect of functionEffects) {
|
||||
CompilerError.invariant(effect.kind === 'ImmutableFunctionCall', {
|
||||
reason:
|
||||
'All effects other than ImmutableFunctionCall should have been handled earlier',
|
||||
loc: null,
|
||||
});
|
||||
if (
|
||||
!usedIdentifiers.has(effect.lvalue) &&
|
||||
(!effect.global ||
|
||||
!names.has(effect.callee) ||
|
||||
!allowedNames.has(names.get(effect.callee)!))
|
||||
) {
|
||||
const name = names.get(effect.callee) ?? '(unknown)';
|
||||
error.push({
|
||||
reason: `Function \'${name}\' is called with arguments that React Compiler expects to be immutable and its return value is ignored. This call is likely to perform unsafe side effects, which violates the rules of React.`,
|
||||
loc: effect.loc,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (error.hasErrors()) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn.effects = functionEffects;
|
||||
}
|
||||
|
||||
// Maintains a mapping of top-level variables to the kind of value they hold
|
||||
@@ -276,6 +394,8 @@ class InferenceState {
|
||||
*/
|
||||
#variables: Map<IdentifierId, Set<InstructionValue>>;
|
||||
|
||||
#functionEffects: Map<InstructionValue, Array<FunctionEffect>> = new Map();
|
||||
|
||||
constructor(
|
||||
env: Environment,
|
||||
values: Map<InstructionValue, AbstractValue>,
|
||||
@@ -302,6 +422,19 @@ class InferenceState {
|
||||
this.#values.set(value, kind);
|
||||
}
|
||||
|
||||
setFunctionEffects(value: InstructionValue, effect: Array<FunctionEffect>): void {
|
||||
CompilerError.invariant(value.kind !== 'LoadLocal', {
|
||||
reason:
|
||||
'Expected all top-level identifiers to be defined as variables, not values',
|
||||
description: null,
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
const curEffects = this.#functionEffects.get(value) ?? [];
|
||||
curEffects.push(...effect);
|
||||
this.#functionEffects.set(value, curEffects);
|
||||
}
|
||||
|
||||
values(place: Place): Array<InstructionValue> {
|
||||
const values = this.#variables.get(place.identifier.id);
|
||||
CompilerError.invariant(values != null, {
|
||||
@@ -400,50 +533,58 @@ class InferenceState {
|
||||
}
|
||||
|
||||
// Propagate effects of function expressions to the outer (ie current) effect context
|
||||
const dependentEffects = [];
|
||||
for (const value of values) {
|
||||
if (
|
||||
(value.kind === 'FunctionExpression' ||
|
||||
value.kind === 'ObjectMethod') &&
|
||||
value.loweredFunc.func.effects != null
|
||||
value.kind === 'ObjectMethod')
|
||||
) {
|
||||
for (const effect of value.loweredFunc.func.effects) {
|
||||
if (
|
||||
effect.kind === 'GlobalMutation' ||
|
||||
effect.kind === 'ReactMutation'
|
||||
) {
|
||||
// Known effects are always propagated upwards
|
||||
functionEffects.push(effect);
|
||||
} else {
|
||||
/**
|
||||
* Contextual effects need to be replayed against the current inference
|
||||
* state, which may know more about the value to which the effect applied.
|
||||
* The main cases are:
|
||||
* 1. The mutated context value is _still_ a context value in the current scope,
|
||||
* so we have to continue propagating the original context mutation.
|
||||
* 2. The mutated context value is a mutable value in the current scope,
|
||||
* so the context mutation was fine and we can skip propagating the effect.
|
||||
* 3. The mutated context value is an immutable value in the current scope,
|
||||
* resulting in a non-ContextMutation FunctionEffect. We propagate that new,
|
||||
* more detailed effect to the current function context.
|
||||
*/
|
||||
for (const place of effect.places) {
|
||||
if (this.isDefined(place)) {
|
||||
const replayedEffect = this.reference(
|
||||
{...place, loc: effect.loc},
|
||||
effect.effect,
|
||||
reason,
|
||||
);
|
||||
if (replayedEffect != null) {
|
||||
if (replayedEffect.kind === 'ContextMutation') {
|
||||
// Case 1, still a context value so propagate the original effect
|
||||
functionEffects.push(effect);
|
||||
} else {
|
||||
// Case 3, immutable value so propagate the more precise effect
|
||||
functionEffects.push(replayedEffect);
|
||||
}
|
||||
} // else case 2, local mutable value so this effect was fine
|
||||
dependentEffects.push(...value.loweredFunc.func.effects ?? []);
|
||||
}
|
||||
const knownEffects = this.#functionEffects.get(value);
|
||||
if (knownEffects != null) {
|
||||
dependentEffects.push(...knownEffects)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (const effect of dependentEffects) {
|
||||
if (
|
||||
effect.kind === 'GlobalMutation' ||
|
||||
effect.kind === 'ReactMutation' ||
|
||||
effect.kind === 'ImmutableFunctionCall'
|
||||
) {
|
||||
// Known effects are always propagated upwards
|
||||
functionEffects.push(effect);
|
||||
} else if (effect.kind === 'ContextMutation') {
|
||||
/**
|
||||
* Contextual effects need to be replayed against the current inference
|
||||
* state, which may know more about the value to which the effect applied.
|
||||
* The main cases are:
|
||||
* 1. The mutated context value is _still_ a context value in the current scope,
|
||||
* so we have to continue propagating the original context mutation.
|
||||
* 2. The mutated context value is a mutable value in the current scope,
|
||||
* so the context mutation was fine and we can skip propagating the effect.
|
||||
* 3. The mutated context value is an immutable value in the current scope,
|
||||
* resulting in a non-ContextMutation FunctionEffect. We propagate that new,
|
||||
* more detailed effect to the current function context.
|
||||
*/
|
||||
for (const place of effect.places) {
|
||||
if (this.isDefined(place)) {
|
||||
const replayedEffect = this.reference(
|
||||
{...place, loc: effect.loc},
|
||||
effect.effect,
|
||||
reason,
|
||||
);
|
||||
if (replayedEffect != null) {
|
||||
if (replayedEffect.kind === 'ContextMutation') {
|
||||
// Case 1, still a context value so propagate the original effect
|
||||
functionEffects.push(effect);
|
||||
} else {
|
||||
// Case 3, immutable value so propagate the more precise effect
|
||||
functionEffects.push(replayedEffect);
|
||||
}
|
||||
}
|
||||
} // else case 2, local mutable value so this effect was fine
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -995,8 +1136,22 @@ function inferBlock(
|
||||
context: new Set(),
|
||||
};
|
||||
effect = {kind: Effect.Capture, reason: ValueReason.Other};
|
||||
lvalueEffect = Effect.Store;
|
||||
break;
|
||||
|
||||
const arrEffects: Array<FunctionEffect> = [];
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
state.referenceAndRecordEffects(
|
||||
operand,
|
||||
effect.kind,
|
||||
effect.reason,
|
||||
arrEffects,
|
||||
);
|
||||
}
|
||||
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect = Effect.Store;
|
||||
state.setFunctionEffects(instrValue, arrEffects);
|
||||
continue;
|
||||
}
|
||||
case 'NewExpression': {
|
||||
/**
|
||||
@@ -1039,6 +1194,7 @@ function inferBlock(
|
||||
continue;
|
||||
}
|
||||
case 'ObjectExpression': {
|
||||
const objEffects: Array<FunctionEffect> = [];
|
||||
valueKind = hasContextRefOperand(state, instrValue)
|
||||
? {
|
||||
kind: ValueKind.Context,
|
||||
@@ -1060,7 +1216,7 @@ function inferBlock(
|
||||
property.key.name,
|
||||
Effect.Freeze,
|
||||
ValueReason.Other,
|
||||
functionEffects,
|
||||
objEffects,
|
||||
);
|
||||
}
|
||||
// Object construction captures but does not modify the key/property values
|
||||
@@ -1068,7 +1224,7 @@ function inferBlock(
|
||||
property.place,
|
||||
Effect.Capture,
|
||||
ValueReason.Other,
|
||||
functionEffects,
|
||||
objEffects,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -1078,7 +1234,7 @@ function inferBlock(
|
||||
property.place,
|
||||
Effect.Capture,
|
||||
ValueReason.Other,
|
||||
functionEffects,
|
||||
objEffects,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -1092,6 +1248,7 @@ function inferBlock(
|
||||
}
|
||||
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.setFunctionEffects(instrValue, objEffects);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect = Effect.Store;
|
||||
continue;
|
||||
@@ -1151,7 +1308,7 @@ function inferBlock(
|
||||
);
|
||||
functionEffects.push(
|
||||
...propEffects.filter(
|
||||
propEffect => propEffect.kind !== 'GlobalMutation',
|
||||
effect => !isEffectSafeOutsideRender(effect),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1330,7 +1487,7 @@ function inferBlock(
|
||||
context: new Set(),
|
||||
};
|
||||
let hasCaptureArgument = false;
|
||||
let isUseEffect = isEffectHook(instrValue.callee.identifier);
|
||||
let isHook = getHookKind(env, instrValue.callee.identifier) != null;
|
||||
for (let i = 0; i < instrValue.args.length; i++) {
|
||||
const argumentEffects: Array<FunctionEffect> = [];
|
||||
const arg = instrValue.args[i];
|
||||
@@ -1356,8 +1513,7 @@ function inferBlock(
|
||||
*/
|
||||
functionEffects.push(
|
||||
...argumentEffects.filter(
|
||||
argEffect =>
|
||||
!isUseEffect || i !== 0 || argEffect.kind !== 'GlobalMutation',
|
||||
argEffect => !isHook || !isEffectSafeOutsideRender(argEffect),
|
||||
),
|
||||
);
|
||||
hasCaptureArgument ||= place.effect === Effect.Capture;
|
||||
@@ -1379,6 +1535,32 @@ function inferBlock(
|
||||
}
|
||||
hasCaptureArgument ||= instrValue.callee.effect === Effect.Capture;
|
||||
|
||||
if (
|
||||
!isSetStateType(instrValue.callee.identifier) &&
|
||||
instrValue.callee.effect === Effect.Read &&
|
||||
signature?.hookKind == null
|
||||
) {
|
||||
const allRead = instrValue.args.every(arg => {
|
||||
switch (arg.kind) {
|
||||
case 'Identifier':
|
||||
return arg.effect === Effect.Read;
|
||||
case 'Spread':
|
||||
return arg.place.effect === Effect.Read;
|
||||
default:
|
||||
assertExhaustive(arg, 'Unexpected arg kind');
|
||||
}
|
||||
});
|
||||
if (allRead) {
|
||||
functionEffects.push({
|
||||
kind: 'ImmutableFunctionCall',
|
||||
lvalue: instr.lvalue.identifier.id,
|
||||
callee: instrValue.callee.identifier.id,
|
||||
loc: instrValue.loc,
|
||||
global: state.kind(instrValue.callee).kind === ValueKind.Global,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect = hasCaptureArgument
|
||||
@@ -1455,7 +1637,7 @@ function inferBlock(
|
||||
const effects =
|
||||
signature !== null ? getFunctionEffects(instrValue, signature) : null;
|
||||
let hasCaptureArgument = false;
|
||||
let isUseEffect = isEffectHook(instrValue.property.identifier);
|
||||
let isHook = getHookKind(env, instrValue.property.identifier) != null;
|
||||
for (let i = 0; i < instrValue.args.length; i++) {
|
||||
const argumentEffects: Array<FunctionEffect> = [];
|
||||
const arg = instrValue.args[i];
|
||||
@@ -1485,8 +1667,7 @@ function inferBlock(
|
||||
*/
|
||||
functionEffects.push(
|
||||
...argumentEffects.filter(
|
||||
argEffect =>
|
||||
!isUseEffect || i !== 0 || argEffect.kind !== 'GlobalMutation',
|
||||
argEffect => !isHook || !isEffectSafeOutsideRender(argEffect),
|
||||
),
|
||||
);
|
||||
hasCaptureArgument ||= place.effect === Effect.Capture;
|
||||
@@ -1508,6 +1689,33 @@ function inferBlock(
|
||||
}
|
||||
hasCaptureArgument ||= instrValue.receiver.effect === Effect.Capture;
|
||||
|
||||
if (
|
||||
!isSetStateType(instrValue.property.identifier) &&
|
||||
instrValue.receiver.effect === Effect.Read &&
|
||||
instrValue.property.effect === Effect.Read &&
|
||||
signature?.hookKind == null
|
||||
) {
|
||||
const allRead = instrValue.args.every(arg => {
|
||||
switch (arg.kind) {
|
||||
case 'Identifier':
|
||||
return arg.effect === Effect.Read;
|
||||
case 'Spread':
|
||||
return arg.place.effect === Effect.Read;
|
||||
default:
|
||||
assertExhaustive(arg, 'Unexpected arg kind');
|
||||
}
|
||||
});
|
||||
if (allRead) {
|
||||
functionEffects.push({
|
||||
kind: 'ImmutableFunctionCall',
|
||||
lvalue: instr.lvalue.identifier.id,
|
||||
callee: instrValue.property.identifier.id,
|
||||
loc: instrValue.loc,
|
||||
global: state.kind(instrValue.property).kind === ValueKind.Global,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect = hasCaptureArgument
|
||||
@@ -1549,14 +1757,16 @@ function inferBlock(
|
||||
break;
|
||||
}
|
||||
case 'PropertyLoad': {
|
||||
const loadEffects: Array<FunctionEffect> = [];
|
||||
state.referenceAndRecordEffects(
|
||||
instrValue.object,
|
||||
Effect.Read,
|
||||
ValueReason.Other,
|
||||
functionEffects,
|
||||
loadEffects,
|
||||
);
|
||||
const lvalue = instr.lvalue;
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
state.setFunctionEffects(instrValue, loadEffects);
|
||||
state.initialize(instrValue, state.kind(instrValue.object));
|
||||
state.define(lvalue, instrValue);
|
||||
continue;
|
||||
@@ -1613,22 +1823,24 @@ function inferBlock(
|
||||
continue;
|
||||
}
|
||||
case 'ComputedLoad': {
|
||||
const loadEffects: Array<FunctionEffect> = [];
|
||||
state.referenceAndRecordEffects(
|
||||
instrValue.object,
|
||||
Effect.Read,
|
||||
ValueReason.Other,
|
||||
functionEffects,
|
||||
loadEffects,
|
||||
);
|
||||
state.referenceAndRecordEffects(
|
||||
instrValue.property,
|
||||
Effect.Read,
|
||||
ValueReason.Other,
|
||||
functionEffects,
|
||||
loadEffects,
|
||||
);
|
||||
const lvalue = instr.lvalue;
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
state.initialize(instrValue, state.kind(instrValue.object));
|
||||
state.define(lvalue, instrValue);
|
||||
state.setFunctionEffects(instrValue, loadEffects);
|
||||
continue;
|
||||
}
|
||||
case 'Await': {
|
||||
@@ -2010,11 +2222,15 @@ function inferBlock(
|
||||
} else {
|
||||
effect = Effect.Read;
|
||||
}
|
||||
const propEffects: Array<FunctionEffect> = [];
|
||||
state.referenceAndRecordEffects(
|
||||
operand,
|
||||
effect,
|
||||
ValueReason.Other,
|
||||
functionEffects,
|
||||
propEffects,
|
||||
);
|
||||
functionEffects.push(
|
||||
...propEffects.filter(effect => !isEffectSafeOutsideRender(effect)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2128,6 +2344,12 @@ function areArgumentsImmutableAndNonMutating(
|
||||
return true;
|
||||
}
|
||||
|
||||
function isEffectSafeOutsideRender(effect: FunctionEffect): boolean {
|
||||
return (
|
||||
effect.kind === 'GlobalMutation' || effect.kind === 'ImmutableFunctionCall'
|
||||
);
|
||||
}
|
||||
|
||||
function getWriteErrorReason(abstractValue: AbstractValue): string {
|
||||
if (abstractValue.reason.has(ValueReason.Global)) {
|
||||
return 'Writing to a variable defined outside a component or hook is not allowed. Consider using an effect';
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Foo() {
|
||||
const x = () => {
|
||||
window.href = 'foo';
|
||||
};
|
||||
const y = {x};
|
||||
return <Bar y={y} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
1 | function Foo() {
|
||||
2 | const x = () => {
|
||||
> 3 | window.href = 'foo';
|
||||
| ^^^^^^ InvalidReact: Writing to a variable defined outside a component or hook is not allowed. Consider using an effect (3:3)
|
||||
4 | };
|
||||
5 | const y = {x};
|
||||
6 | return <Bar y={y} />;
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
let b = 1;
|
||||
|
||||
export default function MyApp() {
|
||||
const fn = () => {
|
||||
b = 2;
|
||||
};
|
||||
return foo(fn);
|
||||
}
|
||||
|
||||
function foo(fn) {}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: MyApp,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
3 | export default function MyApp() {
|
||||
4 | const fn = () => {
|
||||
> 5 | b = 2;
|
||||
| ^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (5:5)
|
||||
6 | };
|
||||
7 | return foo(fn);
|
||||
8 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
let b = 1;
|
||||
|
||||
export default function MyApp() {
|
||||
const fn = () => {
|
||||
b = 2;
|
||||
};
|
||||
return foo(fn);
|
||||
}
|
||||
|
||||
function foo(fn) {}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: MyApp,
|
||||
params: [],
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
let b = 1;
|
||||
|
||||
export default function useMyHook() {
|
||||
const fn = () => {
|
||||
b = 2;
|
||||
};
|
||||
const obj = { fn };
|
||||
obj.fn();
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useMyHook,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
3 | export default function useMyHook() {
|
||||
4 | const fn = () => {
|
||||
> 5 | b = 2;
|
||||
| ^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (5:5)
|
||||
6 | };
|
||||
7 | const obj = { fn };
|
||||
8 | obj.fn();
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
let b = 1;
|
||||
|
||||
export default function useMyHook() {
|
||||
const fn = () => {
|
||||
b = 2;
|
||||
};
|
||||
const obj = { fn };
|
||||
const arr = [obj];
|
||||
foo(arr);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useMyHook,
|
||||
params: [],
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Foo() {
|
||||
const x = () => {
|
||||
window.href = 'foo';
|
||||
};
|
||||
const y = {x};
|
||||
return <Bar y={y} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Foo() {
|
||||
const $ = _c(1);
|
||||
const x = _temp;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const y = { x };
|
||||
t0 = <Bar y={y} />;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp() {
|
||||
window.href = "foo";
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Bar is not defined
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
let b = 1;
|
||||
|
||||
export default function MyApp() {
|
||||
const fn = () => {
|
||||
b = 2;
|
||||
};
|
||||
return useFoo(fn);
|
||||
}
|
||||
|
||||
function useFoo(fn) {}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: MyApp,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
let b = 1;
|
||||
|
||||
export default function MyApp() {
|
||||
const fn = _temp;
|
||||
return useFoo(fn);
|
||||
}
|
||||
function _temp() {
|
||||
b = 2;
|
||||
}
|
||||
|
||||
function useFoo(fn) {}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: MyApp,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok)
|
||||
@@ -0,0 +1,15 @@
|
||||
let b = 1;
|
||||
|
||||
export default function MyApp() {
|
||||
const fn = () => {
|
||||
b = 2;
|
||||
};
|
||||
return useFoo(fn);
|
||||
}
|
||||
|
||||
function useFoo(fn) {}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: MyApp,
|
||||
params: [],
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
let b = 1;
|
||||
|
||||
export default function useMyHook() {
|
||||
const fn = () => {
|
||||
b = 2;
|
||||
};
|
||||
return { fn };
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useMyHook,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
let b = 1;
|
||||
|
||||
export default function useMyHook() {
|
||||
const $ = _c(1);
|
||||
const fn = _temp;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = { fn };
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp() {
|
||||
b = 2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useMyHook,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"fn":"[[ function params=0 ]]"}
|
||||
@@ -0,0 +1,13 @@
|
||||
let b = 1;
|
||||
|
||||
export default function useMyHook() {
|
||||
const fn = () => {
|
||||
b = 2;
|
||||
};
|
||||
return { fn };
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useMyHook,
|
||||
params: [],
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
let b = 1;
|
||||
|
||||
export default function useMyHook() {
|
||||
const fn = () => {
|
||||
b = 2;
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useMyHook,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
let b = 1;
|
||||
|
||||
export default function useMyHook() {
|
||||
const fn = _temp;
|
||||
return fn;
|
||||
}
|
||||
function _temp() {
|
||||
b = 2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useMyHook,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "[[ function params=0 ]]"
|
||||
@@ -0,0 +1,13 @@
|
||||
let b = 1;
|
||||
|
||||
export default function useMyHook() {
|
||||
const fn = () => {
|
||||
b = 2;
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useMyHook,
|
||||
params: [],
|
||||
};
|
||||
Reference in New Issue
Block a user