@react-spectrum/codemods 1.0.1 → 1.2.0

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.
@@ -680,7 +680,7 @@ function transformStyleProps(path, element) {
680
680
  if (isDOMElement) {
681
681
  let index = path.node.openingElement.attributes?.findIndex(a => a.type === 'JSXAttribute' && a.name.name === 'className');
682
682
  if (index != null && index >= 0) {
683
- classNameAttribute = path.get('openingElement').get('attributes').at(index);
683
+ classNameAttribute = path.get('openingElement').get('attributes')[index];
684
684
  }
685
685
  }
686
686
  let valueToAST = (v) => {
@@ -45,6 +45,7 @@ exports.updateComponentWithinCollection = updateComponentWithinCollection;
45
45
  exports.commentIfParentCollectionNotDetected = commentIfParentCollectionNotDetected;
46
46
  exports.movePropToNewChildComponentName = movePropToNewChildComponentName;
47
47
  exports.movePropToParentComponent = movePropToParentComponent;
48
+ exports.movePropToChildComponent = movePropToChildComponent;
48
49
  exports.updateToNewComponentName = updateToNewComponentName;
49
50
  exports.convertDimensionValueToPx = convertDimensionValueToPx;
50
51
  exports.updatePlacementToSingleValue = updatePlacementToSingleValue;
@@ -346,9 +347,9 @@ function moveRenderPropsToChild(path, options) {
346
347
  */
347
348
  function updateComponentWithinCollection(path, options) {
348
349
  const { parentComponentName, newComponentName } = options;
349
- // Collections currently implemented
350
- // TODO: Add 'ActionGroup', 'ListBox', 'ListView' once implemented
351
- const collectionItemParents = new Set(['Menu', 'ActionMenu', 'TagGroup', 'Breadcrumbs', 'Picker', 'ComboBox', 'ListBox', 'TabList', 'TabPanels', 'Collection']);
350
+ // Collections currently implemented.
351
+ // TODO: Add 'ActionGroup', 'ListBox' once implemented
352
+ const collectionItemParents = new Set(['Menu', 'ActionMenu', 'TagGroup', 'Breadcrumbs', 'Picker', 'ComboBox', 'ListBox', 'ListView', 'TabList', 'TabPanels', 'Collection', 'ContextualHelpTrigger']);
352
353
  if (t.isJSXElement(path.node) &&
353
354
  t.isJSXIdentifier(path.node.openingElement.name)) {
354
355
  // Find closest parent collection component
@@ -377,7 +378,7 @@ function updateComponentWithinCollection(path, options) {
377
378
  * Example: If they're declaring declaring Items somewhere above the collection.
378
379
  */
379
380
  function commentIfParentCollectionNotDetected(path) {
380
- const collectionItemParents = new Set(['Menu', 'ActionMenu', 'TagGroup', 'Breadcrumbs', 'Picker', 'ComboBox', 'ListBox', 'TabList', 'TabPanels', 'ActionGroup', 'ActionButtonGroup', 'ToggleButtonGroup', 'ListBox', 'ListView', 'Collection', 'SearchAutocomplete', 'Accordion', 'ActionBar', 'StepList']);
381
+ const collectionItemParents = new Set(['Menu', 'ActionMenu', 'TagGroup', 'Breadcrumbs', 'Picker', 'ComboBox', 'ListBox', 'TabList', 'TabPanels', 'ActionGroup', 'ActionButtonGroup', 'ToggleButtonGroup', 'ListBox', 'ListView', 'Collection', 'SearchAutocomplete', 'Accordion', 'ActionBar', 'StepList', 'ContextualHelpTrigger']);
381
382
  if (t.isJSXElement(path.node)) {
382
383
  // Find closest parent collection component
383
384
  let closestParentCollection = path.findParent((p) => t.isJSXElement(p.node) &&
@@ -404,6 +405,7 @@ function movePropToNewChildComponentName(path, options) {
404
405
  (0, utils_1.getName)(path, path.node.openingElement.name) === childComponentName &&
405
406
  (0, utils_1.getName)(path, path.parentPath.node.openingElement.name) === parentComponentName) {
406
407
  let propValue;
408
+ let localName = newChildComponentName;
407
409
  path.node.openingElement.attributes =
408
410
  path.node.openingElement.attributes.filter((attr) => {
409
411
  if (t.isJSXAttribute(attr) && attr.name.name === propName) {
@@ -413,7 +415,11 @@ function movePropToNewChildComponentName(path, options) {
413
415
  return true;
414
416
  });
415
417
  if (propValue) {
416
- path.node.children.unshift(t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(newChildComponentName), []), t.jsxClosingElement(t.jsxIdentifier(newChildComponentName)), [t.isStringLiteral(propValue) ? t.jsxText(propValue.value) : propValue]));
418
+ if (availableComponents.has(newChildComponentName)) {
419
+ let program = path.findParent((p) => t.isProgram(p.node));
420
+ localName = (0, utils_1.addComponentImport)(program, newChildComponentName);
421
+ }
422
+ path.node.children.unshift(t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(localName), []), t.jsxClosingElement(t.jsxIdentifier(localName)), [t.isStringLiteral(propValue) ? t.jsxText(propValue.value) : propValue]));
417
423
  // TODO: handle dynamic collections. Need to wrap export function child in <Collection> and move `items` prop down.
418
424
  }
419
425
  }
@@ -440,6 +446,35 @@ function movePropToParentComponent(path, options) {
440
446
  }
441
447
  });
442
448
  }
449
+ /**
450
+ * Moves a prop from the parent component onto a direct child JSX element.
451
+ *
452
+ * Example:
453
+ * - MenuTrigger: Remove `shouldCloseOnSelect` and add it to the child `Menu` instead.
454
+ */
455
+ function movePropToChildComponent(path, options) {
456
+ const { parentComponentName, childComponentName, propName } = options;
457
+ if (!t.isJSXIdentifier(path.node.openingElement.name) ||
458
+ (0, utils_1.getName)(path, path.node.openingElement.name) !== parentComponentName) {
459
+ return;
460
+ }
461
+ let attrs = path.node.openingElement.attributes;
462
+ let propAttr = attrs.find((attr) => t.isJSXAttribute(attr) && attr.name.name === propName);
463
+ if (!propAttr) {
464
+ return;
465
+ }
466
+ let childPath = path.get('children').find((child) => child.isJSXElement() &&
467
+ t.isJSXIdentifier(child.node.openingElement.name) &&
468
+ (0, utils_1.getName)(path, child.node.openingElement.name) === childComponentName);
469
+ if (!childPath?.isJSXElement()) {
470
+ return;
471
+ }
472
+ childPath.node.openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier(propName), propAttr.value));
473
+ let index = attrs.indexOf(propAttr);
474
+ if (index !== -1) {
475
+ attrs.splice(index, 1);
476
+ }
477
+ }
443
478
  /**
444
479
  * Update to use a new component.
445
480
  *
@@ -39,6 +39,8 @@ exports.capitalize = capitalize;
39
39
  exports.addComment = addComment;
40
40
  exports.addComponentImport = addComponentImport;
41
41
  exports.removeComponentImport = removeComponentImport;
42
+ exports.removeComponentImportIfUnused = removeComponentImportIfUnused;
43
+ exports.removeUnusedImports = removeUnusedImports;
42
44
  exports.getName = getName;
43
45
  const t = __importStar(require("@babel/types"));
44
46
  function getPropValue(node) {
@@ -108,6 +110,17 @@ function addComment(node, comment) {
108
110
  });
109
111
  }
110
112
  function addComponentImport(path, newComponentName) {
113
+ let existingImport = path.node.body.find((node) => t.isImportDeclaration(node) && node.source.value === '@react-spectrum/s2');
114
+ if (existingImport && t.isImportDeclaration(existingImport)) {
115
+ let existingSpecifier = existingImport.specifiers.find((specifier) => {
116
+ return (t.isImportSpecifier(specifier) &&
117
+ specifier.imported.type === 'Identifier' &&
118
+ specifier.imported.name === newComponentName);
119
+ });
120
+ if (existingSpecifier && t.isImportSpecifier(existingSpecifier)) {
121
+ return existingSpecifier.local?.name ?? newComponentName;
122
+ }
123
+ }
111
124
  // If newComponentName variable already exists in scope, alias new import to avoid conflict.
112
125
  let existingBinding = path.scope.getBinding(newComponentName);
113
126
  let localName = newComponentName;
@@ -120,17 +133,7 @@ function addComponentImport(path, newComponentName) {
120
133
  }
121
134
  localName = newName;
122
135
  }
123
- let existingImport = path.node.body.find((node) => t.isImportDeclaration(node) && node.source.value === '@react-spectrum/s2');
124
136
  if (existingImport && t.isImportDeclaration(existingImport)) {
125
- let specifier = existingImport.specifiers.find((specifier) => {
126
- return (t.isImportSpecifier(specifier) &&
127
- specifier.imported.type === 'Identifier' &&
128
- specifier.imported.name === newComponentName);
129
- });
130
- if (specifier) {
131
- // Already imported
132
- return localName;
133
- }
134
137
  existingImport.specifiers.push(t.importSpecifier(t.identifier(localName), t.identifier(newComponentName)));
135
138
  }
136
139
  else {
@@ -142,18 +145,66 @@ function addComponentImport(path, newComponentName) {
142
145
  return localName;
143
146
  }
144
147
  function removeComponentImport(path, component) {
145
- let existingImport = path.node.body.find((node) => t.isImportDeclaration(node) && node.source.value === '@adobe/react-spectrum' || t.isImportDeclaration(node) && node.source.value.startsWith('@react-spectrum/'));
146
- if (existingImport && t.isImportDeclaration(existingImport)) {
147
- let specifier = existingImport.specifiers.find((specifier) => {
148
- return (t.isImportSpecifier(specifier) &&
149
- specifier.imported.type === 'Identifier' &&
150
- specifier.imported.name === component);
148
+ let imports = path.node.body.filter((node) => {
149
+ return t.isImportDeclaration(node)
150
+ && (node.source.value === '@adobe/react-spectrum'
151
+ || (node.source.value.startsWith('@react-spectrum/') && node.source.value !== '@react-spectrum/s2'));
152
+ });
153
+ for (let importDecl of imports) {
154
+ let previousLength = importDecl.specifiers.length;
155
+ importDecl.specifiers = importDecl.specifiers.filter((specifier) => {
156
+ return !(t.isImportSpecifier(specifier)
157
+ && specifier.imported.type === 'Identifier'
158
+ && specifier.imported.name === component);
159
+ });
160
+ if (importDecl.specifiers.length === 0 && previousLength > 0) {
161
+ path.node.body = path.node.body.filter((node) => node !== importDecl);
162
+ }
163
+ }
164
+ }
165
+ function removeComponentImportIfUnused(path, component) {
166
+ path.scope.crawl();
167
+ let imports = path.node.body.filter((node) => {
168
+ return t.isImportDeclaration(node)
169
+ && (node.source.value === '@adobe/react-spectrum'
170
+ || node.source.value.startsWith('@react-spectrum/'));
171
+ });
172
+ for (let importDecl of imports) {
173
+ let previousLength = importDecl.specifiers.length;
174
+ importDecl.specifiers = importDecl.specifiers.filter((specifier) => {
175
+ if (t.isImportSpecifier(specifier)
176
+ && specifier.imported.type === 'Identifier'
177
+ && specifier.imported.name === component) {
178
+ let localName = specifier.local?.name ?? specifier.imported.name;
179
+ let binding = path.scope.getBinding(localName);
180
+ return !!binding?.referencePaths.length;
181
+ }
182
+ return true;
151
183
  });
152
- if (specifier) {
153
- existingImport.specifiers = existingImport.specifiers.filter((s) => s !== specifier);
154
- if (existingImport.specifiers.length === 0) {
155
- path.node.body = path.node.body.filter((node) => node !== existingImport);
184
+ if (importDecl.specifiers.length === 0 && previousLength > 0) {
185
+ path.node.body = path.node.body.filter((node) => node !== importDecl);
186
+ }
187
+ }
188
+ }
189
+ function removeUnusedImports(path, sources) {
190
+ path.scope.crawl();
191
+ let sourceSet = new Set(sources);
192
+ let imports = path.node.body.filter((node) => {
193
+ return t.isImportDeclaration(node) && sourceSet.has(node.source.value);
194
+ });
195
+ for (let importDecl of imports) {
196
+ let previousLength = importDecl.specifiers.length;
197
+ importDecl.specifiers = importDecl.specifiers.filter((specifier) => {
198
+ if (t.isImportSpecifier(specifier)
199
+ || t.isImportDefaultSpecifier(specifier)
200
+ || t.isImportNamespaceSpecifier(specifier)) {
201
+ let binding = path.scope.getBinding(specifier.local.name);
202
+ return !!binding?.referencePaths.length;
156
203
  }
204
+ return true;
205
+ });
206
+ if (importDecl.specifiers.length === 0 && previousLength > 0) {
207
+ path.node.body = path.node.body.filter((node) => node !== importDecl);
157
208
  }
158
209
  }
159
210
  }
@@ -4,9 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getComponents = getComponents;
7
+ const fs_1 = require("fs");
7
8
  const parser_1 = require("@babel/parser");
8
9
  const path = require('path');
9
- const fs_1 = require("fs");
10
10
  const traverse_1 = __importDefault(require("@babel/traverse"));
11
11
  // These are exported but there are no codemods written for them yet.
12
12
  // Don't replace imports yet.
@@ -19,8 +19,18 @@ const skipped = new Set([
19
19
  function getComponents() {
20
20
  // Determine list of available components in S2 from index.ts
21
21
  let availableComponents = new Set();
22
- const packagePath = require.resolve('@react-spectrum/s2');
23
- const indexPath = path.join(path.dirname(packagePath), process.env.NODE_ENV === 'test' ? 'src/index.ts' : '../src/index.ts');
22
+ let indexPath;
23
+ try {
24
+ const packagePath = require.resolve('@react-spectrum/s2');
25
+ indexPath = path.join(path.dirname(packagePath), process.env.NODE_ENV === 'test' ? 'index.ts' : '../../exports/index.ts');
26
+ }
27
+ catch {
28
+ const workspaceIndexPath = path.resolve(__dirname, '../../../../../@react-spectrum/s2/exports/index.ts');
29
+ if (!(0, fs_1.existsSync)(workspaceIndexPath)) {
30
+ throw new Error('Could not resolve @react-spectrum/s2 source for codemods.');
31
+ }
32
+ indexPath = workspaceIndexPath;
33
+ }
24
34
  let index = (0, parser_1.parse)((0, fs_1.readFileSync)(indexPath, 'utf8'), { sourceType: 'module', plugins: ['typescript'] });
25
35
  (0, traverse_1.default)(index, {
26
36
  ExportNamedDeclaration(path) {
@@ -11,7 +11,24 @@ const logger_js_1 = __importDefault(require("./utils/logger.js"));
11
11
  const transform_js_1 = require("./transform.js");
12
12
  const waitForKeypress_js_1 = require("./utils/waitForKeypress.js");
13
13
  const boxen = require('boxen');
14
+ function printNextSteps(nextSteps) {
15
+ console.log(boxen(`Next steps:\n\n ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join('\n\n\n')}`, { borderStyle: 'round', padding: 1, borderColor: 'green' }));
16
+ }
14
17
  async function s1_to_s2(options) {
18
+ if (options.agent) {
19
+ logger_js_1.default.info('Running s1-to-s2 in agent mode (non-interactive, transform-only).');
20
+ logger_js_1.default.info('Upgrading components...');
21
+ await (0, transform_js_1.transform)(options);
22
+ logger_js_1.default.success('Upgrade complete!');
23
+ printNextSteps([
24
+ `Ensure ${chalk_1.default.bold('@react-spectrum/s2')} is installed.`,
25
+ `If your bundler is not Parcel v2.12.0+, configure the Spectrum 2 style macro support. See: ${chalk_1.default.underline('https://react-spectrum.adobe.com/getting-started#framework-setup')}`,
26
+ `Add ${chalk_1.default.bold('import \'@react-spectrum/s2/page.css\';')} to your entry component if needed.`,
27
+ `Search for ${chalk_1.default.bold('TODO(S2-upgrade)')} and resolve remaining manual migration updates.`,
28
+ `Reference the migration guide: ${chalk_1.default.underline('https://react-spectrum.adobe.com/migrating')}`
29
+ ]);
30
+ return;
31
+ }
15
32
  console.log(boxen('Welcome to the React Spectrum v3 to Spectrum 2 upgrade assistant!\n\n' +
16
33
  'This tool will:\n\n' +
17
34
  `1. Install the ${chalk_1.default.bold('@react-spectrum/s2')} package and setup your bundler to use the Spectrum 2 style macro.\n\n` +
@@ -44,14 +61,14 @@ async function s1_to_s2(options) {
44
61
  ` - Vite: ${chalk_1.default.underline('https://github.com/adobe/react-spectrum/tree/main/examples/s2-vite-project')}\n` +
45
62
  ` - Rollup: ${chalk_1.default.underline('https://github.com/adobe/react-spectrum/tree/main/examples/s2-rollup-starter-app')}\n` +
46
63
  ` - ESBuild: ${chalk_1.default.underline('https://github.com/adobe/react-spectrum/tree/main/examples/s2-esbuild-starter-app')}\n\n` +
47
- `or view documentation here: ${chalk_1.default.underline('https://react-spectrum.adobe.com/s2/index.html?path=/docs/intro--docs#configuring-your-bundler')}`);
64
+ `or view documentation here: ${chalk_1.default.underline('https://react-spectrum.adobe.com/getting-started#framework-setup')}`);
48
65
  }
49
66
  nextSteps.push('Handle remaining upgrades and run your project\'s linter or formatter.\n\n' +
50
67
  'There may have been some upgrades that we couldn\'t handle automatically. We marked these with comments containing:\n\n' +
51
68
  `${chalk_1.default.bold('TODO(S2-upgrade)')}\n\n` +
52
69
  'You should be able to search your codebase and handle these manually. \n\n' +
53
70
  'We also recommend running your project\'s code formatter (i.e. Prettier, ESLint) after the upgrade process to clean up any extraneous formatting from the codemod.\n\n' +
54
- `For additional help, reference the Spectrum 2 Migration Guide: ${chalk_1.default.underline('https://react-spectrum.adobe.com/s2/index.html?path=/docs/migrating--docs')}`);
55
- console.log(boxen(`Next steps:\n\n ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join('\n\n\n')}`, { borderStyle: 'round', padding: 1, borderColor: 'green' }));
71
+ `For additional help, reference the Spectrum 2 Migration Guide: ${chalk_1.default.underline('https://react-spectrum.adobe.com/migrating')}`);
72
+ printNextSteps(nextSteps);
56
73
  process.exit(0);
57
74
  }
@@ -8,6 +8,7 @@ const Runner_js_1 = require("jscodeshift/src/Runner.js");
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const transformPath = path_1.default.join(__dirname, 'codemods', 'codemod.js');
10
10
  async function transform(options) {
11
- let { path: filePath = '.', ...rest } = options;
12
- return await (0, Runner_js_1.run)(transformPath, [filePath], rest);
11
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
12
+ const { path: filePath = '.', agent, ...jscodeshiftOptions } = options;
13
+ return await (0, Runner_js_1.run)(transformPath, [filePath], jscodeshiftOptions);
13
14
  }
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const Module = require('module');
6
+ const url = require('url');
5
7
  function areSpecifiersAlphabetized(specifiers) {
6
8
  const specifierNames = specifiers.map((specifier) => specifier.imported.name);
7
9
  const sortedNames = [...specifierNames].sort();
@@ -41,7 +43,17 @@ const transformer = function transformer(file, api, options) {
41
43
  let anyIndexFound = false;
42
44
  const monopackageExports = {};
43
45
  selectedPackages.forEach((pkg) => {
44
- const indexPath = path.join(process.cwd(), `node_modules/${packages[pkg].monopackage}/dist/types.d.ts`);
46
+ let indexPath;
47
+ try {
48
+ let pkgPath = path.dirname(Module.findPackageJSON(packages[pkg].monopackage, url.pathToFileURL(file.path || `${process.cwd()}/index`)));
49
+ indexPath = `${pkgPath}/dist/types/exports/index.d.ts`;
50
+ if (!fs.existsSync(indexPath)) {
51
+ indexPath = `${pkgPath}/exports/index.ts`;
52
+ }
53
+ }
54
+ catch {
55
+ return;
56
+ }
45
57
  if (fs.existsSync(indexPath)) {
46
58
  anyIndexFound = true;
47
59
  const indexFile = fs.readFileSync(indexPath, 'utf8');
@@ -79,7 +91,7 @@ const transformer = function transformer(file, api, options) {
79
91
  const individualPackageImports = root
80
92
  .find(j.ImportDeclaration)
81
93
  .filter((path) => {
82
- return path.node.source.value?.startsWith(packages[pkg].individualPrefix);
94
+ return path.node.source.value !== '@react-spectrum/s2' && path.node.source.value?.startsWith(packages[pkg].individualPrefix);
83
95
  });
84
96
  if (individualPackageImports.size() === 0) {
85
97
  return;
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.default = transformer;
37
+ const specifiers_1 = require("./specifiers");
38
+ const parser_1 = require("@babel/parser");
39
+ const recast_1 = require("recast");
40
+ const t = __importStar(require("@babel/types"));
41
+ function getImportedName(specifier) {
42
+ return specifier.imported.type === 'Identifier' ? specifier.imported.name : specifier.imported.value;
43
+ }
44
+ function getImportKey(specifier) {
45
+ const importedName = getImportedName(specifier);
46
+ const localName = specifier.local?.name ?? importedName;
47
+ const importKind = specifier.importKind ?? 'value';
48
+ return `${importKind}:${importedName}:${localName}`;
49
+ }
50
+ function getImportDeclarationKey(node) {
51
+ return (node.importKind ?? 'value') + ':' + node.source.value;
52
+ }
53
+ function getNamedSpecifierKeys(importDeclaration) {
54
+ let keys = new Set();
55
+ for (let specifier of importDeclaration.specifiers ?? []) {
56
+ if (specifier.type === 'ImportSpecifier') {
57
+ keys.add(getImportKey(specifier));
58
+ }
59
+ }
60
+ return keys;
61
+ }
62
+ function createImportDeclaration(source, importKind, specifiers) {
63
+ let declaration = t.importDeclaration(specifiers, t.stringLiteral(source));
64
+ declaration.importKind = importKind ?? 'value';
65
+ return declaration;
66
+ }
67
+ function resolveTargetSource(candidates, uniqueSources, existingImports) {
68
+ // Group with the first unique source that contained a candidate.
69
+ // For example if you imported ListBox, ActionGroup, and Item, Item would be grouped with ListBox.
70
+ for (let source of uniqueSources) {
71
+ if (candidates.includes(source)) {
72
+ return source;
73
+ }
74
+ }
75
+ // Group with an already existing import.
76
+ for (let source of existingImports.keys()) {
77
+ if (candidates.includes(source)) {
78
+ return source;
79
+ }
80
+ }
81
+ return candidates[0];
82
+ }
83
+ function transformer(file, api) {
84
+ let specifiersByPackage = (0, specifiers_1.getSpecifiersByPackage)(file.path);
85
+ let j = api.jscodeshift.withParser({
86
+ parse(source) {
87
+ return (0, recast_1.parse)(source, {
88
+ parser: {
89
+ parse(innerSource) {
90
+ return (0, parser_1.parse)(innerSource, {
91
+ sourceType: 'module',
92
+ plugins: [
93
+ 'jsx',
94
+ 'typescript',
95
+ 'importAssertions',
96
+ 'dynamicImport',
97
+ 'decorators-legacy',
98
+ 'classProperties',
99
+ 'classPrivateProperties',
100
+ 'classPrivateMethods',
101
+ 'exportDefaultFrom',
102
+ 'exportNamespaceFrom',
103
+ 'objectRestSpread',
104
+ 'optionalChaining',
105
+ 'nullishCoalescingOperator',
106
+ 'topLevelAwait'
107
+ ],
108
+ tokens: true,
109
+ errorRecovery: true
110
+ });
111
+ }
112
+ }
113
+ });
114
+ }
115
+ });
116
+ let root = j(file.source);
117
+ let program = root.get().node.program;
118
+ let uniqueSources = new Set();
119
+ let existingImports = new Map();
120
+ for (let node of program.body) {
121
+ if (node.type === 'ImportDeclaration') {
122
+ let source = node.source.value;
123
+ if (typeof source !== 'string') {
124
+ continue;
125
+ }
126
+ existingImports.set(getImportDeclarationKey(node), node);
127
+ if (source in specifiersByPackage) {
128
+ let sourceMap = specifiersByPackage[source];
129
+ for (let specifier of node.specifiers ?? []) {
130
+ if (specifier.type !== 'ImportSpecifier') {
131
+ continue;
132
+ }
133
+ let importedName = getImportedName(specifier);
134
+ let candidates = sourceMap[importedName];
135
+ if (candidates && (candidates.length === 1 || candidates[0] === `${source}/${importedName}`)) {
136
+ let importKind = node.importKind || 'value';
137
+ uniqueSources.add(importKind + ':' + candidates[0]);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ let didChange = false;
144
+ program.body = program.body.flatMap(node => {
145
+ if (node.type !== 'ImportDeclaration') {
146
+ return [node];
147
+ }
148
+ let source = node.source.value;
149
+ if (typeof source !== 'string' || !(source in specifiersByPackage)) {
150
+ return [node];
151
+ }
152
+ let importDeclaration = node;
153
+ let sourceMap = specifiersByPackage[node.source.value];
154
+ let movedSpecifiersBySource = new Map();
155
+ importDeclaration.specifiers = importDeclaration.specifiers.filter(specifier => {
156
+ if (specifier.type !== 'ImportSpecifier') {
157
+ return true;
158
+ }
159
+ let importedName = getImportedName(specifier);
160
+ let candidates = sourceMap[importedName];
161
+ if (!candidates || candidates.length === 0) {
162
+ return true;
163
+ }
164
+ let importKind = node.importKind || 'value';
165
+ let targetSource = candidates.length === 1
166
+ ? importKind + ':' + candidates[0]
167
+ : resolveTargetSource(candidates.map(c => importKind + ':' + c), uniqueSources, existingImports);
168
+ let movedSpecifiers = movedSpecifiersBySource.get(targetSource) ?? [];
169
+ movedSpecifiers.push(t.cloneNode(specifier, true));
170
+ movedSpecifiersBySource.set(targetSource, movedSpecifiers);
171
+ didChange = true;
172
+ return false;
173
+ });
174
+ if (movedSpecifiersBySource.size === 0) {
175
+ return [node];
176
+ }
177
+ let newDeclarations = [];
178
+ if (importDeclaration.specifiers.length > 0) {
179
+ newDeclarations.push(node);
180
+ }
181
+ for (let [targetSource, movedSpecifiers] of movedSpecifiersBySource) {
182
+ let destinationImport = existingImports.get(targetSource);
183
+ if (!destinationImport) {
184
+ destinationImport = createImportDeclaration(targetSource.slice(targetSource.indexOf(':') + 1), importDeclaration.importKind, []);
185
+ newDeclarations.push(destinationImport);
186
+ existingImports.set(targetSource, destinationImport);
187
+ }
188
+ let existingSpecifierKeys = getNamedSpecifierKeys(destinationImport);
189
+ for (let movedSpecifier of movedSpecifiers) {
190
+ let key = getImportKey(movedSpecifier);
191
+ if (!existingSpecifierKeys.has(key)) {
192
+ destinationImport.specifiers.push(movedSpecifier);
193
+ existingSpecifierKeys.add(key);
194
+ }
195
+ }
196
+ }
197
+ return newDeclarations;
198
+ });
199
+ return didChange ? root.toSource({ quote: 'single' }) : file.source;
200
+ }
201
+ ;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.use_subpaths = use_subpaths;
7
+ const Runner_js_1 = require("jscodeshift/src/Runner.js");
8
+ const path_1 = __importDefault(require("path"));
9
+ const transformPath = path_1.default.join(__dirname, 'codemod.js');
10
+ async function use_subpaths(options) {
11
+ let { path: filePath = '.', ...rest } = options;
12
+ return await (0, Runner_js_1.run)(transformPath, [filePath], rest);
13
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getSpecifiersByPackage = getSpecifiersByPackage;
7
+ /* eslint-disable max-depth */
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const module_1 = __importDefault(require("module"));
10
+ const parser_1 = require("@babel/parser");
11
+ const path_1 = __importDefault(require("path"));
12
+ const url_1 = __importDefault(require("url"));
13
+ const PACKAGES = [
14
+ '@adobe/react-spectrum',
15
+ '@react-spectrum/s2',
16
+ 'react-aria-components',
17
+ 'react-aria',
18
+ 'react-stately'
19
+ ];
20
+ const specifiersByPackage = {};
21
+ /** Builds a mapping of monopackage -> export -> subpaths that contain the export. */
22
+ function getSpecifiersByPackage(from) {
23
+ for (let pkg of PACKAGES) {
24
+ if (specifiersByPackage[pkg]) {
25
+ continue;
26
+ }
27
+ let dir;
28
+ try {
29
+ let pkgPath = path_1.default.dirname(module_1.default.findPackageJSON(pkg, url_1.default.pathToFileURL(from || `${process.cwd()}/index`)));
30
+ dir = `${pkgPath}/dist/types/exports`;
31
+ if (!fs_1.default.existsSync(dir)) {
32
+ dir = `${pkgPath}/exports`;
33
+ }
34
+ if (!fs_1.default.existsSync(dir)) {
35
+ continue;
36
+ }
37
+ }
38
+ catch {
39
+ continue;
40
+ }
41
+ let exports = {};
42
+ for (let entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
43
+ if (entry.name === 'index.ts' || entry.name === 'index.d.ts' || !entry.isFile()) {
44
+ continue;
45
+ }
46
+ let contents = fs_1.default.readFileSync(`${dir}/${entry.name}`, 'utf8');
47
+ let ast = (0, parser_1.parse)(contents, {
48
+ sourceType: 'module',
49
+ plugins: ['typescript']
50
+ });
51
+ let subpath = entry.name.replace(/(\.d)?\.ts$/, '');
52
+ let importSpecifier = `${pkg}/${subpath}`;
53
+ for (let node of ast.program.body) {
54
+ if (node.type === 'ExportNamedDeclaration') {
55
+ for (let specifier of node.specifiers) {
56
+ if (specifier.type !== 'ExportSpecifier') {
57
+ continue;
58
+ }
59
+ let exported = specifier.exported.type === 'Identifier' ? specifier.exported.name : specifier.exported.value;
60
+ exports[exported] ?? (exports[exported] = []);
61
+ if (exported.startsWith(subpath)) {
62
+ exports[exported].unshift(importSpecifier);
63
+ }
64
+ else {
65
+ exports[exported].push(importSpecifier);
66
+ }
67
+ }
68
+ }
69
+ }
70
+ specifiersByPackage[pkg] = exports;
71
+ }
72
+ }
73
+ return specifiersByPackage;
74
+ }