@next/codemod 15.0.0-rc.0 → 15.0.0-rc.1

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