Skip to content

Commit ea20779

Browse files
committed
fix(rsc): reject inline "use server" inside "use client" module
1 parent bd1487d commit ea20779

File tree

2 files changed

+31
-4
lines changed

2 files changed

+31
-4
lines changed

packages/plugin-rsc/src/plugin.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
transformDirectiveProxyExport,
3030
transformServerActionServer,
3131
transformWrapExport,
32+
findDirectives,
3233
} from './transforms'
3334
import { generateEncryptionKey, toBase64 } from './utils/encryption-utils'
3435
import { createRpcServer } from './utils/rpc'
@@ -1135,6 +1136,16 @@ function vitePluginUseClient(
11351136
return
11361137
}
11371138

1139+
if (code.includes('use server')) {
1140+
const directives = findDirectives(ast, 'use server')
1141+
if (directives.length > 0) {
1142+
this.error(
1143+
`'use server' directive is not allowed within 'use client'`,
1144+
directives[0]?.start,
1145+
)
1146+
}
1147+
}
1148+
11381149
let importId: string
11391150
let referenceKey: string
11401151
const packageSource = packageSources.get(id)

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { tinyassert } from '@hiogawa/utils'
2-
import type { Program } from 'estree'
2+
import type { Program, Literal } from 'estree'
33
import { walk } from 'estree-walker'
44
import MagicString from 'magic-string'
55
import { analyze } from 'periscopic'
@@ -56,7 +56,7 @@ export function transformHoistInlineDirective(
5656
node.type === 'ArrowFunctionExpression') &&
5757
node.body.type === 'BlockStatement'
5858
) {
59-
const match = matchDirective(node.body.body, directive)
59+
const match = matchDirective(node.body.body, directive)?.match
6060
if (!match) return
6161
if (!node.async && rejectNonAsyncFunction) {
6262
throw Object.assign(
@@ -156,7 +156,7 @@ const exactRegex = (s: string): RegExp =>
156156
function matchDirective(
157157
body: Program['body'],
158158
directive: RegExp,
159-
): RegExpMatchArray | undefined {
159+
): { match: RegExpMatchArray; node: Literal } | undefined {
160160
for (const stable of body) {
161161
if (
162162
stable.type === 'ExpressionStatement' &&
@@ -165,8 +165,24 @@ function matchDirective(
165165
) {
166166
const match = stable.expression.value.match(directive)
167167
if (match) {
168-
return match
168+
return { match, node: stable.expression }
169169
}
170170
}
171171
}
172172
}
173+
174+
export function findDirectives(ast: Program, directive: string): Literal[] {
175+
const directiveRE = exactRegex(directive)
176+
const nodes: Literal[] = []
177+
walk(ast, {
178+
enter(node) {
179+
if (node.type === 'Program' || node.type === 'BlockStatement') {
180+
const match = matchDirective(node.body, directiveRE)
181+
if (match) {
182+
nodes.push(match.node)
183+
}
184+
}
185+
},
186+
})
187+
return nodes
188+
}

0 commit comments

Comments
 (0)