From 3e2e39c55939bd156cbb4dab926aaaa126b53d1c Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 7 Nov 2022 15:13:35 +0100 Subject: [PATCH] Show inlined error if the "use client" directive is not before other statements/expressions (#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) --- packages/next/server/next-typescript.ts | 41 ++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/next/server/next-typescript.ts b/packages/next/server/next-typescript.ts index 3daf55ab116c..7625bc3b60bc 100644 --- a/packages/next/server/next-typescript.ts +++ b/packages/next/server/next-typescript.ts @@ -187,19 +187,34 @@ export function createTSPlugin(modules: { ) } - function getIsClientEntry(fileName: string) { + function getIsClientEntry( + fileName: string, + throwOnInvalidDirective?: boolean + ) { const source = info.languageService.getProgram()?.getSourceFile(fileName) if (source) { let isClientEntry = false let isDirective = true ts.forEachChild(source!, (node) => { - if (isClientEntry || !isDirective) return - - if (isDirective && ts.isExpressionStatement(node)) { - if (ts.isStringLiteral(node.expression)) { - if (node.expression.text === 'use client') { + if ( + ts.isExpressionStatement(node) && + ts.isStringLiteral(node.expression) + ) { + if (node.expression.text === 'use client') { + if (isDirective) { isClientEntry = true + } else { + if (throwOnInvalidDirective) { + const e = { + messageText: + 'The `"use client"` directive must be put at the top of the file.', + start: node.expression.getStart(), + length: + node.expression.getEnd() - node.expression.getStart(), + } + throw e + } } } } else { @@ -473,7 +488,19 @@ export function createTSPlugin(modules: { const source = info.languageService.getProgram()?.getSourceFile(fileName) if (source) { - const isClientEntry = getIsClientEntry(fileName) + let isClientEntry = false + + try { + isClientEntry = getIsClientEntry(fileName, true) + } catch (e: any) { + prior.push({ + file: source, + category: ts.DiagnosticCategory.Error, + code: 71004, + ...e, + }) + isClientEntry = false + } ts.forEachChild(source!, (node) => { if (ts.isImportDeclaration(node)) {