From c6a5aa04b62e9327b9e00d3bb2eb5f1e99fad657 Mon Sep 17 00:00:00 2001 From: Towaiji Date: Mon, 2 Mar 2026 23:29:47 -0500 Subject: [PATCH] eslint-plugin-react-hooks: avoid overtainting props in refs rule --- .../Validation/ValidateNoRefAccessInRender.ts | 13 +---- .../__tests__/ReactCompilerRuleRefs-test.ts | 57 +++++++++++++++++++ 2 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleRefs-test.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts index 7da564205475..26cf64b2807b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -150,16 +150,9 @@ function collectTemporariesSidemap(fn: HIRFunction, env: Env): void { break; } case 'PropertyLoad': { - if ( - isUseRefType(value.object.identifier) && - value.property === 'current' - ) { - continue; - } - const temp = env.lookup(value.object); - if (temp != null) { - env.define(lvalue, temp); - } + // Property loads are not aliases of the base object. If we alias them + // to the base place, marking one ref-like property can overtaint the + // whole object and unrelated properties. break; } } diff --git a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleRefs-test.ts b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleRefs-test.ts new file mode 100644 index 000000000000..d15e2fb64e4c --- /dev/null +++ b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleRefs-test.ts @@ -0,0 +1,57 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {RuleTester} from 'eslint'; +import {allRules} from '../src/shared/ReactCompiler'; + +const ESLintTesterV8 = require('eslint-v8').RuleTester; + +type CompilerTestCases = { + valid: RuleTester.ValidTestCase[]; + invalid: RuleTester.InvalidTestCase[]; +}; + +const tests: CompilerTestCases = { + valid: [ + { + name: 'does not mark entire props object as ref when using ref prop', + filename: 'test.js', + code: ` + function Test(props) { + return ( +
+ +
+ ); + } + `, + }, + ], + invalid: [ + { + name: 'disallows direct ref access during render', + filename: 'test.js', + code: ` + import {useRef} from 'react'; + function Test() { + const ref = useRef(null); + return
{ref.current}
; + } + `, + errors: [{message: /Cannot access refs during render/}], + }, + ], +}; + +const eslintTester = new ESLintTesterV8({ + parser: require.resolve('hermes-eslint'), + parserOptions: { + sourceType: 'module', + }, +}); + +eslintTester.run('react-compiler refs', allRules['refs'].rule, tests);