Skip to content

Commit 3e2e39c

Browse files
authored
Show inlined error if the "use client" directive is not before other statements/expressions (vercel#42507)
An example will be: ![image](https://user-images.githubusercontent.com/3676859/200087667-53548f58-4627-422f-a191-548aba194707.png) ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
1 parent 984627a commit 3e2e39c

File tree

1 file changed

+34
-7
lines changed

1 file changed

+34
-7
lines changed

packages/next/server/next-typescript.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,19 +187,34 @@ export function createTSPlugin(modules: {
187187
)
188188
}
189189

190-
function getIsClientEntry(fileName: string) {
190+
function getIsClientEntry(
191+
fileName: string,
192+
throwOnInvalidDirective?: boolean
193+
) {
191194
const source = info.languageService.getProgram()?.getSourceFile(fileName)
192195
if (source) {
193196
let isClientEntry = false
194197
let isDirective = true
195198

196199
ts.forEachChild(source!, (node) => {
197-
if (isClientEntry || !isDirective) return
198-
199-
if (isDirective && ts.isExpressionStatement(node)) {
200-
if (ts.isStringLiteral(node.expression)) {
201-
if (node.expression.text === 'use client') {
200+
if (
201+
ts.isExpressionStatement(node) &&
202+
ts.isStringLiteral(node.expression)
203+
) {
204+
if (node.expression.text === 'use client') {
205+
if (isDirective) {
202206
isClientEntry = true
207+
} else {
208+
if (throwOnInvalidDirective) {
209+
const e = {
210+
messageText:
211+
'The `"use client"` directive must be put at the top of the file.',
212+
start: node.expression.getStart(),
213+
length:
214+
node.expression.getEnd() - node.expression.getStart(),
215+
}
216+
throw e
217+
}
203218
}
204219
}
205220
} else {
@@ -473,7 +488,19 @@ export function createTSPlugin(modules: {
473488

474489
const source = info.languageService.getProgram()?.getSourceFile(fileName)
475490
if (source) {
476-
const isClientEntry = getIsClientEntry(fileName)
491+
let isClientEntry = false
492+
493+
try {
494+
isClientEntry = getIsClientEntry(fileName, true)
495+
} catch (e: any) {
496+
prior.push({
497+
file: source,
498+
category: ts.DiagnosticCategory.Error,
499+
code: 71004,
500+
...e,
501+
})
502+
isClientEntry = false
503+
}
477504

478505
ts.forEachChild(source!, (node) => {
479506
if (ts.isImportDeclaration(node)) {

0 commit comments

Comments
 (0)