Separate flag for ref access violation within function expressions

Adds a separate compiler flag for enabling the incomplete validation of ref 
access within function expressions. Unlike the previous PR for 
set-state-in-render validation, ref access in render can be okay in some 
circumstances so i'm leaving this off by default. The point of splitting this up 
is that our linting will be able to enable the rule without risk of false 
positives.
This commit is contained in:
Joe Savona
2023-11-10 17:07:21 -08:00
parent 6e038f9699
commit fc85f24865
8 changed files with 29 additions and 15 deletions
@@ -122,6 +122,14 @@ const EnvironmentConfigSchema = z.object({
// Validate that ref values (`ref.current`) are not accessed during render.
validateRefAccessDuringRender: z.boolean().default(false),
/**
* Extension of validateRefAccessDuringRender that validates that refs are not accessed during
* render indirectly by calling function expressions which access the ref.
*
* This validation has known issues and is not yet recommended
*/
validateRefAccessDuringRenderFunctionExpressions: z.boolean().default(false),
/*
* Validate that mutable lambdas are not passed where a frozen value is expected, since mutable
* lambdas cannot be frozen. The only mutation allowed inside a frozen lambda is of ref values.
@@ -63,17 +63,19 @@ export function validateNoRefAccessInRender(fn: HIRFunction): void {
}
case "ObjectMethod":
case "FunctionExpression": {
/*
* functions are allowed to capture refs, so long as the function is not called
* during render. see AnalyzeFunctions for how we ensure that functions which
* capture refs get assigned a mutable range so we know here whether the function
* is called or not
*/
const mutableRange = instr.lvalue.identifier.mutableRange;
if (mutableRange.end > mutableRange.start + 1) {
for (const operand of eachInstructionValueOperand(instr.value)) {
validateNonRefValue(error, operand);
validateNonRefObject(error, operand);
if (fn.env.config.validateRefAccessDuringRenderFunctionExpressions) {
/*
* functions are allowed to capture refs, so long as the function is not called
* during render. see AnalyzeFunctions for how we ensure that functions which
* capture refs get assigned a mutable range so we know here whether the function
* is called or not
*/
const mutableRange = instr.lvalue.identifier.mutableRange;
if (mutableRange.end > mutableRange.start + 1) {
for (const operand of eachInstructionValueOperand(instr.value)) {
validateNonRefValue(error, operand);
validateNonRefObject(error, operand);
}
}
}
break;
@@ -2,7 +2,7 @@
## Input
```javascript
// @validateRefAccessDuringRender
// @validateRefAccessDuringRender @validateRefAccessDuringRenderFunctionExpressions
function Component(props) {
const ref = useRef(null);
const renderItem = (item) => {
@@ -1,4 +1,4 @@
// @validateRefAccessDuringRender
// @validateRefAccessDuringRender @validateRefAccessDuringRenderFunctionExpressions
function Component(props) {
const ref = useRef(null);
const renderItem = (item) => {
@@ -2,6 +2,7 @@
## Input
```javascript
// @validateRefAccessDuringRender:false
function Foo({ a }) {
const ref = useRef();
const val = ref.current;
@@ -15,7 +16,7 @@ function Foo({ a }) {
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { unstable_useMemoCache as useMemoCache } from "react"; // @validateRefAccessDuringRender:false
function Foo(t20) {
const $ = useMemoCache(4);
const { a } = t20;
@@ -1,3 +1,4 @@
// @validateRefAccessDuringRender:false
function Foo({ a }) {
const ref = useRef();
const val = ref.current;
@@ -2,6 +2,7 @@
## Input
```javascript
// @validateRefAccessDuringRender:false
function Foo({ a }) {
const ref = useRef();
const x = { a, val: ref.current };
@@ -14,7 +15,7 @@ function Foo({ a }) {
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
import { unstable_useMemoCache as useMemoCache } from "react"; // @validateRefAccessDuringRender:false
function Foo(t17) {
const $ = useMemoCache(4);
const { a } = t17;
@@ -1,3 +1,4 @@
// @validateRefAccessDuringRender:false
function Foo({ a }) {
const ref = useRef();
const x = { a, val: ref.current };