@next/codemod 15.0.0-canary.20 → 15.0.0-canary.201

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 +477 -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 +713 -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,713 @@
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
+ member.typeAnnotation.typeAnnotation,
159
+ ]));
160
+ modified = true;
161
+ }
162
+ }
163
+ });
164
+ }
165
+ else if (typeAnnotation.type === 'TSTypeReference') {
166
+ // If typeAnnotation is a type or interface, change the properties to Promise<type of property>
167
+ // e.g. interface PageProps { params: { slug: string } } => interface PageProps { params: Promise<{ slug: string }> }
168
+ const typeReference = typeAnnotation;
169
+ if (typeReference.typeName.type === 'Identifier') {
170
+ // Find the actual type of the type reference
171
+ const foundTypes = findAllTypes(root, j, typeReference.typeName.name);
172
+ // Deal with interfaces
173
+ if (foundTypes.interfaces.length > 0) {
174
+ const interfaceDeclaration = foundTypes.interfaces[0];
175
+ if (interfaceDeclaration.type === 'TSInterfaceDeclaration' &&
176
+ interfaceDeclaration.body?.type === 'TSInterfaceBody') {
177
+ const typeBody = interfaceDeclaration.body.body;
178
+ // if it's already a Promise, don't wrap it again, return
179
+ // traverse the typeReference's properties, if any is in propNames, wrap it in Promise<> if needed
180
+ typeBody.forEach((member) => {
181
+ if (member.type === 'TSPropertySignature' &&
182
+ member.key.type === 'Identifier' &&
183
+ utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
184
+ // if it's already a Promise, don't wrap it again, return
185
+ if (member.typeAnnotation &&
186
+ member.typeAnnotation.typeAnnotation &&
187
+ member.typeAnnotation?.typeAnnotation?.typeName?.name ===
188
+ 'Promise') {
189
+ return;
190
+ }
191
+ // Wrap the prop type in Promise<>
192
+ if (member.typeAnnotation &&
193
+ member.typeAnnotation.typeAnnotation &&
194
+ // check if member name is in propNames
195
+ utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
196
+ member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
197
+ member.typeAnnotation.typeAnnotation,
198
+ ]));
199
+ modified = true;
200
+ }
201
+ }
202
+ });
203
+ }
204
+ }
205
+ // Deal with type aliases
206
+ if (foundTypes.typeAliases.length > 0) {
207
+ const typeAliasDeclaration = foundTypes.typeAliases[0];
208
+ if (j.TSTypeAliasDeclaration.check(typeAliasDeclaration)) {
209
+ const typeAlias = typeAliasDeclaration.typeAnnotation;
210
+ if (j.TSTypeLiteral.check(typeAlias) &&
211
+ typeAlias.members.length > 0) {
212
+ const typeLiteral = typeAlias;
213
+ typeLiteral.members.forEach((member) => {
214
+ if (j.TSPropertySignature.check(member) &&
215
+ j.Identifier.check(member.key) &&
216
+ utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
217
+ // if it's already a Promise, don't wrap it again, return
218
+ if (member.typeAnnotation &&
219
+ member.typeAnnotation.typeAnnotation &&
220
+ member.typeAnnotation.typeAnnotation.type ===
221
+ 'TSTypeReference' &&
222
+ member.typeAnnotation.typeAnnotation.typeName.type ===
223
+ 'Identifier' &&
224
+ member.typeAnnotation.typeAnnotation.typeName.name ===
225
+ 'Promise') {
226
+ return;
227
+ }
228
+ // Wrap the prop type in Promise<>
229
+ if (member.typeAnnotation &&
230
+ j.TSTypeLiteral.check(member.typeAnnotation.typeAnnotation)) {
231
+ member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
232
+ member.typeAnnotation.typeAnnotation,
233
+ ]));
234
+ modified = true;
235
+ }
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
+ modified ||= modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
342
+ // cases of passing down `props` into any function
343
+ // Page(props) { callback(props) }
344
+ // search for all the argument of CallExpression, where currentParam is one of the arguments
345
+ const callExpressions = j(path).find(j.CallExpression, {
346
+ arguments: (args) => {
347
+ return args.some((arg) => {
348
+ return (j.Identifier.check(arg) &&
349
+ arg.name === argName &&
350
+ arg.type === 'Identifier');
351
+ });
352
+ },
353
+ });
354
+ // Add a comment to warn users that properties of `props` need to be awaited when accessed
355
+ callExpressions.forEach((callExpression) => {
356
+ // find the argument `currentParam`
357
+ const args = callExpression.value.arguments;
358
+ const propPassedAsArg = args.find((arg) => j.Identifier.check(arg) && arg.name === argName);
359
+ const comment = ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} '${argName}' is passed as an argument. Any asynchronous properties of 'props' must be awaited when accessed. `;
360
+ const inserted = (0, utils_1.insertCommentOnce)(propPassedAsArg, j, comment);
361
+ modified ||= inserted;
362
+ });
363
+ if (modified) {
364
+ modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
365
+ }
366
+ }
367
+ if (modifiedPropArgument) {
368
+ const isModified = resolveAsyncProp(path, propertiesMap, propsIdentifier.name, allProperties, isDefaultExport);
369
+ if (isModified) {
370
+ // Make TS happy
371
+ if (j.ObjectPattern.check(currentParam)) {
372
+ modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
373
+ }
374
+ // Override the first param to `props`
375
+ params[propsArgumentIndex] = propsIdentifier;
376
+ modified = true;
377
+ }
378
+ }
379
+ else {
380
+ // When the prop argument is not destructured, we need to add comments to the spread properties
381
+ if (j.Identifier.check(currentParam)) {
382
+ const commented = commentSpreadProps(path, currentParam.name, j);
383
+ const modifiedTypes = modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
384
+ modified ||= commented || modifiedTypes;
385
+ }
386
+ }
387
+ }
388
+ // Helper function to insert `const params = await asyncParams;` at the beginning of the function body
389
+ function resolveAsyncProp(path, propertiesMap, propsIdentifierName, allProperties, isDefaultExport) {
390
+ // Rename props to `prop` argument for the function
391
+ const insertedRenamedPropFunctionNames = new Set();
392
+ const node = path.value;
393
+ // If it's sync default export, and it's also server component, make the function async
394
+ if (isDefaultExport && !isClientComponent) {
395
+ const hasReactHooksUsage = (0, utils_1.containsReactHooksCallExpressions)(path.get('body'), j);
396
+ if (node.async === false && !hasReactHooksUsage) {
397
+ node.async = true;
398
+ (0, utils_1.turnFunctionReturnTypeToAsync)(node, j);
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 = (0, utils_1.findFunctionBody)(path);
425
+ const functionBodyPath = path.get('body');
426
+ const hasReactHooksUsage = (0, utils_1.containsReactHooksCallExpressions)(functionBodyPath, j);
427
+ const hasOtherProperties = allProperties.length > propertiesMap.size;
428
+ function createDestructuringDeclaration(properties, destructPropsIdentifierName) {
429
+ const propsToKeep = [];
430
+ let restProperty = null;
431
+ // Iterate over the destructured properties
432
+ properties.forEach((property) => {
433
+ if (j.ObjectProperty.check(property)) {
434
+ // Handle normal and computed properties
435
+ const keyName = j.Identifier.check(property.key)
436
+ ? property.key.name
437
+ : j.Literal.check(property.key)
438
+ ? property.key.value
439
+ : null; // for computed properties
440
+ if (typeof keyName === 'string') {
441
+ propsToKeep.push(property);
442
+ }
443
+ }
444
+ else if (j.RestElement.check(property)) {
445
+ restProperty = property;
446
+ }
447
+ });
448
+ if (propsToKeep.length === 0 && !restProperty) {
449
+ return null;
450
+ }
451
+ if (restProperty) {
452
+ propsToKeep.push(restProperty);
453
+ }
454
+ return j.variableDeclaration('const', [
455
+ j.variableDeclarator(j.objectPattern(propsToKeep), j.identifier(destructPropsIdentifierName)),
456
+ ]);
457
+ }
458
+ if (hasOtherProperties) {
459
+ /**
460
+ * If there are other properties, we need to keep the original param with destructuring
461
+ * e.g.
462
+ * input:
463
+ * Page({ params: { slug }, otherProp }) {
464
+ * const { slug } = await props.params;
465
+ * }
466
+ *
467
+ * output:
468
+ * Page(props) {
469
+ * const { otherProp } = props; // inserted
470
+ * // ...rest of the function body
471
+ * }
472
+ */
473
+ const restProperties = allProperties.filter((prop) => {
474
+ const isTargetProps = 'key' in prop &&
475
+ prop.key.type === 'Identifier' &&
476
+ utils_1.TARGET_PROP_NAMES.has(prop.key.name);
477
+ return !isTargetProps;
478
+ });
479
+ const destructionOtherPropertiesDeclaration = createDestructuringDeclaration(restProperties, propsIdentifierName);
480
+ if (functionBody && destructionOtherPropertiesDeclaration) {
481
+ functionBody.unshift(destructionOtherPropertiesDeclaration);
482
+ }
483
+ }
484
+ let modifiedPropertyCount = 0;
485
+ for (const [matchedPropName, paramsProperty] of propertiesMap) {
486
+ if (!utils_1.TARGET_PROP_NAMES.has(matchedPropName)) {
487
+ continue;
488
+ }
489
+ // In client component, if the param is already wrapped with `use()`, skip the transformation
490
+ if (isClientComponent) {
491
+ let shouldSkip = false;
492
+ const propPaths = j(path).find(j.Identifier, {
493
+ name: matchedPropName,
494
+ });
495
+ for (const propPath of propPaths.paths()) {
496
+ if ((0, utils_1.isParentUseCallExpression)(propPath, j)) {
497
+ // Skip transformation
498
+ shouldSkip = true;
499
+ break;
500
+ }
501
+ }
502
+ if (shouldSkip) {
503
+ continue;
504
+ }
505
+ }
506
+ const paramsPropertyName = j.Identifier.check(paramsProperty)
507
+ ? paramsProperty.name
508
+ : null;
509
+ const paramPropertyName = paramsPropertyName || matchedPropName;
510
+ // if propName is not used in lower scope, and it stars with unused prefix `_`,
511
+ // also skip the transformation
512
+ const hasUsedInBody = j(functionBodyPath)
513
+ .find(j.Identifier, {
514
+ name: paramPropertyName,
515
+ })
516
+ .size() > 0;
517
+ if (!hasUsedInBody && paramPropertyName.startsWith('_'))
518
+ continue;
519
+ // Search the usage of propName in the function body,
520
+ // if they're all awaited or wrapped with use(), skip the transformation
521
+ const propUsages = j(functionBodyPath).find(j.Identifier, {
522
+ name: paramPropertyName,
523
+ });
524
+ // if there's usage of the propName, then do the check
525
+ if (propUsages.size()) {
526
+ let hasMissingAwaited = false;
527
+ propUsages.forEach((propUsage) => {
528
+ // If the parent is not AwaitExpression, it's not awaited
529
+ const isAwaited = propUsage.parentPath?.value.type === 'AwaitExpression';
530
+ const isAwaitedByUse = (0, utils_1.isParentUseCallExpression)(propUsage, j);
531
+ if (!isAwaited && !isAwaitedByUse) {
532
+ hasMissingAwaited = true;
533
+ return;
534
+ }
535
+ });
536
+ // If all the usages of parm are awaited, skip the transformation
537
+ if (!hasMissingAwaited) {
538
+ continue;
539
+ }
540
+ }
541
+ modifiedPropertyCount++;
542
+ const propNameIdentifier = j.identifier(matchedPropName);
543
+ const propsIdentifier = j.identifier(propsIdentifierName);
544
+ const accessedPropIdExpr = j.memberExpression(propsIdentifier, propNameIdentifier);
545
+ // Check param property value, if it's destructed, we need to destruct it as well
546
+ // e.g.
547
+ // input: Page({ params: { slug } })
548
+ // output: const { slug } = await props.params; rather than const props = await props.params;
549
+ const uid = functionName + ':' + paramPropertyName;
550
+ if (paramsProperty?.type === 'ObjectPattern') {
551
+ const objectPattern = paramsProperty;
552
+ const objectPatternProperties = objectPattern.properties;
553
+ // destruct the object pattern, e.g. { slug } => const { slug } = params;
554
+ const destructedObjectPattern = j.variableDeclaration('const', [
555
+ j.variableDeclarator(j.objectPattern(objectPatternProperties.map((prop) => {
556
+ if (prop.type === 'Property' &&
557
+ prop.key.type === 'Identifier') {
558
+ return j.objectProperty(j.identifier(prop.key.name), j.identifier(prop.key.name));
559
+ }
560
+ return prop;
561
+ })), propNameIdentifier),
562
+ ]);
563
+ if (!insertedDestructPropNames.has(uid) && functionBody) {
564
+ functionBody.unshift(destructedObjectPattern);
565
+ insertedDestructPropNames.add(uid);
566
+ }
567
+ }
568
+ if (isAsyncFunc) {
569
+ // If it's async function, add await to the async props.<propName>
570
+ const paramAssignment = j.variableDeclaration('const', [
571
+ j.variableDeclarator(j.identifier(paramPropertyName), j.awaitExpression(accessedPropIdExpr)),
572
+ ]);
573
+ if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
574
+ functionBody.unshift(paramAssignment);
575
+ insertedRenamedPropFunctionNames.add(uid);
576
+ }
577
+ }
578
+ else {
579
+ if (!isClientComponent &&
580
+ (0, utils_1.isFunctionType)(node.type) &&
581
+ !hasReactHooksUsage) {
582
+ // If it's export function, populate the function to async
583
+ node.async = true;
584
+ (0, utils_1.turnFunctionReturnTypeToAsync)(node, j);
585
+ // Insert `const <propName> = await props.<propName>;` at the beginning of the function body
586
+ const paramAssignment = j.variableDeclaration('const', [
587
+ j.variableDeclarator(j.identifier(paramPropertyName), j.awaitExpression(accessedPropIdExpr)),
588
+ ]);
589
+ if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
590
+ functionBody.unshift(paramAssignment);
591
+ insertedRenamedPropFunctionNames.add(uid);
592
+ }
593
+ }
594
+ else {
595
+ const paramAssignment = j.variableDeclaration('const', [
596
+ j.variableDeclarator(j.identifier(paramPropertyName), j.callExpression(j.identifier('use'), [accessedPropIdExpr])),
597
+ ]);
598
+ if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
599
+ needsReactUseImport = true;
600
+ functionBody.unshift(paramAssignment);
601
+ insertedRenamedPropFunctionNames.add(uid);
602
+ }
603
+ }
604
+ }
605
+ }
606
+ return modifiedPropertyCount > 0;
607
+ }
608
+ const defaultExportsDeclarations = root.find(j.ExportDefaultDeclaration);
609
+ defaultExportsDeclarations.forEach((path) => {
610
+ const functionPath = (0, utils_1.getFunctionPathFromExportPath)(path, j, root, () => true);
611
+ if (functionPath) {
612
+ renameAsyncPropIfExisted(functionPath, true);
613
+ }
614
+ });
615
+ // Matching Next.js functional named export of route entry:
616
+ // - export function <named>(...) { ... }
617
+ // - export const <named> = ...
618
+ const namedExportDeclarations = root.find(j.ExportNamedDeclaration);
619
+ namedExportDeclarations.forEach((path) => {
620
+ const functionPath = (0, utils_1.getFunctionPathFromExportPath)(path, j, root, (idName) => utils_1.TARGET_NAMED_EXPORTS.has(idName));
621
+ if (functionPath) {
622
+ renameAsyncPropIfExisted(functionPath, false);
623
+ }
624
+ });
625
+ }
626
+ const isClientComponent = (0, utils_1.determineClientDirective)(root, j);
627
+ // Apply to `params` and `searchParams`
628
+ processAsyncPropOfEntryFile(isClientComponent);
629
+ // Add import { use } from 'react' if needed and not already imported
630
+ if (needsReactUseImport) {
631
+ (0, utils_1.insertReactUseImport)(root, j);
632
+ }
633
+ const commented = commentOnMatchedReExports(root, j);
634
+ modified ||= commented;
635
+ return modified ? root.toSource() : null;
636
+ }
637
+ function findAllTypes(root, j, typeName) {
638
+ const types = {
639
+ interfaces: [],
640
+ typeAliases: [],
641
+ imports: [],
642
+ references: [],
643
+ };
644
+ // Step 1: Find all interface declarations with the specified name
645
+ root
646
+ .find(j.TSInterfaceDeclaration, {
647
+ id: {
648
+ type: 'Identifier',
649
+ name: typeName,
650
+ },
651
+ })
652
+ .forEach((path) => {
653
+ types.interfaces.push(path.node);
654
+ });
655
+ // Step 2: Find all type alias declarations with the specified name
656
+ root
657
+ .find(j.TSTypeAliasDeclaration, {
658
+ id: {
659
+ type: 'Identifier',
660
+ name: typeName,
661
+ },
662
+ })
663
+ .forEach((path) => {
664
+ types.typeAliases.push(path.node);
665
+ });
666
+ // Step 3: Find all imported types with the specified name
667
+ root
668
+ .find(j.ImportSpecifier, {
669
+ imported: {
670
+ type: 'Identifier',
671
+ name: typeName,
672
+ },
673
+ })
674
+ .forEach((path) => {
675
+ types.imports.push(path.node);
676
+ });
677
+ // Step 4: Find all references to the specified type
678
+ root
679
+ .find(j.TSTypeReference, {
680
+ typeName: {
681
+ type: 'Identifier',
682
+ name: typeName,
683
+ },
684
+ })
685
+ .forEach((path) => {
686
+ types.references.push(path.node);
687
+ });
688
+ return types;
689
+ }
690
+ function commentSpreadProps(path, propsIdentifierName, j) {
691
+ let modified = false;
692
+ const functionBody = (0, utils_1.findFunctionBody)(path);
693
+ const functionBodyCollection = j(functionBody);
694
+ // Find all the usage of spreading properties of `props`
695
+ const jsxSpreadProperties = functionBodyCollection.find(j.JSXSpreadAttribute, { argument: { name: propsIdentifierName } });
696
+ const objSpreadProperties = functionBodyCollection.find(j.SpreadElement, {
697
+ argument: { name: propsIdentifierName },
698
+ });
699
+ const comment = ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} '${propsIdentifierName}' is used with spread syntax (...). Any asynchronous properties of '${propsIdentifierName}' must be awaited when accessed. `;
700
+ // Add comment before it
701
+ jsxSpreadProperties.forEach((spread) => {
702
+ const inserted = (0, utils_1.insertCommentOnce)(spread.value, j, comment);
703
+ if (inserted)
704
+ modified = true;
705
+ });
706
+ objSpreadProperties.forEach((spread) => {
707
+ const inserted = (0, utils_1.insertCommentOnce)(spread.value, j, comment);
708
+ if (inserted)
709
+ modified = true;
710
+ });
711
+ return modified;
712
+ }
713
+ //# sourceMappingURL=next-async-dynamic-prop.js.map