Skip to content

Commit cf33eb6

Browse files
committed
refactor: move validateNonAsyncFunction to utils and consolidate tests
1 parent 75064fd commit cf33eb6

File tree

6 files changed

+108
-125
lines changed

6 files changed

+108
-125
lines changed

packages/plugin-rsc/src/transforms/proxy-export.test.ts

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -242,46 +242,4 @@ export const MyClientComp = () => { throw new Error('...') }
242242
}
243243
`)
244244
})
245-
246-
test('reject non async function', async () => {
247-
const accepted = [
248-
'export async function f() {}',
249-
'export default async function f() {}',
250-
'export const fn = async function fn() {}',
251-
'export const fn = async () => {}',
252-
'export const fn = async () => {}, fn2 = x',
253-
'export const fn = x',
254-
'export const fn = x({ x: y })',
255-
'export const fn = x(async () => {})',
256-
'export default x',
257-
'const y = x; export { y }',
258-
'export const fn = x(() => {})',
259-
'export const testAction = actionClient.action(async () => { return { message: "Hello, world!" }; });',
260-
]
261-
262-
const rejected = [
263-
'export function f() {}',
264-
'export default function f() {}',
265-
'export const fn = function fn() {}',
266-
'export const fn = () => {}',
267-
'export const fn = x, fn2 = () => {}',
268-
'export class Cls {}',
269-
'export const Cls = class {}',
270-
'export const Cls = class Foo {}',
271-
]
272-
273-
for (const code of accepted) {
274-
await expect(
275-
testTransform(code, { rejectNonAsyncFunction: true }),
276-
).resolves.not.toThrow()
277-
}
278-
279-
for (const code of rejected) {
280-
await expect(
281-
testTransform(code, { rejectNonAsyncFunction: true }),
282-
).rejects.toThrow(/unsupported non async function/)
283-
}
284-
285-
expect.assertions(rejected.length + accepted.length)
286-
})
287245
})

packages/plugin-rsc/src/transforms/proxy-export.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { tinyassert } from '@hiogawa/utils'
22
import type { Node, Program } from 'estree'
33
import MagicString from 'magic-string'
4-
import { extractNames, hasDirective } from './utils'
5-
import { validateNonAsyncFunction } from './wrap-export'
4+
import { extractNames, hasDirective, validateNonAsyncFunction } from './utils'
65

76
export type TransformProxyExportOptions = {
87
/** Required for source map and `keep` options */
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { parseAstAsync } from 'vite'
2+
import { describe, expect, test } from 'vitest'
3+
import { transformProxyExport } from './proxy-export'
4+
import { validateNonAsyncFunction } from './utils'
5+
import { transformWrapExport } from './wrap-export'
6+
7+
describe(validateNonAsyncFunction, () => {
8+
// next.js's validation isn't entirely consistent.
9+
// for now we aim to make it at least as forgiving as next.js.
10+
11+
const accepted = [
12+
`export async function f() {}`,
13+
`export default async function f() {}`,
14+
`export const fn = async function fn() {}`,
15+
`export const fn = async () => {}`,
16+
`export const fn = async () => {}, fn2 = x`,
17+
`export const fn = x`,
18+
`export const fn = x({ x: y })`,
19+
`export const fn = x(async () => {})`,
20+
`export default x`,
21+
`const y = x; export { y }`,
22+
`export const fn = x(() => {})`, // rejected by next.js
23+
`export const testAction = actionClient.action(async () => { return { message: "Hello, world!" }; });`,
24+
]
25+
26+
const rejected = [
27+
`export function f() {}`,
28+
`export default function f() {}`,
29+
`export const fn = function fn() {}`,
30+
`export const fn = () => {}`,
31+
`export const fn = x, fn2 = () => {}`,
32+
`export class Cls {}`,
33+
`export const Cls = class {}`,
34+
`export const Cls = class Foo {}`,
35+
]
36+
37+
test(transformWrapExport, async () => {
38+
const testTransform = async (input: string) => {
39+
const ast = await parseAstAsync(input)
40+
const result = transformWrapExport(input, ast, {
41+
runtime: (value, name) =>
42+
`$$wrap(${value}, "<id>", ${JSON.stringify(name)})`,
43+
ignoreExportAllDeclaration: true,
44+
rejectNonAsyncFunction: true,
45+
})
46+
return result.output.hasChanged()
47+
}
48+
49+
for (const code of accepted) {
50+
await expect.soft(testTransform(code)).resolves.toBe(true)
51+
}
52+
for (const code of rejected) {
53+
await expect
54+
.soft(testTransform(code))
55+
.rejects.toMatchInlineSnapshot(
56+
`[Error: unsupported non async function]`,
57+
)
58+
}
59+
})
60+
61+
test(transformProxyExport, async () => {
62+
const testTransform = async (input: string) => {
63+
const ast = await parseAstAsync(input)
64+
const result = transformProxyExport(ast, {
65+
code: input,
66+
rejectNonAsyncFunction: true,
67+
runtime: (name) => `$$proxy("<id>", ${JSON.stringify(name)})`,
68+
})
69+
return result.output.hasChanged()
70+
}
71+
72+
for (const code of accepted) {
73+
await expect.soft(testTransform(code)).resolves.toBe(true)
74+
}
75+
for (const code of rejected) {
76+
await expect
77+
.soft(testTransform(code))
78+
.rejects.toMatchInlineSnapshot(
79+
`[Error: unsupported non async function]`,
80+
)
81+
}
82+
})
83+
})

packages/plugin-rsc/src/transforms/utils.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { tinyassert } from '@hiogawa/utils'
2-
import type { Identifier, Pattern, Program } from 'estree'
2+
import type { ExportDefaultDeclaration } from 'estree'
3+
import type { Identifier, Node, Pattern, Program } from 'estree'
34

45
export function hasDirective(
56
body: Program['body'],
@@ -136,3 +137,23 @@ export function extractIdentifiers(
136137
}
137138
return nodes
138139
}
140+
141+
export function validateNonAsyncFunction(
142+
opts: { rejectNonAsyncFunction?: boolean },
143+
// export default function/class can be unnamed
144+
node: Node | ExportDefaultDeclaration['declaration'],
145+
): void {
146+
if (!opts.rejectNonAsyncFunction) return
147+
if (
148+
node.type === 'ClassDeclaration' ||
149+
node.type === 'ClassExpression' ||
150+
((node.type === 'FunctionDeclaration' ||
151+
node.type === 'FunctionExpression' ||
152+
node.type === 'ArrowFunctionExpression') &&
153+
!node.async)
154+
) {
155+
throw Object.assign(new Error(`unsupported non async function`), {
156+
pos: node.start,
157+
})
158+
}
159+
}

packages/plugin-rsc/src/transforms/wrap-export.test.ts

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -304,58 +304,4 @@ export default Page;
304304
"
305305
`)
306306
})
307-
308-
test('reject non async function', async () => {
309-
// next.js's validataion isn't entirely consisten.
310-
// for now we aim to make it at least as forgiving as next.js.
311-
312-
const accepted = [
313-
`export async function f() {}`,
314-
`export default async function f() {}`,
315-
`export const fn = async function fn() {}`,
316-
`export const fn = async () => {}`,
317-
`export const fn = async () => {}, fn2 = x`,
318-
`export const fn = x`,
319-
`export const fn = x({ x: y })`,
320-
`export const fn = x(async () => {})`,
321-
`export default x`,
322-
`const y = x; export { y }`,
323-
`export const fn = x(() => {})`, // rejected by next.js
324-
`export const testAction = actionClient.action(async () => { return { message: "Hello, world!" }; });`,
325-
]
326-
327-
const rejected = [
328-
`export function f() {}`,
329-
`export default function f() {}`,
330-
`export const fn = function fn() {}`,
331-
`export const fn = () => {}`,
332-
`export const fn = x, fn2 = () => {}`,
333-
`export class Cls {}`,
334-
`export const Cls = class {}`,
335-
`export const Cls = class Foo {}`,
336-
]
337-
338-
async function toActual(input: string) {
339-
try {
340-
await testTransform(input, {
341-
rejectNonAsyncFunction: true,
342-
})
343-
return [input, true]
344-
} catch (e) {
345-
return [input, e instanceof Error ? e.message : e]
346-
}
347-
}
348-
349-
const actual = [
350-
...(await Promise.all(accepted.map((e) => toActual(e)))),
351-
...(await Promise.all(rejected.map((e) => toActual(e)))),
352-
]
353-
354-
const expected = [
355-
...accepted.map((e) => [e, true]),
356-
...rejected.map((e) => [e, 'unsupported non async function']),
357-
]
358-
359-
expect(actual).toEqual(expected)
360-
})
361307
})

