Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1517c63a78 | ||
|
|
5fbe88ab40 |
@@ -94,7 +94,7 @@ import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLoca
|
|||||||
import {outlineFunctions} from '../Optimization/OutlineFunctions';
|
import {outlineFunctions} from '../Optimization/OutlineFunctions';
|
||||||
import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes';
|
import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes';
|
||||||
import {lowerContextAccess} from '../Optimization/LowerContextAccess';
|
import {lowerContextAccess} from '../Optimization/LowerContextAccess';
|
||||||
import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects';
|
import {validateNoSetStateInEffects} from '../Validation/ValidateNoSetStateInEffects';
|
||||||
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
|
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
|
||||||
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
|
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
|
||||||
import {outlineJSX} from '../Optimization/OutlineJsx';
|
import {outlineJSX} from '../Optimization/OutlineJsx';
|
||||||
@@ -292,8 +292,8 @@ function runWithEnvironment(
|
|||||||
validateNoSetStateInRender(hir).unwrap();
|
validateNoSetStateInRender(hir).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env.config.validateNoSetStateInPassiveEffects) {
|
if (env.config.validateNoSetStateInEffects) {
|
||||||
env.logErrors(validateNoSetStateInPassiveEffects(hir));
|
env.logErrors(validateNoSetStateInEffects(hir));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env.config.validateNoJSXInTryStatements) {
|
if (env.config.validateNoJSXInTryStatements) {
|
||||||
|
|||||||
@@ -318,10 +318,10 @@ export const EnvironmentConfigSchema = z.object({
|
|||||||
validateNoSetStateInRender: z.boolean().default(true),
|
validateNoSetStateInRender: z.boolean().default(true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates that setState is not called directly within a passive effect (useEffect).
|
* Validates that setState is not called synchronously within an effect (useEffect and friends).
|
||||||
* Scheduling a setState (with an event listener, subscription, etc) is valid.
|
* Scheduling a setState (with an event listener, subscription, etc) is valid.
|
||||||
*/
|
*/
|
||||||
validateNoSetStateInPassiveEffects: z.boolean().default(false),
|
validateNoSetStateInEffects: z.boolean().default(false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates against creating JSX within a try block and recommends using an error boundary
|
* Validates against creating JSX within a try block and recommends using an error boundary
|
||||||
|
|||||||
@@ -11,20 +11,22 @@ import {
|
|||||||
IdentifierId,
|
IdentifierId,
|
||||||
isSetStateType,
|
isSetStateType,
|
||||||
isUseEffectHookType,
|
isUseEffectHookType,
|
||||||
|
isUseInsertionEffectHookType,
|
||||||
|
isUseLayoutEffectHookType,
|
||||||
Place,
|
Place,
|
||||||
} from '../HIR';
|
} from '../HIR';
|
||||||
import {eachInstructionValueOperand} from '../HIR/visitors';
|
import {eachInstructionValueOperand} from '../HIR/visitors';
|
||||||
import {Result} from '../Utils/Result';
|
import {Result} from '../Utils/Result';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates against calling setState in the body of a *passive* effect (useEffect),
|
* Validates against calling setState in the body of an effect (useEffect and friends),
|
||||||
* while allowing calling setState in callbacks scheduled by the effect.
|
* while allowing calling setState in callbacks scheduled by the effect.
|
||||||
*
|
*
|
||||||
* Calling setState during execution of a useEffect triggers a re-render, which is
|
* Calling setState during execution of a useEffect triggers a re-render, which is
|
||||||
* often bad for performance and frequently has more efficient and straightforward
|
* often bad for performance and frequently has more efficient and straightforward
|
||||||
* alternatives. See https://react.dev/learn/you-might-not-need-an-effect for examples.
|
* alternatives. See https://react.dev/learn/you-might-not-need-an-effect for examples.
|
||||||
*/
|
*/
|
||||||
export function validateNoSetStateInPassiveEffects(
|
export function validateNoSetStateInEffects(
|
||||||
fn: HIRFunction,
|
fn: HIRFunction,
|
||||||
): Result<void, CompilerError> {
|
): Result<void, CompilerError> {
|
||||||
const setStateFunctions: Map<IdentifierId, Place> = new Map();
|
const setStateFunctions: Map<IdentifierId, Place> = new Map();
|
||||||
@@ -79,7 +81,11 @@ export function validateNoSetStateInPassiveEffects(
|
|||||||
instr.value.kind === 'MethodCall'
|
instr.value.kind === 'MethodCall'
|
||||||
? instr.value.receiver
|
? instr.value.receiver
|
||||||
: instr.value.callee;
|
: instr.value.callee;
|
||||||
if (isUseEffectHookType(callee.identifier)) {
|
if (
|
||||||
|
isUseEffectHookType(callee.identifier) ||
|
||||||
|
isUseLayoutEffectHookType(callee.identifier) ||
|
||||||
|
isUseInsertionEffectHookType(callee.identifier)
|
||||||
|
) {
|
||||||
const arg = instr.value.args[0];
|
const arg = instr.value.args[0];
|
||||||
if (arg !== undefined && arg.kind === 'Identifier') {
|
if (arg !== undefined && arg.kind === 'Identifier') {
|
||||||
const setState = setStateFunctions.get(arg.identifier.id);
|
const setState = setStateFunctions.get(arg.identifier.id);
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
## Input
|
## Input
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// @loggerTestOnly @validateNoSetStateInPassiveEffects
|
// @loggerTestOnly @validateNoSetStateInEffects
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
@@ -24,7 +24,7 @@ function Component() {
|
|||||||
## Code
|
## Code
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInPassiveEffects
|
import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInEffects
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
@@ -65,8 +65,8 @@ function _temp(s) {
|
|||||||
## Logs
|
## Logs
|
||||||
|
|
||||||
```
|
```
|
||||||
{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":272},"end":{"line":13,"column":5,"index":273},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null}
|
{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null}
|
||||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":99},"end":{"line":16,"column":1,"index":300},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Eval output
|
### Eval output
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// @loggerTestOnly @validateNoSetStateInPassiveEffects
|
// @loggerTestOnly @validateNoSetStateInEffects
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
## Input
|
## Input
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// @loggerTestOnly @validateNoSetStateInPassiveEffects
|
// @loggerTestOnly @validateNoSetStateInEffects
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
@@ -18,7 +18,7 @@ function Component() {
|
|||||||
## Code
|
## Code
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInPassiveEffects
|
import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInEffects
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
@@ -45,8 +45,8 @@ function _temp(s) {
|
|||||||
## Logs
|
## Logs
|
||||||
|
|
||||||
```
|
```
|
||||||
{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":187},"end":{"line":7,"column":12,"index":195},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null}
|
{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null}
|
||||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":99},"end":{"line":10,"column":1,"index":232},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Eval output
|
### Eval output
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// @loggerTestOnly @validateNoSetStateInPassiveEffects
|
// @loggerTestOnly @validateNoSetStateInEffects
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
## Input
|
## Input
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// @validateNoSetStateInPassiveEffects
|
// @validateNoSetStateInEffects
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
|
|||||||
## Code
|
## Code
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { c as _c } from "react/compiler-runtime"; // @validateNoSetStateInPassiveEffects
|
import { c as _c } from "react/compiler-runtime"; // @validateNoSetStateInEffects
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// @validateNoSetStateInPassiveEffects
|
// @validateNoSetStateInEffects
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
## Input
|
## Input
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// @validateNoSetStateInPassiveEffects
|
// @validateNoSetStateInEffects
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
@@ -23,7 +23,7 @@ export const FIXTURE_ENTRYPOINT = {
|
|||||||
## Code
|
## Code
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { c as _c } from "react/compiler-runtime"; // @validateNoSetStateInPassiveEffects
|
import { c as _c } from "react/compiler-runtime"; // @validateNoSetStateInEffects
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// @validateNoSetStateInPassiveEffects
|
// @validateNoSetStateInEffects
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
function Component() {
|
function Component() {
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ describe('parseConfigPragmaForTests()', () => {
|
|||||||
// Validate defaults first to make sure that the parser is getting the value from the pragma,
|
// Validate defaults first to make sure that the parser is getting the value from the pragma,
|
||||||
// and not just missing it and getting the default value
|
// and not just missing it and getting the default value
|
||||||
expect(defaultConfig.enableUseTypeAnnotations).toBe(false);
|
expect(defaultConfig.enableUseTypeAnnotations).toBe(false);
|
||||||
expect(defaultConfig.validateNoSetStateInPassiveEffects).toBe(false);
|
expect(defaultConfig.validateNoSetStateInEffects).toBe(false);
|
||||||
expect(defaultConfig.validateNoSetStateInRender).toBe(true);
|
expect(defaultConfig.validateNoSetStateInRender).toBe(true);
|
||||||
|
|
||||||
const config = parseConfigPragmaForTests(
|
const config = parseConfigPragmaForTests(
|
||||||
'@enableUseTypeAnnotations @validateNoSetStateInPassiveEffects:true @validateNoSetStateInRender:false',
|
'@enableUseTypeAnnotations @validateNoSetStateInEffects:true @validateNoSetStateInRender:false',
|
||||||
{compilationMode: defaultOptions.compilationMode},
|
{compilationMode: defaultOptions.compilationMode},
|
||||||
);
|
);
|
||||||
expect(config).toEqual({
|
expect(config).toEqual({
|
||||||
@@ -28,7 +28,7 @@ describe('parseConfigPragmaForTests()', () => {
|
|||||||
environment: {
|
environment: {
|
||||||
...defaultOptions.environment,
|
...defaultOptions.environment,
|
||||||
enableUseTypeAnnotations: true,
|
enableUseTypeAnnotations: true,
|
||||||
validateNoSetStateInPassiveEffects: true,
|
validateNoSetStateInEffects: true,
|
||||||
validateNoSetStateInRender: false,
|
validateNoSetStateInRender: false,
|
||||||
enableResetCacheOnSourceFileChanges: false,
|
enableResetCacheOnSourceFileChanges: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -107,6 +107,12 @@ const COMPILER_OPTIONS: Partial<PluginOptions> = {
|
|||||||
flowSuppressions: false,
|
flowSuppressions: false,
|
||||||
environment: validateEnvironmentConfig({
|
environment: validateEnvironmentConfig({
|
||||||
validateRefAccessDuringRender: false,
|
validateRefAccessDuringRender: false,
|
||||||
|
validateNoSetStateInRender: true,
|
||||||
|
validateNoSetStateInEffects: true,
|
||||||
|
validateNoJSXInTryStatements: true,
|
||||||
|
validateNoImpureFunctionsInRender: true,
|
||||||
|
validateStaticComponents: true,
|
||||||
|
validateNoFreezingKnownMutableFunctions: true,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ const COMPILER_OPTIONS: Partial<PluginOptions> = {
|
|||||||
flowSuppressions: false,
|
flowSuppressions: false,
|
||||||
environment: validateEnvironmentConfig({
|
environment: validateEnvironmentConfig({
|
||||||
validateRefAccessDuringRender: false,
|
validateRefAccessDuringRender: false,
|
||||||
|
validateNoSetStateInRender: true,
|
||||||
|
validateNoSetStateInEffects: true,
|
||||||
|
validateNoJSXInTryStatements: true,
|
||||||
|
validateNoImpureFunctionsInRender: true,
|
||||||
|
validateStaticComponents: true,
|
||||||
|
validateNoFreezingKnownMutableFunctions: true,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user