@next/codemod 15.0.0-canary.18 → 15.0.0-canary.182

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,686 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformDynamicProps = transformDynamicProps;
4
+ const utils_1 = require("./utils");
5
+ const PAGE_PROPS = 'props';
6
+ function findFunctionBody(path) {
7
+ let functionBody = path.node.body;
8
+ if (functionBody && functionBody.type === 'BlockStatement') {
9
+ return functionBody.body;
10
+ }
11
+ return null;
12
+ }
13
+ function awaitMemberAccessOfProp(propIdName, path, j) {
14
+ // search the member access of the prop
15
+ const functionBody = findFunctionBody(path);
16
+ const memberAccess = j(functionBody).find(j.MemberExpression, {
17
+ object: {
18
+ type: 'Identifier',
19
+ name: propIdName,
20
+ },
21
+ });
22
+ let hasAwaited = false;
23
+ // await each member access
24
+ memberAccess.forEach((memberAccessPath) => {
25
+ const member = memberAccessPath.value;
26
+ if (isParentPromiseAllCallExpression(memberAccessPath, j)) {
27
+ return;
28
+ }
29
+ // check if it's already awaited
30
+ if (memberAccessPath.parentPath?.value.type === 'AwaitExpression') {
31
+ return;
32
+ }
33
+ const awaitedExpr = j.awaitExpression(member);
34
+ const awaitMemberAccess = (0, utils_1.wrapParentheseIfNeeded)(true, j, awaitedExpr);
35
+ memberAccessPath.replace(awaitMemberAccess);
36
+ hasAwaited = true;
37
+ });
38
+ // If there's any awaited member access, we need to make the function async
39
+ if (hasAwaited) {
40
+ if (!path.value.async) {
41
+ if ('async' in path.value) {
42
+ path.value.async = true;
43
+ (0, utils_1.turnFunctionReturnTypeToAsync)(path.value, j);
44
+ }
45
+ }
46
+ }
47
+ return hasAwaited;
48
+ }
49
+ function isParentUseCallExpression(path, j) {
50
+ if (
51
+ // member access parentPath is argument
52
+ j.CallExpression.check(path.parent.value) &&
53
+ // member access is first argument
54
+ path.parent.value.arguments[0] === path.value &&
55
+ // function name is `use`
56
+ j.Identifier.check(path.parent.value.callee) &&
57
+ path.parent.value.callee.name === 'use') {
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+ function isParentPromiseAllCallExpression(path, j) {
63
+ const argsParent = path.parent;
64
+ const callParent = argsParent?.parent;
65
+ if (j.ArrayExpression.check(argsParent.value) &&
66
+ j.CallExpression.check(callParent.value) &&
67
+ j.MemberExpression.check(callParent.value.callee) &&
68
+ j.Identifier.check(callParent.value.callee.object) &&
69
+ callParent.value.callee.object.name === 'Promise' &&
70
+ j.Identifier.check(callParent.value.callee.property) &&
71
+ callParent.value.callee.property.name === 'all') {
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+ function applyUseAndRenameAccessedProp(propIdName, path, j) {
77
+ // search the member access of the prop, and rename the member access to the member value
78
+ // e.g.
79
+ // props.params => params
80
+ // props.params.foo => params.foo
81
+ // props.searchParams.search => searchParams.search
82
+ let modified = false;
83
+ const functionBody = findFunctionBody(path);
84
+ const memberAccess = j(functionBody).find(j.MemberExpression, {
85
+ object: {
86
+ type: 'Identifier',
87
+ name: propIdName,
88
+ },
89
+ });
90
+ const accessedNames = [];
91
+ // rename each member access
92
+ memberAccess.forEach((memberAccessPath) => {
93
+ // If the member access expression is first argument of `use()`, we skip
94
+ if (isParentUseCallExpression(memberAccessPath, j)) {
95
+ return;
96
+ }
97
+ const member = memberAccessPath.value;
98
+ const memberProperty = member.property;
99
+ if (j.Identifier.check(memberProperty)) {
100
+ accessedNames.push(memberProperty.name);
101
+ }
102
+ else if (j.MemberExpression.check(memberProperty)) {
103
+ let currentMember = memberProperty;
104
+ if (j.Identifier.check(currentMember.object)) {
105
+ accessedNames.push(currentMember.object.name);
106
+ }
107
+ }
108
+ memberAccessPath.replace(memberProperty);
109
+ });
110
+ // If there's any renamed member access, need to call `use()` onto member access
111
+ // e.g. ['params'] => insert `const params = use(props.params)`
112
+ if (accessedNames.length > 0) {
113
+ const accessedPropId = j.identifier(propIdName);
114
+ const accessedProp = j.memberExpression(accessedPropId, j.identifier(accessedNames[0]));
115
+ const useCall = j.callExpression(j.identifier('use'), [accessedProp]);
116
+ const useDeclaration = j.variableDeclaration('const', [
117
+ j.variableDeclarator(j.identifier(accessedNames[0]), useCall),
118
+ ]);
119
+ if (functionBody) {
120
+ functionBody.unshift(useDeclaration);
121
+ }
122
+ modified = true;
123
+ }
124
+ return modified;
125
+ }
126
+ function commentOnMatchedReExports(root, j) {
127
+ let modified = false;
128
+ root.find(j.ExportNamedDeclaration).forEach((path) => {
129
+ if (j.ExportSpecifier.check(path.value.specifiers[0])) {
130
+ const specifiers = path.value.specifiers;
131
+ for (const specifier of specifiers) {
132
+ if (j.ExportSpecifier.check(specifier) &&
133
+ // Find matched named exports and default export
134
+ (utils_1.TARGET_NAMED_EXPORTS.has(specifier.exported.name) ||
135
+ specifier.exported.name === 'default')) {
136
+ if (j.Literal.check(path.value.source)) {
137
+ const localName = specifier.local.name;
138
+ const commentInserted = (0, utils_1.insertCommentOnce)(specifier, j, ` Next.js Dynamic Async API Codemod: \`${localName}\` export is re-exported. Check if this component uses \`params\` or \`searchParams\``);
139
+ modified ||= commentInserted;
140
+ }
141
+ else if (path.value.source === null) {
142
+ const localIdentifier = specifier.local;
143
+ const localName = localIdentifier.name;
144
+ // search if local identifier is from imports
145
+ const importDeclaration = root
146
+ .find(j.ImportDeclaration)
147
+ .filter((importPath) => {
148
+ return importPath.value.specifiers.some((importSpecifier) => importSpecifier.local.name === localName);
149
+ });
150
+ if (importDeclaration.size() > 0) {
151
+ const commentInserted = (0, utils_1.insertCommentOnce)(specifier, j, ` Next.js Dynamic Async API Codemod: \`${localName}\` export is re-exported. Check if this component uses \`params\` or \`searchParams\``);
152
+ modified ||= commentInserted;
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ });
159
+ return modified;
160
+ }
161
+ function modifyTypes(paramTypeAnnotation, propsIdentifier, root, j) {
162
+ let modified = false;
163
+ if (paramTypeAnnotation && paramTypeAnnotation.typeAnnotation) {
164
+ const typeAnnotation = paramTypeAnnotation.typeAnnotation;
165
+ if (typeAnnotation.type === 'TSTypeLiteral') {
166
+ const typeLiteral = typeAnnotation;
167
+ // Find the type property for `params`
168
+ typeLiteral.members.forEach((member) => {
169
+ if (member.type === 'TSPropertySignature' &&
170
+ member.key.type === 'Identifier' &&
171
+ utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
172
+ // if it's already a Promise, don't wrap it again, return
173
+ if (member.typeAnnotation &&
174
+ member.typeAnnotation.typeAnnotation &&
175
+ member.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
176
+ member.typeAnnotation.typeAnnotation.typeName.type ===
177
+ 'Identifier' &&
178
+ member.typeAnnotation.typeAnnotation.typeName.name === 'Promise') {
179
+ return;
180
+ }
181
+ // Wrap the `params` type in Promise<>
182
+ if (member.typeAnnotation &&
183
+ member.typeAnnotation.typeAnnotation &&
184
+ j.TSType.check(member.typeAnnotation.typeAnnotation)) {
185
+ member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
186
+ // @ts-ignore
187
+ member.typeAnnotation.typeAnnotation,
188
+ ]));
189
+ modified = true;
190
+ }
191
+ }
192
+ });
193
+ }
194
+ else if (typeAnnotation.type === 'TSTypeReference') {
195
+ // If typeAnnotation is a type or interface, change the properties to Promise<type of property>
196
+ // e.g. interface PageProps { params: { slug: string } } => interface PageProps { params: Promise<{ slug: string }> }
197
+ const typeReference = typeAnnotation;
198
+ if (typeReference.typeName.type === 'Identifier') {
199
+ // Find the actual type of the type reference
200
+ const foundTypes = findAllTypes(root, j, typeReference.typeName.name);
201
+ // Deal with interfaces
202
+ if (foundTypes.interfaces.length > 0) {
203
+ const interfaceDeclaration = foundTypes.interfaces[0];
204
+ if (interfaceDeclaration.type === 'TSInterfaceDeclaration' &&
205
+ interfaceDeclaration.body?.type === 'TSInterfaceBody') {
206
+ const typeBody = interfaceDeclaration.body.body;
207
+ // if it's already a Promise, don't wrap it again, return
208
+ // traverse the typeReference's properties, if any is in propNames, wrap it in Promise<> if needed
209
+ typeBody.forEach((member) => {
210
+ if (member.type === 'TSPropertySignature' &&
211
+ member.key.type === 'Identifier' &&
212
+ utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
213
+ // if it's already a Promise, don't wrap it again, return
214
+ if (member.typeAnnotation &&
215
+ member.typeAnnotation.typeAnnotation &&
216
+ member.typeAnnotation?.typeAnnotation?.typeName?.name ===
217
+ 'Promise') {
218
+ return;
219
+ }
220
+ // Wrap the prop type in Promise<>
221
+ if (member.typeAnnotation &&
222
+ member.typeAnnotation.typeAnnotation &&
223
+ // check if member name is in propNames
224
+ utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
225
+ member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
226
+ member.typeAnnotation.typeAnnotation,
227
+ ]));
228
+ modified = true;
229
+ }
230
+ }
231
+ });
232
+ }
233
+ }
234
+ }
235
+ }
236
+ propsIdentifier.typeAnnotation = paramTypeAnnotation;
237
+ modified = true;
238
+ }
239
+ return modified;
240
+ }
241
+ function transformDynamicProps(source, api, filePath) {
242
+ const isEntryFile = utils_1.NEXTJS_ENTRY_FILES.test(filePath);
243
+ if (!isEntryFile) {
244
+ return null;
245
+ }
246
+ let modified = false;
247
+ let modifiedPropArgument = false;
248
+ const j = api.jscodeshift.withParser('tsx');
249
+ const root = j(source);
250
+ // Check if 'use' from 'react' needs to be imported
251
+ let needsReactUseImport = false;
252
+ // Based on the prop names
253
+ // e.g. destruct `params` { slug } = params
254
+ // e.g. destruct `searchParams `{ search } = searchParams
255
+ let insertedDestructPropNames = new Set();
256
+ function processAsyncPropOfEntryFile(isClientComponent) {
257
+ // find `params` and `searchParams` in file, and transform the access to them
258
+ function renameAsyncPropIfExisted(path, isDefaultExport) {
259
+ const decl = path.value;
260
+ const params = decl.params;
261
+ let functionName = decl.id?.name;
262
+ // If it's const <id> = function () {}, locate the <id> to get function name
263
+ if (!decl.id) {
264
+ functionName = (0, utils_1.getVariableDeclaratorId)(path, j)?.name;
265
+ }
266
+ // target properties mapping, only contains `params` and `searchParams`
267
+ const propertiesMap = new Map();
268
+ let allProperties = [];
269
+ const isRoute = !isDefaultExport && utils_1.TARGET_ROUTE_EXPORTS.has(functionName);
270
+ // generateMetadata API has 2 params
271
+ if (functionName === 'generateMetadata') {
272
+ if (params.length > 2 || params.length === 0)
273
+ return;
274
+ }
275
+ else if (isRoute) {
276
+ if (params.length !== 2)
277
+ return;
278
+ }
279
+ else {
280
+ // Page/Layout default export have 1 param
281
+ if (params.length !== 1)
282
+ return;
283
+ }
284
+ const propsIdentifier = (0, utils_1.generateUniqueIdentifier)(PAGE_PROPS, path, j);
285
+ const propsArgumentIndex = isRoute ? 1 : 0;
286
+ const currentParam = params[propsArgumentIndex];
287
+ if (!currentParam)
288
+ return;
289
+ // Argument destructuring case
290
+ if (currentParam.type === 'ObjectPattern') {
291
+ // Validate if the properties are not `params` and `searchParams`,
292
+ // if they are, quit the transformation
293
+ let foundTargetProp = false;
294
+ for (const prop of currentParam.properties) {
295
+ if ('key' in prop && prop.key.type === 'Identifier') {
296
+ const propName = prop.key.name;
297
+ if (utils_1.TARGET_PROP_NAMES.has(propName)) {
298
+ foundTargetProp = true;
299
+ }
300
+ }
301
+ }
302
+ // If there's no `params` or `searchParams` matched, return
303
+ if (!foundTargetProp)
304
+ return;
305
+ allProperties = currentParam.properties;
306
+ currentParam.properties.forEach((prop) => {
307
+ if (
308
+ // Could be `Property` or `ObjectProperty`
309
+ (j.Property.check(prop) || j.ObjectProperty.check(prop)) &&
310
+ j.Identifier.check(prop.key) &&
311
+ utils_1.TARGET_PROP_NAMES.has(prop.key.name)) {
312
+ const value = 'value' in prop ? prop.value : null;
313
+ propertiesMap.set(prop.key.name, value);
314
+ }
315
+ });
316
+ modifiedPropArgument = true;
317
+ }
318
+ else if (currentParam.type === 'Identifier') {
319
+ // case of accessing the props.params.<name>:
320
+ // Page(props) {}
321
+ // generateMetadata(props, parent?) {}
322
+ const argName = currentParam.name;
323
+ if (isClientComponent) {
324
+ const modifiedProp = applyUseAndRenameAccessedProp(argName, path, j);
325
+ if (modifiedProp) {
326
+ needsReactUseImport = true;
327
+ modified = true;
328
+ }
329
+ }
330
+ else {
331
+ const awaited = awaitMemberAccessOfProp(argName, path, j);
332
+ modified ||= awaited;
333
+ }
334
+ // cases of passing down `props` into any function
335
+ // Page(props) { callback(props) }
336
+ // search for all the argument of CallExpression, where currentParam is one of the arguments
337
+ const callExpressions = j(path).find(j.CallExpression, {
338
+ arguments: (args) => {
339
+ return args.some((arg) => {
340
+ return (j.Identifier.check(arg) &&
341
+ arg.name === argName &&
342
+ arg.type === 'Identifier');
343
+ });
344
+ },
345
+ });
346
+ // Add a comment to warn users that properties of `props` need to be awaited when accessed
347
+ callExpressions.forEach((callExpression) => {
348
+ // find the argument `currentParam`
349
+ const args = callExpression.value.arguments;
350
+ const propPassedAsArg = args.find((arg) => j.Identifier.check(arg) && arg.name === argName);
351
+ const comment = ` Next.js Dynamic Async API Codemod: '${argName}' is passed as an argument. Any asynchronous properties of 'props' must be awaited when accessed. `;
352
+ const inserted = (0, utils_1.insertCommentOnce)(propPassedAsArg, j, comment);
353
+ modified ||= inserted;
354
+ });
355
+ if (modified) {
356
+ modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
357
+ }
358
+ }
359
+ if (modifiedPropArgument) {
360
+ const isModified = resolveAsyncProp(path, propertiesMap, propsIdentifier.name, allProperties, isDefaultExport);
361
+ if (isModified) {
362
+ // Make TS happy
363
+ if (j.ObjectPattern.check(currentParam)) {
364
+ modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
365
+ }
366
+ // Override the first param to `props`
367
+ params[propsArgumentIndex] = propsIdentifier;
368
+ modified = true;
369
+ }
370
+ }
371
+ else {
372
+ // When the prop argument is not destructured, we need to add comments to the spread properties
373
+ if (j.Identifier.check(currentParam)) {
374
+ const commented = commentSpreadProps(path, currentParam.name, j);
375
+ const modifiedTypes = modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
376
+ modified ||= commented || modifiedTypes;
377
+ }
378
+ }
379
+ }
380
+ // Helper function to insert `const params = await asyncParams;` at the beginning of the function body
381
+ function resolveAsyncProp(path, propertiesMap, propsIdentifierName, allProperties, isDefaultExport) {
382
+ // Rename props to `prop` argument for the function
383
+ const insertedRenamedPropFunctionNames = new Set();
384
+ const node = path.value;
385
+ // If it's sync default export, and it's also server component, make the function async
386
+ if (isDefaultExport && !isClientComponent) {
387
+ if (!node.async) {
388
+ if ('async' in node) {
389
+ node.async = true;
390
+ (0, utils_1.turnFunctionReturnTypeToAsync)(node, j);
391
+ }
392
+ }
393
+ }
394
+ // If it's arrow function and function body is not block statement, check if the properties are used there
395
+ if (j.ArrowFunctionExpression.check(path.node) &&
396
+ !j.BlockStatement.check(path.node.body)) {
397
+ const objectExpression = path.node.body;
398
+ let hasUsedProps = false;
399
+ j(objectExpression)
400
+ .find(j.Identifier)
401
+ .forEach((identifierPath) => {
402
+ const idName = identifierPath.value.name;
403
+ if (propertiesMap.has(idName)) {
404
+ hasUsedProps = true;
405
+ return;
406
+ }
407
+ });
408
+ // Turn the function body to block statement, return the object expression
409
+ if (hasUsedProps) {
410
+ path.node.body = j.blockStatement([
411
+ j.returnStatement(objectExpression),
412
+ ]);
413
+ }
414
+ }
415
+ const isAsyncFunc = !!node.async;
416
+ const functionName = path.value.id?.name || 'default';
417
+ const functionBody = findFunctionBody(path);
418
+ const hasOtherProperties = allProperties.length > propertiesMap.size;
419
+ function createDestructuringDeclaration(properties, destructPropsIdentifierName) {
420
+ const propsToKeep = [];
421
+ let restProperty = null;
422
+ // Iterate over the destructured properties
423
+ properties.forEach((property) => {
424
+ if (j.ObjectProperty.check(property)) {
425
+ // Handle normal and computed properties
426
+ const keyName = j.Identifier.check(property.key)
427
+ ? property.key.name
428
+ : j.Literal.check(property.key)
429
+ ? property.key.value
430
+ : null; // for computed properties
431
+ if (typeof keyName === 'string') {
432
+ propsToKeep.push(property);
433
+ }
434
+ }
435
+ else if (j.RestElement.check(property)) {
436
+ restProperty = property;
437
+ }
438
+ });
439
+ if (propsToKeep.length === 0 && !restProperty) {
440
+ return null;
441
+ }
442
+ if (restProperty) {
443
+ propsToKeep.push(restProperty);
444
+ }
445
+ return j.variableDeclaration('const', [
446
+ j.variableDeclarator(j.objectPattern(propsToKeep), j.identifier(destructPropsIdentifierName)),
447
+ ]);
448
+ }
449
+ if (hasOtherProperties) {
450
+ /**
451
+ * If there are other properties, we need to keep the original param with destructuring
452
+ * e.g.
453
+ * input:
454
+ * Page({ params: { slug }, otherProp }) {
455
+ * const { slug } = await props.params;
456
+ * }
457
+ *
458
+ * output:
459
+ * Page(props) {
460
+ * const { otherProp } = props; // inserted
461
+ * // ...rest of the function body
462
+ * }
463
+ */
464
+ const restProperties = allProperties.filter((prop) => {
465
+ const isTargetProps = 'key' in prop &&
466
+ prop.key.type === 'Identifier' &&
467
+ utils_1.TARGET_PROP_NAMES.has(prop.key.name);
468
+ return !isTargetProps;
469
+ });
470
+ const destructionOtherPropertiesDeclaration = createDestructuringDeclaration(restProperties, propsIdentifierName);
471
+ if (functionBody && destructionOtherPropertiesDeclaration) {
472
+ functionBody.unshift(destructionOtherPropertiesDeclaration);
473
+ }
474
+ }
475
+ let modifiedPropertyCount = 0;
476
+ for (const [matchedPropName, paramsProperty] of propertiesMap) {
477
+ if (!utils_1.TARGET_PROP_NAMES.has(matchedPropName)) {
478
+ continue;
479
+ }
480
+ // In client component, if the param is already wrapped with `use()`, skip the transformation
481
+ if (isClientComponent) {
482
+ let shouldSkip = false;
483
+ const propPaths = j(path).find(j.Identifier, {
484
+ name: matchedPropName,
485
+ });
486
+ for (const propPath of propPaths.paths()) {
487
+ if (isParentUseCallExpression(propPath, j)) {
488
+ // Skip transformation
489
+ shouldSkip = true;
490
+ break;
491
+ }
492
+ }
493
+ if (shouldSkip) {
494
+ continue;
495
+ }
496
+ }
497
+ const propRenamedId = j.Identifier.check(paramsProperty)
498
+ ? paramsProperty.name
499
+ : null;
500
+ const propName = propRenamedId || matchedPropName;
501
+ // if propName is not used in lower scope, and it stars with unused prefix `_`,
502
+ // also skip the transformation
503
+ const functionBodyPath = path.get('body');
504
+ const hasUsedInBody = j(functionBodyPath)
505
+ .find(j.Identifier, {
506
+ name: propName,
507
+ })
508
+ .size() > 0;
509
+ if (!hasUsedInBody && propName.startsWith('_'))
510
+ continue;
511
+ modifiedPropertyCount++;
512
+ const propNameIdentifier = j.identifier(matchedPropName);
513
+ const propsIdentifier = j.identifier(propsIdentifierName);
514
+ const accessedPropIdExpr = j.memberExpression(propsIdentifier, propNameIdentifier);
515
+ // Check param property value, if it's destructed, we need to destruct it as well
516
+ // e.g.
517
+ // input: Page({ params: { slug } })
518
+ // output: const { slug } = await props.params; rather than const props = await props.params;
519
+ const uid = functionName + ':' + propName;
520
+ if (paramsProperty?.type === 'ObjectPattern') {
521
+ const objectPattern = paramsProperty;
522
+ const objectPatternProperties = objectPattern.properties;
523
+ // destruct the object pattern, e.g. { slug } => const { slug } = params;
524
+ const destructedObjectPattern = j.variableDeclaration('const', [
525
+ j.variableDeclarator(j.objectPattern(objectPatternProperties.map((prop) => {
526
+ if (prop.type === 'Property' &&
527
+ prop.key.type === 'Identifier') {
528
+ return j.objectProperty(j.identifier(prop.key.name), j.identifier(prop.key.name));
529
+ }
530
+ return prop;
531
+ })), propNameIdentifier),
532
+ ]);
533
+ if (!insertedDestructPropNames.has(uid) && functionBody) {
534
+ functionBody.unshift(destructedObjectPattern);
535
+ insertedDestructPropNames.add(uid);
536
+ }
537
+ }
538
+ if (isAsyncFunc) {
539
+ // If it's async function, add await to the async props.<propName>
540
+ const paramAssignment = j.variableDeclaration('const', [
541
+ j.variableDeclarator(j.identifier(propName), j.awaitExpression(accessedPropIdExpr)),
542
+ ]);
543
+ if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
544
+ functionBody.unshift(paramAssignment);
545
+ insertedRenamedPropFunctionNames.add(uid);
546
+ }
547
+ }
548
+ else {
549
+ // const isFromExport = true
550
+ if (!isClientComponent) {
551
+ // If it's export function, populate the function to async
552
+ if ((0, utils_1.isFunctionType)(node.type) &&
553
+ // Make TS happy
554
+ 'async' in node) {
555
+ node.async = true;
556
+ (0, utils_1.turnFunctionReturnTypeToAsync)(node, j);
557
+ // Insert `const <propName> = await props.<propName>;` at the beginning of the function body
558
+ const paramAssignment = j.variableDeclaration('const', [
559
+ j.variableDeclarator(j.identifier(propName), j.awaitExpression(accessedPropIdExpr)),
560
+ ]);
561
+ if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
562
+ functionBody.unshift(paramAssignment);
563
+ insertedRenamedPropFunctionNames.add(uid);
564
+ }
565
+ }
566
+ }
567
+ else {
568
+ const paramAssignment = j.variableDeclaration('const', [
569
+ j.variableDeclarator(j.identifier(propName), j.callExpression(j.identifier('use'), [accessedPropIdExpr])),
570
+ ]);
571
+ if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
572
+ needsReactUseImport = true;
573
+ functionBody.unshift(paramAssignment);
574
+ insertedRenamedPropFunctionNames.add(uid);
575
+ }
576
+ }
577
+ }
578
+ }
579
+ return modifiedPropertyCount > 0;
580
+ }
581
+ const defaultExportsDeclarations = root.find(j.ExportDefaultDeclaration);
582
+ defaultExportsDeclarations.forEach((path) => {
583
+ const functionPath = (0, utils_1.getFunctionPathFromExportPath)(path, j, root, () => true);
584
+ if (functionPath) {
585
+ renameAsyncPropIfExisted(functionPath, true);
586
+ }
587
+ });
588
+ // Matching Next.js functional named export of route entry:
589
+ // - export function <named>(...) { ... }
590
+ // - export const <named> = ...
591
+ const namedExportDeclarations = root.find(j.ExportNamedDeclaration);
592
+ namedExportDeclarations.forEach((path) => {
593
+ const functionPath = (0, utils_1.getFunctionPathFromExportPath)(path, j, root, (idName) => utils_1.TARGET_NAMED_EXPORTS.has(idName));
594
+ if (functionPath) {
595
+ renameAsyncPropIfExisted(functionPath, false);
596
+ }
597
+ });
598
+ }
599
+ const isClientComponent = (0, utils_1.determineClientDirective)(root, j);
600
+ // Apply to `params` and `searchParams`
601
+ processAsyncPropOfEntryFile(isClientComponent);
602
+ // Add import { use } from 'react' if needed and not already imported
603
+ if (needsReactUseImport) {
604
+ (0, utils_1.insertReactUseImport)(root, j);
605
+ }
606
+ const commented = commentOnMatchedReExports(root, j);
607
+ modified ||= commented;
608
+ return modified ? root.toSource() : null;
609
+ }
610
+ function findAllTypes(root, j, typeName) {
611
+ const types = {
612
+ interfaces: [],
613
+ typeAliases: [],
614
+ imports: [],
615
+ references: [],
616
+ };
617
+ // Step 1: Find all interface declarations with the specified name
618
+ root
619
+ .find(j.TSInterfaceDeclaration, {
620
+ id: {
621
+ type: 'Identifier',
622
+ name: typeName,
623
+ },
624
+ })
625
+ .forEach((path) => {
626
+ types.interfaces.push(path.node);
627
+ });
628
+ // Step 2: Find all type alias declarations with the specified name
629
+ root
630
+ .find(j.TSTypeAliasDeclaration, {
631
+ id: {
632
+ type: 'Identifier',
633
+ name: typeName,
634
+ },
635
+ })
636
+ .forEach((path) => {
637
+ types.typeAliases.push(path.node);
638
+ });
639
+ // Step 3: Find all imported types with the specified name
640
+ root
641
+ .find(j.ImportSpecifier, {
642
+ imported: {
643
+ type: 'Identifier',
644
+ name: typeName,
645
+ },
646
+ })
647
+ .forEach((path) => {
648
+ types.imports.push(path.node);
649
+ });
650
+ // Step 4: Find all references to the specified type
651
+ root
652
+ .find(j.TSTypeReference, {
653
+ typeName: {
654
+ type: 'Identifier',
655
+ name: typeName,
656
+ },
657
+ })
658
+ .forEach((path) => {
659
+ types.references.push(path.node);
660
+ });
661
+ return types;
662
+ }
663
+ function commentSpreadProps(path, propsIdentifierName, j) {
664
+ let modified = false;
665
+ const functionBody = findFunctionBody(path);
666
+ const functionBodyCollection = j(functionBody);
667
+ // Find all the usage of spreading properties of `props`
668
+ const jsxSpreadProperties = functionBodyCollection.find(j.JSXSpreadAttribute, { argument: { name: propsIdentifierName } });
669
+ const objSpreadProperties = functionBodyCollection.find(j.SpreadElement, {
670
+ argument: { name: propsIdentifierName },
671
+ });
672
+ const comment = ` Next.js Dynamic Async API Codemod: '${propsIdentifierName}' is used with spread syntax (...). Any asynchronous properties of '${propsIdentifierName}' must be awaited when accessed. `;
673
+ // Add comment before it
674
+ jsxSpreadProperties.forEach((spread) => {
675
+ const inserted = (0, utils_1.insertCommentOnce)(spread.value, j, comment);
676
+ if (inserted)
677
+ modified = true;
678
+ });
679
+ objSpreadProperties.forEach((spread) => {
680
+ const inserted = (0, utils_1.insertCommentOnce)(spread.value, j, comment);
681
+ if (inserted)
682
+ modified = true;
683
+ });
684
+ return modified;
685
+ }
686
+ //# sourceMappingURL=next-async-dynamic-prop.js.map