fix(react_compiler): render unchanged programs as source in fixture snapshots (#24129)

## Root cause

The `oxc_react_compiler` snapshot harness (`tests/snapshot.rs`) rendered a clean no-op compile as the literal `No changes.` marker:

```rust
if result.changed {
    out.push_str(&Codegen::new().build(&program).code);
} else {
    out.push_str("No changes.");
}
```

Upstream's `snap` runner instead **always** re-emits the program as `## Code`, and only replaces it with `## Error` on a hard error. So for every fixture where oxc "bails" with `No changes.`, the fork's golden shows the reprinted source — a permanent, opaque diff.

Investigating the **rules-of-hooks** batch showed this is purely a harness-representation gap, not a compiler bug:

- All 17 "bailing" rules-of-hooks fixtures carry `@expectNothingCompiled` (e.g. top-level statements, classes, or `@compilationMode:"infer"` functions that aren't components/hooks). oxc correctly compiles nothing — matching the fork, which also compiles nothing and just reprints the source.
- Every rules-of-hooks **error** fixture already matches the fork on category, message text, and source location (Rules-of-Hooks validation is a complete port).
- Every rules-of-hooks **compiling** fixture already matches the fork structurally; residual diffs are pure codegen formatting (trailing commas, JSX paren-wrapping) which are out of scope.

## Fix

Test-only change. On a clean no-op (no parse errors **and** no compile diagnostics), codegen the unchanged program instead of the marker, mirroring the upstream runner. The marker is kept when an error was reported, since upstream emits `## Error` with no code there — and reprinting a parse-recovered AST would be misleading.

```rust
let clean = parsed.diagnostics.is_empty() && result.diagnostics.as_slice().is_empty();
if result.changed || clean {
    out.push_str(&Codegen::new().build(&program).code);
} else {
    out.push_str("No changes.");
}
```

No compiler source is touched, so there is no risk to the compiler, napi bindings, or other consumers. The snapshot test still catches any future behavior change.

## Fixtures changed

54 snapshots that previously said `No changes.` now render the reprinted source:

- **17 rules-of-hooks** (this batch) — all `@expectNothingCompiled`; now match the fork's `## Code` modulo whitespace.
- **37 elsewhere** (guarded corpus-wide no-ops: `infer-*`, `use-no-forget-*`, `use-no-memo-*`, `gating/*`, `effect-derived-computations/*`, `valid-setState-*`, etc.).

52 of the 54 match the fork's `## Code` modulo formatting. The 11 parse-error fixtures (`error.todo-*`) keep the marker as intended.

## Remaining work

Two of the changed snapshots move **closer** to the fork but retain **pre-existing, out-of-batch** compiler gaps (unchanged by this PR, now just more visible):

- `ignore-use-no-forget.js` — oxc doesn't implement the `@ignoreUseNoForget` plugin option, so it honors the `'use no forget'` opt-out and skips; the fork ignores the opt-out and fully compiles.
- `valid-setState-in-effect-from-ref-function-call.js` — in `@outputMode:"lint"` the fork reprints with an SSA-renamed shadowed param (`ref` → `ref_0`); oxc leaves the source unchanged.

Both are separate from Rules-of-Hooks and left to their respective batches.
This commit is contained in:
Boshen committed 2026-07-04 01:22:38 +00:00
1 parent 5bd3f28eff
commit 5771982655
55 files changed
+647 -55

No files matched your search

+9 -1
View File
@@ -70,7 +70,15 @@ fn run_fixture(source: &str) -> String {
}
push_diagnostics(&mut out, "Diagnostics", result.diagnostics.as_slice());
if result.changed {
// Mirror the upstream `snap` runner, which always re-emits the program as
// `## Code` unless a hard error turns the output into `## Error`. So when the
// compiler cleanly declines to change anything (e.g. `@expectNothingCompiled`,
// or a file with no React-like functions), echo the reprinted source rather
// than the `No changes.` marker. The marker is kept only when an error was
// reported (parse failure or a compile diagnostic), where upstream emits no
// code — and echoing a parse-recovered AST would be misleading.
let clean = parsed.diagnostics.is_empty() && result.diagnostics.as_slice().is_empty();
if result.changed || clean {
out.push_str(&Codegen::new().build(&program).code);
} else {
out.push_str("No changes.");
@@ -2,4 +2,16 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/class-component-with-render-helper.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
class Component {
_renderMessage = () => {
const Message = () => {
const message = this.state.message;
return <div>{message}</div>;
};
return <Message />;
};
render() {
return this._renderMessage();
}
}
@@ -2,4 +2,12 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/custom-opt-out-directive.tsx
---
No changes.
// @expectNothingCompiled @customOptOutDirectives:["use todo memo"]
function Component() {
"use todo memo";
return <div>hello world!</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: []
};
@@ -2,4 +2,29 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/dont-memoize-primitive-function-call-non-escaping.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import { useMemo } from "react";
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
function Component(props) {
const result = makeObject(props.value).value + 1;
console.log(result);
return "ok";
}
function makeObject(value) {
console.log(value);
return { value };
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
sequentialRenders: [
{ value: 42 },
{ value: 42 },
{ value: 3.14 },
{ value: 3.14 },
{ value: 42 },
{ value: 3.14 },
{ value: 42 },
{ value: 3.14 }
]
};
@@ -2,4 +2,18 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/effect-derived-computations/derived-state-from-prop-setter-call-outside-effect-no-error.js
---
No changes.
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
import { useEffect, useState } from "react";
function Component({ initialName }) {
const [name, setName] = useState("");
useEffect(() => {
setName(initialName);
}, [initialName]);
return <div>
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ initialName: "John" }]
};
@@ -2,4 +2,19 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/effect-derived-computations/derived-state-from-prop-setter-used-outside-effect-no-error.js
---
No changes.
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
import { useEffect, useState } from "react";
function MockComponent({ onSet }) {
return <div onClick={() => onSet("clicked")}>Mock Component</div>;
}
function Component({ propValue }) {
const [value, setValue] = useState(null);
useEffect(() => {
setValue(propValue);
}, [propValue]);
return <MockComponent onSet={setValue} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ propValue: "test" }]
};
@@ -2,4 +2,17 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/effect-derived-computations/derived-state-from-ref-and-state-no-error.js
---
No changes.
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
import { useEffect, useState, useRef } from "react";
export default function Component({ test }) {
const [local, setLocal] = useState("");
const myRef = useRef(null);
useEffect(() => {
setLocal(myRef.current + test);
}, [test]);
return <>{local}</>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: "testString" }]
};
@@ -2,4 +2,20 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/effect-derived-computations/effect-contains-prop-function-call-no-error.js
---
No changes.
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
import { useEffect, useState } from "react";
function Component({ propValue, onChange }) {
const [value, setValue] = useState(null);
useEffect(() => {
setValue(propValue);
onChange();
}, [propValue]);
return <div>{value}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{
propValue: "test",
onChange: () => {}
}]
};
@@ -2,4 +2,20 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/effect-derived-computations/effect-with-cleanup-function-depending-on-derived-computation-value.js
---
No changes.
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
import { useEffect, useState } from "react";
function Component(file: File) {
const [imageUrl, setImageUrl] = useState(null);
/*
* Cleaning up the variable or a source of the variable used to setState
* inside the effect communicates that we always need to clean up something
* which is a valid use case for useEffect. In which case we want to
* avoid an throwing
*/
useEffect(() => {
const imageUrlPrepared = URL.createObjectURL(file);
setImageUrl(imageUrlPrepared);
return () => URL.revokeObjectURL(imageUrlPrepared);
}, [file]);
return <Image src={imageUrl} xstyle={styles.imageSizeLimits} />;
}
@@ -2,4 +2,17 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/effect-derived-computations/effect-with-global-function-call-no-error.js
---
No changes.
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
import { useEffect, useState } from "react";
function Component({ propValue }) {
const [value, setValue] = useState(null);
useEffect(() => {
setValue(propValue);
globalCall();
}, [propValue]);
return <div>{value}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ propValue: "test" }]
};
@@ -2,4 +2,10 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/effect-derived-computations/from-props-setstate-in-effect-no-error.js
---
No changes.
// @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly @outputMode:"lint"
function Component({ setParentState, prop }) {
useEffect(() => {
setParentState(prop);
}, [prop]);
return <div>{prop}</div>;
}
@@ -2,4 +2,21 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/effect-derived-computations/ref-conditional-in-effect-no-error.js
---
No changes.
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
import { useEffect, useState, useRef } from "react";
export default function Component({ test }) {
const [local, setLocal] = useState(0);
const myRef = useRef(null);
useEffect(() => {
if (myRef.current) {
setLocal(test);
} else {
setLocal(test + test);
}
}, [test]);
return <>{local}</>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ test: 4 }]
};
@@ -2,4 +2,11 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/fn-name-no-leak-to-nested-arrow.js
---
No changes.
// @loggerTestOnly @outputMode:"lint"
function Component() {
return null;
}
export const ENTRYPOINT = {
fn: Component,
params: [{ onChange: () => {} }]
};
@@ -2,4 +2,12 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/gating/dynamic-gating-noemit.js
---
No changes.
// @dynamicGating:{"source":"shared-runtime"} @outputMode:"lint"
function Foo() {
"use memo if(getTrue)";
return <div>hello world</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}]
};
@@ -2,4 +2,6 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/gating/repro-no-gating-import-without-compiled-functions.js
---
No changes.
// @expectNothingCompiled @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
export default 42;
@@ -2,4 +2,13 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/ignore-use-no-forget.js
---
No changes.
// @ignoreUseNoForget
function Component(prop) {
"use no forget";
const result = prop.x.toFixed();
return <div>{result}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ x: 1 }]
};
@@ -2,4 +2,12 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/infer-dont-compile-components-with-multiple-params.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
// Takes multiple parameters - not a component!
function Component(foo, bar) {
return <div />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [null, null]
};
@@ -2,4 +2,13 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/infer-no-component-annot.ts
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
import { useIdentity, identity } from "shared-runtime";
function Component(fakeProps: number) {
const x = useIdentity(fakeProps);
return identity(x);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [42]
};
@@ -2,4 +2,19 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/infer-no-component-nested-jsx.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
function Component(props) {
const result = f(props);
function helper() {
return <foo />;
}
helper();
return result;
}
function f(props) {
return props;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}]
};
@@ -2,4 +2,15 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/infer-no-component-obj-return.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
function Component(props) {
const ignore = <foo />;
return { foo: f(props) };
}
function f(props) {
return props;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}]
};
@@ -2,4 +2,9 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/infer-skip-components-without-hooks-or-jsx.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
// This component is skipped bc it doesn't call any hooks or
// use JSX:
function Component(props) {
return render();
}
@@ -2,4 +2,8 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/repro-bailout-nopanic-shouldnt-outline.js
---
No changes.
// @panicThreshold:"none"
"use no memo";
function Foo() {
return <button onClick={() => alert("hello!")}>Click me!</button>;
}
@@ -2,4 +2,15 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/rules-of-hooks-0592bd574811.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
// Regression test for some internal code.
// This shows how the "callback rule" is more relaxed,
// and doesn't kick in unless we're confident we're in
// a component or a hook.
function makeListener(instance) {
each(pixelsWithInferredEvents, (pixel) => {
if (useExtendedSelector(pixel.id) && extendedButton) {
foo();
}
});
}
@@ -2,4 +2,11 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/rules-of-hooks-2bec02ac982b.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
// Valid because hooks can call hooks.
function createHook() {
return function useHook() {
useHook1();
useHook2();
};
}
@@ -2,4 +2,10 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/rules-of-hooks-33a6e23edac1.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
// Valid because hooks can use hooks.
function createHook() {
return function useHookWithHook() {
useHook();
};
}
@@ -2,4 +2,10 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
// Valid because components can use hooks.
function createComponentWithHook() {
return function ComponentWithHook() {
useHook();
};
}
@@ -2,4 +2,11 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/rules-of-hooks-df4d750736f3.js
---
No changes.
// @expectNothingCompiled
// Valid because they're not matching use[A-Z].
fooState();
_use();
_useState();
use_hook();
// also valid because it's not matching the PascalCase namespace
jest.useFakeTimer();
@@ -2,4 +2,12 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/rules-of-hooks-dfde14171fcd.js
---
No changes.
// @expectNothingCompiled
// Valid because classes can call functions.
// We don't consider these to be hooks.
class C {
m() {
this.useHook();
super.useHook();
}
}
@@ -2,4 +2,11 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/rules-of-hooks-fe6042f7628b.js
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
// This is valid because "use"-prefixed functions called in
// unnamed function arguments are not assumed to be hooks.
unknownFunction(function(foo, bar) {
if (foo) {
useNotAHook(bar);
}
});
@@ -2,4 +2,16 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/todo.invalid.invalid-rules-of-hooks-191029ac48c8.js
---
No changes.
// @expectNothingCompiled @skip
// Passed but should have failed
// Invalid because it's dangerous.
// Normally, this would crash, but not if you use inline requires.
// This *must* be invalid.
// It's expected to have some false positives, but arguably
// they are confusing anyway due to the use*() convention
// already being associated with Hooks.
useState();
if (foo) {
const foo = React.useCallback(() => {});
}
useCustomHook();
@@ -2,4 +2,14 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/todo.invalid.invalid-rules-of-hooks-206e2811c87c.js
---
No changes.
// @expectNothingCompiled @skip
// Passed but should have failed
// This is a false positive (it's valid) that unfortunately
// we cannot avoid. Prefer to rename it to not start with "use"
class Foo extends Component {
render() {
if (cond) {
FooStore.useFeatureFlag();
}
}
}
@@ -2,4 +2,14 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/todo.invalid.invalid-rules-of-hooks-28a7111f56a7.js
---
No changes.
// @expectNothingCompiled @skip
// Passed but should have failed
// Technically this is a false positive.
// We *could* make it valid (and it used to be).
//
// However, top-level Hook-like calls can be very dangerous
// in environments with inline requires because they can mask
// the runtime error by accident.
// So we prefer to disallow it despite the false positive.
const { createHistory, useBasename } = require("history-2.1.2");
const browserHistory = useBasename(createHistory)({ basename: "/" });
@@ -2,4 +2,10 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/todo.invalid.invalid-rules-of-hooks-2c51251df67a.js
---
No changes.
// @expectNothingCompiled @skip
// Passed but should have failed
(class {
useHook() {
useState();
}
});
@@ -2,4 +2,10 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/todo.invalid.invalid-rules-of-hooks-8303403b8e4c.js
---
No changes.
// @expectNothingCompiled @skip
// Passed but should have failed
class ClassComponentWithHook extends React.Component {
render() {
React.useState();
}
}
@@ -2,4 +2,12 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/todo.invalid.invalid-rules-of-hooks-99b5c750d1d1.js
---
No changes.
// @expectNothingCompiled @skip
// Passed but should have failed
class ClassComponentWithFeatureFlag extends React.Component {
render() {
if (foo) {
useFeatureFlag();
}
}
}
@@ -2,4 +2,10 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/todo.invalid.invalid-rules-of-hooks-9c79feec4b9b.js
---
No changes.
// @expectNothingCompiled @skip
// Passed but should have failed
(class {
h = () => {
useState();
};
});
@@ -2,4 +2,11 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/todo.invalid.invalid-rules-of-hooks-acb56658fe7e.js
---
No changes.
// @expectNothingCompiled @skip
// Passed but should have failed
class C {
m() {
This.useHook();
Super.useHook();
}
}
@@ -2,4 +2,10 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/todo.invalid.invalid-rules-of-hooks-ddeca9708b63.js
---
No changes.
// @expectNothingCompiled @skip
// Passed but should have failed
(class {
i() {
useState();
}
});
@@ -2,4 +2,10 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/rules-of-hooks/todo.invalid.invalid-rules-of-hooks-e69ffce323c3.js
---
No changes.
// @expectNothingCompiled @skip
// Passed but should have failed
(class {
useHook = () => {
useState();
};
});
@@ -2,4 +2,20 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/skip-useMemoCache.js
---
No changes.
// @expectNothingCompiled
import { c as useMemoCache } from "react/compiler-runtime";
function Component(props) {
const $ = useMemoCache();
let x;
if ($[0] === undefined) {
x = [props.value];
$[0] = x;
} else {
x = $[0];
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }]
};
@@ -2,4 +2,12 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/typescript-this-param-in-uncompiled-function.ts
---
No changes.
// @expectNothingCompiled @compilationMode:"infer"
export function Decorate() {
return function(_target: object, _key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(this: unknown) {
return original.apply(this, []);
};
};
}
@@ -2,4 +2,12 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/use-memo-noemit.js
---
No changes.
// @outputMode:"lint"
function Foo() {
"use memo";
return <button onClick={() => alert("hello!")}>Click me!</button>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: []
};
@@ -2,4 +2,10 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/use-no-forget-module-level.js
---
No changes.
"use no forget";
export default function foo(x, y) {
if (x) {
return foo(false, y);
}
return [y * 10];
}
@@ -2,4 +2,13 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/use-no-forget-with-no-errors.js
---
No changes.
// @expectNothingCompiled
function Component() {
"use no forget";
return <div>Hello World</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
isComponent: true
};
@@ -2,4 +2,10 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/use-no-memo-module-level.js
---
No changes.
"use no memo";
export default function foo(x, y) {
if (x) {
return foo(false, y);
}
return [y * 10];
}
@@ -2,4 +2,9 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/use-no-memo-module-scope-usememo-function-scope.js
---
No changes.
// @compilationMode:"all"
"use no memo";
function TestComponent({ x }) {
"use memo";
return <Button>{x}</Button>;
}
@@ -2,4 +2,14 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/use-no-memo-simple.js
---
No changes.
// @expectNothingCompiled
function Component(props) {
"use no memo";
let x = [props.foo];
return <div x={x}>"foo"</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 1 }],
isComponent: true
};
@@ -2,4 +2,18 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/valid-set-state-in-useEffect-from-ref.js
---
No changes.
// @validateNoSetStateInEffects @outputMode:"lint"
import { useState, useRef, useEffect } from "react";
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
return tooltipHeight;
}
export const FIXTURE_ENTRYPOINT = {
fn: Tooltip,
params: []
};
@@ -2,4 +2,17 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/valid-setState-in-effect-from-ref-arithmetic.js
---
No changes.
// @validateNoSetStateInEffects @enableAllowSetStateFromRefsInEffects @outputMode:"lint"
import { useState, useRef, useLayoutEffect } from "react";
function Component() {
const ref = useRef({ size: 5 });
const [computedSize, setComputedSize] = useState(0);
useLayoutEffect(() => {
setComputedSize(ref.current.size * 10);
}, []);
return computedSize;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: []
};
@@ -2,4 +2,24 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/valid-setState-in-effect-from-ref-array-index.js
---
No changes.
// @validateNoSetStateInEffects @enableAllowSetStateFromRefsInEffects @outputMode:"lint"
import { useState, useRef, useEffect } from "react";
function Component() {
const ref = useRef([
1,
2,
3,
4,
5
]);
const [value, setValue] = useState(0);
useEffect(() => {
const index = 2;
setValue(ref.current[index]);
}, []);
return value;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: []
};
@@ -2,4 +2,23 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/valid-setState-in-effect-from-ref-function-call.js
---
No changes.
// @validateNoSetStateInEffects @enableAllowSetStateFromRefsInEffects @outputMode:"lint"
import { useState, useRef, useEffect } from "react";
function Component() {
const ref = useRef(null);
const [width, setWidth] = useState(0);
useEffect(() => {
function getBoundingRect(ref) {
if (ref.current) {
return ref.current.getBoundingClientRect?.()?.width ?? 100;
}
return 100;
}
setWidth(getBoundingRect(ref));
}, []);
return width;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: []
};
@@ -2,4 +2,51 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/valid-setState-in-useEffect-controlled-by-ref-value.js
---
No changes.
// @validateNoSetStateInEffects @enableAllowSetStateFromRefsInEffects @loggerTestOnly @compilationMode:"infer" @outputMode:"lint"
import { useState, useRef, useEffect } from "react";
function Component({ x, y }) {
const previousXRef = useRef(null);
const previousYRef = useRef(null);
const [data, setData] = useState(null);
useEffect(() => {
const previousX = previousXRef.current;
previousXRef.current = x;
const previousY = previousYRef.current;
previousYRef.current = y;
if (!areEqual(x, previousX) || !areEqual(y, previousY)) {
const data = load({
x,
y
});
setData(data);
}
}, [x, y]);
return data;
}
function areEqual(a, b) {
return a === b;
}
function load({ x, y }) {
return x * y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{
x: 0,
y: 0
}],
sequentialRenders: [
{
x: 0,
y: 0
},
{
x: 1,
y: 0
},
{
x: 1,
y: 1
}
]
};
@@ -2,4 +2,19 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/valid-setState-in-useEffect-listener-transitive.js
---
No changes.
// @validateNoSetStateInEffects @outputMode:"lint"
import { useEffect, useState } from "react";
function Component() {
const [state, setState] = useState(0);
useEffect(() => {
const f = () => {
setState();
};
setTimeout(() => f(), 10);
});
return state;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}]
};
@@ -2,4 +2,16 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/valid-setState-in-useEffect-listener.js
---
No changes.
// @validateNoSetStateInEffects @outputMode:"lint"
import { useEffect, useState } from "react";
function Component() {
const [state, setState] = useState(0);
useEffect(() => {
setTimeout(setState, 10);
});
return state;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}]
};
@@ -2,4 +2,18 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/valid-setState-in-useLayoutEffect-from-ref.js
---
No changes.
// @validateNoSetStateInEffects @enableAllowSetStateFromRefsInEffects @outputMode:"lint"
import { useState, useRef, useLayoutEffect } from "react";
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
return tooltipHeight;
}
export const FIXTURE_ENTRYPOINT = {
fn: Tooltip,
params: []
};