@next/codemod 15.0.0-canary.197 → 15.0.0-canary.199

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.
package/bin/upgrade.js CHANGED
@@ -30,7 +30,6 @@ exports.runUpgrade = runUpgrade;
30
30
  const os = __importStar(require("os"));
31
31
  const prompts_1 = __importDefault(require("prompts"));
32
32
  const fs_1 = __importDefault(require("fs"));
33
- const semver_1 = __importDefault(require("semver"));
34
33
  const compare_1 = __importDefault(require("semver/functions/compare"));
35
34
  const child_process_1 = require("child_process");
36
35
  const path_1 = __importDefault(require("path"));
@@ -149,8 +148,8 @@ async function runUpgrade(revision, options) {
149
148
  let execCommand = 'npx';
150
149
  // The following React codemods are for React 19
151
150
  if (!shouldStayOnReact18 &&
152
- (0, compare_1.default)(targetReactVersion, '18.9999.9999') > 0 &&
153
- (0, compare_1.default)(installedReactVersion, '18.9999.9999') <= 0) {
151
+ (0, compare_1.default)(targetReactVersion, '19.0.0-0') >= 0 &&
152
+ (0, compare_1.default)(installedReactVersion, '19.0.0-0') < 0) {
154
153
  shouldRunReactCodemods = await suggestReactCodemods();
155
154
  shouldRunReactTypesCodemods = await suggestReactTypesCodemods();
156
155
  const execCommandMap = {
@@ -336,25 +335,21 @@ async function suggestTurbopack(packageJson) {
336
335
  responseCustomDevScript.customDevScript || devScript;
337
336
  }
338
337
  async function suggestCodemods(initialNextVersion, targetNextVersion) {
339
- // Here we suggest pre-released codemods by their "stable" version.
340
- // It is because if we suggest by the version range (installed ~ target),
341
- // pre-released codemods for the target version are not suggested when upgrading.
342
- // Let's say we have a codemod for v15.0.0-canary.x, and we're upgrading from
343
- // v15.x -> v15.x. Our initial version is higher than the codemod's version,
344
- // so the codemod will not be suggested.
345
- // This is not ideal as the codemods for pre-releases are also targeting the major version.
346
- // Also, when the user attempts to run the upgrade command twice, and have installed the
347
- // target version, the behavior must be idempotent and suggest the codemods including the
348
- // pre-releases of the target version.
349
- const initial = semver_1.default.parse(initialNextVersion);
350
- const initialVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((versionCodemods) => {
351
- const codemod = semver_1.default.parse(versionCodemods.version);
352
- return ((0, compare_1.default)(`${codemod.major}.${codemod.minor}.${codemod.patch}`, `${initial.major}.${initial.minor}.${initial.patch}`) >= 0);
338
+ // example:
339
+ // codemod version: 15.0.0-canary.45
340
+ // 14.3 -> 15.0.0-canary.45: apply
341
+ // 14.3 -> 15.0.0-canary.44: don't apply
342
+ // 15.0.0-canary.44 -> 15.0.0-canary.45: apply
343
+ // 15.0.0-canary.45 -> 15.0.0-canary.46: don't apply
344
+ // 15.0.0-canary.45 -> 15.0.0 : don't apply
345
+ // 15.0.0-canary.44 -> 15.0.0 : apply
346
+ const initialVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((codemod) => {
347
+ return (0, compare_1.default)(codemod.version, initialNextVersion) > 0;
353
348
  });
354
349
  if (initialVersionIndex === -1) {
355
350
  return [];
356
351
  }
357
- let targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((versionCodemods) => (0, compare_1.default)(versionCodemods.version, targetNextVersion) > 0);
352
+ let targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((codemod) => (0, compare_1.default)(codemod.version, targetNextVersion) > 0);
358
353
  if (targetVersionIndex === -1) {
359
354
  targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.length;
360
355
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next/codemod",
3
- "version": "15.0.0-canary.197",
3
+ "version": "15.0.0-canary.199",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -108,10 +108,10 @@ function transformDynamicAPI(source, _api, filePath) {
108
108
  // check if current path is under the default export function
109
109
  if (isEntryFileExport) {
110
110
  // if default export function is not async, convert it to async, and await the api call
111
- if (!isCallAwaited) {
111
+ if (!isCallAwaited && (0, utils_1.isFunctionType)(exportFunctionNode.type)) {
112
+ const hasReactHooksUsage = (0, utils_1.containsReactHooksCallExpressions)(closetScopePath, j);
112
113
  // If the scoped function is async function
113
- if ((0, utils_1.isFunctionType)(exportFunctionNode.type) &&
114
- exportFunctionNode.async === false) {
114
+ if (exportFunctionNode.async === false && !hasReactHooksUsage) {
115
115
  canConvertToAsync = true;
116
116
  exportFunctionNode.async = true;
117
117
  }
@@ -121,15 +121,25 @@ function transformDynamicAPI(source, _api, filePath) {
121
121
  (0, utils_1.turnFunctionReturnTypeToAsync)(closetScopePath.node, j);
122
122
  modified = true;
123
123
  }
124
+ else {
125
+ // If it's still sync function that cannot be converted to async, wrap the api call with 'use()' if needed
126
+ if (!(0, utils_1.isParentUseCallExpression)(path, j)) {
127
+ j(path).replaceWith(j.callExpression(j.identifier('use'), [
128
+ j.callExpression(j.identifier(asyncRequestApiName), []),
129
+ ]));
130
+ needsReactUseImport = true;
131
+ modified = true;
132
+ }
133
+ }
124
134
  }
125
135
  }
126
136
  else {
127
137
  // if parent is function and it's a hook, which starts with 'use', wrap the api call with 'use()'
128
138
  const parentFunction = (0, utils_1.findClosetParentFunctionScope)(path, j);
129
139
  if (parentFunction) {
130
- const parentFunctionName = parentFunction.get().node.id?.name;
131
- const isParentFunctionHook = parentFunctionName?.startsWith('use');
132
- if (isParentFunctionHook) {
140
+ const parentFunctionName = parentFunction.get().node.id?.name || '';
141
+ const isParentFunctionHook = (0, utils_1.isReactHookName)(parentFunctionName);
142
+ if (isParentFunctionHook && !(0, utils_1.isParentUseCallExpression)(path, j)) {
133
143
  j(path).replaceWith(j.callExpression(j.identifier('use'), [
134
144
  j.callExpression(j.identifier(asyncRequestApiName), []),
135
145
  ]));
@@ -4,16 +4,9 @@ exports.transformDynamicProps = transformDynamicProps;
4
4
  const utils_1 = require("./utils");
5
5
  const parser_1 = require("../../../lib/parser");
6
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
7
  function awaitMemberAccessOfProp(propIdName, path, j) {
15
8
  // search the member access of the prop
16
- const functionBody = findFunctionBody(path);
9
+ const functionBody = (0, utils_1.findFunctionBody)(path);
17
10
  const memberAccess = j(functionBody).find(j.MemberExpression, {
18
11
  object: {
19
12
  type: 'Identifier',
@@ -30,7 +23,7 @@ function awaitMemberAccessOfProp(propIdName, path, j) {
30
23
  if (!isAccessingMatchedProperty) {
31
24
  return;
32
25
  }
33
- if (isParentPromiseAllCallExpression(memberAccessPath, j)) {
26
+ if ((0, utils_1.isParentPromiseAllCallExpression)(memberAccessPath, j)) {
34
27
  return;
35
28
  }
36
29
  // check if it's already awaited
@@ -42,44 +35,16 @@ function awaitMemberAccessOfProp(propIdName, path, j) {
42
35
  memberAccessPath.replace(awaitMemberAccess);
43
36
  hasAwaited = true;
44
37
  });
38
+ const hasReactHooksUsage = (0, utils_1.containsReactHooksCallExpressions)(path.get('body'), j);
45
39
  // If there's any awaited member access, we need to make the function async
46
40
  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
- }
41
+ if (path.value.async === false && !hasReactHooksUsage) {
42
+ path.value.async = true;
43
+ (0, utils_1.turnFunctionReturnTypeToAsync)(path.value, j);
52
44
  }
53
45
  }
54
46
  return hasAwaited;
55
47
  }
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
48
  function applyUseAndRenameAccessedProp(propIdName, path, j) {
84
49
  // search the member access of the prop, and rename the member access to the member value
85
50
  // e.g.
@@ -87,7 +52,7 @@ function applyUseAndRenameAccessedProp(propIdName, path, j) {
87
52
  // props.params.foo => params.foo
88
53
  // props.searchParams.search => searchParams.search
89
54
  let modified = false;
90
- const functionBody = findFunctionBody(path);
55
+ const functionBody = (0, utils_1.findFunctionBody)(path);
91
56
  const memberAccess = j(functionBody).find(j.MemberExpression, {
92
57
  object: {
93
58
  type: 'Identifier',
@@ -98,7 +63,7 @@ function applyUseAndRenameAccessedProp(propIdName, path, j) {
98
63
  // rename each member access
99
64
  memberAccess.forEach((memberAccessPath) => {
100
65
  // If the member access expression is first argument of `use()`, we skip
101
- if (isParentUseCallExpression(memberAccessPath, j)) {
66
+ if ((0, utils_1.isParentUseCallExpression)(memberAccessPath, j)) {
102
67
  return;
103
68
  }
104
69
  const member = memberAccessPath.value;
@@ -391,11 +356,10 @@ function transformDynamicProps(source, _api, filePath) {
391
356
  const node = path.value;
392
357
  // If it's sync default export, and it's also server component, make the function async
393
358
  if (isDefaultExport && !isClientComponent) {
394
- if (!node.async) {
395
- if ('async' in node) {
396
- node.async = true;
397
- (0, utils_1.turnFunctionReturnTypeToAsync)(node, j);
398
- }
359
+ const hasReactHooksUsage = (0, utils_1.containsReactHooksCallExpressions)(path.get('body'), j);
360
+ if (node.async === false && !hasReactHooksUsage) {
361
+ node.async = true;
362
+ (0, utils_1.turnFunctionReturnTypeToAsync)(node, j);
399
363
  }
400
364
  }
401
365
  // If it's arrow function and function body is not block statement, check if the properties are used there
@@ -421,7 +385,9 @@ function transformDynamicProps(source, _api, filePath) {
421
385
  }
422
386
  const isAsyncFunc = !!node.async;
423
387
  const functionName = path.value.id?.name || 'default';
424
- const functionBody = findFunctionBody(path);
388
+ const functionBody = (0, utils_1.findFunctionBody)(path);
389
+ const functionBodyPath = path.get('body');
390
+ const hasReactHooksUsage = (0, utils_1.containsReactHooksCallExpressions)(functionBodyPath, j);
425
391
  const hasOtherProperties = allProperties.length > propertiesMap.size;
426
392
  function createDestructuringDeclaration(properties, destructPropsIdentifierName) {
427
393
  const propsToKeep = [];
@@ -491,7 +457,7 @@ function transformDynamicProps(source, _api, filePath) {
491
457
  name: matchedPropName,
492
458
  });
493
459
  for (const propPath of propPaths.paths()) {
494
- if (isParentUseCallExpression(propPath, j)) {
460
+ if ((0, utils_1.isParentUseCallExpression)(propPath, j)) {
495
461
  // Skip transformation
496
462
  shouldSkip = true;
497
463
  break;
@@ -507,7 +473,6 @@ function transformDynamicProps(source, _api, filePath) {
507
473
  const paramPropertyName = paramsPropertyName || matchedPropName;
508
474
  // if propName is not used in lower scope, and it stars with unused prefix `_`,
509
475
  // also skip the transformation
510
- const functionBodyPath = path.get('body');
511
476
  const hasUsedInBody = j(functionBodyPath)
512
477
  .find(j.Identifier, {
513
478
  name: paramPropertyName,
@@ -526,7 +491,7 @@ function transformDynamicProps(source, _api, filePath) {
526
491
  propUsages.forEach((propUsage) => {
527
492
  // If the parent is not AwaitExpression, it's not awaited
528
493
  const isAwaited = propUsage.parentPath?.value.type === 'AwaitExpression';
529
- const isAwaitedByUse = isParentUseCallExpression(propUsage, j);
494
+ const isAwaitedByUse = (0, utils_1.isParentUseCallExpression)(propUsage, j);
530
495
  if (!isAwaited && !isAwaitedByUse) {
531
496
  hasMissingAwaited = true;
532
497
  return;
@@ -575,22 +540,19 @@ function transformDynamicProps(source, _api, filePath) {
575
540
  }
576
541
  }
577
542
  else {
578
- // const isFromExport = true
579
- if (!isClientComponent) {
543
+ if (!isClientComponent &&
544
+ (0, utils_1.isFunctionType)(node.type) &&
545
+ !hasReactHooksUsage) {
580
546
  // 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
- }
547
+ node.async = true;
548
+ (0, utils_1.turnFunctionReturnTypeToAsync)(node, j);
549
+ // Insert `const <propName> = await props.<propName>;` at the beginning of the function body
550
+ const paramAssignment = j.variableDeclaration('const', [
551
+ j.variableDeclarator(j.identifier(paramPropertyName), j.awaitExpression(accessedPropIdExpr)),
552
+ ]);
553
+ if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
554
+ functionBody.unshift(paramAssignment);
555
+ insertedRenamedPropFunctionNames.add(uid);
594
556
  }
595
557
  }
596
558
  else {
@@ -691,7 +653,7 @@ function findAllTypes(root, j, typeName) {
691
653
  }
692
654
  function commentSpreadProps(path, propsIdentifierName, j) {
693
655
  let modified = false;
694
- const functionBody = findFunctionBody(path);
656
+ const functionBody = (0, utils_1.findFunctionBody)(path);
695
657
  const functionBodyCollection = j(functionBody);
696
658
  // Find all the usage of spreading properties of `props`
697
659
  const jsxSpreadProperties = functionBodyCollection.find(j.JSXSpreadAttribute, { argument: { name: propsIdentifierName } });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TARGET_PROP_NAMES = exports.TARGET_NAMED_EXPORTS = exports.TARGET_ROUTE_EXPORTS = exports.NEXT_CODEMOD_ERROR_PREFIX = exports.NEXTJS_ENTRY_FILES = void 0;
3
+ exports.isReactHookName = exports.TARGET_PROP_NAMES = exports.TARGET_NAMED_EXPORTS = exports.TARGET_ROUTE_EXPORTS = exports.NEXT_CODEMOD_ERROR_PREFIX = exports.NEXTJS_ENTRY_FILES = void 0;
4
4
  exports.isFunctionType = isFunctionType;
5
5
  exports.isMatchedFunctionExported = isMatchedFunctionExported;
6
6
  exports.determineClientDirective = determineClientDirective;
@@ -14,6 +14,10 @@ exports.getFunctionPathFromExportPath = getFunctionPathFromExportPath;
14
14
  exports.wrapParentheseIfNeeded = wrapParentheseIfNeeded;
15
15
  exports.insertCommentOnce = insertCommentOnce;
16
16
  exports.getVariableDeclaratorId = getVariableDeclaratorId;
17
+ exports.findFunctionBody = findFunctionBody;
18
+ exports.containsReactHooksCallExpressions = containsReactHooksCallExpressions;
19
+ exports.isParentUseCallExpression = isParentUseCallExpression;
20
+ exports.isParentPromiseAllCallExpression = isParentPromiseAllCallExpression;
17
21
  exports.NEXTJS_ENTRY_FILES = /([\\/]|^)(page|layout|route|default)\.(t|j)sx?$/;
18
22
  exports.NEXT_CODEMOD_ERROR_PREFIX = '@next-codemod-error';
19
23
  const NEXT_CODEMOD_IGNORE_ERROR_PREFIX = '@next-codemod-ignore';
@@ -371,4 +375,99 @@ function getVariableDeclaratorId(path, j) {
371
375
  }
372
376
  return undefined;
373
377
  }
378
+ function findFunctionBody(path) {
379
+ let functionBody = path.node.body;
380
+ if (functionBody && functionBody.type === 'BlockStatement') {
381
+ return functionBody.body;
382
+ }
383
+ return null;
384
+ }
385
+ const isPascalCase = (s) => /^[A-Z][a-z0-9]*$/.test(s);
386
+ const isReactHookName = (name) =>
387
+ // function name is `use`
388
+ name === 'use' ||
389
+ // function name is `useX*`
390
+ (name.startsWith('use') && name[3] === name[3].toUpperCase());
391
+ exports.isReactHookName = isReactHookName;
392
+ // Determine a path of function contains any React hooks call expressions.
393
+ // e.g. if there's any of those call expressions in the function body:
394
+ // use() => true
395
+ // React.use() => false
396
+ // useXxxx() => true
397
+ // Foo.use() => true
398
+ // Foo.useXxxx() => true
399
+ function containsReactHooksCallExpressions(path, j) {
400
+ const hasReactHooks = j(path)
401
+ .find(j.CallExpression)
402
+ .filter((callPath) => {
403
+ // It's matching:
404
+ // - use(<callPath>) => true
405
+ // - useX*(<callPath>) => true
406
+ const isUseHookOrReactHookCall = j.Identifier.check(callPath.value.callee) &&
407
+ (0, exports.isReactHookName)(callPath.value.callee.name);
408
+ // It's matching member access:
409
+ // - React.use(<callPath>) => true
410
+ // - Foo.useFoo(<callPath>) => true
411
+ // - foo.useFoo(<callPath>) => false
412
+ // - foo.use(<callPath>) => false
413
+ const isReactUseCall = j.MemberExpression.check(callPath.value.callee) &&
414
+ j.Identifier.check(callPath.value.callee.object) &&
415
+ j.Identifier.check(callPath.value.callee.property) &&
416
+ isPascalCase(callPath.value.callee.object.name) &&
417
+ (0, exports.isReactHookName)(callPath.value.callee.property.name);
418
+ return isUseHookOrReactHookCall || isReactUseCall;
419
+ })
420
+ .size() > 0;
421
+ return hasReactHooks;
422
+ }
423
+ // Capture the parent of the current path is wrapped by `use()` call expression
424
+ // e.g.
425
+ // use(<path>) => true
426
+ // use2(<path>) => false
427
+ // React.use(<path>) => true
428
+ // Robust.use(<path>) => false
429
+ function isParentUseCallExpression(path, j) {
430
+ const isParentUseCall =
431
+ // member access parentPath is argument
432
+ j.CallExpression.check(path.parent.value) &&
433
+ // member access is first argument
434
+ path.parent.value.arguments[0] === path.value &&
435
+ path.parent.value.arguments.length === 1 &&
436
+ // function name is `use`
437
+ j.Identifier.check(path.parent.value.callee) &&
438
+ path.parent.value.callee.name === 'use';
439
+ const isParentReactUseCall =
440
+ // member access parentPath is argument
441
+ j.CallExpression.check(path.parent.value) &&
442
+ // member access is first argument
443
+ path.parent.value.arguments[0] === path.value &&
444
+ path.parent.value.arguments.length === 1 &&
445
+ // function name is `use`
446
+ j.MemberExpression.check(path.parent.value.callee) &&
447
+ j.Identifier.check(path.parent.value.callee.object) &&
448
+ path.parent.value.callee.object.name === 'React' &&
449
+ j.Identifier.check(path.parent.value.callee.property) &&
450
+ path.parent.value.callee.property.name === 'use';
451
+ return isParentUseCall || isParentReactUseCall;
452
+ }
453
+ // Determine if a path is wrapped by `Promise.all()`
454
+ // e.g.
455
+ // Promise.all(<path>) => true
456
+ // Promise.allSettled(<path>) => false
457
+ function isParentPromiseAllCallExpression(path, j) {
458
+ const argsParent = path.parent;
459
+ const callParent = argsParent?.parent;
460
+ if (argsParent &&
461
+ callParent &&
462
+ j.ArrayExpression.check(argsParent.value) &&
463
+ j.CallExpression.check(callParent.value) &&
464
+ j.MemberExpression.check(callParent.value.callee) &&
465
+ j.Identifier.check(callParent.value.callee.object) &&
466
+ callParent.value.callee.object.name === 'Promise' &&
467
+ j.Identifier.check(callParent.value.callee.property) &&
468
+ callParent.value.callee.property.name === 'all') {
469
+ return true;
470
+ }
471
+ return false;
472
+ }
374
473
  //# sourceMappingURL=utils.js.map