From 19fc786de940684331023b6e4a655418b5419daa Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:23:43 +0900 Subject: [PATCH 1/2] test(rsc): migrate hoist output fixtures Co-authored-by: OpenCode --- .../src/transforms/fixtures/hoist/arrow.js | 13 + .../fixtures/hoist/arrow.js.snap.encode.js | 17 + .../fixtures/hoist/arrow.js.snap.js | 16 + .../src/transforms/fixtures/hoist/closure.js | 12 + .../fixtures/hoist/closure.js.snap.encode.js | 16 + .../fixtures/hoist/closure.js.snap.js | 15 + .../fixtures/hoist/export-before-import.js | 10 + .../export-before-import.js.snap.encode.js | 13 + .../hoist/export-before-import.js.snap.js | 13 + .../transforms/fixtures/hoist/higher-order.js | 15 + .../hoist/higher-order.js.snap.encode.js | 23 ++ .../fixtures/hoist/higher-order.js.snap.js | 21 ++ .../src/transforms/fixtures/hoist/multiple.js | 17 + .../fixtures/hoist/multiple.js.snap.encode.js | 25 ++ .../fixtures/hoist/multiple.js.snap.js | 23 ++ .../transforms/fixtures/hoist/top-level.js | 17 + .../hoist/top-level.js.snap.encode.js | 27 ++ .../fixtures/hoist/top-level.js.snap.js | 27 ++ .../plugin-rsc/src/transforms/hoist.test.ts | 327 +----------------- 19 files changed, 333 insertions(+), 314 deletions(-) create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js.snap.encode.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js.snap.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js.snap.encode.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js.snap.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js.snap.encode.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js.snap.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js.snap.encode.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js.snap.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js.snap.encode.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js.snap.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js.snap.encode.js create mode 100644 packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js.snap.js diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js new file mode 100644 index 000000000..bd9fc6f5a --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js @@ -0,0 +1,13 @@ +let count = 0 + +function Counter() { + const name = 'value' + + return { + type: 'form', + action: (formData) => { + 'use server' + count += Number(formData.get(name)) + }, + } +} diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js.snap.encode.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js.snap.encode.js new file mode 100644 index 000000000..53f1bf2a9 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js.snap.encode.js @@ -0,0 +1,17 @@ +let count = 0 + +function Counter() { + const name = 'value' + + return { + type: 'form', + action: /* #__PURE__ */ $$register($$hoist_0_anonymous_server_function, "", "$$hoist_0_anonymous_server_function").bind(null, __enc([name])), + } +} + +;export function $$hoist_0_anonymous_server_function($$hoist_encoded, formData) { + const [name] = __dec($$hoist_encoded); +'use server' + count += Number(formData.get(name)) + }; +/* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js.snap.js new file mode 100644 index 000000000..eb6ed0b04 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/arrow.js.snap.js @@ -0,0 +1,16 @@ +let count = 0 + +function Counter() { + const name = 'value' + + return { + type: 'form', + action: /* #__PURE__ */ $$register($$hoist_0_anonymous_server_function, "", "$$hoist_0_anonymous_server_function").bind(null, name), + } +} + +;export function $$hoist_0_anonymous_server_function(name, formData) { + 'use server' + count += Number(formData.get(name)) + }; +/* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js new file mode 100644 index 000000000..c4a90b085 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js @@ -0,0 +1,12 @@ +let count = 0 + +function Counter() { + const name = 'value' + + async function changeCount(formData) { + 'use server' + count += Number(formData.get(name)) + } + + return 'something' +} diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js.snap.encode.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js.snap.encode.js new file mode 100644 index 000000000..6af41deea --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js.snap.encode.js @@ -0,0 +1,16 @@ +let count = 0 + +function Counter() { + const name = 'value' + + const changeCount = /* #__PURE__ */ $$register($$hoist_0_changeCount, "", "$$hoist_0_changeCount").bind(null, __enc([name])); + + return 'something' +} + +;export async function $$hoist_0_changeCount($$hoist_encoded, formData) { + const [name] = __dec($$hoist_encoded); +'use server' + count += Number(formData.get(name)) + }; +/* #__PURE__ */ Object.defineProperty($$hoist_0_changeCount, "name", { value: "changeCount" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js.snap.js new file mode 100644 index 000000000..5893ab328 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/closure.js.snap.js @@ -0,0 +1,15 @@ +let count = 0 + +function Counter() { + const name = 'value' + + const changeCount = /* #__PURE__ */ $$register($$hoist_0_changeCount, "", "$$hoist_0_changeCount").bind(null, name); + + return 'something' +} + +;export async function $$hoist_0_changeCount(name, formData) { + 'use server' + count += Number(formData.get(name)) + }; +/* #__PURE__ */ Object.defineProperty($$hoist_0_changeCount, "name", { value: "changeCount" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js new file mode 100644 index 000000000..be1e7031b --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js @@ -0,0 +1,10 @@ +// https://github.com/remix-run/react-router/blob/98367e49900701c460cb08eb16c2441da5007efc/playground/rsc-vite/src/routes/home/home.tsx +export {} from 'edge-case' +import { redirect } from 'react-router/rsc' + +export default () => { + const redirectOnServer = async () => { + 'use server' + throw redirect() + } +} diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js.snap.encode.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js.snap.encode.js new file mode 100644 index 000000000..73ddb2a51 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js.snap.encode.js @@ -0,0 +1,13 @@ +// https://github.com/remix-run/react-router/blob/98367e49900701c460cb08eb16c2441da5007efc/playground/rsc-vite/src/routes/home/home.tsx +export {} from 'edge-case' +import { redirect } from 'react-router/rsc' + +export default () => { + const redirectOnServer = /* #__PURE__ */ $$register($$hoist_0_redirectOnServer, "", "$$hoist_0_redirectOnServer") +} + +;export async function $$hoist_0_redirectOnServer() { + 'use server' + throw redirect() + }; +/* #__PURE__ */ Object.defineProperty($$hoist_0_redirectOnServer, "name", { value: "redirectOnServer" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js.snap.js new file mode 100644 index 000000000..73ddb2a51 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/export-before-import.js.snap.js @@ -0,0 +1,13 @@ +// https://github.com/remix-run/react-router/blob/98367e49900701c460cb08eb16c2441da5007efc/playground/rsc-vite/src/routes/home/home.tsx +export {} from 'edge-case' +import { redirect } from 'react-router/rsc' + +export default () => { + const redirectOnServer = /* #__PURE__ */ $$register($$hoist_0_redirectOnServer, "", "$$hoist_0_redirectOnServer") +} + +;export async function $$hoist_0_redirectOnServer() { + 'use server' + throw redirect() + }; +/* #__PURE__ */ Object.defineProperty($$hoist_0_redirectOnServer, "name", { value: "redirectOnServer" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js new file mode 100644 index 000000000..870b97879 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js @@ -0,0 +1,15 @@ +// Adapted from React's Next.js server action examples. +export default function Page() { + const x = 0 + const action = validator(async (y) => { + 'use server' + return x + y + }) +} + +function validator(action) { + return async function (arg) { + 'use server' + return action(arg) + } +} diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js.snap.encode.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js.snap.encode.js new file mode 100644 index 000000000..8b7e53f9f --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js.snap.encode.js @@ -0,0 +1,23 @@ +// Adapted from React's Next.js server action examples. +export default function Page() { + const x = 0 + const action = validator(/* #__PURE__ */ $$register($$hoist_0_anonymous_server_function, "", "$$hoist_0_anonymous_server_function").bind(null, __enc([x]))) +} + +function validator(action) { + return /* #__PURE__ */ $$register($$hoist_1_anonymous_server_function, "", "$$hoist_1_anonymous_server_function").bind(null, __enc([action])) +} + +;export async function $$hoist_0_anonymous_server_function($$hoist_encoded, y) { + const [x] = __dec($$hoist_encoded); +'use server' + return x + y + }; +/* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" }); + +;export async function $$hoist_1_anonymous_server_function($$hoist_encoded, arg) { + const [action] = __dec($$hoist_encoded); +'use server' + return action(arg) + }; +/* #__PURE__ */ Object.defineProperty($$hoist_1_anonymous_server_function, "name", { value: "anonymous_server_function" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js.snap.js new file mode 100644 index 000000000..e07758575 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/higher-order.js.snap.js @@ -0,0 +1,21 @@ +// Adapted from React's Next.js server action examples. +export default function Page() { + const x = 0 + const action = validator(/* #__PURE__ */ $$register($$hoist_0_anonymous_server_function, "", "$$hoist_0_anonymous_server_function").bind(null, x)) +} + +function validator(action) { + return /* #__PURE__ */ $$register($$hoist_1_anonymous_server_function, "", "$$hoist_1_anonymous_server_function").bind(null, action) +} + +;export async function $$hoist_0_anonymous_server_function(x, y) { + 'use server' + return x + y + }; +/* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" }); + +;export async function $$hoist_1_anonymous_server_function(action, arg) { + 'use server' + return action(arg) + }; +/* #__PURE__ */ Object.defineProperty($$hoist_1_anonymous_server_function, "name", { value: "anonymous_server_function" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js new file mode 100644 index 000000000..ce9c16521 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js @@ -0,0 +1,17 @@ +let count = 0 + +function Counter() { + const name = 'value' + + async function changeCount(formData) { + 'use server' + count += Number(formData.get(name)) + } + + async function changeCount2(formData) { + 'use server' + count += Number(formData.get(name)) + } + + return 'something' +} diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js.snap.encode.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js.snap.encode.js new file mode 100644 index 000000000..2b794a619 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js.snap.encode.js @@ -0,0 +1,25 @@ +let count = 0 + +function Counter() { + const name = 'value' + + const changeCount = /* #__PURE__ */ $$register($$hoist_0_changeCount, "", "$$hoist_0_changeCount").bind(null, __enc([name])); + + const changeCount2 = /* #__PURE__ */ $$register($$hoist_1_changeCount2, "", "$$hoist_1_changeCount2").bind(null, __enc([name])); + + return 'something' +} + +;export async function $$hoist_0_changeCount($$hoist_encoded, formData) { + const [name] = __dec($$hoist_encoded); +'use server' + count += Number(formData.get(name)) + }; +/* #__PURE__ */ Object.defineProperty($$hoist_0_changeCount, "name", { value: "changeCount" }); + +;export async function $$hoist_1_changeCount2($$hoist_encoded, formData) { + const [name] = __dec($$hoist_encoded); +'use server' + count += Number(formData.get(name)) + }; +/* #__PURE__ */ Object.defineProperty($$hoist_1_changeCount2, "name", { value: "changeCount2" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js.snap.js new file mode 100644 index 000000000..d1c2a9fa5 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/multiple.js.snap.js @@ -0,0 +1,23 @@ +let count = 0 + +function Counter() { + const name = 'value' + + const changeCount = /* #__PURE__ */ $$register($$hoist_0_changeCount, "", "$$hoist_0_changeCount").bind(null, name); + + const changeCount2 = /* #__PURE__ */ $$register($$hoist_1_changeCount2, "", "$$hoist_1_changeCount2").bind(null, name); + + return 'something' +} + +;export async function $$hoist_0_changeCount(name, formData) { + 'use server' + count += Number(formData.get(name)) + }; +/* #__PURE__ */ Object.defineProperty($$hoist_0_changeCount, "name", { value: "changeCount" }); + +;export async function $$hoist_1_changeCount2(name, formData) { + 'use server' + count += Number(formData.get(name)) + }; +/* #__PURE__ */ Object.defineProperty($$hoist_1_changeCount2, "name", { value: "changeCount2" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js new file mode 100644 index 000000000..fe86bc92d --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js @@ -0,0 +1,17 @@ +const x = 'x' + +async function f() { + 'use server' + return x +} + +async function g() {} + +export async function h(formData) { + 'use server' + return formData.get(x) +} + +export default function w() { + 'use server' +} diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js.snap.encode.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js.snap.encode.js new file mode 100644 index 000000000..2f151f924 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js.snap.encode.js @@ -0,0 +1,27 @@ +const x = 'x' + +const f = /* #__PURE__ */ $$register($$hoist_0_f, "", "$$hoist_0_f"); + +async function g() {} + +export const h = /* #__PURE__ */ $$register($$hoist_1_h, "", "$$hoist_1_h"); + +const w = /* #__PURE__ */ $$register($$hoist_2_w, "", "$$hoist_2_w"); +export default w; + +;export async function $$hoist_0_f() { + 'use server' + return x +}; +/* #__PURE__ */ Object.defineProperty($$hoist_0_f, "name", { value: "f" }); + +;export async function $$hoist_1_h(formData) { + 'use server' + return formData.get(x) +}; +/* #__PURE__ */ Object.defineProperty($$hoist_1_h, "name", { value: "h" }); + +;export function $$hoist_2_w() { + 'use server' +}; +/* #__PURE__ */ Object.defineProperty($$hoist_2_w, "name", { value: "w" }); diff --git a/packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js.snap.js new file mode 100644 index 000000000..2f151f924 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/hoist/top-level.js.snap.js @@ -0,0 +1,27 @@ +const x = 'x' + +const f = /* #__PURE__ */ $$register($$hoist_0_f, "", "$$hoist_0_f"); + +async function g() {} + +export const h = /* #__PURE__ */ $$register($$hoist_1_h, "", "$$hoist_1_h"); + +const w = /* #__PURE__ */ $$register($$hoist_2_w, "", "$$hoist_2_w"); +export default w; + +;export async function $$hoist_0_f() { + 'use server' + return x +}; +/* #__PURE__ */ Object.defineProperty($$hoist_0_f, "name", { value: "f" }); + +;export async function $$hoist_1_h(formData) { + 'use server' + return formData.get(x) +}; +/* #__PURE__ */ Object.defineProperty($$hoist_1_h, "name", { value: "h" }); + +;export function $$hoist_2_w() { + 'use server' +}; +/* #__PURE__ */ Object.defineProperty($$hoist_2_w, "name", { value: "w" }); diff --git a/packages/plugin-rsc/src/transforms/hoist.test.ts b/packages/plugin-rsc/src/transforms/hoist.test.ts index 3950f8419..43d48092a 100644 --- a/packages/plugin-rsc/src/transforms/hoist.test.ts +++ b/packages/plugin-rsc/src/transforms/hoist.test.ts @@ -125,7 +125,7 @@ async function f() { return x; } ` - expect(await testTransform(input)).toMatchInlineSnapshot(`undefined`) + expect(await testTransform(input)).toBeUndefined() }) it('ignores strings outside a function directive prologue', async () => { @@ -152,333 +152,32 @@ async function action() { expect(await testTransformNames(input)).toEqual(['$$hoist_0_action']) }) - it('finds directives only in directive-capable bodies', async () => { + it('returns generated names in order', async () => { const input = ` -{ +async function first() { "use server"; } -async function action() { +async function second() { "use server"; } ` - const ast = await parseAstAsync(input) - expect(findDirectives(ast, 'use server')).toHaveLength(1) + expect(await testTransformNames(input)).toEqual([ + '$$hoist_0_first', + '$$hoist_1_second', + ]) }) - it('top level', async () => { + it('finds directives only in directive-capable bodies', async () => { const input = ` -const x = "x"; - -async function f() { - "use server"; - return x; -} - -async function g() { -} - -export async function h(formData) { +{ "use server"; - return formData.get(x); } - -export default function w() { +async function action() { "use server"; } ` - expect(await testTransform(input)).toMatchInlineSnapshot(` - " - const x = "x"; - - const f = /* #__PURE__ */ $$register($$hoist_0_f, "", "$$hoist_0_f"); - - async function g() { - } - - export const h = /* #__PURE__ */ $$register($$hoist_1_h, "", "$$hoist_1_h"); - - const w = /* #__PURE__ */ $$register($$hoist_2_w, "", "$$hoist_2_w"); - export default w; - - ;export async function $$hoist_0_f() { - "use server"; - return x; - }; - /* #__PURE__ */ Object.defineProperty($$hoist_0_f, "name", { value: "f" }); - - ;export async function $$hoist_1_h(formData) { - "use server"; - return formData.get(x); - }; - /* #__PURE__ */ Object.defineProperty($$hoist_1_h, "name", { value: "h" }); - - ;export function $$hoist_2_w() { - "use server"; - }; - /* #__PURE__ */ Object.defineProperty($$hoist_2_w, "name", { value: "w" }); - " - `) - - // nothing to encode - expect(await testTransform(input, { encode: true })).toBe( - await testTransform(input), - ) - - expect(await testTransformNames(input)).toMatchInlineSnapshot(` - [ - "$$hoist_0_f", - "$$hoist_1_h", - "$$hoist_2_w", - ] - `) - }) - - it('closure', async () => { - const input = ` -let count = 0; - -function Counter() { - const name = "value"; - - async function changeCount(formData) { - "use server"; - count += Number(formData.get(name)); - } - - return "something"; -} -` - expect(await testTransform(input)).toMatchInlineSnapshot(` - " - let count = 0; - - function Counter() { - const name = "value"; - - const changeCount = /* #__PURE__ */ $$register($$hoist_0_changeCount, "", "$$hoist_0_changeCount").bind(null, name); - - return "something"; - } - - ;export async function $$hoist_0_changeCount(name, formData) { - "use server"; - count += Number(formData.get(name)); - }; - /* #__PURE__ */ Object.defineProperty($$hoist_0_changeCount, "name", { value: "changeCount" }); - " - `) - }) - - it('many', async () => { - const input = ` -let count = 0; - -function Counter() { - const name = "value"; - - async function changeCount(formData) { - "use server"; - count += Number(formData.get(name)); - } - - async function changeCount2(formData) { - "use server"; - count += Number(formData.get(name)); - } - - return "something"; -} -` - expect(await testTransform(input)).toMatchInlineSnapshot(` - " - let count = 0; - - function Counter() { - const name = "value"; - - const changeCount = /* #__PURE__ */ $$register($$hoist_0_changeCount, "", "$$hoist_0_changeCount").bind(null, name); - - const changeCount2 = /* #__PURE__ */ $$register($$hoist_1_changeCount2, "", "$$hoist_1_changeCount2").bind(null, name); - - return "something"; - } - - ;export async function $$hoist_0_changeCount(name, formData) { - "use server"; - count += Number(formData.get(name)); - }; - /* #__PURE__ */ Object.defineProperty($$hoist_0_changeCount, "name", { value: "changeCount" }); - - ;export async function $$hoist_1_changeCount2(name, formData) { - "use server"; - count += Number(formData.get(name)); - }; - /* #__PURE__ */ Object.defineProperty($$hoist_1_changeCount2, "name", { value: "changeCount2" }); - " - `) - }) - - it('arrow', async () => { - const input = ` -let count = 0; - -function Counter() { - const name = "value"; - - return { - type: "form", - action: (formData) => { - "use server"; - count += Number(formData.get(name)); - } - } -} -` - expect(await testTransform(input)).toMatchInlineSnapshot(` - " - let count = 0; - - function Counter() { - const name = "value"; - - return { - type: "form", - action: /* #__PURE__ */ $$register($$hoist_0_anonymous_server_function, "", "$$hoist_0_anonymous_server_function").bind(null, name) - } - } - - ;export function $$hoist_0_anonymous_server_function(name, formData) { - "use server"; - count += Number(formData.get(name)); - }; - /* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" }); - " - `) - - expect(await testTransform(input, { encode: true })).toMatchInlineSnapshot(` - " - let count = 0; - - function Counter() { - const name = "value"; - - return { - type: "form", - action: /* #__PURE__ */ $$register($$hoist_0_anonymous_server_function, "", "$$hoist_0_anonymous_server_function").bind(null, __enc([name])) - } - } - - ;export function $$hoist_0_anonymous_server_function($$hoist_encoded, formData) { - const [name] = __dec($$hoist_encoded); - "use server"; - count += Number(formData.get(name)); - }; - /* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" }); - " - `) - }) - - it('higher order', async () => { - // packages/react-server/examples/next/app/actions/header/page.tsx - // packages/react-server/examples/next/app/actions/header/validator.ts - const input = ` -export default function Page() { - const x = 0; - const action = validator(async (y) => { - "use server"; - return x + y; - }) -} - -function validator(action) { - return async function (arg) { - "use server"; - return action(arg); - }; -} -` - expect(await testTransform(input)).toMatchInlineSnapshot(` - " - export default function Page() { - const x = 0; - const action = validator(/* #__PURE__ */ $$register($$hoist_0_anonymous_server_function, "", "$$hoist_0_anonymous_server_function").bind(null, x)) - } - - function validator(action) { - return /* #__PURE__ */ $$register($$hoist_1_anonymous_server_function, "", "$$hoist_1_anonymous_server_function").bind(null, action); - } - - ;export async function $$hoist_0_anonymous_server_function(x, y) { - "use server"; - return x + y; - }; - /* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" }); - - ;export async function $$hoist_1_anonymous_server_function(action, arg) { - "use server"; - return action(arg); - }; - /* #__PURE__ */ Object.defineProperty($$hoist_1_anonymous_server_function, "name", { value: "anonymous_server_function" }); - " - `) - - expect(await testTransform(input, { encode: true })).toMatchInlineSnapshot(` - " - export default function Page() { - const x = 0; - const action = validator(/* #__PURE__ */ $$register($$hoist_0_anonymous_server_function, "", "$$hoist_0_anonymous_server_function").bind(null, __enc([x]))) - } - - function validator(action) { - return /* #__PURE__ */ $$register($$hoist_1_anonymous_server_function, "", "$$hoist_1_anonymous_server_function").bind(null, __enc([action])); - } - - ;export async function $$hoist_0_anonymous_server_function($$hoist_encoded, y) { - const [x] = __dec($$hoist_encoded); - "use server"; - return x + y; - }; - /* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" }); - - ;export async function $$hoist_1_anonymous_server_function($$hoist_encoded, arg) { - const [action] = __dec($$hoist_encoded); - "use server"; - return action(arg); - }; - /* #__PURE__ */ Object.defineProperty($$hoist_1_anonymous_server_function, "name", { value: "anonymous_server_function" }); - " - `) - }) - - // edge case found in https://github.com/remix-run/react-router/blob/98367e49900701c460cb08eb16c2441da5007efc/playground/rsc-vite/src/routes/home/home.tsx - it('export before import', async () => { - const input = ` -export {} from "edge-case"; -import { redirect } from "react-router/rsc"; - -export default () => { - const redirectOnServer = async () => { - "use server"; - throw redirect(); - }; -} -` - expect(await testTransform(input)).toMatchInlineSnapshot(` - " - export {} from "edge-case"; - import { redirect } from "react-router/rsc"; - - export default () => { - const redirectOnServer = /* #__PURE__ */ $$register($$hoist_0_redirectOnServer, "", "$$hoist_0_redirectOnServer"); - } - - ;export async function $$hoist_0_redirectOnServer() { - "use server"; - throw redirect(); - }; - /* #__PURE__ */ Object.defineProperty($$hoist_0_redirectOnServer, "name", { value: "redirectOnServer" }); - " - `) + const ast = await parseAstAsync(input) + expect(findDirectives(ast, 'use server')).toHaveLength(1) }) it('noExport', async () => { From da283d5a31c20746774003ae7e056d7de6248223 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:35:19 +0900 Subject: [PATCH 2/2] nit --- .../plugin-rsc/src/transforms/hoist.test.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/plugin-rsc/src/transforms/hoist.test.ts b/packages/plugin-rsc/src/transforms/hoist.test.ts index 43d48092a..97b1deac2 100644 --- a/packages/plugin-rsc/src/transforms/hoist.test.ts +++ b/packages/plugin-rsc/src/transforms/hoist.test.ts @@ -128,6 +128,21 @@ async function f() { expect(await testTransform(input)).toBeUndefined() }) + it('returns generated names in order', async () => { + const input = ` +async function first() { + "use server"; +} +async function second() { + "use server"; +} +` + expect(await testTransformNames(input)).toEqual([ + '$$hoist_0_first', + '$$hoist_1_second', + ]) + }) + it('ignores strings outside a function directive prologue', async () => { const input = ` async function initialized() { @@ -152,21 +167,6 @@ async function action() { expect(await testTransformNames(input)).toEqual(['$$hoist_0_action']) }) - it('returns generated names in order', async () => { - const input = ` -async function first() { - "use server"; -} -async function second() { - "use server"; -} -` - expect(await testTransformNames(input)).toEqual([ - '$$hoist_0_first', - '$$hoist_1_second', - ]) - }) - it('finds directives only in directive-capable bodies', async () => { const input = ` {