packages/plugin-rsc/src/transforms/wrap-export.ts

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import { tinyassert } from '@hiogawa/utils'
2-
import type {
3-
MaybeNamedClassDeclaration,
4-
MaybeNamedFunctionDeclaration,
5-
Node,
6-
Program,
7-
} from 'estree'
2+
import type { Program } from 'estree'
83
import MagicString from 'magic-string'
9-
import { extractNames } from './utils'
4+
import { extractNames, validateNonAsyncFunction } from './utils'
105

116
type ExportMeta = {
127
declName?: string
@@ -26,25 +21,6 @@ export type TransformWrapExportOptions = {
2621
filter?: TransformWrapExportFilter
2722
}
2823

29-
export function validateNonAsyncFunction(
30-
opts: { rejectNonAsyncFunction?: boolean },
31-
node: Node | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration,
32-
): void {
33-
if (!opts.rejectNonAsyncFunction) return
34-
if (
35-
node.type === 'ClassDeclaration' ||
36-
node.type === 'ClassExpression' ||
37-
((node.type === 'FunctionDeclaration' ||
38-
node.type === 'FunctionExpression' ||
39-
node.type === 'ArrowFunctionExpression') &&
40-
!node.async)
41-
) {
42-
throw Object.assign(new Error(`unsupported non async function`), {
43-
pos: node.start,
44-
})
45-
}
46-
}
47-
4824
export function transformWrapExport(
4925
input: string,
5026
ast: Program,

0 commit comments

Comments
 (0)