@next/codemod 15.0.0-canary.2 → 15.0.0-canary.200

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