diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.expect.md new file mode 100644 index 0000000000..0ae7395500 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +function Component(props) { + let x; + let i = 0; + do { + if (i > 10) { + x = 10; + } else { + x = 1; + } + i++; + } while (i < props.test); + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is affected by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + + let i = 0; + do { + let x = undefined; + if (i > 10) { + x = 10; + } else { + x = 1; + } + + i++; + } while (i < props.test); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.js new file mode 100644 index 0000000000..8c1382e4a1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.js @@ -0,0 +1,22 @@ +function Component(props) { + let x; + let i = 0; + do { + if (i > 10) { + x = 10; + } else { + x = 1; + } + i++; + } while (i < props.test); + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is affected by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.expect.md new file mode 100644 index 0000000000..922fb3e3bb --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +function Component(props) { + let x; + for (let i = props.init; i < 10; i++) { + if (i === 0) { + x = 0; + break; + } else { + x = 1; + break; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose initial value `props.init` is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ init: 0 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + for (const i = props.init; i < 10; ) { + if (i === 0) { + x = 0; + break; + } else { + x = 1; + break; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ init: 0 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.js new file mode 100644 index 0000000000..11c0bb101b --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-init.js @@ -0,0 +1,21 @@ +function Component(props) { + let x; + for (let i = props.init; i < 10; i++) { + if (i === 0) { + x = 0; + break; + } else { + x = 1; + break; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose initial value `props.init` is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ init: 0 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.expect.md new file mode 100644 index 0000000000..b754aff7dc --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +function Component(props) { + let x; + for (let i = 0; i < props.test; i++) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is capped by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + for (let i = 0; i < props.test; i++) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.js new file mode 100644 index 0000000000..1f16801221 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-test.js @@ -0,0 +1,20 @@ +function Component(props) { + let x; + for (let i = 0; i < props.test; i++) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is capped by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.expect.md new file mode 100644 index 0000000000..5ae53a1e6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +function Component(props) { + let x; + for (let i = 0; i < 10; i += props.update) { + if (i > 0 && i % 2 === 0) { + x = 2; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose possible values are + // affected by `props.update` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ update: 2 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + for (let i = 0; i < 10; i = i + props.update, i) { + if (i > 0 && i % 2 === 0) { + x = 2; + } else { + x = 1; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ update: 2 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.js new file mode 100644 index 0000000000..bc6e7e0033 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-for-update.js @@ -0,0 +1,20 @@ +function Component(props) { + let x; + for (let i = 0; i < 10; i += props.update) { + if (i > 0 && i % 2 === 0) { + x = 2; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose possible values are + // affected by `props.update` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ update: 2 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.expect.md new file mode 100644 index 0000000000..d2c917a468 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.expect.md @@ -0,0 +1,60 @@ + +## Input + +```javascript +function Component(props) { + let x; + for (const key in props.values) { + const i = parseInt(key, 10); + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is derived from + // `props.values` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: { "12": true } }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + for (const key in props.values) { + const i = parseInt(key, 10); + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: { "12": true } }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.js new file mode 100644 index 0000000000..870c5085fc --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forin-collection.js @@ -0,0 +1,21 @@ +function Component(props) { + let x; + for (const key in props.values) { + const i = parseInt(key, 10); + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is derived from + // `props.values` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: { "12": true } }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.expect.md new file mode 100644 index 0000000000..fb0b473b67 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +function Component(props) { + let x; + for (const i of props.values) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is derived from + // `props.values` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: [12] }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + for (const i of props.values) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: [12] }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.js new file mode 100644 index 0000000000..18a5e720d3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-forof-collection.js @@ -0,0 +1,20 @@ +function Component(props) { + let x; + for (const i of props.values) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is derived from + // `props.values` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ values: [12] }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.expect.md new file mode 100644 index 0000000000..c7fb89fe9e --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +function Component(props) { + let x; + if (props.cond) { + x = 1; + } else { + x = 2; + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.cond` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x = undefined; + if (props.cond) { + x = 1; + } else { + x = 2; + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.js new file mode 100644 index 0000000000..436328d817 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.js @@ -0,0 +1,17 @@ +function Component(props) { + let x; + if (props.cond) { + x = 1; + } else { + x = 2; + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.cond` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.expect.md new file mode 100644 index 0000000000..700da688d0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.expect.md @@ -0,0 +1,78 @@ + +## Input + +```javascript +function Component(props) { + let x = 0; + + let value = null; + loop: for (let i = 0; i < 10; i++) { + switch (value) { + case true: { + x = 1; + break loop; + } + case false: { + x = 2; + break loop; + } + } + + value = props.value; + } + + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `value` used as the switch test + // condition. That variable is initially null on the first iteration + // of the loop, but is later set to `props.value` which is reactive. + // Therefore x should be treated as reactive. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x = 0; + + let value = null; + for (let i = 0; i < 10; i++) { + switch (value) { + case true: { + x = 1; + break; + } + case false: { + x = 2; + break; + } + } + + value = props.value; + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.js new file mode 100644 index 0000000000..f6c90d6906 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-reactive-after-fixpoint.js @@ -0,0 +1,31 @@ +function Component(props) { + let x = 0; + + let value = null; + loop: for (let i = 0; i < 10; i++) { + switch (value) { + case true: { + x = 1; + break loop; + } + case false: { + x = 2; + break loop; + } + } + + value = props.value; + } + + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `value` used as the switch test + // condition. That variable is initially null on the first iteration + // of the loop, but is later set to `props.value` which is reactive. + // Therefore x should be treated as reactive. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.expect.md new file mode 100644 index 0000000000..306369b10e --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.expect.md @@ -0,0 +1,69 @@ + +## Input + +```javascript +function Component(props) { + let x; + switch (props.cond) { + case true: { + x = 1; + break; + } + case false: { + x = 2; + break; + } + default: { + x = 3; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.cond` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x = undefined; + bb1: switch (props.cond) { + case true: { + x = 1; + break bb1; + } + case false: { + x = 2; + break bb1; + } + default: { + x = 3; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.js new file mode 100644 index 0000000000..cde6e913e7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.js @@ -0,0 +1,25 @@ +function Component(props) { + let x; + switch (props.cond) { + case true: { + x = 1; + break; + } + case false: { + x = 2; + break; + } + default: { + x = 3; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.cond` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.expect.md new file mode 100644 index 0000000000..e17147c356 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.expect.md @@ -0,0 +1,68 @@ + +## Input + +```javascript +const GLOBAL = 42; + +function Component({ value }) { + let x; + switch (GLOBAL) { + case value: { + x = 1; + break; + } + default: { + x = 2; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.value` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: GLOBAL }], + // TODO: test executing the sequence {value: GLOBAL}, {value: null}, {value: GLOBAL} +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +const GLOBAL = 42; + +function Component(t14) { + const $ = useMemoCache(1); + const { value } = t14; + let x = undefined; + bb1: switch (GLOBAL) { + case value: { + x = 1; + break bb1; + } + default: { + x = 2; + } + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: GLOBAL }], + // TODO: test executing the sequence {value: GLOBAL}, {value: null}, {value: GLOBAL} +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.js new file mode 100644 index 0000000000..b11940cbbe --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.js @@ -0,0 +1,24 @@ +const GLOBAL = 42; + +function Component({ value }) { + let x; + switch (GLOBAL) { + case value: { + x = 1; + break; + } + default: { + x = 2; + } + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" value `props.value` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: GLOBAL }], + // TODO: test executing the sequence {value: GLOBAL}, {value: null}, {value: GLOBAL} +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.expect.md new file mode 100644 index 0000000000..f9d853ddeb --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.expect.md @@ -0,0 +1,63 @@ + +## Input + +```javascript +function Component(props) { + let x; + let i = 0; + while (i < props.test) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + i++; + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is affected by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x; + let i = 0; + while (i < props.test) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + + i++; + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.js new file mode 100644 index 0000000000..26061e12a4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-while-test.js @@ -0,0 +1,22 @@ +function Component(props) { + let x; + let i = 0; + while (i < props.test) { + if (i > 10) { + x = 10; + } else { + x = 1; + } + i++; + } + // The values assigned to `x` are non-reactive, but the value of `x` + // depends on the "control" variable `i`, whose value is affected by + // `props.test` which is reactive. + // Therefore x should be treated as reactive too. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ test: 12 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.expect.md new file mode 100644 index 0000000000..0be5812f5e --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.expect.md @@ -0,0 +1,56 @@ + +## Input + +```javascript +function Component(props) { + let x = 0; + let y = 0; + + while (x === 0) { + x = y; + y = props.value; + } + + // x and y initially start out with non-reactive values, + // but after an iteration of the loop y becomes reactive, + // and this reactive value then flows into x on the next + // loop iteration, making x reactive. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x = 0; + let y = 0; + while (x === 0) { + x = y; + y = props.value; + } + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [x]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.js new file mode 100644 index 0000000000..0c00f61e55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-dependency-fixpoint.js @@ -0,0 +1,20 @@ +function Component(props) { + let x = 0; + let y = 0; + + while (x === 0) { + x = y; + y = props.value; + } + + // x and y initially start out with non-reactive values, + // but after an iteration of the loop y becomes reactive, + // and this reactive value then flows into x on the next + // loop iteration, making x reactive. + return [x]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], +}; diff --git a/compiler/packages/sprout/src/SproutTodoFilter.ts b/compiler/packages/sprout/src/SproutTodoFilter.ts index 94b04a9680..88f7a408b2 100644 --- a/compiler/packages/sprout/src/SproutTodoFilter.ts +++ b/compiler/packages/sprout/src/SproutTodoFilter.ts @@ -416,6 +416,8 @@ const skipFilter = new Set([ "readonly-object-method-calls", "readonly-object-method-calls-mutable-lambda", + "bug.reactive-control-dependency-do-while-test", + // TODO: 🌲 "forest-basic",