## Summary
The Rust port (#36173) changed `CompileError` `LoggerEvent`s to carry
plain serialized detail objects instead of
`CompilerError`/`CompilerDiagnostic` class instances. As a result, the
ESLint integrations could no longer call
`detail.printErrorMessage(source, {eslint: true})` and were given a
replacement `printErrorMessage()` helper that only emitted the `reason`
and `description`.
This regressed error printing: the **source code frame(s) and
`file:line:column` location** that used to appear for each error detail
were dropped from lint output.
This PR restores the previous behaviour:
- Export `printCodeFrame` from `CompilerError` and reuse it from both
ESLint integrations instead of duplicating it.
- Rebuild the full message (reason, description, per-detail code frames,
and hints) in `printErrorMessage`.
- Handle **both** detail shapes that flow through `LoggerEvent`s:
- a `details` array (`CompilerDiagnostic` and the **Rust** compiler),
and
- a legacy flat `loc` (deprecated `CompilerErrorDetail`).
`formatDetailForLogging` emits one or the other, so the previous
unconditional iteration over `error.details` would have thrown
`TypeError: not iterable` on the flat-`loc` path. Normalizing to a list
fixes that and keeps the code working with both the TypeScript and Rust
compilers.
## Test plan
- `tsc --noEmit` on both ESLint packages is clean (no new errors vs.
baseline).
- Verified the detail loop handles the Rust compiler's `details` array
shape and the legacy flat `loc` shape.
This is an experimental, work-in-progress port of React Compiler to
Rust. Key points:
* Work-in-progress - we are sharing early, prior to testing internally
at Meta, to get feedback from partners in parallel with continued
development.
* No builds available yet, you'll have to do some hacking if you want to
try this.
* All fixtures pass, no known gaps but there may be lurking bugs.
* The architecture was heavily guided by humans (me, @josephsavona) but
majority coded by AI. I was very hands-on in setting the architecture,
the testing and verification strategy, incremental migration approach,
etc. I also kept a close eye on the code and spent a decent amount of
time going back and forth to get code quality to a decent level.
* The public API is basically "Rust Babel AST" + Scope Info in, Rust
Babel AST out. We use a Rust representation of the Babel AST as our
"public API", as it were, and then each integration (Babel, OXC, SWC)
converts to/from their native representation. For now integrations must
also provide scope information - in the future React Compiler may
compute bindings and references itself from the AST.
* Internally, the Rust version uses the same architecture as the
TypeScript version. The compiler converts from the AST into our own
intermediate representation (HIR, short for High-level Intermediate
Representation) which uses a control-flow graph (CFG) and single-static
assignment (SSA). We go through the same series of passes, with the same
overall algorithms. It's very much a pass-by-pass port. The main
differences are in the data representation - using arena-like structures
(and indices into these arenas) to work within Rust's borrowing system.
* Early performance numbers are derived from AI and i haven't spent much
time validating the benchmark setup, beyond the fact that the
optimization opportunities it discovered made complete sense and the
fixes were right. With that caveat, itt does appear that the Rust
version is quite fast already: 3x faster when operating as a Babel
plugin. The serialization cost is quite high, but the actual
transformation logic is ~10x faster, so it's net faster. Native
integrations (oxc, swc) should be even faster.
* There are 3 integrations right now: an alternative Babel plugin (which
will eventually get removed as we integrate into
babel-plugin-react-compiler), and examples of what OXC and SWC
integrations could look like (see react_compiler_oxc and
react_compiler_swc crates).
correctness:
* all 1725 fixtures pass in snap when comparing the temporary rust
version of the plugin with the main version. this compares generated
code output as well as errors.
* all fixtures also pass a full comparison of the per-pass compiler
intermediate representation — the intermediate state (including log
events and errors) are ~identical after every single pass (modulo some
normalization of ids)
* The OXC and SWC example integrations seem to be working well, though i
haven't manually verified this to the same extent as i have the Babel
integration.
development:
* `yarn snap --rust` is the primary test suite, testing that we error or
compile as expected. It does not test the inner state of the compiler
along the way, though, making it less suitable for finding subtle logic
gaps btw the TS and Rust versions. It's also Babel based, making it less
easy to test OXC and SWC integrations.
* `compiler/scripts/test-e2e.sh` is an e2e test of all 3 variants (babel
wrapper around Rust, OXC/SWC integrations) against the TS
implementation. This does a partial comparison, focused on final output
code only (doesn't test error details etc). Useful for getting the swc
and oxc integrations closer to parity.
* `compiler/script/test-rust-port.sh` does detailed testing of the
internal compiler state after each pass, in addition to checking the
final output code. This is the key script used to port the compiler,
ensuring not just that the output was the same but that each pass was
capturing all the same detail. This script can be pointed at *any*
directory of JS files, which we expect to use for internal testing at
Meta.
## For Partners
We're excited to partner with teams to integrate the Rust version of
React Compiler into other tools, like OXC and SWC. If you're interested
in working with us on this, the best place to start is by taking a look
at the react_compiler_swc and react_compiler_oxc crates. These give you
an idea of the API shape that we're thinking of.
Note that the conversion from any AST into our HIR is complex, and we
can only maintain one version. Hence we've aligned on using a Babel-like
AST as our public API. Another key point is that we don't yet implement
our own scope analysis (since the TS version of the compiler relied on
Babel's scope analysis), so for now we require that the scope data be
serialized. It's a denormalized graph, and some metadata has to be
stored to associate nodes with scopes. We're open to feedback about the
AST and scope representation - we iterated a bit just to get things to
work, but it can be more optimal.
Key changes that we are considering:
* Currently the compiler returns `Option<Program>`, which is `Some` if
anything changed. This requires replacing the entire program. We plan to
change this to return a series of patches to apply, in a form that is
reasonably usable and efficient for all the integrations we care about
(Babel, OXC, SWC, etc).
* The Rust representation of the Babel AST is fine enough, but we could
make it more optimal by doing arena allocation. We also plan to change
the string representation to smol_str.
* The scope representation, and association of data btw AST and scope,
is very much a first pass approach that is good enough. We expect to
implement our own scope resolution, though, so we hopefully won't need
to iterate on the scope representation and can just throw it away.
In terms of the shape of the integration, we anticipate that each
integration would have the following:
* Implementor repo (OXC, SWC, etc): lightweight code transform and lint
pipeline integration that delegates to `crates/react_compiler_<name>`
from our repo
* Our repo: one crate per implementor, eg react_compiler_swc,
react_compiler_oxc, where most of the logic lives.
This setup lets us make changes to the integration layer easily within
our repo. Feedback appreciated!
---------
Co-authored-by: Joe Savona <[email protected]>
Co-authored-by: Mike Vitousek <[email protected]>
Co-authored-by: Mike Vitousek <[email protected]>
Co-authored-by: lauren <[email protected]>
Co-authored-by: lauren <[email protected]>
The `component-hook-factories` rule was removed in #35825 as part of a
feature flag cleanup, but was listed in the README as part of the manual
config example. This broke users who used a manual config (copied from
the old README) in eslint-plugin-react-hooks 7.1.0. This adds back a
deprecated no-op rule as a fix.
#35825 removed other rules (`automatic-effect-dependencies` and `fire`),
but these were for experimental features that did not ship. These were
also not referenced in the README.
We're currently hardcoding experimental options to
`eslint-plugin-react-hooks`. This blocks the release on features that
might not be ready.
This PR extends the ReactFeatureFlag infra to support flags for
`eslint-plugin-react-hooks`. An alternative would be to create a
separate flag system for build tools, but for now we have a small number
of these and reusing existing infra seems like the simplest approach.
I ran a full `yarn build` and checked the output resolved the flag
values as expected:
_build/oss-stable-semver/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js_
```js
var eprh_enableUseKeyedStateCompilerLint = false;
var eprh_enableVerboseNoSetStateInEffectCompilerLint = false;
var eprh_enableExhaustiveEffectDependenciesCompilerLint = 'off';
```
_build/facebook-www/ESLintPluginReactHooks-dev.classic.js_
```js
var eprh_enableUseKeyedStateCompilerLint = true;
var eprh_enableVerboseNoSetStateInEffectCompilerLint = true;
var eprh_enableExhaustiveEffectDependenciesCompilerLint = 'extra-only';
```
---------
Co-authored-by: lauren <[email protected]>
## Summary
ESLint v10.0.0 was released on February 7, 2026. The current
`peerDependencies` for `eslint-plugin-react-hooks` only allows up to
`^9.0.0`, which causes peer dependency warnings when installing with
ESLint v10.
This PR:
- Adds `^10.0.0` to the eslint peer dependency range
- Adds `eslint-v10` to devDependencies for testing
- Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9`
fixture)
ESLint v10's main breaking changes (removal of legacy eslintrc config,
deprecated context methods) don't affect this plugin - flat config is
already supported since v7.0.0, and the deprecated APIs already have
fallbacks in place.
## How did you test this change?
Ran the existing unit test suite:
```
cd packages/eslint-plugin-react-hooks && yarn test
```
All 5082 tests passed.
Summary:
I noticed there's a bug where the lint will recognize the type on a cast
annotation as a missing dependency;
```
function MyComponent() {
type ColumnKey = 'id' | 'name';
type Item = {id: string, name: string};
const columns = useMemo(
() => [
{
type: 'text',
key: 'id',
} as TextColumn<ColumnKey, Item>,
^^^^^^^^ here
],
[],
);
}
```
This is due to the AST of AsExpressions being something like:
AsExpression
└── typeAnnotation: GenericTypeAnnotation
└── typeParameters: TypeParameterInstantiation
└── params[0]: GenericTypeAnnotation
└── id: Identifier (name: "ColumnKey")
Where `ColumnKey` never has a TypeParameter Annotation. So we need to
consider it to be a flow type due to it belonging to a
GenericTypeAnnotation
Test Plan:
Added unit tests
Before:
```
Test Suites: 1 failed, 2 passed, 3 total
Tests: 2 failed, 5065 passed, 5067 total
Snapshots: 0 total
Time: 16.517 s
Ran all test suites.
error Command failed with exit code 1.
```
After:
```
PASS __tests__/ReactCompilerRuleTypescript-test.ts
PASS __tests__/ESLintRulesOfHooks-test.js (6.192 s)
PASS __tests__/ESLintRuleExhaustiveDeps-test.js (9.97 s)
Test Suites: 3 passed, 3 total
Tests: 5067 passed, 5067 total
Snapshots: 0 total
Time: 10.21 s, estimated 11 s
Ran all test suites.
✨ Done in 12.66s.
```
Add a fast heuristic to detect whether a file may contain React
components or hooks before running the full compiler. This avoids the
overhead of Babel AST parsing and compilation for utility files, config
files, and other non-React code.
The heuristic uses ESLint's already-parsed AST to check for functions
with React-like names at module scope:
- Capitalized functions: MyComponent, Button, App
- Hook pattern functions: useEffect, useState, useMyCustomHook
Files without matching function names are skipped and return an empty
result, which is cached to avoid re-checking for subsequent rules.
Also adds test coverage for the heuristic edge cases.
`react-hooks/exhaustive-effect-dependencies` from
`ValidateExhaustiveDeps` reports errors for both missing and extra
effect deps. We already have `react-hooks/exhaustive-deps` that errors
on missing dependencies. In the future we'd like to consolidate this all
to the compiler based error, but for now there's a lot of overlap. Let's
enable testing the extra dep warning by splitting out reporting modes.
This PR
- Creates `on`, `off`, `missing-only`, and `extra-only` reporting modes
for the effect dep validation flag
- Temporarily enables the new rule with `extra-only` in
`eslint-plugin-react-hooks`
- Adds additional null checking to `manualMemoLoc` to fix a bug found
when running against the fixture
This deprecates the `noEmit: boolean` flag and adds `outputMode:
'client' | 'client-no-memo' | 'ssr' | 'lint'` as the replacement.
OutputMode defaults to null and takes precedence if specified, otherwise
we use 'client' mode for noEmit=false and 'lint' mode for noEmit=true.
Key points:
* Retrying failed compilation switches from 'client' mode to
'client-no-memo'
* Validations are enabled behind
Environment.proto.shouldEnableValidations, enabled for all modes except
'client-no-memo'. Similar for dropping manual memoization.
* OptimizeSSR is now gated by the outputMode==='ssr', not a feature flag
* Creation of reactive scopes, and related codegen logic, is now gated
by outputMode==='client'
We were not recording uEE calls in component/hook syntax. Easy fix.
Added tests matching function component syntax for component syntax +
added one for hooks
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
Supersedes #34951
## Summary
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
Fix the runtime error with named imports and make the last remaining
[Are The Types
Wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%400.0.0-experimental-557a4fbc-20251022)
error with `eslint-plugin-react-hooks` go away, thanks to the hint from
Andrew Branch:
- https://github.com/facebook/react/issues/34801#issuecomment-3433478810
## How did you test this change?
<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
If you leave this empty, your PR will very likely be closed.
-->
I tried adding this to `node_modules` and it fixed the failures when
importing named imports like `import { configs, meta, rules } from
'eslint-plugin-react-hooks'`:
```bash
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
Oops! Something went wrong! :(
ESLint: 9.37.0
file:///Users/k/p/eslint-config-upleveled/index.js:13
import reactHooks, { configs } from 'eslint-plugin-react-hooks';
^^^^^^^
SyntaxError: Named export 'configs' not found. The requested module 'eslint-plugin-react-hooks' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'eslint-plugin-react-hooks';
const { configs } = pkg;
at ModuleJob._instantiate (node:internal/modules/esm/module_job:228:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:335:5)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:647:26)
at async dynamicImportConfig (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/[email protected].0/node_modules/eslint/lib/config/config-loader.js:186:17)
at async loadConfigFile (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/[email protected].0/node_modules/eslint/lib/config/config-loader.js:276:9)
at async ConfigLoader.calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/[email protected].0/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/[email protected].0/node_modules/eslint/lib/config/config-loader.js:743:23)
at async directoryFilter (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/[email protected].0/node_modules/eslint/lib/eslint/eslint-helpers.js:309:5)
at async NodeHfs.<anonymous> (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@[email protected].1/node_modules/@humanfs/core/src/hfs.js:586:29)
at async NodeHfs.walk (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@[email protected].1/node_modules/@humanfs/core/src/hfs.js:614:3)
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
➜ eslint-config-upleveled git:(renovate/react-monorepo) # no error
```
The named imports identifiers `configs`, `meta`, and `rules` also
contain values, as a sanity check:
- https://github.com/facebook/react/pull/34951#issuecomment-3433555636
cc @poteto
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
## Summary
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
Fix the runtime error with named imports and make the last remaining
[Are The Types
Wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%400.0.0-experimental-557a4fbc-20251022)
error with `eslint-plugin-react-hooks` go away, thanks to the hint from
@andrewbranch:
- https://github.com/facebook/react/issues/34801#issuecomment-3433478810
## How did you test this change?
<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
If you leave this empty, your PR will very likely be closed.
-->
I tried adding this to `node_modules` and it fixed the failures when
importing named imports like `import { configs, meta, rules } from
'eslint-plugin-react-hooks'`:
```bash
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
Oops! Something went wrong! :(
ESLint: 9.37.0
file:///Users/k/p/eslint-config-upleveled/index.js:13
import reactHooks, { configs } from 'eslint-plugin-react-hooks';
^^^^^^^
SyntaxError: Named export 'configs' not found. The requested module 'eslint-plugin-react-hooks' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'eslint-plugin-react-hooks';
const { configs } = pkg;
at ModuleJob._instantiate (node:internal/modules/esm/module_job:228:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:335:5)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:647:26)
at async dynamicImportConfig (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/[email protected].0/node_modules/eslint/lib/config/config-loader.js:186:17)
at async loadConfigFile (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/[email protected].0/node_modules/eslint/lib/config/config-loader.js:276:9)
at async ConfigLoader.calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/[email protected].0/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/[email protected].0/node_modules/eslint/lib/config/config-loader.js:743:23)
at async directoryFilter (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/[email protected].0/node_modules/eslint/lib/eslint/eslint-helpers.js:309:5)
at async NodeHfs.<anonymous> (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@[email protected].1/node_modules/@humanfs/core/src/hfs.js:586:29)
at async NodeHfs.walk (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@[email protected].1/node_modules/@humanfs/core/src/hfs.js:614:3)
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
➜ eslint-config-upleveled git:(renovate/react-monorepo) # no error
```
The named imports identifiers `configs`, `meta`, and `rules` also
contain values, as a sanity check:
- https://github.com/facebook/react/pull/34951#issuecomment-3433555636
cc @poteto
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
## Summary
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
Resolve the type error with the types, according to [Are the types
wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%407.0.0),
as an additional
- Last attempt: https://github.com/facebook/react/pull/34746
- Original issue: https://github.com/facebook/react/issues/34745
## How did you test this change?
I edited `node_modules/eslint-plugin-react-hooks/index.d.ts` in my
`"module": "Node16"` + `"type": "module"` project and my error went
away:
- https://github.com/facebook/react/issues/34801#issuecomment-3433053067
cc @poteto @michaelfaith @andrewbranch
<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
If you leave this empty, your PR will very likely be closed.
-->
## Summary
Fixes https://github.com/facebook/react/issues/34793.
We are allowing passing down effect events when they are inlined as a
prop.
```
<Child onClick={useEffectEvent(...)} />
```
This seems like a case that someone not familiar with `useEffectEvent`'s
purpose could fall for so this PR introduces logic to disallow its
usage.
An alternative implementation would be to modify the name and function
of `recordAllUseEffectEventFunctions` to record all `useEffectEvent`
instances either assigned to a variable or not, but this seems clearer.
Or we could also specifically disallow its usage inside JSX. Feel free
to suggest any improvements.
## How did you test this change?
- Added a new test in
`packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js`.
All tests pass.
## Summary
When upgrading to `[email protected]` in a project that
uses `zod@3` we are running into TypeScript errors like:
```
node_modules/babel-plugin-react-compiler/dist/index.d.ts:435:10 - error TS2694: Namespace '"/REDACTED/node_modules/zod/v3/external"' has no exported member 'core'.
435 }, z.core.$strip>>>;
~~~~
```
This problem seems to be related to
a76e065ec3, which introduced zod v3/v4
compatibility. Since `zod` is bundled into the compiler source this does
not cause runtime issues and only manifests as TypeScript errors. My
proposed solution is this PR is to use zod's [subpath versioning
strategy](https://zod.dev/v4/versioning?id=versioning-in-zod-4) which
allows you to support v3 and v4 APIs on both major versions.
Changes in this PR include:
- Updated `zod` import paths to `zod/v4`
- Bumped min `zod` version to `^3.25.0` for zod which guarantees the
`zod/v4` subpath is available.
- Updated `zod-validation-error` import paths to
`zod-validation-error/v4`
- Bumped min `zod-validation-error ` version to `^3.5.0`
- Updated `externals` tsup configuration where appropriate.
Once the compiler drops zod v3 support we could optionally remove the
`/v4` subpath from the imports.
## How did you test this change?
Not totally sure the best way to test. I ran `NODE_ENV=production yarn
workspace babel-plugin-react-compiler run build --dts` and diffed the
`dist/` folder between my change and `v1.0.0` and it looks correct. We
have a `patch-package` patch to workaround this for now and it works as
expected.
```diff
diff --git a/node_modules/babel-plugin-react-compiler/dist/index.d.ts b/node_modules/babel-plugin-react-compiler/dist/index.d.ts
index 81c3f3d..daafc2c 100644
--- a/node_modules/babel-plugin-react-compiler/dist/index.d.ts
+++ b/node_modules/babel-plugin-react-compiler/dist/index.d.ts
@@ -1,7 +1,7 @@
import * as BabelCore from '@babel/core';
import { NodePath as NodePath$1 } from '@babel/core';
import * as t from '@babel/types';
-import { z } from 'zod';
+import { z } from 'zod/v4';
import { NodePath, Scope } from '@babel/traverse';
interface Result<T, E> {
```
Co-authored-by: Henry Q. Dineen <[email protected]>
Renames the `recommended` property on LintRule to `preset`, to allow
exporting rules for different presets. For now the `Recommended` and
`RecommendedLatest` presets are the same, but in the next PR I will
enable more rules for the latest preset.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34782).
* #34783
* __->__ #34782
For 7.0.0:
Slim down presets to just 2 configurations:
- `recommended`: legacy and flat config with all recommended rules, and
- `recommended-latest`: legacy and flat config with all recommended
rules plus new bleeding edge experimental compiler rules
Removed:
- `recommended-latest-legacy`
- `flat/recommended`
Please see the README for new install instructions.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34757).
* #34783
* #34782
* __->__ #34757
Adds back HermesParser to eslint-plugin-react-hooks. There are still
[external users of
Flow](https://github.com/facebook/react/pull/34719#issuecomment-3368137743)
using the plugin, so we shouldn't break the plugin for them. However, we
still have the problem of double parsing: once from eslint (which we
discard) and then another via babel/hermes parser.
In the long run we should investigate a translation layer from estree to
babel (or alternatively, update the compiler to take estree as input).
But for now, I am reverting the PR.
This does mean that [Sandpack in
react.dev](https://github.com/reactjs/react.dev/blob/11cb6b591571caf5fa2a192117b6a6445c3f2027/src/components/MDX/Sandpack/runESLint.tsx#L31)
cannot update to the latest eprh as HermesParser does not appear to be
able to be run in a browser. I discovered this while trying to update
eprh on react.dev last week, but didn't investigate deeply. I'll need to
double check that again to find out more.
Another attempt to fix#34745. I updated our fixture for eslint-v9 to
include running tsc. I believe there were 2 issues:
1. `export * from './cjs/eslint-plugin-react-hooks'` in npm/index.d.ts
was no longer correct as we updated index.ts to export default instead
of named exports
2. After fixing ^ there was a typescript error which I fixed by making
some small tweaks
Partial redo of #34710. The changes there tried to use `z.function(args,
return)` to be compatible across Zod v3 and v4, but Zod 4's function API
has completely changed. Instead, I've updated to just use `z.any()`
where we expect a function, and manually validate that it's a function
before we call the value. We already have validation of the return type
(also using Zod).
Co-authored-by: kolvian <[email protected]>
We will be focusing eslint-plugin-react-hooks as the primary OSS-only
package for our lint plugin. eslint-plugin-react-compiler will remain as
a Meta only package as some limitations of our internal infra require us
to use packages that aren't widely adopted by the rest of the industry.
This PR removes `hermes-parser`, which was meant to support parsing Flow
syntax.
This rule was a leftover from a while ago and doesn't actually lint
anything useful. Specifically, you get a lint error if you try to opt
out a component that isn't already bailing out. If there's a bailout the
compiler already safely skips over it, so adding `'use no memo'` there
is unnecessary.
Fixes#31407
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34703).
* __->__ #34703
* #34700
Previously, the `recommended` config used the legacy ESLint format
(plugins as an array of strings). This causes errors when used with
ESLint v9's `defineConfig()` helper. This was following [eslint's own
docs](https://eslint.org/docs/latest/extend/plugins#backwards-compatibility-for-legacy-configs):
> With this approach, both configuration systems recognize
"recommended". The old config system uses the recommended key while the
current config system uses the flat/recommended key. The defineConfig()
helper first looks at the recommended key, and if that is not in the
correct format, it looks for the flat/recommended key. This allows you
an upgrade path if you’d later like to rename flat/recommended to
recommended when you no longer need to support the old config system.
However,
[`isLegacyConfig()`](https://github.com/eslint/rewrite/blob/main/packages/config-helpers/src/define-config.js#L73-L81)
(also see
[`eslintrcKeys`](https://github.com/eslint/rewrite/blob/main/packages/config-helpers/src/define-config.js#L24-L35))
function doesn't check for the `plugins` key, so our config was
incorrectly treated as flat config despite being in legacy format.
This PR fixes the issue, along with a few other fixes combined:
1. Convert `recommended` to flat config format
2. Separate basic rules (exhaustive-deps, rules-of-hooks) from compiler
rules
3. Add `recommended-latest-legacy` config for non-flat config users who
want all recommended rules (including compiler rules)
4. Adding more types for the exported config
Our shipped presets in 6.x.x will essentially be:
- `recommended-legacy`: legacy (non-flat), with basic rules only
- `recommended-latest-legacy`: legacy (non-flat), all rules (basic +
compiler)
- `flat/recommended`: flat, basic rules only (now the same as
recommended, but to avoid making a breaking change we'll just keep it
around in 6.x.x)
- `recommended-latest`: flat, all rules (basic + compiler)
- `recommended`: flat, basic rules only
In the next breaking release 7.x.x, we will collapse down the presets
into three:
- `recommended-legacy`: all recommended rules
- `recommended`: all recommended rules
- `recommended-experimental`: all recommended rules + new bleeding edge
experimental rules
Closes#34679
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34700).
* #34703
* __->__ #34700
Follow up to #34649. This adds the compiler rules back so they can be
opted-in 6.1.0, but aren't included in the presets as that would be a
breaking change.
Called Before:
> `logEvent` is a function created with React Hook "useEffectEvent", and
can only be called from the same component.
Called After:
> `logEvent` is a function created with React Hook "useEffectEvent", and
can only be called from Effects and Effect Events in the same component.
Referenced Before:
> `logEvent` is a function created with React Hook "useEffectEvent", and
can only be called from the same component. They cannot be assigned to
variables or passed down.
Referenced After:
> `logEvent` is a function created with React Hook "useEffectEvent", and
can only be called from Effects and Effect Events in the same component.
It cannot be assigned to a variable or passed down.
Like in the diff below, we can read from the shared configuration to
check exhaustive deps.
I allow the classic additionalHooks configuration to override it so that
this change
is backwards compatible.
--
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34637).
* __->__ #34637
* #34497
We need to be able to specify additional effect hooks for the
RulesOfHooks lint rule
in order to allow useEffectEvent to be called by custom effects.
ExhaustiveDeps
does this with a regex suppplied to the rule, but that regex is not
accessible from
other rules.
This diff introduces a `react-hooks` entry you can put in the eslint
settings that
allows you to specify custom effect hooks and share them across all
rules.
This works like:
```
{
settings: {
'react-hooks': {
additionalEffectHooks: string,
},
},
}
```
The next diff allows useEffect to read from the same configuration.
----
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34497).
* #34637
* __->__ #34497
With #34176 we now have granular lint rules created for each compiler
ErrorCategory. However, we had remnants of our old error severities
still in use which makes reporting errors quite clunky. Previously you
would need to specify both a category and severity which often ended up
being the same.
This PR moves severity definition into our rules which are generated
from our categories. For now I decided to defer "upgrading" categories
from a simple string to a sum type since we are only using severities to
map errors to eslint severity.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34401).
* #34409
* #34404
* #34403
* #34402
* __->__ #34401
NOTE: this is a merged version of @mofeiZ's original PR along with my
edits per offline discussion. The description is updated to reflect the
latest approach.
The key problem we're trying to solve with this PR is to allow
developers more control over the compiler's various validations. The
idea is to have a number of rules targeting a specific category of
issues, such as enforcing immutability of props/state/etc or disallowing
access to refs during render. We don't want to have to run the compiler
again for every single rule, though, so @mofeiZ added an LRU cache that
caches the full compilation output of N most recent files. The first
rule to run on a given file will cause it to get cached, and then
subsequent rules can pull from the cache, with each rule filtering down
to its specific category of errors.
For the categories, I went through and assigned a category roughly 1:1
to existing validations, and then used my judgement on some places that
felt distinct enough to warrant a separate error. Every error in the
compiler now has to supply both a severity (for legacy reasons) and a
category (for ESLint). Each category corresponds 1:1 to a ESLint rule
definition, so that the set of rules is automatically populated based on
the defined categories.
Categories include a flag for whether they should be in the recommended
set or not.
Note that as with the original version of this PR, only
eslint-plugin-react-compiler is changed. We still have to update the
main lint rule.
## Test Plan
* Created a sample project using ESLint v9 and verified that the plugin
can be configured correctly and detects errors
* Edited `fixtures/eslint-v9` and introduced errors, verified that the w
latest config changes in that fixture it correctly detects the errors
* In the sample project, confirmed that the LRU caching is correctly
caching compiler output, ie compiling files just once.
Co-authored-by: Mofei Zhang <[email protected]>