Compare commits

..

52 Commits

Author SHA1 Message Date
Joe Savona
5008fd1d18 [compiler] Repro for missed case of global mutation
I realized a pattern of global mutations that we don't currently detect (both in the old and new inference models). If you hide the mutation inside a function returned from another function, we lose track of it:

```js
const f = () => () => {
  global.property = true;
};
f()();
```

Roughly speaking, we need to track that if the return value of `f` is mutated, that it should count as triggering some effects. Right now we encode the idea that a function specifically can have side effects if it is mutated, but other values don't have a way to represent this. I'm thinking that we change the shape of the `Create` effect a bit, and allow room for an optional "mutation effects" array. Then, InferMutationAliasingRanges can visit these effects like it does when trying to find transitive function effects.
2025-08-28 14:55:40 -07:00
Joe Savona
d2b2a1b195 [compiler] Propagate CreateFunction effects for functions that return functions
If you have a local helper function that itself returns a function (`() => () => { ... }`), we currently infer the return effect of the outer function as `Create mutable`. We correctly track the aliasing, but we lose some precision because we don't understand that a function specifically is being returned.

Here, we do some extra analysis of which values are returned in InferMutationAliasingRanges, and if the sole return value is a function we infer a `CreateFunction` effect. We also infer an `Assign` (instead of a Create) if the sole return value was one of the context variables or parameters.
2025-08-28 14:55:39 -07:00
Joe Savona
1671142ea8 [compiler] InferEffects uses effects as value keys
In InferReferenceEffects we used `InstructionValue` as the key to represent values, since each time we process an instruction this object will be the same. However this was always a bit of a hack, and in the new model and InferMutationAliasingEffects we can instead use the (creation) effect as the stable value. This avoids an extra layer of memoization since the effects are already interned anyway.
2025-08-28 14:55:38 -07:00
Eugene Choi
c5362a380f [compiler][playground] (1/N) Config override panel (#34303)
## Summary
Part 1 of adding a "Config Override" panel to the React compiler
playground. The panel is placed to the left of the current input
section, and supports converting the comment pragmas in the input
section to a JavaScript-based config. Backwards sync has not been
implemented yet.

NOTE: I have added support for a new `OVERRIDE` type pragma to add
support for Map and Function types. (For now, the old pragma format is
still intact)

## Testing
Example of the config overrides synced to the source code:
<img width="1542" height="527" alt="Screenshot 2025-08-28 at 3 38 13 PM"
src="https://github.com/user-attachments/assets/d46e7660-61b9-4145-93b5-a4005d30064a"
/>
2025-08-28 16:26:15 -04:00
Sebastian "Sebbie" Silbermann
89a803fcec [DevTools] Add breadcrumbs to Suspense tab (#34312) 2025-08-28 16:03:54 +02:00
Joseph Savona
8d7b5e4903 [compiler] Show a ref name hint when assigning to non-ref in a callback (#34298)
In #34125 I added a hint where if you assign to the .current property of
a frozen object, we suggest naming the variable as `ref` or `-Ref`.
However, the tracking for mutations that assign to .current specifically
wasn't propagated past function expression boundaries, which meant that
the hint only showed up if you mutated the ref in the main body of the
component/hook. That's less likely to happen since most folks know not
to access refs in render. What's more likely is that you'll (correctly)
assign a ref in an effect or callback, but the compiler will throw an
error. By showing a hint in this case we can help people understand the
naming pattern.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34298).
* #34276
* __->__ #34298
2025-08-27 17:05:44 -07:00
Jack Pope
3434ff4f4b Add scrollIntoView to fragment instances (#32814)
This adds `experimental_scrollIntoView(alignToTop)`. It doesn't yet
support `scrollIntoView(options)`.

Cases:
- No host children: Without host children, we represent the virtual
space of the Fragment by attempting to scroll to the nearest edge by
using its siblings. If the preferred sibling is not found, we'll try the
other side, and then the parent.
- 1 or more host children: In order to handle the case of children
spread between multiple scroll containers, we scroll to each child in
reverse order based on the `alignToTop` flag.

Due to the complexity of multiple scroll containers and dealing with
portals, I've added this under a separate feature flag with an
experimental prefix. We may stabilize it along with the other APIs, but
this allows us to not block the whole feature on it.

This PR was previously implementing a much more complex approach to
handling multiple scroll containers and portals. We're going to start
with the simple loop and see if we can find any concrete use cases where
that doesn't suffice. 01f31d43013ba7f6f54fd8a36990bbafc3c3cc68 is the
diff between approaches here.
2025-08-27 18:05:57 -04:00
lauren
bd5b1b7639 [compiler] Emit better error for unsupported syntax this (#34322) 2025-08-27 17:58:44 -04:00
lauren
0a1f1fcd50 [ci] Cache playwright in run_devtools_e2e_tests (#34320)
I happened to notice that I forgot to cache playwright in
run_devtools_e2e_tests, so it would try to install it every time which
can randomly take a while to complete (I'm not sure why it's not
deterministic, but the dependencies appear to be installed
inconsistently across multiple workflows).

This PR adds the same cache we use for other steps that use playwright,
which should shave off some time from this workflow when the cache is
warm.

Additionally I omitted the standalone install-deps command as it appears
to be redundant and adds a lot of extra time to CI, due to the fact that
it installs many unrelated dependencies.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34320).
* #34321
* __->__ #34320
2025-08-27 14:37:18 -04:00
lauren
b870042915 [compiler] Validate against component/hook factories (#34305)
Previously, the compiler would incorrectly attempt to compile nested
components/hooks defined inside non-React functions. This would lead to
scope reference errors at runtime because the compiler would optimize
the nested React function without understanding its closure over the
parent function's variables.

This PR adds detection when non-React functions declare components or
hooks, and reports a clear error before compilation. I put this under a
new compiler flag defaulting to false. I'll run a test on this
internally first, but I expect we should be able to just turn it on in
both compiler (so we stop miscompiling) and linter.

Closes #33978

Playground example:
https://react-compiler-playground-git-pr34305-fbopensource.vercel.app/#N4Igzg9grgTgxgUxALhAejQAgAIDcCGANgJYAm+ALggHIQAiAngHb4C2xcRhDAwjApQSkeEVgAcITBEwpgA8jAASECAGswAHSkAPCTAqYAZlCZwKxSZgDmCCgEkmYqBQAU+AJSZgWzJjiSwAwB1GHwxMQQYTABeTBdPaIA+Lx9fPwCDAAt8JlJCBB5sphsYuITk7yY0tPwAOklCnJt4gG5U3wBfNqZ2zH4KWCqAHmJHZ0wGopto4CK8gqmEDsw0RO7O7tT+wcwQsIiYbo6QDqA
2025-08-27 13:59:26 -04:00
Joseph Savona
33a1095d72 [compiler] Infer render helpers for additional validation (#33647)
We currently assume that any functions passes as props may be event
handlers or effect functions, and thus don't check for side effects such
as mutating globals. However, if a prop is a function that returns JSX
that is a sure sign that it's actually a render helper and not an event
handler or effect function. So we now emit a `Render` effect for any
prop that is a JSX-returning function, triggering all of our render
validation.

This required a small fix to InferTypes: we weren't correctly populating
the `return` type of function types during unification. I also improved
the printing of types so we can see the inferred return types.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33647).
* #33643
* #33650
* #33642
* __->__ #33647
2025-08-27 08:44:09 -07:00
Sebastian "Sebbie" Silbermann
213594860f [DevTools] Better scrolling in Suspense tab (#34299) 2025-08-27 16:00:06 +02:00
Hendrik Liebau
9c2e2b8475 [Flight] Don't drop debug info if there's only a readable debug channel (#34304)
When the Flight Client is waiting for pending debug chunks, it drops the
debug info if there is no writable side of the debug channel defined.
However, it should instead check if there's no readable side defined.

Fixing this is not only important for browser clients that don't want or
need a return channel, but it's also crucial for server-side rendering,
because the Node and Edge clients only accept a readable side of the
debug channel. So they can't even define a noop writable side as a
workaround.
2025-08-27 13:50:19 +02:00
Sebastian "Sebbie" Silbermann
4123f6b771 [Fizz] Skip past hidden inputs when attempting to hydrate hydration boundaries (#34302) 2025-08-26 17:28:36 +02:00
Sebastian "Sebbie" Silbermann
cb1e73be04 [DevTools] Batch Suspense toggles when advancing the Suspense timeline (#34251) 2025-08-26 17:22:30 +02:00
Hendrik Liebau
cacc20e37c [Flight] Wait for both streams to end before closing the response (#34301)
When a debug channel is defined, we must ensure that we don't close the
Flight Client's response when the debug channel's readable is done, but
the RSC stream is still flowing. Now, we wait for both streams to end
before closing the response.
2025-08-26 17:15:25 +02:00
Sebastian "Sebbie" Silbermann
bb7c9c1b8a Create more realistic containers in DevTools fixture (#34296) 2025-08-26 17:13:37 +02:00
Sebastian "Sebbie" Silbermann
44f8451ede [DevTools] Avoid tearing Suspense store (#34294) 2025-08-26 17:09:55 +02:00
Sebastian "Sebbie" Silbermann
ad4ecb6e6e [DevTools] Fix symbolication with Index Source Maps (#34300) 2025-08-26 15:18:20 +02:00
Jan Kassens
26e87b5f15 Fix Flow issue from land race (#34293)
A Flow upgrade removed the bundled library definitinos for
SynthaticEvent and we probably want to use our internal definitions.
Those are not properly typed at this point yet, but we can look into
that as a followup.
2025-08-25 12:58:12 -04:00
Sebastian "Sebbie" Silbermann
75dc0026d6 [DevTools] Initial version of Suspense timeline (#34233) 2025-08-25 17:47:29 +02:00
Jan Kassens
df10309e2b Update Flow to 0.279 (#34277)
Multiple of these version upgrades required minor additional
annotations.
2025-08-25 11:02:56 -04:00
Sebastian "Sebbie" Silbermann
e42f3d30ca [DevTools] Include name prop when highlighting host instances (#34258) 2025-08-25 16:40:56 +02:00
Sebastian "Sebbie" Silbermann
67e743fba5 [compiler] Fix missing dependency in eslint-plugin-react-hooks (#34287) 2025-08-25 16:39:23 +02:00
Sebastian "Sebbie" Silbermann
9eede45646 Stop treating all Node.js builtins implicitly as externals (#34249) 2025-08-25 09:39:56 +02:00
Jan Kassens
090777d78a Update Flow to 0.274 (#34275)
An exported needed explicit typing as it was inferred incorrectly.
2025-08-22 17:46:37 -04:00
Jan Kassens
4049cfeeab Update Flow to 0.273 (#34274)
This version introduces "Natural Inference" which requires a couple more
type annotations to make Flow pass.
2025-08-22 16:58:01 -04:00
Jan Kassens
e67e3bed92 Update Flow to 0.272 (#34273)
This is the last version before "Natural Inference" change to Flow that
will require more changes, so doing a quick fast-forward PR here.

- Disabled a new Flow lint against unsafe `Object.assign`.
2025-08-22 16:25:49 -04:00
Jan Kassens
06cfa99f37 Update Flow to 0.267 (#34272)
Changes to type inference require some more annotations.
2025-08-22 15:53:07 -04:00
Jan Kassens
05addfc663 Update Flow to 0.266 (#34271)
- replace `$ElementType` and `$PropertyType` with `T[K]` accesses.
- Use component types
2025-08-22 15:46:41 -04:00
Jan Kassens
d260b0d8b8 Update Flow to 0.265 (#34270)
Looks like this version removed `Object.prototype` although I didn't see
that in the changelog. This is fine for this code here.
2025-08-22 15:22:22 -04:00
Joseph Savona
425ba0ad6d [compiler] Script to produce markdown of lint rule docs (#34260)
The docs site is in a separate repo, but this gives us a semi-automated
way to update the docs about our lint rules. The script generates
markdown files from the rule definitions which we can then manually
copy/paste into the docs site somewhere. In the future we can automate
this fully.
2025-08-22 09:59:28 -07:00
Jan Kassens
6de32a5a07 Update Flow to 0.263 (#34269)
This update was a bit more involved.

- `React$Component` was removed, I replaced it with Flow component
types.
- Flow removed shipping the standard library. This adds the environment
libraries back from `flow-typed` which seemed to have changed slightly
(probably got more precise and less `any`s). Suppresses some new type
errors.
2025-08-22 12:10:13 -04:00
Abdulwahab Omira
698bb4deb7 Add support for ARIA 1.3 attributes (#34264)
Co-authored-by: Abdulwahab Omira <abdulwahabomira@gmail.com>
Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
2025-08-22 16:22:18 +02:00
Sebastian Markbåge
11d7bcf88c [DevTools] Use source maps to infer name asynchronously (#34212) 2025-08-22 00:38:09 +02:00
Sebastian Markbåge
a85ec041d6 [DevTools] Ignore List Stack Traces (#34210)
Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
2025-08-22 00:03:05 +02:00
Joseph Savona
7d29ecbeb2 [compiler] Aggregate error reporting, separate eslint rules (#34176)
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 <feifei0@meta.com>
2025-08-21 14:53:34 -07:00
Sebastian Markbåge
253abc78a1 [Flight] Transfer Debug Info from a synchronous Reference to another Chunk (#34229) 2025-08-21 23:50:20 +02:00
Jan Kassens
d73b6f1110 Update Flow to 0.261 (#34255)
- 0.261 required to pull out a constant to preserve refinement
- 0.259 needed some updated suppressions for hacky stuff
2025-08-21 15:02:49 -04:00
Jan Kassens
d5586e2059 Update Flow to 0.258 (#34254)
Minor new suppressions only.
2025-08-21 14:17:13 -04:00
Jan Kassens
ec5dd0ab3a Update Flow to 0.257 (#34253)
After an easy couple version with #34252, this version is less flexible
(and safer) on inferring exported types mainly.

We require to annotate some exported types to differentiate between
`boolean` and literal `true` types, etc.
2025-08-21 13:30:01 -04:00
Ruslan Lesiutin
8120753665 [DevTools] fix: always send a response to fetch-file request in the extension (#34235)
This fixes the displaying of "rendered by" section if owner stacks
contained any native frames. This regressed after
https://github.com/facebook/react/pull/34185, where we added the
Suspense boundary for the StackTraceView.

This fails because the Promise that is responsible for symbolication of
the source is never getting resolved or rejected.
Previously, we would just throw an Error without sending a corresponding
message to the `main` script, and it would just cache a Promise that is
never resolved, hence the Suspense boundary for "rendered by" section is
never resolved.

In a separate change, I think we need to update StackTraceView component
to display `native` as location, instead of `:0`:
<img width="712" height="118" alt="Screenshot 2025-08-20 at 00 20 42"
src="https://github.com/user-attachments/assets/c79735c9-fdd2-467c-96cd-2bc29d38c4e0"
/>
2025-08-21 18:28:33 +01:00
Jan Kassens
3770ff3853 Update Flow to 0.256 (#34252)
Looks like these versions didn't require changes, so easy fast forward.
2025-08-21 12:33:56 -04:00
Jan Kassens
873f711299 Update Flow to 0.248 (#34248)
This update remove support for `%checks`.

Thanks @SamChou19815 for finding a close replacement that works.
2025-08-21 11:15:34 -04:00
Jan Kassens
5f06c3d22a Update Flow to 0.247 (#34245)
`$Call` was removed.
2025-08-20 22:19:57 -04:00
Jan Kassens
243a56b9a2 Update Flow to 0.246 (#34244)
Catching up Flow versions. Since there's plenty new errors, I'm taking
each version with breaking changes as a new PR.
2025-08-20 21:46:55 -04:00
Jan Kassens
83c7379b96 Add flow suppression for Constant Condition rollout (#34243) 2025-08-20 18:24:01 -04:00
lauren
c2ac8b4f0e [ci] Fix permissions for direct sync branch PRs workflow (#34241)
Because we sync built artifacts into Meta, we can't support edits from
inside www/fbsource to be synced back into OSS as it would cause merge
conflicts for future OSS PRs.

We have a workflow that should automatically catch and close these PRs,
but it looks like this one was missing one permission.
2025-08-20 17:09:38 -04:00
Sebastian "Sebbie" Silbermann
03fda05d2c [DevTools] Fix display of stack frames with anonymous sources (#34237) 2025-08-20 17:31:42 +02:00
Hendrik Liebau
0bc71e67ab [Flight] Add debugChannel option to Edge and Node clients (#34236)
When a debug channel is used between the Flight server and a browser
Flight client, we want to allow the same RSC stream to be used for
server-side rendering. To support this, the Edge and Node Flight clients
also need to accept a `debugChannel` option. Without it, debug
information would be missing (e.g. for SSR error stacks), and in some
cases this could result in `Connection closed` errors.

This PR adds support for the `debugChannel` option in the Edge and Node
clients for ESM, Parcel, Turbopack, and Webpack. Unlike the browser
clients, these clients only support a one-way channel, since the Flight
server’s return protocol is not designed for multiple clients.

The implementation follows the approach used in the browser clients, but
excludes the writable parts.
2025-08-20 16:46:34 +02:00
Sebastian "Sebbie" Silbermann
3e20dc8b9c [DevTools] Fix crash when inspecting Components suspended on data awaited in anonymous functions (#34234) 2025-08-20 09:34:06 +02:00
Sebastian "Sebbie" Silbermann
ae5c2f82b3 [DevTools] Handle reorders when resuspending while fallback contains Suspense (#34225) 2025-08-19 20:22:54 +02:00
309 changed files with 20195 additions and 2083 deletions

View File

@@ -28,3 +28,6 @@ packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
packages/react-devtools-shell/dist
packages/react-devtools-timeline/dist
packages/react-devtools-timeline/static
# Imported third-party Flow types
flow-typed/

View File

@@ -547,13 +547,10 @@ module.exports = {
},
globals: {
$Call: 'readonly',
$ElementType: 'readonly',
$Flow$ModuleRef: 'readonly',
$FlowFixMe: 'readonly',
$Keys: 'readonly',
$NonMaybeType: 'readonly',
$PropertyType: 'readonly',
$ReadOnly: 'readonly',
$ReadOnlyArray: 'readonly',
$ArrayBufferView: 'readonly',
@@ -568,6 +565,7 @@ module.exports = {
BigInt: 'readonly',
BigInt64Array: 'readonly',
BigUint64Array: 'readonly',
CacheType: 'readonly',
Class: 'readonly',
ClientRect: 'readonly',
CopyInspectedElementPath: 'readonly',
@@ -582,13 +580,15 @@ module.exports = {
IteratorResult: 'readonly',
JSONValue: 'readonly',
JSResourceReference: 'readonly',
mixin$Animatable: 'readonly',
MouseEventHandler: 'readonly',
NavigateEvent: 'readonly',
PerformanceMeasureOptions: 'readonly',
PropagationPhases: 'readonly',
PropertyDescriptor: 'readonly',
React$AbstractComponent: 'readonly',
PropertyDescriptorMap: 'readonly',
Proxy$traps: 'readonly',
React$Component: 'readonly',
React$ComponentType: 'readonly',
React$Config: 'readonly',
React$Context: 'readonly',
React$Element: 'readonly',
@@ -620,7 +620,6 @@ module.exports = {
PropertyIndexedKeyframes: 'readonly',
KeyframeAnimationOptions: 'readonly',
GetAnimationsOptions: 'readonly',
Animatable: 'readonly',
ScrollTimeline: 'readonly',
EventListenerOptionsOrUseCapture: 'readonly',
FocusOptions: 'readonly',

View File

@@ -57,8 +57,6 @@ jobs:
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- run: npx playwright install --with-deps chromium
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
- run: npx playwright install-deps
if: steps.cache_playwright_browsers.outputs.cache-hit == 'true'
- run: CI=true yarn test
- run: ls -R test-results
if: '!cancelled()'

View File

@@ -316,7 +316,7 @@ jobs:
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/react-compiler/build-compiler.sh && ./scripts/react-compiler/link-compiler.sh
- run: yarn workspace eslint-plugin-react-hooks test
# ----- BUILD -----
build_and_lint:
name: yarn build and lint
@@ -811,9 +811,18 @@ jobs:
pattern: _build_*
path: build
merge-multiple: true
- run: |
npx playwright install
sudo npx playwright install-deps
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- name: Playwright install deps
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium
- run: ./scripts/ci/run_devtools_e2e_tests.js
env:
RELEASE_CHANNEL: experimental

View File

@@ -18,6 +18,7 @@ jobs:
permissions:
# Used to create a review and close PRs
pull-requests: write
contents: write
steps:
- name: Close PR
uses: actions/github-script@v7

View File

@@ -0,0 +1,103 @@
/**
* 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 MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
import {parseConfigPragmaAsString} from 'babel-plugin-react-compiler';
import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';
import parserBabel from 'prettier/plugins/babel';
import * as prettierPluginEstree from 'prettier/plugins/estree';
import * as prettier from 'prettier/standalone';
import {useState, useEffect} from 'react';
import {Resizable} from 're-resizable';
import {useStore} from '../StoreContext';
import {monacoOptions} from './monacoOptions';
loader.config({monaco});
export default function ConfigEditor(): JSX.Element {
const [, setMonaco] = useState<Monaco | null>(null);
const store = useStore();
// Parse string-based override config from pragma comment and format it
const [configJavaScript, setConfigJavaScript] = useState('');
useEffect(() => {
const pragma = store.source.substring(0, store.source.indexOf('\n'));
const configString = `(${parseConfigPragmaAsString(pragma)})`;
prettier
.format(configString, {
semi: true,
parser: 'babel-ts',
plugins: [parserBabel, prettierPluginEstree],
})
.then(formatted => {
setConfigJavaScript(formatted);
})
.catch(error => {
console.error('Error formatting config:', error);
setConfigJavaScript('({})'); // Return empty object if not valid for now
//TODO: Add validation and error handling for config
});
console.log('Config:', configString);
}, [store.source]);
const handleChange: (value: string | undefined) => void = value => {
if (!value) return;
// TODO: Implement sync logic to update pragma comments in the source
console.log('Config changed:', value);
};
const handleMount: (
_: editor.IStandaloneCodeEditor,
monaco: Monaco,
) => void = (_, monaco) => {
setMonaco(monaco);
const uri = monaco.Uri.parse(`file:///config.js`);
const model = monaco.editor.getModel(uri);
if (model) {
model.updateOptions({tabSize: 2});
}
};
return (
<div className="relative flex flex-col flex-none border-r border-gray-200">
<h2 className="p-4 duration-150 ease-in border-b cursor-default border-grey-200 font-light text-secondary">
Config Overrides
</h2>
<Resizable
minWidth={300}
maxWidth={600}
defaultSize={{width: 350, height: 'auto'}}
enable={{right: true}}
className="!h-[calc(100vh_-_3.5rem_-_4rem)]">
<MonacoEditor
path={'config.js'}
language={'javascript'}
value={configJavaScript}
onMount={handleMount}
onChange={handleChange}
options={{
...monacoOptions,
readOnly: true,
lineNumbers: 'off',
folding: false,
renderLineHighlight: 'none',
scrollBeyondLastLine: false,
hideCursorInOverviewRuler: true,
overviewRulerBorder: false,
overviewRulerLanes: 0,
fontSize: 12,
}}
/>
</Resizable>
</div>
);
}

View File

@@ -37,6 +37,7 @@ import {
type Store,
} from '../../lib/stores';
import {useStore, useStoreDispatch} from '../StoreContext';
import ConfigEditor from './ConfigEditor';
import Input from './Input';
import {
CompilerOutput,
@@ -46,6 +47,7 @@ import {
} from './Output';
import {transformFromAstSync} from '@babel/core';
import {LoggerEvent} from 'babel-plugin-react-compiler/dist/Entrypoint';
import {useSearchParams} from 'next/navigation';
function parseInput(
input: string,
@@ -291,7 +293,13 @@ export default function Editor(): JSX.Element {
[deferredStore.source],
);
// TODO: Remove this once the config editor is more stable
const searchParams = useSearchParams();
const search = searchParams.get('showConfig');
const shouldShowConfig = search === 'true';
useMountEffect(() => {
// Initialize store
let mountStore: Store;
try {
mountStore = initStoreFromUrlOrLocalStorage();
@@ -328,6 +336,7 @@ export default function Editor(): JSX.Element {
return (
<>
<div className="relative flex basis top-14">
{shouldShowConfig && <ConfigEditor />}
<div className={clsx('relative sm:basis-1/4')}>
<Input language={language} errors={errors} />
</div>

View File

@@ -19,7 +19,8 @@
"test": "yarn workspaces run test",
"snap": "yarn workspace babel-plugin-react-compiler run snap",
"snap:build": "yarn workspace snap run build",
"npm:publish": "node scripts/release/publish"
"npm:publish": "node scripts/release/publish",
"eslint-docs": "yarn workspace babel-plugin-react-compiler build && node scripts/build-eslint-docs.js"
},
"dependencies": {
"fs-extra": "^4.0.2",

View File

@@ -7,9 +7,10 @@
import * as t from '@babel/types';
import {codeFrameColumns} from '@babel/code-frame';
import type {SourceLocation} from './HIR';
import {type SourceLocation} from './HIR';
import {Err, Ok, Result} from './Utils/Result';
import {assertExhaustive} from './Utils/utils';
import invariant from 'invariant';
export enum ErrorSeverity {
/**
@@ -540,6 +541,9 @@ export enum ErrorCategory {
// Checking for valid usage of manual memoization
UseMemo = 'UseMemo',
// Checking for higher order functions acting as factories for components/hooks
Factories = 'Factories',
// Checks that manual memoization is preserved
PreserveManualMemo = 'PreserveManualMemo',
@@ -628,7 +632,18 @@ export type LintRule = {
recommended: boolean;
};
const RULE_NAME_PATTERN = /^[a-z]+(-[a-z]+)*$/;
export function getRuleForCategory(category: ErrorCategory): LintRule {
const rule = getRuleForCategoryImpl(category);
invariant(
RULE_NAME_PATTERN.test(rule.name),
`Invalid rule name, got '${rule.name}' but rules must match ${RULE_NAME_PATTERN.toString()}`,
);
return rule;
}
function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
switch (category) {
case ErrorCategory.AutomaticEffectDependencies: {
return {
@@ -636,7 +651,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
name: 'automatic-effect-dependencies',
description:
'Verifies that automatic effect dependencies are compiled if opted-in',
recommended: true,
recommended: false,
};
}
case ErrorCategory.CapitalizedCalls: {
@@ -652,7 +667,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
return {
category,
name: 'config',
description: 'Validates the configuration',
description: 'Validates the compiler configuration options',
recommended: true,
};
}
@@ -678,7 +693,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'set-state-in-effect',
description:
'Validates against calling setState synchronously in an effect',
'Validates against calling setState synchronously in an effect, which can lead to re-renders that degrade performance',
recommended: true,
};
}
@@ -687,7 +702,17 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'error-boundaries',
description:
'Validates usage of error boundaries instead of try/catch for errors in JSX',
'Validates usage of error boundaries instead of try/catch for errors in child components',
recommended: true,
};
}
case ErrorCategory.Factories: {
return {
category,
name: 'component-hook-factories',
description:
'Validates against higher order functions defining nested components or hooks. ' +
'Components and hooks should be defined at the module level',
recommended: true,
};
}
@@ -711,7 +736,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
return {
category,
name: 'gating',
description: 'Validates configuration of gating mode',
description:
'Validates configuration of [gating mode](https://react.dev/reference/react-compiler/gating)',
recommended: true,
};
}
@@ -720,7 +746,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'globals',
description:
'Validates against assignment/mutation of globals during render',
'Validates against assignment/mutation of globals during render, part of ensuring that ' +
'[side effects must render outside of render](https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)',
recommended: true,
};
}
@@ -742,7 +769,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'immutability',
description:
'Validates that immutable values (props, state, etc) are not mutated',
'Validates against mutating props, state, and other values that [are immutable](https://react.dev/reference/rules/components-and-hooks-must-be-pure#props-and-state-are-immutable)',
recommended: true,
};
}
@@ -759,7 +786,9 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'preserve-manual-memoization',
description:
'Validates that existing manual memoized is preserved by the compiler',
'Validates that existing manual memoized is preserved by the compiler. ' +
'React Compiler will only compile components and hooks if its inference ' +
'[matches or exceeds the existing manual memoization](https://react.dev/learn/react-compiler/introduction#what-should-i-do-about-usememo-usecallback-and-reactmemo)',
recommended: true,
};
}
@@ -768,7 +797,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'purity',
description:
'Validates that the component/hook is pure, and does not call known-impure functions',
'Validates that [components/hooks are pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure) by checking that they do not call known-impure functions',
recommended: true,
};
}
@@ -777,7 +806,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'refs',
description:
'Validates correct usage of refs, not reading/writing during render',
'Validates correct usage of refs, not reading/writing during render. See the "pitfalls" section in [`useRef()` usage](https://react.dev/reference/react/useRef#usage)',
recommended: true,
};
}
@@ -785,7 +814,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
return {
category,
name: 'set-state-in-render',
description: 'Validates against setting state during render',
description:
'Validates against setting state during render, which can trigger additional renders and potential infinite render loops',
recommended: true,
};
}
@@ -794,7 +824,7 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
category,
name: 'static-components',
description:
'Validates that components are static, not recreated every render',
'Validates that components are static, not recreated every render. Components that are recreated dynamically can reset state and trigger excessive re-rendering',
recommended: true,
};
}
@@ -826,7 +856,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
return {
category,
name: 'unsupported-syntax',
description: 'Validates against syntax that we do not plan to support',
description:
'Validates against syntax that we do not plan to support in React Compiler',
recommended: true,
};
}
@@ -834,7 +865,8 @@ export function getRuleForCategory(category: ErrorCategory): LintRule {
return {
category,
name: 'use-memo',
description: 'Validates usage of the useMemo() hook',
description:
'Validates usage of the useMemo() hook against common mistakes. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
recommended: true,
};
}

View File

@@ -494,7 +494,20 @@ function findFunctionsToCompile(
): Array<CompileSource> {
const queue: Array<CompileSource> = [];
const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => {
// In 'all' mode, compile only top level functions
if (
pass.opts.compilationMode === 'all' &&
fn.scope.getProgramParent() !== fn.scope.parent
) {
return;
}
const fnType = getReactFunctionType(fn, pass);
if (pass.opts.environment.validateNoDynamicallyCreatedComponentsOrHooks) {
validateNoDynamicallyCreatedComponentsOrHooks(fn, pass, programContext);
}
if (fnType === null || programContext.alreadyCompiled.has(fn.node)) {
return;
}
@@ -839,6 +852,73 @@ function shouldSkipCompilation(
return false;
}
/**
* Validates that Components/Hooks are always defined at module level. This prevents scope reference
* errors that occur when the compiler attempts to optimize the nested component/hook while its
* parent function remains uncompiled.
*/
function validateNoDynamicallyCreatedComponentsOrHooks(
fn: BabelFn,
pass: CompilerPass,
programContext: ProgramContext,
): void {
const parentNameExpr = getFunctionName(fn);
const parentName =
parentNameExpr !== null && parentNameExpr.isIdentifier()
? parentNameExpr.node.name
: '<anonymous>';
const validateNestedFunction = (
nestedFn: NodePath<
t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression
>,
): void => {
if (
nestedFn.node === fn.node ||
programContext.alreadyCompiled.has(nestedFn.node)
) {
return;
}
if (nestedFn.scope.getProgramParent() !== nestedFn.scope.parent) {
const nestedFnType = getReactFunctionType(nestedFn as BabelFn, pass);
const nestedFnNameExpr = getFunctionName(nestedFn as BabelFn);
const nestedName =
nestedFnNameExpr !== null && nestedFnNameExpr.isIdentifier()
? nestedFnNameExpr.node.name
: '<anonymous>';
if (nestedFnType === 'Component' || nestedFnType === 'Hook') {
CompilerError.throwDiagnostic({
category: ErrorCategory.Factories,
severity: ErrorSeverity.InvalidReact,
reason: `Components and hooks cannot be created dynamically`,
description: `The function \`${nestedName}\` appears to be a React ${nestedFnType.toLowerCase()}, but it's defined inside \`${parentName}\`. Components and Hooks should always be declared at module scope`,
details: [
{
kind: 'error',
message: 'this function dynamically created a component/hook',
loc: parentNameExpr?.node.loc ?? fn.node.loc ?? null,
},
{
kind: 'error',
message: 'the component is created here',
loc: nestedFnNameExpr?.node.loc ?? nestedFn.node.loc ?? null,
},
],
});
}
}
nestedFn.skip();
};
fn.traverse({
FunctionDeclaration: validateNestedFunction,
FunctionExpression: validateNestedFunction,
ArrowFunctionExpression: validateNestedFunction,
});
}
function getReactFunctionType(
fn: BabelFn,
pass: CompilerPass,
@@ -877,11 +957,6 @@ function getReactFunctionType(
return componentSyntaxType;
}
case 'all': {
// Compile only top level functions
if (fn.scope.getProgramParent() !== fn.scope.parent) {
return null;
}
return getComponentOrHookLike(fn, hookPattern) ?? 'Other';
}
default: {

View File

@@ -650,6 +650,13 @@ export const EnvironmentConfigSchema = z.object({
* useMemo(() => { ... }, [...]);
*/
validateNoVoidUseMemo: z.boolean().default(false),
/**
* Validates that Components/Hooks are always defined at module level. This prevents scope
* reference errors that occur when the compiler attempts to optimize the nested component/hook
* while its parent function remains uncompiled.
*/
validateNoDynamicallyCreatedComponentsOrHooks: z.boolean().default(false),
});
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;

View File

@@ -323,6 +323,22 @@ export default class HIRBuilder {
],
});
}
if (node.name === 'this') {
CompilerError.throwDiagnostic({
severity: ErrorSeverity.UnsupportedJS,
category: ErrorCategory.UnsupportedSyntax,
reason: '`this` is not supported syntax',
description:
'React Compiler does not support compiling functions that use `this`',
details: [
{
kind: 'error',
message: '`this` was used here',
loc: node.loc ?? GeneratedSource,
},
],
});
}
const originalName = node.name;
let name = originalName;
let index = 0;

View File

@@ -880,7 +880,8 @@ export function printType(type: Type): string {
if (type.kind === 'Object' && type.shapeId != null) {
return `:T${type.kind}<${type.shapeId}>`;
} else if (type.kind === 'Function' && type.shapeId != null) {
return `:T${type.kind}<${type.shapeId}>`;
const returnType = printType(type.return);
return `:T${type.kind}<${type.shapeId}>()${returnType !== '' ? `: ${returnType}` : ''}`;
} else {
return `:T${type.kind}`;
}
@@ -983,7 +984,7 @@ export function printAliasingEffect(effect: AliasingEffect): string {
case 'MutateConditionally':
case 'MutateTransitive':
case 'MutateTransitiveConditionally': {
return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}`;
return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}${effect.kind === 'Mutate' && effect.reason?.kind === 'AssignCurrentProperty' ? ' (assign `.current`)' : ''}`;
}
case 'MutateFrozen': {
return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;

View File

@@ -25,8 +25,8 @@ import {
IdentifierId,
Instruction,
InstructionKind,
InstructionValue,
isArrayType,
isJsxType,
isMapType,
isPrimitiveType,
isRefOrRefValue,
@@ -56,7 +56,6 @@ import {
printAliasingSignature,
printIdentifier,
printInstruction,
printInstructionValue,
printPlace,
printSourceLocation,
} from '../HIR/PrintHIR';
@@ -107,11 +106,11 @@ export function inferMutationAliasingEffects(
const statesByBlock: Map<BlockId, InferenceState> = new Map();
for (const ref of fn.context) {
// TODO: using InstructionValue as a bit of a hack, but it's pragmatic
const value: InstructionValue = {
kind: 'ObjectExpression',
properties: [],
loc: ref.loc,
const value: AliasingEffect = {
kind: 'Create',
into: ref,
value: ValueKind.Context,
reason: ValueReason.Other,
};
initialState.initialize(value, {
kind: ValueKind.Context,
@@ -144,10 +143,11 @@ export function inferMutationAliasingEffects(
}
if (ref != null) {
const place = ref.kind === 'Identifier' ? ref : ref.place;
const value: InstructionValue = {
kind: 'ObjectExpression',
properties: [],
loc: place.loc,
const value: AliasingEffect = {
kind: 'Create',
into: place,
value: ValueKind.Mutable,
reason: ValueReason.Other,
};
initialState.initialize(value, {
kind: ValueKind.Mutable,
@@ -264,8 +264,6 @@ function findHoistedContextDeclarations(
class Context {
internedEffects: Map<string, AliasingEffect> = new Map();
instructionSignatureCache: Map<Instruction, InstructionSignature> = new Map();
effectInstructionValueCache: Map<AliasingEffect, InstructionValue> =
new Map();
applySignatureCache: Map<
AliasingSignature,
Map<AliasingEffect, Array<AliasingEffect> | null>
@@ -317,10 +315,11 @@ function inferParam(
paramKind: AbstractValue,
): void {
const place = param.kind === 'Identifier' ? param : param.place;
const value: InstructionValue = {
kind: 'Primitive',
loc: place.loc,
value: undefined,
const value: AliasingEffect = {
kind: 'Create',
into: place,
value: paramKind.kind,
reason: ValueReason.Other,
};
initialState.initialize(value, paramKind);
initialState.define(place, value);
@@ -541,20 +540,11 @@ function applyEffect(
});
initialized.add(effect.into.identifier.id);
let value = context.effectInstructionValueCache.get(effect);
if (value == null) {
value = {
kind: 'ObjectExpression',
properties: [],
loc: effect.into.loc,
};
context.effectInstructionValueCache.set(effect, value);
}
state.initialize(value, {
state.initialize(effect, {
kind: effect.value,
reason: new Set([effect.reason]),
});
state.define(effect.into, value);
state.define(effect.into, effect);
effects.push(effect);
break;
}
@@ -581,20 +571,11 @@ function applyEffect(
initialized.add(effect.into.identifier.id);
const fromValue = state.kind(effect.from);
let value = context.effectInstructionValueCache.get(effect);
if (value == null) {
value = {
kind: 'ObjectExpression',
properties: [],
loc: effect.into.loc,
};
context.effectInstructionValueCache.set(effect, value);
}
state.initialize(value, {
state.initialize(effect, {
kind: fromValue.kind,
reason: new Set(fromValue.reason),
});
state.define(effect.into, value);
state.define(effect.into, effect);
switch (fromValue.kind) {
case ValueKind.Primitive:
case ValueKind.Global: {
@@ -682,11 +663,11 @@ function applyEffect(
operand.effect = Effect.Read;
}
}
state.initialize(effect.function, {
state.initialize(effect, {
kind: isMutable ? ValueKind.Mutable : ValueKind.Frozen,
reason: new Set([]),
});
state.define(effect.into, effect.function);
state.define(effect.into, effect);
for (const capture of effect.captures) {
applyEffect(
context,
@@ -792,38 +773,20 @@ function applyEffect(
initialized,
effects,
);
let value = context.effectInstructionValueCache.get(effect);
if (value == null) {
value = {
kind: 'Primitive',
value: undefined,
loc: effect.from.loc,
};
context.effectInstructionValueCache.set(effect, value);
}
state.initialize(value, {
state.initialize(effect, {
kind: fromKind,
reason: new Set(fromValue.reason),
});
state.define(effect.into, value);
state.define(effect.into, effect);
break;
}
case ValueKind.Global:
case ValueKind.Primitive: {
let value = context.effectInstructionValueCache.get(effect);
if (value == null) {
value = {
kind: 'Primitive',
value: undefined,
loc: effect.from.loc,
};
context.effectInstructionValueCache.set(effect, value);
}
state.initialize(value, {
state.initialize(effect, {
kind: fromKind,
reason: new Set(fromValue.reason),
});
state.define(effect.into, value);
state.define(effect.into, effect);
break;
}
default: {
@@ -838,14 +801,15 @@ function applyEffect(
const functionValues = state.values(effect.function);
if (
functionValues.length === 1 &&
functionValues[0].kind === 'FunctionExpression' &&
functionValues[0].loweredFunc.func.aliasingEffects != null
functionValues[0].kind === 'CreateFunction' &&
functionValues[0].function.kind === 'FunctionExpression' &&
functionValues[0].function.loweredFunc.func.aliasingEffects != null
) {
/*
* We're calling a locally declared function, we already know it's effects!
* We just have to substitute in the args for the params
*/
const functionExpr = functionValues[0];
const functionExpr = functionValues[0].function;
let signature = context.functionSignatureCache.get(functionExpr);
if (signature == null) {
signature = buildSignatureFromFunctionExpression(
@@ -1135,19 +1099,19 @@ class InferenceState {
#isFunctionExpression: boolean;
// The kind of each value, based on its allocation site
#values: Map<InstructionValue, AbstractValue>;
#values: Map<AliasingEffect, AbstractValue>;
/*
* The set of values pointed to by each identifier. This is a set
* to accomodate phi points (where a variable may have different
* values from different control flow paths).
*/
#variables: Map<IdentifierId, Set<InstructionValue>>;
#variables: Map<IdentifierId, Set<AliasingEffect>>;
constructor(
env: Environment,
isFunctionExpression: boolean,
values: Map<InstructionValue, AbstractValue>,
variables: Map<IdentifierId, Set<InstructionValue>>,
values: Map<AliasingEffect, AbstractValue>,
variables: Map<IdentifierId, Set<AliasingEffect>>,
) {
this.env = env;
this.#isFunctionExpression = isFunctionExpression;
@@ -1167,18 +1131,11 @@ class InferenceState {
}
// (Re)initializes a @param value with its default @param kind.
initialize(value: InstructionValue, kind: AbstractValue): void {
CompilerError.invariant(value.kind !== 'LoadLocal', {
reason:
'[InferMutationAliasingEffects] Expected all top-level identifiers to be defined as variables, not values',
description: null,
loc: value.loc,
suggestions: null,
});
initialize(value: AliasingEffect, kind: AbstractValue): void {
this.#values.set(value, kind);
}
values(place: Place): Array<InstructionValue> {
values(place: Place): Array<AliasingEffect> {
const values = this.#variables.get(place.identifier.id);
CompilerError.invariant(values != null, {
reason: `[InferMutationAliasingEffects] Expected value kind to be initialized`,
@@ -1241,13 +1198,13 @@ class InferenceState {
}
// Defines (initializing or updating) a variable with a specific kind of value.
define(place: Place, value: InstructionValue): void {
define(place: Place, value: AliasingEffect): void {
CompilerError.invariant(this.#values.has(value), {
reason: `[InferMutationAliasingEffects] Expected value to be initialized at '${printSourceLocation(
value.loc,
place.loc,
)}'`,
description: printInstructionValue(value),
loc: value.loc,
description: printAliasingEffect(value),
loc: place.loc,
suggestions: null,
});
this.#variables.set(place.identifier.id, new Set([value]));
@@ -1287,17 +1244,17 @@ class InferenceState {
}
}
freezeValue(value: InstructionValue, reason: ValueReason): void {
freezeValue(value: AliasingEffect, reason: ValueReason): void {
this.#values.set(value, {
kind: ValueKind.Frozen,
reason: new Set([reason]),
});
if (
value.kind === 'FunctionExpression' &&
value.kind === 'CreateFunction' &&
(this.env.config.enablePreserveExistingMemoizationGuarantees ||
this.env.config.enableTransitivelyFreezeFunctionExpressions)
) {
for (const place of value.loweredFunc.func.context) {
for (const place of value.function.loweredFunc.func.context) {
this.freeze(place, reason);
}
}
@@ -1372,8 +1329,8 @@ class InferenceState {
* termination.
*/
merge(other: InferenceState): InferenceState | null {
let nextValues: Map<InstructionValue, AbstractValue> | null = null;
let nextVariables: Map<IdentifierId, Set<InstructionValue>> | null = null;
let nextValues: Map<AliasingEffect, AbstractValue> | null = null;
let nextVariables: Map<IdentifierId, Set<AliasingEffect>> | null = null;
for (const [id, thisValue] of this.#values) {
const otherValue = other.#values.get(id);
@@ -1397,7 +1354,7 @@ class InferenceState {
for (const [id, thisValues] of this.#variables) {
const otherValues = other.#variables.get(id);
if (otherValues !== undefined) {
let mergedValues: Set<InstructionValue> | null = null;
let mergedValues: Set<AliasingEffect> | null = null;
for (const otherValue of otherValues) {
if (!thisValues.has(otherValue)) {
mergedValues = mergedValues ?? new Set(thisValues);
@@ -1450,8 +1407,8 @@ class InferenceState {
*/
debug(): any {
const result: any = {values: {}, variables: {}};
const objects: Map<InstructionValue, number> = new Map();
function identify(value: InstructionValue): number {
const objects: Map<AliasingEffect, number> = new Map();
function identify(value: AliasingEffect): number {
let id = objects.get(value);
if (id == null) {
id = objects.size;
@@ -1463,7 +1420,7 @@ class InferenceState {
const id = identify(value);
result.values[id] = {
abstract: this.debugAbstractValue(kind),
value: printInstructionValue(value),
value: printAliasingEffect(value),
};
}
for (const [variable, values] of this.#variables) {
@@ -1480,7 +1437,7 @@ class InferenceState {
}
inferPhi(phi: Phi): void {
const values: Set<InstructionValue> = new Set();
const values: Set<AliasingEffect> = new Set();
for (const [_, operand] of phi.operands) {
const operandValues = this.#variables.get(operand.identifier.id);
// This is a backedge that will be handled later by State.merge
@@ -1871,6 +1828,23 @@ function computeSignatureForInstruction(
});
}
}
for (const prop of value.props) {
if (
prop.kind === 'JsxAttribute' &&
prop.place.identifier.type.kind === 'Function' &&
(isJsxType(prop.place.identifier.type.return) ||
(prop.place.identifier.type.return.kind === 'Phi' &&
prop.place.identifier.type.return.operands.some(operand =>
isJsxType(operand),
)))
) {
// Any props which return jsx are assumed to be called during render
effects.push({
kind: 'Render',
place: prop.place,
});
}
}
}
break;
}
@@ -2329,8 +2303,9 @@ function areArgumentsImmutableAndNonMutating(
const values = state.values(place);
for (const value of values) {
if (
value.kind === 'FunctionExpression' &&
value.loweredFunc.func.params.some(param => {
value.kind === 'CreateFunction' &&
value.function.kind === 'FunctionExpression' &&
value.function.loweredFunc.func.params.some(param => {
const place = param.kind === 'Identifier' ? param : param.place;
const range = place.identifier.mutableRange;
return range.end > range.start + 1;
@@ -2523,10 +2498,47 @@ function computeEffectsForSignature(
break;
}
case 'CreateFunction': {
CompilerError.throwTodo({
reason: `Support CreateFrom effects in signatures`,
loc: receiver.loc,
const applyInto = substitutions.get(effect.into.identifier.id);
if (applyInto == null || applyInto.length !== 1) {
return null;
}
const captures: Array<Place> = [];
for (let i = 0; i < effect.captures.length; i++) {
const substitution = substitutions.get(
effect.captures[i].identifier.id,
);
if (substitution == null || substitution.length !== 1) {
return null;
}
captures.push(substitution[0]);
}
const context: Array<Place> = [];
const originalContext = effect.function.loweredFunc.func.context;
for (let i = 0; i < originalContext.length; i++) {
const substitution = substitutions.get(
originalContext[i].identifier.id,
);
if (substitution == null || substitution.length !== 1) {
return null;
}
context.push(substitution[0]);
}
effects.push({
kind: 'CreateFunction',
into: applyInto[0],
function: {
...effect.function,
loweredFunc: {
...effect.function.loweredFunc,
func: {
...effect.function.loweredFunc.func,
context,
},
},
},
captures,
});
break;
}
default: {
assertExhaustive(

View File

@@ -27,7 +27,7 @@ import {
} from '../HIR/visitors';
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
import {Err, Ok, Result} from '../Utils/Result';
import {AliasingEffect} from './AliasingEffects';
import {AliasingEffect, MutationReason} from './AliasingEffects';
/**
* This pass builds an abstract model of the heap and interprets the effects of the
@@ -101,6 +101,7 @@ export function inferMutationAliasingRanges(
transitive: boolean;
kind: MutationKind;
place: Place;
reason: MutationReason | null;
}> = [];
const renders: Array<{index: number; place: Place}> = [];
@@ -140,7 +141,7 @@ export function inferMutationAliasingRanges(
} else if (effect.kind === 'CreateFunction') {
state.create(effect.into, {
kind: 'Function',
function: effect.function.loweredFunc.func,
effect,
});
} else if (effect.kind === 'CreateFrom') {
state.createFrom(index++, effect.from, effect.into);
@@ -155,7 +156,7 @@ export function inferMutationAliasingRanges(
* invariant here.
*/
if (!state.nodes.has(effect.into.identifier)) {
state.create(effect.into, {kind: 'Object'});
state.create(effect.into, {kind: 'Assign'});
}
state.assign(index++, effect.from, effect.into);
} else if (effect.kind === 'Alias') {
@@ -176,6 +177,7 @@ export function inferMutationAliasingRanges(
effect.kind === 'MutateTransitive'
? MutationKind.Definite
: MutationKind.Conditional,
reason: null,
place: effect.value,
});
} else if (
@@ -190,6 +192,7 @@ export function inferMutationAliasingRanges(
effect.kind === 'Mutate'
? MutationKind.Definite
: MutationKind.Conditional,
reason: effect.kind === 'Mutate' ? (effect.reason ?? null) : null,
place: effect.value,
});
} else if (
@@ -241,6 +244,7 @@ export function inferMutationAliasingRanges(
mutation.transitive,
mutation.kind,
mutation.place.loc,
mutation.reason,
errors,
);
}
@@ -267,6 +271,7 @@ export function inferMutationAliasingRanges(
functionEffects.push({
kind: 'Mutate',
value: {...place, loc: node.local.loc},
reason: node.mutationReason,
});
}
}
@@ -469,6 +474,99 @@ export function inferMutationAliasingRanges(
}
}
const tracked: Array<Place> = [];
for (const param of [...fn.params, ...fn.context, fn.returns]) {
const place = param.kind === 'Identifier' ? param : param.place;
tracked.push(place);
}
const returned: Set<Node> = new Set();
const queue: Array<Node> = [state.nodes.get(fn.returns.identifier)!];
const seen: Set<Node> = new Set();
while (queue.length !== 0) {
const node = queue.pop()!;
if (seen.has(node)) {
continue;
}
seen.add(node);
for (const id of node.aliases.keys()) {
queue.push(state.nodes.get(id)!);
}
for (const id of node.createdFrom.keys()) {
queue.push(state.nodes.get(id)!);
}
if (node.id.id === fn.returns.identifier.id) {
continue;
}
switch (node.value.kind) {
case 'Assign':
case 'CreateFrom': {
break;
}
case 'Phi':
case 'Object':
case 'Function': {
returned.add(node);
break;
}
default: {
assertExhaustive(
node.value,
`Unexpected node value kind '${(node.value as any).kind}'`,
);
}
}
}
const returnedValues = [...returned];
if (
returnedValues.length === 1 &&
returnedValues[0].value.kind === 'Object' &&
tracked.some(place => place.identifier.id === returnedValues[0].id.id)
) {
const from = tracked.find(
place => place.identifier.id === returnedValues[0].id.id,
)!;
functionEffects.push({
kind: 'Assign',
from,
into: fn.returns,
});
} else if (
returnedValues.length === 1 &&
returnedValues[0].value.kind === 'Function'
) {
const outerContext = new Set(fn.context.map(p => p.identifier.id));
const effect = returnedValues[0].value.effect;
functionEffects.push({
kind: 'CreateFunction',
function: {
...effect.function,
loweredFunc: {
func: {
...effect.function.loweredFunc.func,
context: effect.function.loweredFunc.func.context.filter(p =>
outerContext.has(p.identifier.id),
),
},
},
},
captures: effect.captures.filter(p => outerContext.has(p.identifier.id)),
into: fn.returns,
});
} else {
const returns = fn.returns.identifier;
functionEffects.push({
kind: 'Create',
into: fn.returns,
value: isPrimitiveType(returns)
? ValueKind.Primitive
: isJsxType(returns.type)
? ValueKind.Frozen
: ValueKind.Mutable,
reason: ValueReason.KnownReturnSignature,
});
}
/**
* Part 3
* Finish populating the externally visible effects. Above we bubble-up the side effects
@@ -476,28 +574,12 @@ export function inferMutationAliasingRanges(
* Here we populate an effect to create the return value as well as populating alias/capture
* effects for how data flows between the params, context vars, and return.
*/
const returns = fn.returns.identifier;
functionEffects.push({
kind: 'Create',
into: fn.returns,
value: isPrimitiveType(returns)
? ValueKind.Primitive
: isJsxType(returns.type)
? ValueKind.Frozen
: ValueKind.Mutable,
reason: ValueReason.KnownReturnSignature,
});
/**
* Determine precise data-flow effects by simulating transitive mutations of the params/
* captures and seeing what other params/context variables are affected. Anything that
* would be transitively mutated needs a capture relationship.
*/
const tracked: Array<Place> = [];
const ignoredErrors = new CompilerError();
for (const param of [...fn.params, ...fn.context, fn.returns]) {
const place = param.kind === 'Identifier' ? param : param.place;
tracked.push(place);
}
for (const into of tracked) {
const mutationIndex = index++;
state.mutate(
@@ -507,6 +589,7 @@ export function inferMutationAliasingRanges(
true,
MutationKind.Conditional,
into.loc,
null,
ignoredErrors,
);
for (const from of tracked) {
@@ -580,10 +663,16 @@ type Node = {
transitive: {kind: MutationKind; loc: SourceLocation} | null;
local: {kind: MutationKind; loc: SourceLocation} | null;
lastMutated: number;
mutationReason: MutationReason | null;
value:
| {kind: 'Assign'}
| {kind: 'CreateFrom'}
| {kind: 'Object'}
| {kind: 'Phi'}
| {kind: 'Function'; function: HIRFunction};
| {
kind: 'Function';
effect: Extract<AliasingEffect, {kind: 'CreateFunction'}>;
};
};
class AliasingState {
nodes: Map<Identifier, Node> = new Map();
@@ -599,12 +688,13 @@ class AliasingState {
transitive: null,
local: null,
lastMutated: 0,
mutationReason: null,
value,
});
}
createFrom(index: number, from: Place, into: Place): void {
this.create(into, {kind: 'Object'});
this.create(into, {kind: 'CreateFrom'});
const fromNode = this.nodes.get(from.identifier);
const toNode = this.nodes.get(into.identifier);
if (fromNode == null || toNode == null) {
@@ -666,7 +756,10 @@ class AliasingState {
continue;
}
if (node.value.kind === 'Function') {
appendFunctionErrors(errors, node.value.function);
appendFunctionErrors(
errors,
node.value.effect.function.loweredFunc.func,
);
}
for (const [alias, when] of node.createdFrom) {
if (when >= index) {
@@ -697,6 +790,7 @@ class AliasingState {
transitive: boolean,
startKind: MutationKind,
loc: SourceLocation,
reason: MutationReason | null,
errors: CompilerError,
): void {
const seen = new Map<Identifier, MutationKind>();
@@ -717,6 +811,7 @@ class AliasingState {
if (node == null) {
continue;
}
node.mutationReason ??= reason;
node.lastMutated = Math.max(node.lastMutated, index);
if (end != null) {
node.id.mutableRange.end = makeInstructionId(
@@ -728,7 +823,10 @@ class AliasingState {
node.transitive == null &&
node.local == null
) {
appendFunctionErrors(errors, node.value.function);
appendFunctionErrors(
errors,
node.value.effect.function.loweredFunc.func,
);
}
if (transitive) {
if (node.transitive == null || node.transitive.kind < kind) {

View File

@@ -777,6 +777,15 @@ class Unifier {
return {kind: 'Phi', operands: type.operands.map(o => this.get(o))};
}
if (type.kind === 'Function') {
return {
kind: 'Function',
isConstructor: type.isConstructor,
shapeId: type.shapeId,
return: this.get(type.return),
};
}
return type;
}
}

View File

@@ -182,6 +182,11 @@ export function parseConfigPragmaForTests(
environment?: PartialEnvironmentConfig;
},
): PluginOptions {
const overridePragma = parseConfigPragmaAsString(pragma);
if (overridePragma !== '') {
return parseConfigStringAsJS(overridePragma, defaults);
}
const environment = parseConfigPragmaEnvironmentForTest(
pragma,
defaults.environment ?? {},
@@ -217,3 +222,104 @@ export function parseConfigPragmaForTests(
}
return parsePluginOptions(options);
}
export function parseConfigPragmaAsString(pragma: string): string {
// Check if it's in JS override format
for (const {key, value: val} of splitPragma(pragma)) {
if (key === 'OVERRIDE' && val != null) {
return val;
}
}
return '';
}
function parseConfigStringAsJS(
configString: string,
defaults: {
compilationMode: CompilationMode;
environment?: PartialEnvironmentConfig;
},
): PluginOptions {
let parsedConfig: any;
try {
// Parse the JavaScript object literal
parsedConfig = new Function(`return ${configString}`)();
} catch (error) {
CompilerError.invariant(false, {
reason: 'Failed to parse config pragma as JavaScript object',
description: `Could not parse: ${configString}. Error: ${error}`,
loc: null,
suggestions: null,
});
}
console.log('OVERRIDE:', parsedConfig);
const options: Record<keyof PluginOptions, unknown> = {
...defaultOptions,
panicThreshold: 'all_errors',
compilationMode: defaults.compilationMode,
environment: defaults.environment ?? defaultOptions.environment,
};
// Apply parsed config, merging environment if it exists
if (parsedConfig.environment) {
const mergedEnvironment = {
...(options.environment as Record<string, unknown>),
...parsedConfig.environment,
};
// Apply complex defaults for environment flags that are set to true
const environmentConfig: Partial<Record<keyof EnvironmentConfig, unknown>> =
{};
for (const [key, value] of Object.entries(mergedEnvironment)) {
if (hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
if (value === true && key in testComplexConfigDefaults) {
environmentConfig[key] = testComplexConfigDefaults[key];
} else {
environmentConfig[key] = value;
}
}
}
// Validate environment config
const validatedEnvironment =
EnvironmentConfigSchema.safeParse(environmentConfig);
if (!validatedEnvironment.success) {
CompilerError.invariant(false, {
reason: 'Invalid environment configuration in config pragma',
description: `${fromZodError(validatedEnvironment.error)}`,
loc: null,
suggestions: null,
});
}
if (validatedEnvironment.data.enableResetCacheOnSourceFileChanges == null) {
validatedEnvironment.data.enableResetCacheOnSourceFileChanges = false;
}
options.environment = validatedEnvironment.data;
}
// Apply other config options
for (const [key, value] of Object.entries(parsedConfig)) {
if (key === 'environment') {
continue;
}
if (hasOwnProperty(defaultOptions, key)) {
if (value === true && key in testComplexPluginOptionDefaults) {
options[key] = testComplexPluginOptionDefaults[key];
} else if (key === 'target' && value === 'donotuse_meta_internal') {
options[key] = {
kind: value,
runtimeModule: 'react',
};
} else {
options[key] = value;
}
}
}
return parsePluginOptions(options);
}

View File

@@ -7,8 +7,8 @@
import * as t from '@babel/types';
import {
CompilerDiagnostic,
CompilerError,
CompilerErrorDetail,
ErrorCategory,
ErrorSeverity,
} from '../CompilerError';
@@ -95,16 +95,16 @@ export function validateHooksUsage(
const unconditionalBlocks = computeUnconditionalBlocks(fn);
const errors = new CompilerError();
const errorsByPlace = new Map<t.SourceLocation, CompilerDiagnostic>();
const errorsByPlace = new Map<t.SourceLocation, CompilerErrorDetail>();
function recordError(
loc: SourceLocation,
diagnostic: CompilerDiagnostic,
errorDetail: CompilerErrorDetail,
): void {
if (typeof loc === 'symbol') {
errors.pushDiagnostic(diagnostic);
errors.pushErrorDetail(errorDetail);
} else {
errorsByPlace.set(loc, diagnostic);
errorsByPlace.set(loc, errorDetail);
}
}
@@ -112,7 +112,7 @@ export function validateHooksUsage(
// Once a particular hook has a conditional call error, don't report any further issues for this hook
setKind(place, Kind.Error);
const description =
const reason =
'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)';
const previousError =
typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined;
@@ -121,19 +121,16 @@ export function validateHooksUsage(
* In some circumstances such as optional calls, we may first encounter a "hook may not be referenced as normal values" error.
* If that same place is also used as a conditional call, upgrade the error to a conditonal hook error
*/
if (previousError === undefined || previousError.reason !== description) {
if (previousError === undefined || previousError.reason !== reason) {
recordError(
place.loc,
CompilerDiagnostic.create({
new CompilerErrorDetail({
category: ErrorCategory.Hooks,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot call hooks conditionally',
description,
suggestions: null,
}).withDetail({
kind: 'error',
description: null,
reason,
loc: place.loc,
message: 'Cannot call hook conditionally',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}),
);
}
@@ -144,17 +141,14 @@ export function validateHooksUsage(
if (previousError === undefined) {
recordError(
place.loc,
CompilerDiagnostic.create({
new CompilerErrorDetail({
category: ErrorCategory.Hooks,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot reference hooks as regular values',
description:
description: null,
reason:
'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values',
suggestions: null,
}).withDetail({
kind: 'error',
loc: place.loc,
message: 'Hooks may not be referenced as values',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}),
);
}
@@ -165,17 +159,14 @@ export function validateHooksUsage(
if (previousError === undefined) {
recordError(
place.loc,
CompilerDiagnostic.create({
new CompilerErrorDetail({
category: ErrorCategory.Hooks,
severity: ErrorSeverity.InvalidReact,
reason: 'Hooks must be the same on every render',
description:
description: null,
reason:
'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks',
suggestions: null,
}).withDetail({
kind: 'error',
loc: place.loc,
message: 'This value may change to a different function',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}),
);
}
@@ -436,8 +427,8 @@ export function validateHooksUsage(
}
}
for (const [, diagnostic] of errorsByPlace) {
errors.pushDiagnostic(diagnostic);
for (const [, error] of errorsByPlace) {
errors.pushErrorDetail(error);
}
return errors.asResult();
}
@@ -459,18 +450,15 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
: instr.value.property;
const hookKind = getHookKind(fn.env, callee.identifier);
if (hookKind != null) {
errors.pushDiagnostic(
CompilerDiagnostic.create({
errors.pushErrorDetail(
new CompilerErrorDetail({
category: ErrorCategory.Hooks,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot call hooks within function expressions',
description:
reason:
'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)',
suggestions: null,
}).withDetail({
kind: 'error',
loc: callee.loc,
message: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`,
description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`,
suggestions: null,
}),
);
}

View File

@@ -24,9 +24,18 @@ function useThing(fn) {
```
Found 1 error:
Error: Expected a non-reserved identifier name
Error: `this` is not supported syntax
`this` is a reserved word in JavaScript and cannot be used as an identifier name.
React Compiler does not support compiling functions that use `this`
error.reserved-words.ts:8:28
6 |
7 | if (ref.current === null) {
> 8 | ref.current = function (this: unknown, ...args) {
| ^^^^^^^^^^^^^ `this` was used here
9 | return fnRef.current.call(this, ...args);
10 | };
11 | }
```

View File

@@ -0,0 +1,37 @@
## Input
```javascript
// Fixture to test that we show a hint to name as `ref` or `-Ref` when attempting
// to assign .current inside an effect
function Component({foo}) {
useEffect(() => {
foo.current = true;
}, [foo]);
}
```
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
error.assign-ref-in-effect-hint.ts:5:4
3 | function Component({foo}) {
4 | useEffect(() => {
> 5 | foo.current = true;
| ^^^ `foo` cannot be modified
6 | }, [foo]);
7 | }
8 |
Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in "Ref".
```

View File

@@ -0,0 +1,7 @@
// Fixture to test that we show a hint to name as `ref` or `-Ref` when attempting
// to assign .current inside an effect
function Component({foo}) {
useEffect(() => {
foo.current = true;
}, [foo]);
}

View File

@@ -18,15 +18,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.conditional-hook-unknown-hook-react-namespace.ts:4:8
2 | let x = null;
3 | if (props.cond) {
> 4 | x = React.useNonexistentHook();
| ^^^^^^^^^^^^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
5 | }
6 | return x;
7 | }

View File

@@ -18,15 +18,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.conditional-hooks-as-method-call.ts:4:8
2 | let x = null;
3 | if (props.cond) {
> 4 | x = Foo.useFoo();
| ^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
5 | }
6 | return x;
7 | }

View File

@@ -25,28 +25,24 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 2 errors:
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.hook-property-load-local-hook.ts:7:12
5 |
6 | function Foo() {
> 7 | let bar = useFoo.useBar;
| ^^^^^^^^^^^^^ Hooks may not be referenced as values
| ^^^^^^^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
8 | return bar();
9 | }
10 |
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.hook-property-load-local-hook.ts:8:9
6 | function Foo() {
7 | let bar = useFoo.useBar;
> 8 | return bar();
| ^^^ Hooks may not be referenced as values
| ^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
9 | }
10 |
11 | export const FIXTURE_ENTRYPOINT = {

View File

@@ -0,0 +1,7 @@
function Component() {
const f = () => () => {
global.property = true;
};
f()();
return <div>Ooops</div>;
}

View File

@@ -16,14 +16,12 @@ function Component(props) {
```
Found 1 error:
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-assign-hook-to-local.ts:2:12
1 | function Component(props) {
> 2 | const x = useState;
| ^^^^^^^^ Hooks may not be referenced as values
| ^^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | const state = x(null);
4 | return state[0];
5 | }

View File

@@ -20,15 +20,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-conditional-call-aliased-hook-import.ts:6:11
4 | let data;
5 | if (props.cond) {
> 6 | data = readFragment();
| ^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | return data;
9 | }

View File

@@ -20,15 +20,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-conditional-call-aliased-react-hook.ts:6:10
4 | let s;
5 | if (props.cond) {
> 6 | [s] = state();
| ^^^^^ Cannot call hook conditionally
| ^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | return s;
9 | }

View File

@@ -20,15 +20,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-conditional-call-non-hook-imported-as-hook.ts:6:11
4 | let data;
5 | if (props.cond) {
> 6 | data = useArray();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | return data;
9 | }

View File

@@ -38,6 +38,8 @@ error.invalid-mutate-context-in-callback.ts:12:4
13 | };
14 | return <div onClick={onClick} />;
15 | }
Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in "Ref".
```

View File

@@ -0,0 +1,44 @@
## Input
```javascript
function Component() {
const renderItem = item => {
// Multiple returns so that the return type is a Phi (union)
if (item == null) {
return null;
}
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}
```
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
error.invalid-mutate-global-in-render-helper-phi-return-prop.ts:12:4
10 | // called during render, and thus it's unsafe to mutate globals or call
11 | // other impure code.
> 12 | global.property = true;
| ^^^^^^ value cannot be modified
13 | return <Item item={item} value={rand} />;
14 | };
15 | return <ItemList renderItem={renderItem} />;
```

View File

@@ -0,0 +1,16 @@
function Component() {
const renderItem = item => {
// Multiple returns so that the return type is a Phi (union)
if (item == null) {
return null;
}
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}

View File

@@ -0,0 +1,40 @@
## Input
```javascript
function Component() {
const renderItem = item => {
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}
```
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
error.invalid-mutate-global-in-render-helper-prop.ts:8:4
6 | // called during render, and thus it's unsafe to mutate globals or call
7 | // other impure code.
> 8 | global.property = true;
| ^^^^^^ value cannot be modified
9 | return <Item item={item} value={rand} />;
10 | };
11 | return <ItemList renderItem={renderItem} />;
```

View File

@@ -0,0 +1,12 @@
function Component() {
const renderItem = item => {
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}

View File

@@ -14,14 +14,12 @@ function Component(props) {
```
Found 1 error:
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-pass-hook-as-call-arg.ts:2:13
1 | function Component(props) {
> 2 | return foo(useFoo);
| ^^^^^^ Hooks may not be referenced as values
| ^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | }
4 |
```

View File

@@ -14,14 +14,12 @@ function Component(props) {
```
Found 1 error:
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-pass-hook-as-prop.ts:2:21
1 | function Component(props) {
> 2 | return <Child foo={useFoo} />;
| ^^^^^^ Hooks may not be referenced as values
| ^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | }
4 |
```

View File

@@ -15,51 +15,43 @@ function Component(props) {
```
Found 4 errors:
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-ternary-with-hook-values.ts:2:25
1 | function Component(props) {
> 2 | const x = props.cond ? useA : useB;
| ^^^^ Hooks may not be referenced as values
| ^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | return x();
4 | }
5 |
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-ternary-with-hook-values.ts:2:32
1 | function Component(props) {
> 2 | const x = props.cond ? useA : useB;
| ^^^^ Hooks may not be referenced as values
| ^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | return x();
4 | }
5 |
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-ternary-with-hook-values.ts:2:12
1 | function Component(props) {
> 2 | const x = props.cond ? useA : useB;
| ^^^^^^^^^^^^^^^^^^^^^^^^ Hooks may not be referenced as values
| ^^^^^^^^^^^^^^^^^^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | return x();
4 | }
5 |
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-ternary-with-hook-values.ts:3:9
1 | function Component(props) {
2 | const x = props.cond ? useA : useB;
> 3 | return x();
| ^ Hooks may not be referenced as values
| ^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
4 | }
5 |
```

View File

@@ -0,0 +1,54 @@
## Input
```javascript
// @validateNoDynamicallyCreatedComponentsOrHooks
export function getInput(a) {
const Wrapper = () => {
const handleChange = () => {
a.onChange();
};
return <input onChange={handleChange} />;
};
return Wrapper;
}
export const FIXTURE_ENTRYPOINT = {
fn: getInput,
isComponent: false,
params: [{onChange() {}}],
};
```
## Error
```
Found 1 error:
Error: Components and hooks cannot be created dynamically
The function `Wrapper` appears to be a React component, but it's defined inside `getInput`. Components and Hooks should always be declared at module scope
error.nested-component-in-normal-function.ts:2:16
1 | // @validateNoDynamicallyCreatedComponentsOrHooks
> 2 | export function getInput(a) {
| ^^^^^^^^ this function dynamically created a component/hook
3 | const Wrapper = () => {
4 | const handleChange = () => {
5 | a.onChange();
error.nested-component-in-normal-function.ts:3:8
1 | // @validateNoDynamicallyCreatedComponentsOrHooks
2 | export function getInput(a) {
> 3 | const Wrapper = () => {
| ^^^^^^^ the component is created here
4 | const handleChange = () => {
5 | a.onChange();
6 | };
```

View File

@@ -0,0 +1,18 @@
// @validateNoDynamicallyCreatedComponentsOrHooks
export function getInput(a) {
const Wrapper = () => {
const handleChange = () => {
a.onChange();
};
return <input onChange={handleChange} />;
};
return Wrapper;
}
export const FIXTURE_ENTRYPOINT = {
fn: getInput,
isComponent: false,
params: [{onChange() {}}],
};

View File

@@ -0,0 +1,59 @@
## Input
```javascript
// @validateNoDynamicallyCreatedComponentsOrHooks
import {useState} from 'react';
function createCustomHook(config) {
function useConfiguredState() {
const [state, setState] = useState(0);
const increment = () => {
setState(state + config.step);
};
return [state, increment];
}
return useConfiguredState;
}
export const FIXTURE_ENTRYPOINT = {
fn: createCustomHook,
isComponent: false,
params: [{step: 1}],
};
```
## Error
```
Found 1 error:
Error: Components and hooks cannot be created dynamically
The function `useConfiguredState` appears to be a React hook, but it's defined inside `createCustomHook`. Components and Hooks should always be declared at module scope
error.nested-hook-in-normal-function.ts:4:9
2 | import {useState} from 'react';
3 |
> 4 | function createCustomHook(config) {
| ^^^^^^^^^^^^^^^^ this function dynamically created a component/hook
5 | function useConfiguredState() {
6 | const [state, setState] = useState(0);
7 |
error.nested-hook-in-normal-function.ts:5:11
3 |
4 | function createCustomHook(config) {
> 5 | function useConfiguredState() {
| ^^^^^^^^^^^^^^^^^^ the component is created here
6 | const [state, setState] = useState(0);
7 |
8 | const increment = () => {
```

View File

@@ -0,0 +1,22 @@
// @validateNoDynamicallyCreatedComponentsOrHooks
import {useState} from 'react';
function createCustomHook(config) {
function useConfiguredState() {
const [state, setState] = useState(0);
const increment = () => {
setState(state + config.step);
};
return [state, increment];
}
return useConfiguredState;
}
export const FIXTURE_ENTRYPOINT = {
fn: createCustomHook,
isComponent: false,
params: [{step: 1}],
};

View File

@@ -15,27 +15,23 @@ function Component() {
```
Found 2 errors:
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.propertyload-hook.ts:2:12
1 | function Component() {
> 2 | const x = Foo.useFoo;
| ^^^^^^^^^^ Hooks may not be referenced as values
| ^^^^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | return x();
4 | }
5 |
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.propertyload-hook.ts:3:9
1 | function Component() {
2 | const x = Foo.useFoo;
> 3 | return x();
| ^ Hooks may not be referenced as values
| ^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
4 | }
5 |
```

View File

@@ -55,7 +55,7 @@ import { makeArray, Stringify, useIdentity } from "shared-runtime";
*/
function Foo(t0) {
"use memo";
const $ = _c(3);
const $ = _c(5);
const { b } = t0;
const fnFactory = () => () => {
@@ -66,18 +66,26 @@ function Foo(t0) {
useIdentity();
const fn = fnFactory();
const arr = makeArray(b);
fn(arr);
let t1;
if ($[0] !== arr || $[1] !== myVar) {
t1 = <Stringify cb={myVar} value={arr} shouldInvokeFns={true} />;
$[0] = arr;
$[1] = myVar;
$[2] = t1;
if ($[0] !== b) {
t1 = makeArray(b);
$[0] = b;
$[1] = t1;
} else {
t1 = $[2];
t1 = $[1];
}
return t1;
const arr = t1;
fn(arr);
let t2;
if ($[2] !== arr || $[3] !== myVar) {
t2 = <Stringify cb={myVar} value={arr} shouldInvokeFns={true} />;
$[2] = arr;
$[3] = myVar;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
}
function _temp2() {
return console.log("b");

View File

@@ -22,15 +22,15 @@ const ComponentWithHookInsideCallback = React.forwardRef((props, ref) => {
```
Found 1 error:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call hook within a function expression.
error.bail.rules-of-hooks-3d692676194b.ts:8:4
6 | const ComponentWithHookInsideCallback = React.forwardRef((props, ref) => {
7 | useEffect(() => {
> 8 | useHookInsideCallback();
| ^^^^^^^^^^^^^^^^^^^^^ Cannot call hook within a function expression
| ^^^^^^^^^^^^^^^^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
9 | });
10 | return <button {...props} ref={ref} />;
11 | });

View File

@@ -22,15 +22,15 @@ const ComponentWithHookInsideCallback = React.memo(props => {
```
Found 1 error:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call hook within a function expression.
error.bail.rules-of-hooks-8503ca76d6f8.ts:8:4
6 | const ComponentWithHookInsideCallback = React.memo(props => {
7 | useEffect(() => {
> 8 | useHookInsideCallback();
| ^^^^^^^^^^^^^^^^^^^^^ Cannot call hook within a function expression
| ^^^^^^^^^^^^^^^^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
9 | });
10 | return <button {...props} />;
11 | });

View File

@@ -20,41 +20,35 @@ function Component(props) {
```
Found 3 errors:
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-call-phi-possibly-hook.ts:3:31
1 | function Component(props) {
2 | // This is a violation of using a hook as a normal value rule:
> 3 | const getUser = props.cond ? useGetUser : emptyFunction;
| ^^^^^^^^^^ Hooks may not be referenced as values
| ^^^^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
4 |
5 | // Ideally we would report a "conditional hook call" error here.
6 | // It's an unconditional call, but the value may or may not be a hook.
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-call-phi-possibly-hook.ts:3:18
1 | function Component(props) {
2 | // This is a violation of using a hook as a normal value rule:
> 3 | const getUser = props.cond ? useGetUser : emptyFunction;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Hooks may not be referenced as values
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
4 |
5 | // Ideally we would report a "conditional hook call" error here.
6 | // It's an unconditional call, but the value may or may not be a hook.
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-call-phi-possibly-hook.ts:8:9
6 | // It's an unconditional call, but the value may or may not be a hook.
7 | // TODO: report a conditional hook call error here
> 8 | return getUser();
| ^^^^^^^ Hooks may not be referenced as values
| ^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
9 | }
10 |
```

View File

@@ -19,15 +19,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-conditionally-call-local-named-like-hook.ts:6:4
4 | const useFoo = makeObject_Primitives();
5 | if (props.cond) {
> 6 | useFoo();
| ^^^^^^ Cannot call hook conditionally
| ^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | }
9 |

View File

@@ -16,15 +16,13 @@ function Component({cond, useFoo}) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-conditionally-call-prop-named-like-hook.ts:3:4
1 | function Component({cond, useFoo}) {
2 | if (cond) {
> 3 | useFoo();
| ^^^^^^ Cannot call hook conditionally
| ^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
4 | }
5 | }
6 |

View File

@@ -19,15 +19,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-conditionally-methodcall-hooklike-property-of-local.ts:6:4
4 | const local = makeObject_Primitives();
5 | if (props.cond) {
> 6 | local.useFoo();
| ^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | }
9 |

View File

@@ -20,15 +20,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-condtionally-call-hooklike-property-of-local.ts:7:4
5 | if (props.cond) {
6 | const foo = local.useFoo;
> 7 | foo();
| ^^^ Cannot call hook conditionally
| ^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
8 | }
9 | }
10 |

View File

@@ -16,15 +16,13 @@ function Component() {
```
Found 1 error:
Error: Hooks must be the same on every render
Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
Error: Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
error.invalid-dynamic-hook-via-hooklike-local.ts:4:2
2 | const someFunction = useContext(FooContext);
3 | const useOhItsNamedLikeAHookNow = someFunction;
> 4 | useOhItsNamedLikeAHookNow();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ This value may change to a different function
| ^^^^^^^^^^^^^^^^^^^^^^^^^ Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
5 | }
6 |
```

View File

@@ -17,15 +17,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-hook-after-early-return.ts:5:9
3 | return null;
4 | }
> 5 | return useHook();
| ^^^^^^^ Cannot call hook conditionally
| ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
6 | }
7 |
```

View File

@@ -15,14 +15,12 @@ function Component(props) {
```
Found 1 error:
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-hook-as-conditional-test.ts:2:26
1 | function Component(props) {
> 2 | const x = props.cond ? (useFoo ? 1 : 2) : 3;
| ^^^^^^ Hooks may not be referenced as values
| ^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | return x;
4 | }
5 |

View File

@@ -14,14 +14,12 @@ function Component({useFoo}) {
```
Found 1 error:
Error: Hooks must be the same on every render
Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
Error: Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
error.invalid-hook-as-prop.ts:2:2
1 | function Component({useFoo}) {
> 2 | useFoo();
| ^^^^^^ This value may change to a different function
| ^^^^^^ Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
3 | }
4 |
```

View File

@@ -18,28 +18,24 @@ function Component(props) {
```
Found 2 errors:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-hook-for.ts:4:9
2 | let i = 0;
3 | for (let x = 0; useHook(x) < 10; useHook(i), x++) {
> 4 | i += useHook(x);
| ^^^^^^^ Cannot call hook conditionally
| ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
5 | }
6 | return i;
7 | }
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-hook-for.ts:3:35
1 | function Component(props) {
2 | let i = 0;
> 3 | for (let x = 0; useHook(x) < 10; useHook(i), x++) {
| ^^^^^^^ Cannot call hook conditionally
| ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
4 | i += useHook(x);
5 | }
6 | return i;

View File

@@ -16,15 +16,13 @@ function useFoo({data}) {
```
Found 1 error:
Error: Hooks must be the same on every render
Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
Error: Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
error.invalid-hook-from-hook-return.ts:3:14
1 | function useFoo({data}) {
2 | const useMedia = useVideoPlayer();
> 3 | const foo = useMedia();
| ^^^^^^^^ This value may change to a different function
| ^^^^^^^^ Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
4 | return foo;
5 | }
6 |

View File

@@ -16,15 +16,13 @@ function useFoo({data}) {
```
Found 1 error:
Error: Hooks must be the same on every render
Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
Error: Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
error.invalid-hook-from-property-of-other-hook.ts:3:14
1 | function useFoo({data}) {
2 | const player = useVideoPlayer();
> 3 | const foo = player.useMedia();
| ^^^^^^^^^^^^^^^ This value may change to a different function
| ^^^^^^^^^^^^^^^ Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
4 | return foo;
5 | }
6 |

View File

@@ -19,15 +19,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-hook-if-alternate.ts:5:8
3 | if (props.cond) {
4 | } else {
> 5 | x = useHook();
| ^^^^^^^ Cannot call hook conditionally
| ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
6 | }
7 | return x;
8 | }

View File

@@ -18,15 +18,13 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-hook-if-consequent.ts:4:8
2 | let x = null;
3 | if (props.cond) {
> 4 | x = useHook();
| ^^^^^^^ Cannot call hook conditionally
| ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
5 | }
6 | return x;
7 | }

View File

@@ -30,15 +30,15 @@ function Component() {
```
Found 1 error:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call hook within a function expression.
error.invalid-hook-in-nested-function-expression-object-expression.ts:10:21
8 | const y = {
9 | inner() {
> 10 | return useFoo();
| ^^^^^^ Cannot call hook within a function expression
| ^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
11 | },
12 | };
13 | return y;

View File

@@ -26,15 +26,15 @@ function Component() {
```
Found 1 error:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call hook within a function expression.
error.invalid-hook-in-nested-object-method.ts:8:17
6 | const y = {
7 | inner() {
> 8 | return useFoo();
| ^^^^^^ Cannot call hook within a function expression
| ^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
9 | },
10 | };
11 | return y;

View File

@@ -15,14 +15,12 @@ function Component() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-hook-optional-methodcall.ts:2:19
1 | function Component() {
> 2 | const {result} = Module.useConditionalHook?.() ?? {};
| ^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
3 | return result;
4 | }
5 |

View File

@@ -15,14 +15,12 @@ function Component() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-hook-optional-property.ts:2:19
1 | function Component() {
> 2 | const {result} = Module?.useConditionalHook() ?? {};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
3 | return result;
4 | }
5 |

View File

@@ -15,14 +15,12 @@ function Component() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-hook-optionalcall.ts:2:19
1 | function Component() {
> 2 | const {result} = useConditionalHook?.() ?? {};
| ^^^^^^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
3 | return result;
4 | }
5 |

View File

@@ -16,41 +16,35 @@ function Component(props) {
```
Found 3 errors:
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-hook-reassigned-in-conditional.ts:3:20
1 | function Component(props) {
2 | let y;
> 3 | props.cond ? (y = useFoo) : null;
| ^^^^^^ Hooks may not be referenced as values
| ^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
4 | return y();
5 | }
6 |
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-hook-reassigned-in-conditional.ts:3:16
1 | function Component(props) {
2 | let y;
> 3 | props.cond ? (y = useFoo) : null;
| ^ Hooks may not be referenced as values
| ^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
4 | return y();
5 | }
6 |
Error: Cannot reference hooks as regular values
Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-hook-reassigned-in-conditional.ts:4:9
2 | let y;
3 | props.cond ? (y = useFoo) : null;
> 4 | return y();
| ^ Hooks may not be referenced as values
| ^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
5 | }
6 |
```

View File

@@ -27,54 +27,46 @@ function useHookInLoops() {
```
Found 4 errors:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-1b9527f967f3.ts:7:4
5 | function useHookInLoops() {
6 | while (a) {
> 7 | useHook1();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
8 | if (b) return;
9 | useHook2();
10 | }
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-1b9527f967f3.ts:9:4
7 | useHook1();
8 | if (b) return;
> 9 | useHook2();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
10 | }
11 | while (c) {
12 | useHook3();
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-1b9527f967f3.ts:12:4
10 | }
11 | while (c) {
> 12 | useHook3();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
13 | if (d) return;
14 | useHook4();
15 | }
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-1b9527f967f3.ts:14:4
12 | useHook3();
13 | if (d) return;
> 14 | useHook4();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
15 | }
16 | }
17 |

View File

@@ -20,15 +20,13 @@ function ComponentWithConditionalHook() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-2aabd222fc6a.ts:7:4
5 | function ComponentWithConditionalHook() {
6 | if (cond) {
> 7 | useConditionalHook();
| ^^^^^^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
8 | }
9 | }
10 |

View File

@@ -21,15 +21,13 @@ function useLabeledBlock() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-49d341e5d68f.ts:8:4
6 | label: {
7 | if (a) break label;
> 8 | useHook();
| ^^^^^^^ Cannot call hook conditionally
| ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
9 | }
10 | }
11 |

View File

@@ -20,15 +20,13 @@ function ComponentWithHookInsideLoop() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-79128a755612.ts:7:4
5 | function ComponentWithHookInsideLoop() {
6 | while (cond) {
> 7 | useHookInsideLoop();
| ^^^^^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
8 | }
9 | }
10 |

View File

@@ -24,15 +24,13 @@ function useHook() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-9718e30b856c.ts:12:2
10 | console.log('false');
11 | }
> 12 | useState();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
13 | }
14 |
```

View File

@@ -19,28 +19,24 @@ function useHook() {
```
Found 2 errors:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-9bf17c174134.ts:6:7
4 | // This *must* be invalid.
5 | function useHook() {
> 6 | a && useHook1();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | b && useHook2();
8 | }
9 |
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-9bf17c174134.ts:7:7
5 | function useHook() {
6 | a && useHook1();
> 7 | b && useHook2();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
8 | }
9 |
```

View File

@@ -18,15 +18,13 @@ function ComponentWithTernaryHook() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-b4dcda3d60ed.ts:6:9
4 | // This *must* be invalid.
5 | function ComponentWithTernaryHook() {
> 6 | cond ? useTernaryHook() : null;
| ^^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 |
```

View File

@@ -19,15 +19,13 @@ function useHook() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-c906cace44e9.ts:7:2
5 | function useHook() {
6 | if (a) return;
> 7 | useState();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
8 | }
9 |
```

View File

@@ -20,15 +20,13 @@ function normalFunctionWithConditionalHook() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-d740d54e9c21.ts:7:4
5 | function normalFunctionWithConditionalHook() {
6 | if (cond) {
> 7 | useHookInsideNormalFunction();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
8 | }
9 | }
10 |

View File

@@ -22,28 +22,24 @@ function useHookInLoops() {
```
Found 2 errors:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-d85c144bdf40.ts:7:4
5 | function useHookInLoops() {
6 | while (a) {
> 7 | useHook1();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
8 | if (b) continue;
9 | useHook2();
10 | }
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-d85c144bdf40.ts:9:4
7 | useHook1();
8 | if (b) continue;
> 9 | useHook2();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
10 | }
11 | }
12 |

View File

@@ -20,15 +20,13 @@ function useHookWithConditionalHook() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-ea7c2fb545a9.ts:7:4
5 | function useHookWithConditionalHook() {
6 | if (cond) {
> 7 | useConditionalHook();
| ^^^^^^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
8 | }
9 | }
10 |

View File

@@ -24,15 +24,13 @@ function useHook() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-f3d6c5e9c83d.ts:12:2
10 | }
11 | if (a) return;
> 12 | useState();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
13 | }
14 |
```

View File

@@ -20,41 +20,35 @@ function useHook({bar}) {
```
Found 3 errors:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-f69800950ff0.ts:6:20
4 | // This *must* be invalid.
5 | function useHook({bar}) {
> 6 | let foo1 = bar && useState();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | let foo2 = bar || useState();
8 | let foo3 = bar ?? useState();
9 | }
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-f69800950ff0.ts:7:20
5 | function useHook({bar}) {
6 | let foo1 = bar && useState();
> 7 | let foo2 = bar || useState();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
8 | let foo3 = bar ?? useState();
9 | }
10 |
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-rules-of-hooks-f69800950ff0.ts:8:20
6 | let foo1 = bar && useState();
7 | let foo2 = bar || useState();
> 8 | let foo3 = bar ?? useState();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
9 | }
10 |
```

View File

@@ -20,15 +20,15 @@ function createHook() {
```
Found 1 error:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call hook within a function expression.
error.invalid.invalid-rules-of-hooks-0a1dbff27ba0.ts:6:6
4 | return function useHookWithConditionalHook() {
5 | if (cond) {
> 6 | useConditionalHook();
| ^^^^^^^^^^^^^^^^^^ Cannot call hook within a function expression
| ^^^^^^^^^^^^^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | };
9 | }

View File

@@ -20,28 +20,28 @@ function createComponent() {
```
Found 2 errors:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call hook within a function expression.
error.invalid.invalid-rules-of-hooks-0de1224ce64b.ts:6:6
4 | return function ComponentWithHookInsideCallback() {
5 | useEffect(() => {
> 6 | useHookInsideCallback();
| ^^^^^^^^^^^^^^^^^^^^^ Cannot call hook within a function expression
| ^^^^^^^^^^^^^^^^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | });
8 | };
9 | }
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call useEffect within a function expression.
error.invalid.invalid-rules-of-hooks-0de1224ce64b.ts:5:4
3 | function createComponent() {
4 | return function ComponentWithHookInsideCallback() {
> 5 | useEffect(() => {
| ^^^^^^^^^ Cannot call useEffect within a function expression
| ^^^^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
6 | useHookInsideCallback();
7 | });
8 | };

View File

@@ -20,15 +20,15 @@ function createComponent() {
```
Found 1 error:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call useState within a function expression.
error.invalid.invalid-rules-of-hooks-449a37146a83.ts:6:6
4 | return function ComponentWithHookInsideCallback() {
5 | function handleClick() {
> 6 | useState();
| ^^^^^^^^ Cannot call useState within a function expression
| ^^^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | };
9 | }

View File

@@ -18,15 +18,15 @@ function ComponentWithHookInsideCallback() {
```
Found 1 error:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call useState within a function expression.
error.invalid.invalid-rules-of-hooks-76a74b4666e9.ts:5:4
3 | function ComponentWithHookInsideCallback() {
4 | function handleClick() {
> 5 | useState();
| ^^^^^^^^ Cannot call useState within a function expression
| ^^^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
6 | }
7 | }
8 |

View File

@@ -20,15 +20,15 @@ function createComponent() {
```
Found 1 error:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call hook within a function expression.
error.invalid.invalid-rules-of-hooks-d842d36db450.ts:6:6
4 | return function ComponentWithConditionalHook() {
5 | if (cond) {
> 6 | useConditionalHook();
| ^^^^^^^^^^^^^^^^^^ Cannot call hook within a function expression
| ^^^^^^^^^^^^^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | };
9 | }

View File

@@ -18,15 +18,15 @@ function ComponentWithHookInsideCallback() {
```
Found 1 error:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call hook within a function expression.
error.invalid.invalid-rules-of-hooks-d952b82c2597.ts:5:4
3 | function ComponentWithHookInsideCallback() {
4 | useEffect(() => {
> 5 | useHookInsideCallback();
| ^^^^^^^^^^^^^^^^^^^^^ Cannot call hook within a function expression
| ^^^^^^^^^^^^^^^^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
6 | });
7 | }
8 |

View File

@@ -22,15 +22,13 @@ const FancyButton = forwardRef(function (props, ref) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
todo.error.invalid-rules-of-hooks-368024110a58.ts:8:4
6 | const FancyButton = forwardRef(function (props, ref) {
7 | if (props.fancy) {
> 8 | useCustomHook();
| ^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
9 | }
10 | return <button ref={ref}>{props.children}</button>;
11 | });

View File

@@ -22,15 +22,13 @@ const MemoizedButton = memo(function (props) {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
todo.error.invalid-rules-of-hooks-8566f9a360e2.ts:8:4
6 | const MemoizedButton = memo(function (props) {
7 | if (props.fancy) {
> 8 | useCustomHook();
| ^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
9 | }
10 | return <button>{props.children}</button>;
11 | });

View File

@@ -21,15 +21,13 @@ function ComponentWithConditionalHook() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
todo.error.invalid-rules-of-hooks-a0058f0b446d.ts:8:4
6 | function ComponentWithConditionalHook() {
7 | if (cond) {
> 8 | Namespace.useConditionalHook();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
9 | }
10 | }
11 |

View File

@@ -22,15 +22,13 @@ const FancyButton = React.forwardRef((props, ref) => {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
todo.error.rules-of-hooks-27c18dc8dad2.ts:8:4
6 | const FancyButton = React.forwardRef((props, ref) => {
7 | if (props.fancy) {
> 8 | useCustomHook();
| ^^^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
9 | }
10 | return <button ref={ref}>{props.children}</button>;
11 | });

View File

@@ -21,15 +21,13 @@ React.unknownFunction((foo, bar) => {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
todo.error.rules-of-hooks-d0935abedc42.ts:8:4
6 | React.unknownFunction((foo, bar) => {
7 | if (foo) {
> 8 | useNotAHook(bar);
| ^^^^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
9 | }
10 | });
11 |

View File

@@ -22,15 +22,13 @@ function useHook() {
```
Found 1 error:
Error: Cannot call hooks conditionally
Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
todo.error.rules-of-hooks-e29c874aa913.ts:9:4
7 | try {
8 | f();
> 9 | useState();
| ^^^^^^^^ Cannot call hook conditionally
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
10 | } catch {}
11 | }
12 |

View File

@@ -30,15 +30,15 @@ function Component(props) {
```
Found 1 error:
Error: Cannot call hooks within function expressions
Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Cannot call useEffect within a function expression.
error.invalid-nested-use-effect.ts:9:4
7 | };
8 | useEffect(() => {
> 9 | useEffect(() => {
| ^^^^^^^^^ Cannot call useEffect within a function expression
| ^^^^^^^^^ Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
10 | function nested() {
11 | fire(foo(props));
12 | }

View File

@@ -48,7 +48,10 @@ export {
printReactiveFunction,
printReactiveFunctionWithOutlined,
} from './ReactiveScopes';
export {parseConfigPragmaForTests} from './Utils/TestUtils';
export {
parseConfigPragmaForTests,
parseConfigPragmaAsString,
} from './Utils/TestUtils';
declare global {
let __DEV__: boolean | null | undefined;
}

View File

@@ -68,7 +68,9 @@ testRule('plugin-recommended', TestRecommendedRules, {
</>;
}
`,
errors: [makeTestCaseError('Cannot call hooks conditionally')],
errors: [
makeTestCaseError('Hooks must always be called in a consistent order'),
],
},
{
name: 'Multiple diagnostics within the same file are surfaced',
@@ -82,8 +84,8 @@ testRule('plugin-recommended', TestRecommendedRules, {
return props.cond && useConditionalHook();
}`,
errors: [
makeTestCaseError('Cannot call hooks conditionally'),
makeTestCaseError('Cannot call hooks conditionally'),
makeTestCaseError('Hooks must always be called in a consistent order'),
makeTestCaseError('Hooks must always be called in a consistent order'),
],
},
{
@@ -96,7 +98,9 @@ testRule('plugin-recommended', TestRecommendedRules, {
}
`,
errors: [makeTestCaseError('Cannot call hooks conditionally')],
errors: [
makeTestCaseError('Hooks must always be called in a consistent order'),
],
},
{
name: 'Multiple non-fatal useMemo diagnostics are surfaced',

View File

@@ -15,7 +15,7 @@ const configs = {
recommended: {
plugins: {
'react-compiler': {
rules: recommendedRules,
rules: allRules,
},
},
rules: Object.fromEntries(

View File

@@ -112,28 +112,6 @@ function runReactCompilerImpl({
userOpts,
}: RunParams): RunCacheEntry {
// Compat with older versions of eslint
for (const [key, entry] of Object.entries(userOpts)) {
if (key === 'environment' && COMPILER_OPTIONS.environment != null) {
for (const envKey of Object.keys(entry as Record<string, any>)) {
if (
Object.prototype.hasOwnProperty.call(
COMPILER_OPTIONS.environment,
envKey,
) &&
!isDeepStrictEqual(
(entry as Record<string, any>)[envKey],
(COMPILER_OPTIONS.environment as Record<string, any>)[envKey],
)
) {
console.warn('Conflicting environment option detected: ' + envKey);
}
}
} else if (Object.prototype.hasOwnProperty.call(COMPILER_OPTIONS, key)) {
if (!isDeepStrictEqual(entry, (COMPILER_OPTIONS as any)[key])) {
console.warn('Conflicting option detected: ' + key);
}
}
}
const options: PluginOptions = parsePluginOptions({
...COMPILER_OPTIONS,
...userOpts,

View File

@@ -0,0 +1,32 @@
const ReactCompiler = require('../packages/babel-plugin-react-compiler/dist');
const combinedRules = [
{
name: 'rules-of-hooks',
recommended: true,
description:
'Validates that components and hooks follow the [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks)',
},
{
name: 'exhaustive-deps',
recommended: true,
description:
'Validates that hooks which accept dependency arrays (`useMemo()`, `useCallback()`, `useEffect()`, etc) ' +
'list all referenced variables in their dependency array. Referencing a value without including it in the ' +
'dependency array can lead to stale UI or callbacks.',
},
...ReactCompiler.LintRules,
];
const printed = combinedRules
.filter(rule => rule.recommended)
.map(rule => {
return `
## \`react-hooks/${rule.name}\`
${rule.description}
`.trim();
})
.join('\n\n');
console.log(printed);

View File

@@ -3,7 +3,7 @@ import Fixture from '../../Fixture';
const React = window.React;
const {Fragment, useEffect, useRef, useState} = React;
const {Fragment, useRef} = React;
export default function FocusCase() {
const fragmentRef = useRef(null);

View File

@@ -2,7 +2,7 @@ import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
const React = window.React;
const {Fragment, useEffect, useRef, useState} = React;
const {Fragment, useRef, useState} = React;
export default function GetClientRectsCase() {
const fragmentRef = useRef(null);

View File

@@ -0,0 +1,184 @@
import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
import ScrollIntoViewCaseComplex from './ScrollIntoViewCaseComplex';
import ScrollIntoViewCaseSimple from './ScrollIntoViewCaseSimple';
import ScrollIntoViewTargetElement from './ScrollIntoViewTargetElement';
const React = window.React;
const {Fragment, useRef, useState, useEffect} = React;
const ReactDOM = window.ReactDOM;
function Controls({
alignToTop,
setAlignToTop,
scrollVertical,
exampleType,
setExampleType,
}) {
return (
<div>
<label>
Example Type:
<select
value={exampleType}
onChange={e => setExampleType(e.target.value)}>
<option value="simple">Simple</option>
<option value="multiple">Multiple Scroll Containers</option>
<option value="horizontal">Horizontal</option>
<option value="empty">Empty Fragment</option>
</select>
</label>
<div>
<label>
Align to Top:
<input
type="checkbox"
checked={alignToTop}
onChange={e => setAlignToTop(e.target.checked)}
/>
</label>
</div>
<div>
<button onClick={scrollVertical}>scrollIntoView()</button>
</div>
</div>
);
}
export default function ScrollIntoViewCase() {
const [exampleType, setExampleType] = useState('simple');
const [alignToTop, setAlignToTop] = useState(true);
const [caseInViewport, setCaseInViewport] = useState(false);
const fragmentRef = useRef(null);
const testCaseRef = useRef(null);
const noChildRef = useRef(null);
const scrollContainerRef = useRef(null);
const scrollVertical = () => {
fragmentRef.current.experimental_scrollIntoView(alignToTop);
};
const scrollVerticalNoChildren = () => {
noChildRef.current.experimental_scrollIntoView(alignToTop);
};
useEffect(() => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setCaseInViewport(true);
} else {
setCaseInViewport(false);
}
});
});
testCaseRef.current.observeUsing(observer);
const lastRef = testCaseRef.current;
return () => {
lastRef.unobserveUsing(observer);
observer.disconnect();
};
});
return (
<Fragment ref={testCaseRef}>
<TestCase title="ScrollIntoView">
<TestCase.Steps>
<li>Toggle alignToTop and click the buttons to scroll</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
<p>When the Fragment has children:</p>
<p>
In order to handle the case where children are split between
multiple scroll containers, we call scrollIntoView on each child in
reverse order.
</p>
<p>When the Fragment does not have children:</p>
<p>
The Fragment still represents a virtual space. We can scroll to the
nearest edge by selecting the host sibling before if
alignToTop=false, or after if alignToTop=true|undefined. We'll fall
back to the other sibling or parent in the case that the preferred
sibling target doesn't exist.
</p>
</TestCase.ExpectedResult>
<Fixture>
<Fixture.Controls>
<Controls
alignToTop={alignToTop}
setAlignToTop={setAlignToTop}
scrollVertical={scrollVertical}
exampleType={exampleType}
setExampleType={setExampleType}
/>
</Fixture.Controls>
{exampleType === 'simple' && (
<Fragment ref={fragmentRef}>
<ScrollIntoViewCaseSimple />
</Fragment>
)}
{exampleType === 'horizontal' && (
<div
style={{
display: 'flex',
overflowX: 'auto',
flexDirection: 'row',
border: '1px solid #ccc',
padding: '1rem 10rem',
marginBottom: '1rem',
width: '100%',
whiteSpace: 'nowrap',
justifyContent: 'space-between',
}}>
<Fragment ref={fragmentRef}>
<ScrollIntoViewCaseSimple />
</Fragment>
</div>
)}
{exampleType === 'multiple' && (
<Fragment>
<div
style={{
height: '50vh',
overflowY: 'auto',
border: '1px solid black',
marginBottom: '1rem',
}}
ref={scrollContainerRef}
/>
<Fragment ref={fragmentRef}>
<ScrollIntoViewCaseComplex
caseInViewport={caseInViewport}
scrollContainerRef={scrollContainerRef}
/>
</Fragment>
</Fragment>
)}
{exampleType === 'empty' && (
<Fragment>
<ScrollIntoViewTargetElement
color="lightyellow"
id="ABOVE EMPTY FRAGMENT"
/>
<Fragment ref={fragmentRef}></Fragment>
<ScrollIntoViewTargetElement
color="lightblue"
id="BELOW EMPTY FRAGMENT"
/>
</Fragment>
)}
<Fixture.Controls>
<Controls
alignToTop={alignToTop}
setAlignToTop={setAlignToTop}
scrollVertical={scrollVertical}
exampleType={exampleType}
setExampleType={setExampleType}
/>
</Fixture.Controls>
</Fixture>
</TestCase>
</Fragment>
);
}

View File

@@ -0,0 +1,50 @@
import ScrollIntoViewTargetElement from './ScrollIntoViewTargetElement';
const React = window.React;
const {Fragment, useRef, useState, useEffect} = React;
const ReactDOM = window.ReactDOM;
export default function ScrollIntoViewCaseComplex({
caseInViewport,
scrollContainerRef,
}) {
const [didMount, setDidMount] = useState(false);
// Hack to portal child into the scroll container
// after the first render. This is to simulate a case where
// an item is portaled into another scroll container.
useEffect(() => {
if (!didMount) {
setDidMount(true);
}
}, []);
return (
<Fragment>
{caseInViewport && (
<div
style={{position: 'fixed', top: 0, backgroundColor: 'red'}}
id="header">
Fixed header
</div>
)}
{didMount &&
ReactDOM.createPortal(
<ScrollIntoViewTargetElement color="red" id="FROM_PORTAL" />,
scrollContainerRef.current
)}
<ScrollIntoViewTargetElement color="lightgreen" id="A" />
<ScrollIntoViewTargetElement color="lightcoral" id="B" />
<ScrollIntoViewTargetElement color="lightblue" id="C" />
{caseInViewport && (
<div
style={{
position: 'fixed',
bottom: 0,
backgroundColor: 'purple',
}}
id="footer">
Fixed footer
</div>
)}
</Fragment>
);
}

Some files were not shown because too many files have changed in this diff Show More