diff --git a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts index 015df1a722..5d39e97033 100644 --- a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -504,7 +504,7 @@ function codegenReactiveScope( if (testCondition === null) { CompilerError.invariant(firstOutputIndex !== null, { reason: `Expected scope to have at least one declaration`, - description: `Scope '@{scope.id} has no declarations`, + description: `Scope '@${scope.id}' has no declarations`, loc: null, suggestions: null, }); diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md new file mode 100644 index 0000000000..335b09d320 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -0,0 +1,121 @@ + +## Input + +```javascript +// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions +function Component() { + const items = useItems(); + const filteredItems = useMemo( + () => + items.filter(([item]) => { + return item.name != null; + }), + [item] + ); + + if (filteredItems.length === 0) { + // note: this must return nested JSX to create the right scope + // shape that causes no declarations to be emitted + return ( +
+ +
+ ); + } + + return ( + <> + {filteredItems.map(([item]) => ( + + ))} + + ); +} + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions +function Component() { + const $ = useMemoCache(9); + const items = useItems(); + let t0; + let t1; + let t2; + if ($[0] !== items) { + t2 = Symbol.for("react.early_return_sentinel"); + bb14: { + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (t4) => { + const [item] = t4; + return item.name != null; + }; + $[4] = t3; + } else { + t3 = $[4]; + } + t0 = items.filter(t3); + const filteredItems = t0; + if (filteredItems.length === 0) { + let t4; + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { + t4 = ( +
+ +
+ ); + $[5] = t4; + } else { + t4 = $[5]; + } + t2 = t4; + break bb14; + } + let t4; + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { + t4 = (t5) => { + const [item_0] = t5; + return ; + }; + $[6] = t4; + } else { + t4 = $[6]; + } + t1 = filteredItems.map(t4); + } + $[0] = items; + $[1] = t1; + $[2] = t2; + $[3] = t0; + } else { + t1 = $[1]; + t2 = $[2]; + t0 = $[3]; + } + if (t2 !== Symbol.for("react.early_return_sentinel")) { + return t2; + } + let t3; + if ($[7] !== t1) { + t3 = <>{t1}; + $[7] = t1; + $[8] = t3; + } else { + t3 = $[8]; + } + return t3; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented! +logs: ['The above error occurred in the component:\n' + + '\n' + + ' at WrapperTestComponent (/packages/snap/dist/sprout/evaluator.js:54:26)\n' + + '\n' + + 'Consider adding an error boundary to your tree to customize error handling behavior.\n' + + 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.'] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.js new file mode 100644 index 0000000000..1d01c20993 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.js @@ -0,0 +1,29 @@ +// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions +function Component() { + const items = useItems(); + const filteredItems = useMemo( + () => + items.filter(([item]) => { + return item.name != null; + }), + [item] + ); + + if (filteredItems.length === 0) { + // note: this must return nested JSX to create the right scope + // shape that causes no declarations to be emitted + return ( +
+ +
+ ); + } + + return ( + <> + {filteredItems.map(([item]) => ( + + ))} + + ); +}