mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 06:39:17 +00:00
fix(react_compiler): disable exhaustive memo validation by default (#25417)
Disable exhaustive memo dependency validation by default and align suppression handling. AI-assisted. <sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
This commit is contained in:
1 parent
ad4479ea28
commit
937825c6a6
18 files changed
+87
-67
No files matched your search
@@ -77,16 +77,6 @@ source: crates/oxc_linter/src/tester.rs
|
||||
╰────
|
||||
help: Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)
|
||||
|
||||
⚠ react(react-compiler): MemoDependencies: Found extra memoization dependencies
|
||||
╭─[react_compiler.tsx:13:19]
|
||||
12 │ }
|
||||
13 │ }, [cond, item, init]);
|
||||
· ──┬─
|
||||
· ╰── Unnecessary dependency `init`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change
|
||||
14 │
|
||||
╰────
|
||||
help: Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often
|
||||
|
||||
⚠ react(react-compiler): Hooks: 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)
|
||||
╭─[react_compiler.tsx:4:5]
|
||||
3 │ if (cond) {
|
||||
|
||||
@@ -72,6 +72,7 @@ pub struct EnvironmentConfig {
|
||||
|
||||
pub enable_preserve_existing_memoization_guarantees: bool,
|
||||
pub validate_preserve_existing_memoization_guarantees: bool,
|
||||
/// Exhaustive manual memo dependency validation, disabled by default.
|
||||
pub validate_exhaustive_memoization_dependencies: bool,
|
||||
pub validate_exhaustive_effect_dependencies: ExhaustiveEffectDepsMode,
|
||||
|
||||
@@ -124,7 +125,7 @@ impl Default for EnvironmentConfig {
|
||||
module_type_provider: None,
|
||||
enable_preserve_existing_memoization_guarantees: true,
|
||||
validate_preserve_existing_memoization_guarantees: true,
|
||||
validate_exhaustive_memoization_dependencies: true,
|
||||
validate_exhaustive_memoization_dependencies: false,
|
||||
validate_exhaustive_effect_dependencies: ExhaustiveEffectDepsMode::Off,
|
||||
enable_optional_dependencies: true,
|
||||
enable_name_anonymous_functions: false,
|
||||
|
||||
+5
-16
@@ -2,19 +2,8 @@
|
||||
source: crates/oxc_react_compiler/tests/snapshot.rs
|
||||
input_file: crates/oxc_react_compiler/fixtures/default-suppression-eslint-block-range.js
|
||||
---
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
function Component(t0) {
|
||||
const $ = _c(2);
|
||||
const { value } = t0;
|
||||
const doubled = value * 2;
|
||||
let t1;
|
||||
if ($[0] !== doubled) {
|
||||
t1 = <div>{doubled}</div>;
|
||||
$[0] = doubled;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
Diagnostics:
|
||||
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] Suppression: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled", labels: One([LabeledSpan { label: Some("Found React rule suppression"), span: SourceSpan { offset: SourceOffset(0), length: 47 }, primary: false }]), help: Some("React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
No changes.
|
||||
+5
-15
@@ -2,18 +2,8 @@
|
||||
source: crates/oxc_react_compiler/tests/snapshot.rs
|
||||
input_file: crates/oxc_react_compiler/fixtures/default-suppression-eslint-next-line.js
|
||||
---
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(t0) {
|
||||
const $ = _c(2);
|
||||
const { value } = t0;
|
||||
const doubled = value * 2;
|
||||
let t1;
|
||||
if ($[0] !== doubled) {
|
||||
t1 = <div>{doubled}</div>;
|
||||
$[0] = doubled;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
Diagnostics:
|
||||
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] Suppression: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled", labels: One([LabeledSpan { label: Some("Found React rule suppression"), span: SourceSpan { offset: SourceOffset(34), length: 55 }, primary: false }]), help: Some("React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps`"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
No changes.
|
||||
+1
-1
@@ -6,6 +6,6 @@ Diagnostics:
|
||||
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] VoidUseMemo: useMemo() callbacks must return a value", labels: One([LabeledSpan { label: Some("useMemo() callbacks must return a value"), span: SourceSpan { offset: SourceOffset(51), length: 30 }, primary: false }]), help: Some("This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] UseMemo: useMemo() callbacks may not be async or generator functions", labels: One([LabeledSpan { label: Some("Async and generator functions are not supported"), span: SourceSpan { offset: SourceOffset(51), length: 30 }, primary: false }]), help: Some("useMemo() callbacks are called once and must synchronously return a value"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] MemoDependencies: Found missing memoization dependencies", labels: One([LabeledSpan { label: Some("Missing dependency `a`"), span: SourceSpan { offset: SourceOffset(75), length: 1 }, primary: false }]), help: Some("Missing dependencies can cause a value to update less often than it should, resulting in stale UI"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] PreserveManualMemo: Existing memoization could not be preserved", labels: One([LabeledSpan { label: Some("Could not preserve existing manual memoization"), span: SourceSpan { offset: SourceOffset(51), length: 30 }, primary: false }]), help: Some("React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
No changes.
|
||||
-1
@@ -7,6 +7,5 @@ Diagnostics:
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] VoidUseMemo: useMemo() callbacks must return a value", labels: One([LabeledSpan { label: Some("useMemo() callbacks must return a value"), span: SourceSpan { offset: SourceOffset(137), length: 77 }, primary: false }]), help: Some("This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] RenderSetState: Calling setState from useMemo may trigger an infinite loop", labels: One([LabeledSpan { label: Some("Found setState() within useMemo()"), span: SourceSpan { offset: SourceOffset(167), length: 11 }, primary: false }]), help: Some("Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] RenderSetState: Calling setState from useMemo may trigger an infinite loop", labels: One([LabeledSpan { label: Some("Found setState() within useMemo()"), span: SourceSpan { offset: SourceOffset(192), length: 8 }, primary: false }]), help: Some("Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] MemoDependencies: Found missing/extra memoization dependencies", labels: Many([LabeledSpan { label: Some("Missing dependency `item`"), span: SourceSpan { offset: SourceOffset(179), length: 4 }, primary: false }, LabeledSpan { label: Some("Unnecessary dependency `key`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change"), span: SourceSpan { offset: SourceOffset(223), length: 3 }, primary: false }, LabeledSpan { label: Some("Unnecessary dependency `init`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change"), span: SourceSpan { offset: SourceOffset(228), length: 4 }, primary: false }]), help: Some("Missing dependencies can cause a value to update less often than it should, resulting in stale UI. Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
No changes.
|
||||
-2
@@ -6,7 +6,5 @@ Diagnostics:
|
||||
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] VoidUseMemo: useMemo() callbacks must return a value", labels: One([LabeledSpan { label: Some("useMemo() callbacks must return a value"), span: SourceSpan { offset: SourceOffset(257), length: 21 }, primary: false }]), help: Some("This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] RenderSetState: Calling setState from useMemo may trigger an infinite loop", labels: One([LabeledSpan { label: Some("Found setState() within useMemo()"), span: SourceSpan { offset: SourceOffset(269), length: 2 }, primary: false }]), help: Some("Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] MemoDependencies: Found missing memoization dependencies", labels: Two([LabeledSpan { label: Some("Missing dependency `init`"), span: SourceSpan { offset: SourceOffset(233), length: 4 }, primary: false }, LabeledSpan { label: Some("Missing dependency `key`"), span: SourceSpan { offset: SourceOffset(214), length: 3 }, primary: false }]), help: Some("Missing dependencies can cause a value to update less often than it should, resulting in stale UI"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] MemoDependencies: Found missing/extra memoization dependencies", labels: Many([LabeledSpan { label: Some("Missing dependency `fn`"), span: SourceSpan { offset: SourceOffset(269), length: 2 }, primary: false }, LabeledSpan { label: Some("Unnecessary dependency `key`"), span: SourceSpan { offset: SourceOffset(281), length: 3 }, primary: false }, LabeledSpan { label: Some("Unnecessary dependency `init`"), span: SourceSpan { offset: SourceOffset(286), length: 4 }, primary: false }]), help: Some("Missing dependencies can cause a value to update less often than it should, resulting in stale UI. Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
No changes.
|
||||
@@ -6,6 +6,6 @@ Diagnostics:
|
||||
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] VoidUseMemo: useMemo() callbacks must return a value", labels: One([LabeledSpan { label: Some("useMemo() callbacks must return a value"), span: SourceSpan { offset: SourceOffset(45), length: 30 }, primary: false }]), help: Some("This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] UseMemo: useMemo() callbacks may not be async or generator functions", labels: One([LabeledSpan { label: Some("Async and generator functions are not supported"), span: SourceSpan { offset: SourceOffset(45), length: 30 }, primary: false }]), help: Some("useMemo() callbacks are called once and must synchronously return a value"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] MemoDependencies: Found missing memoization dependencies", labels: One([LabeledSpan { label: Some("Missing dependency `a`"), span: SourceSpan { offset: SourceOffset(69), length: 1 }, primary: false }]), help: Some("Missing dependencies can cause a value to update less often than it should, resulting in stale UI"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] PreserveManualMemo: Existing memoization could not be preserved", labels: One([LabeledSpan { label: Some("Could not preserve existing manual memoization"), span: SourceSpan { offset: SourceOffset(45), length: 30 }, primary: false }]), help: Some("React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
No changes.
|
||||
@@ -5,6 +5,6 @@ input_file: crates/oxc_react_compiler/fixtures/error.invalid-useMemo-callback-ar
|
||||
Diagnostics:
|
||||
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] UseMemo: useMemo() callbacks may not accept parameters", labels: One([LabeledSpan { label: Some("Callbacks with parameters are not supported"), span: SourceSpan { offset: SourceOffset(45), length: 1 }, primary: false }]), help: Some("useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] MemoDependencies: Found missing memoization dependencies", labels: One([LabeledSpan { label: Some("Missing dependency `a`"), span: SourceSpan { offset: SourceOffset(50), length: 1 }, primary: false }]), help: Some("Missing dependencies can cause a value to update less often than it should, resulting in stale UI"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] PreserveManualMemo: Existing memoization could not be preserved", labels: One([LabeledSpan { label: Some("Could not preserve existing manual memoization"), span: SourceSpan { offset: SourceOffset(45), length: 6 }, primary: false }]), help: Some("React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
No changes.
|
||||
+1
-1
@@ -4,6 +4,6 @@ input_file: crates/oxc_react_compiler/fixtures/exhaustive-deps/error.sketchy-cod
|
||||
---
|
||||
Diagnostics:
|
||||
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] MemoDependencies: Found missing memoization dependencies", labels: One([LabeledSpan { label: Some("Missing dependency `item`"), span: SourceSpan { offset: SourceOffset(186), length: 4 }, primary: false }]), help: Some("Missing dependencies can cause a value to update less often than it should, resulting in stale UI"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] Suppression: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled", labels: One([LabeledSpan { label: Some("Found React rule suppression"), span: SourceSpan { offset: SourceOffset(203), length: 55 }, primary: false }]), help: Some("React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps`"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
No changes.
|
||||
+1
-1
@@ -4,6 +4,6 @@ input_file: crates/oxc_react_compiler/fixtures/preserve-memo-validation/error.us
|
||||
---
|
||||
Diagnostics:
|
||||
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] MemoDependencies: Found missing memoization dependencies", labels: One([LabeledSpan { label: Some("Missing dependency `x`"), span: SourceSpan { offset: SourceOffset(673), length: 1 }, primary: false }]), help: Some("Missing dependencies can cause a value to update less often than it should, resulting in stale UI"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] PreserveManualMemo: Existing memoization could not be preserved", labels: One([LabeledSpan { label: Some("Could not preserve existing manual memoization"), span: SourceSpan { offset: SourceOffset(635), length: 27 }, primary: false }]), help: Some("React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `input1`, but the source dependencies were [y]. Inferred different dependency than source"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
No changes.
|
||||
+1
-1
@@ -4,7 +4,7 @@ input_file: crates/oxc_react_compiler/fixtures/use-no-forget-multiple-with-eslin
|
||||
---
|
||||
Diagnostics:
|
||||
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] Refs: Cannot access refs during render", labels: One([LabeledSpan { label: Some("Cannot update ref during render"), span: SourceSpan { offset: SourceOffset(233), length: 11 }, primary: false }]), help: Some("React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] Suppression: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled", labels: One([LabeledSpan { label: Some("Found React rule suppression"), span: SourceSpan { offset: SourceOffset(176), length: 54 }, primary: false }]), help: Some("React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks`"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
import { useRef } from "react";
|
||||
const useControllableState = (options) => {};
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ input_file: crates/oxc_react_compiler/fixtures/use-no-forget-with-eslint-suppres
|
||||
---
|
||||
Diagnostics:
|
||||
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] Refs: Cannot access refs during render", labels: One([LabeledSpan { label: Some("Cannot update ref during render"), span: SourceSpan { offset: SourceOffset(160), length: 11 }, primary: false }]), help: Some("React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
OxcDiagnostic { inner: OxcDiagnosticInner { message: "[ReactCompiler] Suppression: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled", labels: One([LabeledSpan { label: Some("Found React rule suppression"), span: SourceSpan { offset: SourceOffset(103), length: 54 }, primary: false }]), help: Some("React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks`"), note: None, severity: Error, code: OxcCode { scope: None, number: None }, url: None } }
|
||||
|
||||
import { useRef } from "react";
|
||||
function Component() {
|
||||
|
||||
@@ -115,7 +115,7 @@ fn skips_non_react_code() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_eslint_suppressions_do_not_bail_out() {
|
||||
fn default_eslint_suppressions_bail_out() {
|
||||
let fixtures = [
|
||||
(
|
||||
"eslint-disable-next-line",
|
||||
@@ -132,13 +132,10 @@ fn default_eslint_suppressions_do_not_bail_out() {
|
||||
let (_program, result) =
|
||||
transform_source(source, SourceType::tsx(), &allocator, PluginOptions::default());
|
||||
|
||||
assert!(result.changed, "{kind} must not prevent compilation");
|
||||
assert!(!result.changed, "{kind} must prevent compilation");
|
||||
assert!(!result.fatal, "{kind} must not produce a fatal result");
|
||||
assert!(
|
||||
!result.diagnostics.has_errors(),
|
||||
"{kind} produced unexpected diagnostics: {:?}",
|
||||
result.diagnostics
|
||||
);
|
||||
assert_eq!(result.diagnostics.len(), 1);
|
||||
assert!(result.diagnostics[0].message.contains("[ReactCompiler] Suppression"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +158,7 @@ fn eslint_suppressions_bail_out_when_either_internal_validation_is_disabled() {
|
||||
|
||||
for disabled_validation in ["memo dependencies", "hooks usage"] {
|
||||
let mut options = PluginOptions::default();
|
||||
options.environment.validate_exhaustive_memoization_dependencies = true;
|
||||
match disabled_validation {
|
||||
"memo dependencies" => {
|
||||
options.environment.validate_exhaustive_memoization_dependencies = false;
|
||||
@@ -219,9 +217,10 @@ function Component({ condition }) {
|
||||
];
|
||||
|
||||
for (kind, source, expected_category) in cases {
|
||||
let mut options = PluginOptions::default();
|
||||
options.environment.validate_exhaustive_memoization_dependencies = true;
|
||||
let allocator = Allocator::default();
|
||||
let (_program, result) =
|
||||
transform_source(source, SourceType::tsx(), &allocator, PluginOptions::default());
|
||||
let (_program, result) = transform_source(source, SourceType::tsx(), &allocator, options);
|
||||
|
||||
assert!(!result.changed, "{kind} validation must prevent compilation");
|
||||
assert_eq!(result.diagnostics.len(), 1);
|
||||
@@ -249,19 +248,19 @@ function Component({ value }) {
|
||||
..PluginOptions::default()
|
||||
};
|
||||
let (_program, result) = transform_source(source, SourceType::tsx(), &allocator, options);
|
||||
assert!(result.changed, "custom suppression must be ignored with both validations enabled");
|
||||
assert!(!result.diagnostics.has_errors());
|
||||
assert!(!result.changed, "custom suppression must bail out by default");
|
||||
assert_eq!(result.diagnostics.len(), 1);
|
||||
assert!(result.diagnostics[0].message.contains("[ReactCompiler] Suppression"));
|
||||
|
||||
let allocator = Allocator::default();
|
||||
let mut options = PluginOptions {
|
||||
eslint_suppression_rules: Some(vec!["custom/react-rule".to_string()]),
|
||||
..PluginOptions::default()
|
||||
};
|
||||
options.environment.validate_exhaustive_memoization_dependencies = false;
|
||||
options.environment.validate_exhaustive_memoization_dependencies = true;
|
||||
let (_program, result) = transform_source(source, SourceType::tsx(), &allocator, options);
|
||||
assert!(!result.changed, "custom suppression must bail out when validation is disabled");
|
||||
assert_eq!(result.diagnostics.len(), 1);
|
||||
assert!(result.diagnostics[0].message.contains("[ReactCompiler] Suppression"));
|
||||
assert!(result.changed, "custom suppression must be ignored with both validations enabled");
|
||||
assert!(!result.diagnostics.has_errors());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Vendored
+5
@@ -122,6 +122,11 @@ export interface ReactCompilerEnvironmentOptions {
|
||||
enableResetCacheOnSourceFileChanges?: boolean
|
||||
enablePreserveExistingMemoizationGuarantees?: boolean
|
||||
validatePreserveExistingMemoizationGuarantees?: boolean
|
||||
/**
|
||||
* Enable exhaustive manual memo dependency validation.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
validateExhaustiveMemoizationDependencies?: boolean
|
||||
validateExhaustiveEffectDependencies?: 'off' | 'all' | 'missing-only' | 'extra-only'
|
||||
enableOptionalDependencies?: boolean
|
||||
|
||||
@@ -228,6 +228,9 @@ pub struct ReactCompilerEnvironmentOptions {
|
||||
pub enable_reset_cache_on_source_file_changes: Option<bool>,
|
||||
pub enable_preserve_existing_memoization_guarantees: Option<bool>,
|
||||
pub validate_preserve_existing_memoization_guarantees: Option<bool>,
|
||||
/// Enable exhaustive manual memo dependency validation.
|
||||
///
|
||||
/// @default false
|
||||
pub validate_exhaustive_memoization_dependencies: Option<bool>,
|
||||
#[napi(ts_type = "'off' | 'all' | 'missing-only' | 'extra-only'")]
|
||||
pub validate_exhaustive_effect_dependencies: Option<String>,
|
||||
|
||||
@@ -174,6 +174,13 @@ describe("transformSync", () => {
|
||||
const doubled: number = value * 2;
|
||||
return <div>{doubled}</div>;
|
||||
}`,
|
||||
{
|
||||
reactCompiler: {
|
||||
environment: {
|
||||
validateExhaustiveMemoizationDependencies: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.errors).toEqual([]);
|
||||
@@ -181,6 +188,22 @@ describe("transformSync", () => {
|
||||
expect(result.code).toContain("_c(");
|
||||
});
|
||||
|
||||
it("honors ESLint suppressions by default", () => {
|
||||
const result = transformSync(
|
||||
"Component.tsx",
|
||||
`function Component({ value }: { value: number }) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const doubled = value * 2;
|
||||
return <div>{doubled}</div>;
|
||||
}`,
|
||||
);
|
||||
|
||||
expect(result.fatal).toBe(false);
|
||||
expect(result.errors).toHaveLength(1);
|
||||
expect(result.errors[0].message).toContain("[ReactCompiler] Suppression:");
|
||||
expect(result.code).not.toContain("react/compiler-runtime");
|
||||
});
|
||||
|
||||
it("honors ESLint suppressions when internal validations are disabled", () => {
|
||||
const result = transformSync(
|
||||
"Component.tsx",
|
||||
@@ -235,6 +258,24 @@ describe("transformSync", () => {
|
||||
expect(result.code).not.toContain("<span");
|
||||
});
|
||||
|
||||
it("does not enable manual memo dependency validation by default", () => {
|
||||
const result = transformSync(
|
||||
"Component.tsx",
|
||||
`import { useMemo } from "react";
|
||||
import { typedCapture, typedCreateFrom, typedMutate, ValidateMemoization } from "shared-runtime";
|
||||
function Component({ a, b }: { a: number; b: number }) {
|
||||
const x = useMemo(() => ({ a }), [a, b]);
|
||||
const y = typedCapture(x);
|
||||
const z = typedCreateFrom(y);
|
||||
typedMutate(z, b);
|
||||
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||
}`,
|
||||
);
|
||||
|
||||
expect(result.errors).toEqual([]);
|
||||
expect(result.code).toContain("react/compiler-runtime");
|
||||
});
|
||||
|
||||
it("compiles an unsuppressed sibling after a suppression bailout", () => {
|
||||
const result = transformSync(
|
||||
"Components.tsx",
|
||||
|
||||
@@ -122,6 +122,11 @@ export interface ReactCompilerEnvironmentOptions {
|
||||
enableResetCacheOnSourceFileChanges?: boolean
|
||||
enablePreserveExistingMemoizationGuarantees?: boolean
|
||||
validatePreserveExistingMemoizationGuarantees?: boolean
|
||||
/**
|
||||
* Enable exhaustive manual memo dependency validation.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
validateExhaustiveMemoizationDependencies?: boolean
|
||||
validateExhaustiveEffectDependencies?: 'off' | 'all' | 'missing-only' | 'extra-only'
|
||||
enableOptionalDependencies?: boolean
|
||||
|
||||
Reference in new issue
Block a user