Skip to content

Commit 43d71ae

Browse files
committed
resolve conflict with canary
1 parent 2637d43 commit 43d71ae

13 files changed

+657
-161
lines changed

packages/next-codemod/src/transforms/__testfixtures__/next-async-request-api-dynamic-apis/async-api-15.output.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { cookies } from "next/headers";
22

33
async function MyComponent() {
44
function asyncFunction() {
5-
callSomething(/* TODO: please manually await this call, codemod cannot transform due to undetermined async scope */
5+
callSomething(/* TODO: please manually await this call, if it's a server component, you can turn it to async function */
66
cookies());
77
}
88
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { cookies } from "next/headers";
22

33
function MyComponent() {
4-
callSomething(/* TODO: please manually await this call, codemod cannot transform due to undetermined async scope */
4+
callSomething(/* TODO: please manually await this call, if it's a server component, you can turn it to async function */
55
cookies());
66
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { cookies, headers } from 'next/headers'
2+
3+
export function myFun() {
4+
return async function () {
5+
cookies().get('name')
6+
}
7+
}
8+
9+
export function myFun2() {
10+
return function () {
11+
headers()
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { cookies, headers, type UnsafeUnwrappedHeaders } from 'next/headers';
2+
3+
export function myFun() {
4+
return async function () {
5+
(await cookies()).get('name')
6+
};
7+
}
8+
9+
export function myFun2() {
10+
return function () {
11+
(headers() as unknown as UnsafeUnwrappedHeaders)
12+
};
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { cookies } from 'cookies'
2+
3+
export function MyCls() {
4+
return async function Page() {
5+
return (await cookies()).get('token')
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { cookies } from 'cookies'
2+
3+
export function MyCls() {
4+
return async function Page() {
5+
return (await cookies()).get('token')
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const nextHeaders = import('next/headers')
2+
3+
function myFunc() {
4+
nextHeaders.cookies()
5+
}
6+
7+
const nextHeaders2 = /* The APIs under 'next/headers' are async now, need to be manually awaited. */ import('next/headers')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const nextHeaders = /* The APIs under 'next/headers' are async now, need to be manually awaited. */
2+
import('next/headers')
3+
4+
function myFunc() {
5+
nextHeaders.cookies()
6+
}
7+
8+
const nextHeaders2 = /* The APIs under 'next/headers' are async now, need to be manually awaited. */ import('next/headers')

packages/next-codemod/src/transforms/__testfixtures__/next-async-request-api-dynamic-apis/async-api-type-cast-02.output.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from 'next/headers'
77

88
export function MyDraftComponent() {
9-
if (/* TODO: please manually await this call, codemod cannot transform due to undetermined async scope */
9+
if (/* TODO: please manually await this call, if it's a server component, you can turn it to async function */
1010
draftMode().isEnabled) {
1111
return null
1212
}
@@ -15,13 +15,13 @@ draftMode().isEnabled) {
1515
}
1616

1717
export function MyCookiesComponent() {
18-
const c = /* TODO: please manually await this call, codemod cannot transform due to undetermined async scope */
18+
const c = /* TODO: please manually await this call, if it's a server component, you can turn it to async function */
1919
cookies()
2020
return c.get('name')
2121
}
2222

2323
export function MyHeadersComponent() {
24-
const h = /* TODO: please manually await this call, codemod cannot transform due to undetermined async scope */
24+
const h = /* TODO: please manually await this call, if it's a server component, you can turn it to async function */
2525
headers()
2626
return (
2727
<p>{h.get('x-foo')}</p>

packages/next-codemod/src/transforms/lib/async-request-api/next-async-dynamic-api.ts

+72-43
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,34 @@ import {
55
isMatchedFunctionExported,
66
turnFunctionReturnTypeToAsync,
77
insertReactUseImport,
8+
isFunctionScope,
9+
findClosetParentFunctionScope,
10+
wrapParentheseIfNeeded,
11+
insertCommentOnce,
812
} from './utils'
913

10-
function wrapParathnessIfNeeded(
11-
hasChainAccess: boolean,
12-
j: API['jscodeshift'],
13-
expression
14-
) {
15-
return hasChainAccess ? j.parenthesizedExpression(expression) : expression
14+
const DYNAMIC_IMPORT_WARN_COMMENT = ` The APIs under 'next/headers' are async now, need to be manually awaited. `
15+
16+
function findDynamicImportsAndComment(root: Collection<any>, j: API['j']) {
17+
let modified = false
18+
// find all the dynamic imports of `next/headers`,
19+
// and add a comment to the import expression to inform this needs to be manually handled
20+
21+
// find all the dynamic imports of `next/cookies`,
22+
// Notice, import() is not handled as ImportExpression in current jscodeshift version,
23+
// we need to use CallExpression to capture the dynamic imports.
24+
const importPaths = root.find(j.CallExpression, {
25+
callee: {
26+
type: 'Import',
27+
},
28+
arguments: [{ value: 'next/headers' }],
29+
})
30+
31+
importPaths.forEach((path) => {
32+
insertCommentOnce(path, j, DYNAMIC_IMPORT_WARN_COMMENT)
33+
modified = true
34+
})
35+
return modified
1636
}
1737

1838
export function transformDynamicAPI(
@@ -55,29 +75,33 @@ export function transformDynamicAPI(
5575
if (!isImportedTopLevel) {
5676
return
5777
}
78+
let parentFunctionPath = findClosetParentFunctionScope(path, j)
5879

59-
const closetScope = j(path).closestScope()
60-
61-
// First search the closed function of the current path
62-
let closestFunction: Collection<any>
63-
closestFunction = j(path).closest(j.FunctionDeclaration)
64-
if (closestFunction.size() === 0) {
65-
closestFunction = j(path).closest(j.FunctionExpression)
66-
}
67-
if (closestFunction.size() === 0) {
68-
closestFunction = j(path).closest(j.ArrowFunctionExpression)
80+
// We found the parent scope is not a function
81+
let parentFunctionNode
82+
if (parentFunctionPath) {
83+
if (isFunctionScope(parentFunctionPath, j)) {
84+
parentFunctionNode = parentFunctionPath.node
85+
} else {
86+
const scopeNode = parentFunctionPath.node
87+
if (
88+
scopeNode.type === 'ReturnStatement' &&
89+
isFunctionType(scopeNode.argument.type)
90+
) {
91+
parentFunctionNode = scopeNode.argument
92+
}
93+
}
6994
}
7095

71-
const isAsyncFunction = closestFunction
72-
.nodes()
73-
.some((node) => node.async)
96+
const isAsyncFunction = parentFunctionNode?.async || false
7497

7598
const isCallAwaited = path.parentPath?.node?.type === 'AwaitExpression'
7699

77100
const hasChainAccess =
78101
path.parentPath.value.type === 'MemberExpression' &&
79102
path.parentPath.value.object === path.node
80103

104+
const closetScope = j(path).closestScope()
81105
// For cookies/headers API, only transform server and shared components
82106
if (isAsyncFunction) {
83107
if (!isCallAwaited) {
@@ -86,7 +110,7 @@ export function transformDynamicAPI(
86110
// add parentheses to wrap the function call
87111
j.callExpression(j.identifier(asyncRequestApiName), [])
88112
)
89-
j(path).replaceWith(wrapParathnessIfNeeded(hasChainAccess, j, expr))
113+
j(path).replaceWith(wrapParentheseIfNeeded(hasChainAccess, j, expr))
90114
modified = true
91115
}
92116
} else {
@@ -134,7 +158,7 @@ export function transformDynamicAPI(
134158
j.callExpression(j.identifier(asyncRequestApiName), [])
135159
)
136160
j(path).replaceWith(
137-
wrapParathnessIfNeeded(hasChainAccess, j, expr)
161+
wrapParentheseIfNeeded(hasChainAccess, j, expr)
138162
)
139163

140164
turnFunctionReturnTypeToAsync(closetScopePath.node, j)
@@ -144,12 +168,9 @@ export function transformDynamicAPI(
144168
}
145169
} else {
146170
// if parent is function and it's a hook, which starts with 'use', wrap the api call with 'use()'
147-
const parentFunction =
148-
j(path).closest(j.FunctionDeclaration) ||
149-
j(path).closest(j.FunctionExpression) ||
150-
j(path).closest(j.ArrowFunctionExpression)
171+
const parentFunction = findClosetParentFunctionScope(path, j)
151172

152-
if (parentFunction.size() > 0) {
173+
if (parentFunction) {
153174
const parentFunctionName = parentFunction.get().node.id?.name
154175
const isParentFunctionHook = parentFunctionName?.startsWith('use')
155176
if (isParentFunctionHook) {
@@ -166,7 +187,8 @@ export function transformDynamicAPI(
166187
originRequestApiName,
167188
root,
168189
filePath,
169-
insertedTypes
190+
insertedTypes,
191+
` TODO: please manually await this call, if it's a server component, you can turn it to async function `
170192
)
171193
}
172194
} else {
@@ -176,7 +198,8 @@ export function transformDynamicAPI(
176198
originRequestApiName,
177199
root,
178200
filePath,
179-
insertedTypes
201+
insertedTypes,
202+
' TODO: please manually await this call, codemod cannot transform due to undetermined async scope '
180203
)
181204
}
182205
modified = true
@@ -187,22 +210,26 @@ export function transformDynamicAPI(
187210

188211
const isClientComponent = determineClientDirective(root, j, source)
189212

213+
// Only transform the valid calls in server or shared components
214+
if (isClientComponent) return null
215+
216+
// Import declaration case, e.g. import { cookies } from 'next/headers'
190217
const importedNextAsyncRequestApisMapping = findImportMappingFromNextHeaders(
191218
root,
192219
j
193220
)
221+
for (const originName in importedNextAsyncRequestApisMapping) {
222+
const aliasName = importedNextAsyncRequestApisMapping[originName]
223+
processAsyncApiCalls(aliasName, originName)
224+
}
194225

195-
// Only transform the valid calls in server or shared components
196-
if (!isClientComponent) {
197-
for (const originName in importedNextAsyncRequestApisMapping) {
198-
const aliasName = importedNextAsyncRequestApisMapping[originName]
199-
processAsyncApiCalls(aliasName, originName)
200-
}
226+
// Add import { use } from 'react' if needed and not already imported
227+
if (needsReactUseImport) {
228+
insertReactUseImport(root, j)
229+
}
201230

202-
// Add import { use } from 'react' if needed and not already imported
203-
if (needsReactUseImport) {
204-
insertReactUseImport(root, j)
205-
}
231+
if (findDynamicImportsAndComment(root, j)) {
232+
modified = true
206233
}
207234

208235
return modified ? root.toSource() : null
@@ -217,14 +244,18 @@ const API_CAST_TYPE_MAP = {
217244

218245
function castTypesOrAddComment(
219246
j: API['jscodeshift'],
220-
path: ASTPath<any>,
247+
path: ASTPath<CallExpression>,
221248
originRequestApiName: string,
222249
root: Collection<any>,
223250
filePath: string,
224-
insertedTypes: Set<string>
251+
insertedTypes: Set<string>,
252+
customMessage: string
225253
) {
226254
const isTsFile = filePath.endsWith('.ts') || filePath.endsWith('.tsx')
227255
if (isTsFile) {
256+
// if the path of call expression is already being awaited, no need to cast
257+
if (path.parentPath?.node?.type === 'AwaitExpression') return
258+
228259
/* Do type cast for headers, cookies, draftMode
229260
import {
230261
type UnsafeUnwrappedHeaders,
@@ -278,9 +309,7 @@ function castTypesOrAddComment(
278309
} else {
279310
// Otherwise for JS file, leave a message to the user to manually handle the transformation
280311
path.node.comments = [
281-
j.commentBlock(
282-
' TODO: please manually await this call, codemod cannot transform due to undetermined async scope '
283-
),
312+
j.commentBlock(customMessage),
284313
...(path.node.comments || []),
285314
]
286315
}

0 commit comments

Comments
 (0)