@react-spectrum/codemods 1.1.0 → 1.2.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.
- package/dist/index.js +18 -12
- package/dist/s1-to-s2/src/codemods/codemod.js +184 -12
- package/dist/s1-to-s2/src/codemods/components/ActionGroup/transform.js +2 -2
- package/dist/s1-to-s2/src/codemods/components/ActionMenu/transform.js +3 -3
- package/dist/s1-to-s2/src/codemods/components/ContextualHelpTrigger/transform.js +1 -0
- package/dist/s1-to-s2/src/codemods/components/DialogTrigger/transform.js +15 -1
- package/dist/s1-to-s2/src/codemods/components/MenuTrigger/transform.js +16 -0
- package/dist/s1-to-s2/src/codemods/components/Picker/transform.js +18 -0
- package/dist/s1-to-s2/src/codemods/components/Tabs/transform.js +7 -5
- package/dist/s1-to-s2/src/codemods/illustrations/illustrationMap.js +14 -0
- package/dist/s1-to-s2/src/codemods/shared/transforms.js +36 -1
- package/dist/s1-to-s2/src/codemods/shared/utils.js +71 -20
- package/dist/s1-to-s2/src/getComponents.js +13 -3
- package/dist/s1-to-s2/src/index.js +20 -3
- package/dist/s1-to-s2/src/transform.js +3 -2
- package/dist/use-monopackages/src/codemod.js +14 -2
- package/dist/use-subpaths/src/codemod.js +228 -0
- package/dist/use-subpaths/src/index.js +13 -0
- package/dist/use-subpaths/src/specifiers.js +75 -0
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -4,9 +4,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const { parseArgs } = require('node:util');
|
|
5
5
|
const src_1 = require("./s1-to-s2/src");
|
|
6
6
|
const src_2 = require("./use-monopackages/src");
|
|
7
|
+
const src_3 = require("./use-subpaths/src");
|
|
7
8
|
const codemods = {
|
|
8
9
|
's1-to-s2': src_1.s1_to_s2,
|
|
9
|
-
'use-monopackages': src_2.use_monopackages
|
|
10
|
+
'use-monopackages': src_2.use_monopackages,
|
|
11
|
+
'use-subpaths': src_3.use_subpaths
|
|
10
12
|
};
|
|
11
13
|
// https://github.com/facebook/jscodeshift?tab=readme-ov-file#usage-cli
|
|
12
14
|
const options = {
|
|
@@ -25,6 +27,9 @@ const options = {
|
|
|
25
27
|
},
|
|
26
28
|
'components': {
|
|
27
29
|
type: 'string'
|
|
30
|
+
},
|
|
31
|
+
'agent': {
|
|
32
|
+
type: 'boolean'
|
|
28
33
|
}
|
|
29
34
|
};
|
|
30
35
|
const { values, positionals } = parseArgs({
|
|
@@ -35,21 +40,22 @@ if (positionals.length < 1) {
|
|
|
35
40
|
console.error('Please specify a codemod to run. Available codemods: ', Object.keys(codemods).join(', '));
|
|
36
41
|
process.exit(1);
|
|
37
42
|
}
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
codemodFunction({
|
|
43
|
+
async function main() {
|
|
44
|
+
const codemodName = positionals[0];
|
|
45
|
+
const codemodFunction = codemods[codemodName];
|
|
46
|
+
if (!codemodFunction) {
|
|
47
|
+
console.error(`Unknown codemod: ${codemodName}, available codemods: ${Object.keys(codemods).join(', ')}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
await Promise.resolve(codemodFunction({
|
|
46
51
|
parser: 'tsx',
|
|
47
52
|
ignorePattern: '**/node_modules/**',
|
|
48
53
|
path: '.',
|
|
54
|
+
extensions: 'js,jsx,mjs,cjs,ts,tsx',
|
|
49
55
|
...values
|
|
50
|
-
});
|
|
56
|
+
}));
|
|
51
57
|
}
|
|
52
|
-
catch
|
|
58
|
+
main().catch((error) => {
|
|
53
59
|
console.error(`Error running codemod: ${error}`);
|
|
54
60
|
process.exit(1);
|
|
55
|
-
}
|
|
61
|
+
});
|
|
@@ -41,6 +41,7 @@ exports.default = transformer;
|
|
|
41
41
|
const utils_1 = require("./shared/utils");
|
|
42
42
|
const getComponents_1 = require("../getComponents");
|
|
43
43
|
const iconMap_1 = require("./icons/iconMap");
|
|
44
|
+
const illustrationMap_1 = require("./illustrations/illustrationMap");
|
|
44
45
|
const recast_1 = require("recast");
|
|
45
46
|
const t = __importStar(require("@babel/types"));
|
|
46
47
|
const styleProps_1 = __importDefault(require("./shared/styleProps"));
|
|
@@ -63,6 +64,108 @@ availableComponents.add('ActionGroup');
|
|
|
63
64
|
let renamedComponents = {
|
|
64
65
|
ContextualHelpTrigger: 'UnavailableMenuItemTrigger'
|
|
65
66
|
};
|
|
67
|
+
const relatedComponentGroups = {
|
|
68
|
+
ActionMenu: {
|
|
69
|
+
scopedComponents: {
|
|
70
|
+
Item: ['ActionMenu']
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
Breadcrumbs: {
|
|
74
|
+
scopedComponents: {
|
|
75
|
+
Item: ['Breadcrumbs']
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
ComboBox: {
|
|
79
|
+
scopedComponents: {
|
|
80
|
+
Item: ['ComboBox'],
|
|
81
|
+
Section: ['ComboBox']
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
DialogContainer: {
|
|
85
|
+
components: ['Dialog']
|
|
86
|
+
},
|
|
87
|
+
DialogTrigger: {
|
|
88
|
+
components: ['Dialog']
|
|
89
|
+
},
|
|
90
|
+
Menu: {
|
|
91
|
+
components: ['ContextualHelpTrigger', 'MenuTrigger', 'SubmenuTrigger'],
|
|
92
|
+
scopedComponents: {
|
|
93
|
+
Item: ['Menu'],
|
|
94
|
+
Section: ['Menu']
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
Picker: {
|
|
98
|
+
scopedComponents: {
|
|
99
|
+
Item: ['Picker'],
|
|
100
|
+
Section: ['Picker']
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
TableView: {
|
|
104
|
+
components: ['Cell', 'Column', 'Row', 'TableBody', 'TableHeader']
|
|
105
|
+
},
|
|
106
|
+
Tabs: {
|
|
107
|
+
components: ['TabList', 'TabPanels']
|
|
108
|
+
},
|
|
109
|
+
TagGroup: {
|
|
110
|
+
scopedComponents: {
|
|
111
|
+
Item: ['TagGroup']
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
TooltipTrigger: {
|
|
115
|
+
components: ['Tooltip']
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function addScopedParents(scopedComponents, component, parents) {
|
|
119
|
+
let existingParents = scopedComponents.get(component) ?? new Set();
|
|
120
|
+
for (let parent of parents) {
|
|
121
|
+
existingParents.add(parent);
|
|
122
|
+
}
|
|
123
|
+
scopedComponents.set(component, existingParents);
|
|
124
|
+
}
|
|
125
|
+
function getComponentSelection(components) {
|
|
126
|
+
if (!components) {
|
|
127
|
+
return {
|
|
128
|
+
components: new Set(availableComponents),
|
|
129
|
+
explicitComponents: new Set(availableComponents),
|
|
130
|
+
scopedComponents: new Map()
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
let explicitComponents = new Set(components.split(',').map(s => s.trim()).filter(Boolean));
|
|
134
|
+
let expandedComponents = new Set(explicitComponents);
|
|
135
|
+
let scopedComponents = new Map();
|
|
136
|
+
for (let component of explicitComponents) {
|
|
137
|
+
let relatedComponents = relatedComponentGroups[component];
|
|
138
|
+
if (!relatedComponents) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
for (let relatedComponent of relatedComponents.components ?? []) {
|
|
142
|
+
expandedComponents.add(relatedComponent);
|
|
143
|
+
}
|
|
144
|
+
for (let [relatedComponent, parents] of Object.entries(relatedComponents.scopedComponents ?? {})) {
|
|
145
|
+
expandedComponents.add(relatedComponent);
|
|
146
|
+
if (!explicitComponents.has(relatedComponent)) {
|
|
147
|
+
addScopedParents(scopedComponents, relatedComponent, parents);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
components: new Set([...expandedComponents].filter(component => availableComponents.has(component))),
|
|
153
|
+
explicitComponents: new Set([...explicitComponents].filter(component => availableComponents.has(component))),
|
|
154
|
+
scopedComponents
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function shouldTransformElement(componentName, path, selection) {
|
|
158
|
+
if (selection.explicitComponents.has(componentName)) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
let allowedParents = selection.scopedComponents.get(componentName);
|
|
162
|
+
if (!allowedParents || allowedParents.size === 0) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
return !!path.findParent((parentPath) => t.isJSXElement(parentPath.node)
|
|
166
|
+
&& t.isJSXIdentifier(parentPath.node.openingElement.name)
|
|
167
|
+
&& allowedParents.has((0, utils_1.getName)(path, parentPath.node.openingElement.name)));
|
|
168
|
+
}
|
|
66
169
|
function transformer(file, api, options) {
|
|
67
170
|
let j = api.jscodeshift.withParser({
|
|
68
171
|
parse(source) {
|
|
@@ -72,7 +175,8 @@ function transformer(file, api, options) {
|
|
|
72
175
|
}
|
|
73
176
|
});
|
|
74
177
|
let root = j(file.source);
|
|
75
|
-
let
|
|
178
|
+
let selection = getComponentSelection(options.components);
|
|
179
|
+
let componentsToTransform = selection.components;
|
|
76
180
|
let v3ComponentsToRename = new Set(Object.keys(renamedComponents));
|
|
77
181
|
let S2ComponentsToImport = new Set();
|
|
78
182
|
let bindings = [];
|
|
@@ -80,8 +184,13 @@ function transformer(file, api, options) {
|
|
|
80
184
|
let elements = [];
|
|
81
185
|
let lastImportPath = null;
|
|
82
186
|
let iconImports = new Map();
|
|
187
|
+
let illustrationImports = new Map();
|
|
188
|
+
let programPath = null;
|
|
83
189
|
const leadingComments = root.find(j.Program).get('body', 0).node.leadingComments;
|
|
84
190
|
(0, traverse_1.default)(root.paths()[0].node, {
|
|
191
|
+
Program(path) {
|
|
192
|
+
programPath = path;
|
|
193
|
+
},
|
|
85
194
|
ImportDeclaration(path) {
|
|
86
195
|
if (path.node.source.value === '@adobe/react-spectrum' || (path.node.source.value.startsWith('@react-spectrum/') && path.node.source.value !== '@react-spectrum/s2')) {
|
|
87
196
|
lastImportPath = path;
|
|
@@ -97,7 +206,10 @@ function transformer(file, api, options) {
|
|
|
97
206
|
if (propName && path.parentPath.parentPath?.parentPath?.isJSXElement()) {
|
|
98
207
|
if (componentsToTransform.has(propName)) {
|
|
99
208
|
importedComponents.set(propName, clonedSpecifier);
|
|
100
|
-
|
|
209
|
+
let elementPath = path.parentPath.parentPath.parentPath;
|
|
210
|
+
if (shouldTransformElement(propName, elementPath, selection)) {
|
|
211
|
+
elements.push([propName, elementPath]);
|
|
212
|
+
}
|
|
101
213
|
}
|
|
102
214
|
else if (v3ComponentsToRename.has(propName)) {
|
|
103
215
|
S2ComponentsToImport.add(renamedComponents[propName]);
|
|
@@ -141,7 +253,10 @@ function transformer(file, api, options) {
|
|
|
141
253
|
bindings.push(binding);
|
|
142
254
|
for (let path of binding.referencePaths) {
|
|
143
255
|
if (path.parentPath?.isJSXOpeningElement() && path.parentPath.parentPath.isJSXElement()) {
|
|
144
|
-
|
|
256
|
+
let elementPath = path.parentPath.parentPath;
|
|
257
|
+
if (shouldTransformElement(specifier.imported.name, elementPath, selection)) {
|
|
258
|
+
elements.push([specifier.imported.name, elementPath]);
|
|
259
|
+
}
|
|
145
260
|
}
|
|
146
261
|
}
|
|
147
262
|
}
|
|
@@ -167,6 +282,24 @@ function transformer(file, api, options) {
|
|
|
167
282
|
iconImports.set(localName, { path, newName: null });
|
|
168
283
|
}
|
|
169
284
|
}
|
|
285
|
+
else if (path.node.source.value.startsWith('@spectrum-icons/illustrations/')) {
|
|
286
|
+
let illustrationName = path.node.source.value.split('/').pop();
|
|
287
|
+
if (!illustrationName) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
let specifier = path.node.specifiers[0];
|
|
291
|
+
if (!specifier || !t.isImportDefaultSpecifier(specifier)) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
let localName = specifier.local.name;
|
|
295
|
+
if (illustrationMap_1.illustrationMap.has(illustrationName)) {
|
|
296
|
+
let newIllustrationName = illustrationMap_1.illustrationMap.get(illustrationName);
|
|
297
|
+
illustrationImports.set(localName, { path, newName: newIllustrationName });
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
illustrationImports.set(localName, { path, newName: null });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
170
303
|
},
|
|
171
304
|
Import(path) {
|
|
172
305
|
let call = path.parentPath;
|
|
@@ -177,7 +310,9 @@ function transformer(file, api, options) {
|
|
|
177
310
|
if (arg.type !== 'StringLiteral') {
|
|
178
311
|
return;
|
|
179
312
|
}
|
|
180
|
-
|
|
313
|
+
let isV3ImportSource = arg.value === '@adobe/react-spectrum'
|
|
314
|
+
|| (arg.value.startsWith('@react-spectrum/') && arg.value !== '@react-spectrum/s2');
|
|
315
|
+
if (!isV3ImportSource) {
|
|
181
316
|
return;
|
|
182
317
|
}
|
|
183
318
|
// TODO: implement this. could be a bit challenging. punting for now.
|
|
@@ -191,6 +326,12 @@ function transformer(file, api, options) {
|
|
|
191
326
|
(0, utils_1.addComment)(path.node, ` TODO(S2-upgrade): A Spectrum 2 equivalent to '${name.name}' was not found. Please update this icon manually.`);
|
|
192
327
|
}
|
|
193
328
|
}
|
|
329
|
+
if (t.isJSXIdentifier(name) && illustrationImports.has(name.name)) {
|
|
330
|
+
let illustrationInfo = illustrationImports.get(name.name);
|
|
331
|
+
if (illustrationInfo.newName === null) {
|
|
332
|
+
(0, utils_1.addComment)(path.node, ` TODO(S2-upgrade): A Spectrum 2 equivalent to '${name.name}' was not found. Please update this illustration manually.`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
194
335
|
}
|
|
195
336
|
});
|
|
196
337
|
iconImports.forEach((iconInfo, localName) => {
|
|
@@ -216,6 +357,26 @@ function transformer(file, api, options) {
|
|
|
216
357
|
path.node.specifiers = [t.importDefaultSpecifier(t.identifier(newLocalName))];
|
|
217
358
|
}
|
|
218
359
|
});
|
|
360
|
+
illustrationImports.forEach((illustrationInfo, localName) => {
|
|
361
|
+
let { path, newName } = illustrationInfo;
|
|
362
|
+
if (newName) {
|
|
363
|
+
let newImportSource = `@react-spectrum/s2/illustrations/linear/${newName}`;
|
|
364
|
+
let newLocalName = localName;
|
|
365
|
+
if (localName === path.node.source.value.split('/').pop() && localName !== newName) {
|
|
366
|
+
let binding = path.scope.getBinding(localName);
|
|
367
|
+
if (binding && !path.scope.hasBinding(newName)) {
|
|
368
|
+
newLocalName = newName;
|
|
369
|
+
binding.referencePaths.forEach(refPath => {
|
|
370
|
+
if (t.isJSXIdentifier(refPath.node)) {
|
|
371
|
+
refPath.node.name = newName;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
path.node.source = t.stringLiteral(newImportSource);
|
|
377
|
+
path.node.specifiers = [t.importDefaultSpecifier(t.identifier(newLocalName))];
|
|
378
|
+
}
|
|
379
|
+
});
|
|
219
380
|
let hasMacros = false;
|
|
220
381
|
let usedLightDark = false;
|
|
221
382
|
elements.forEach(([elementName, path]) => {
|
|
@@ -268,16 +429,24 @@ function transformer(file, api, options) {
|
|
|
268
429
|
});
|
|
269
430
|
if (existingImport.length) {
|
|
270
431
|
let importDecl = existingImport.get();
|
|
271
|
-
|
|
272
|
-
if (specifier.type === 'ImportSpecifier'
|
|
273
|
-
&& importedComponents.has(specifier.imported.name)) {
|
|
274
|
-
importSpecifiers.add(specifier);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
432
|
+
let existingSpecifiers = importDecl.value.specifiers;
|
|
277
433
|
// add importSpecifiers to existing import
|
|
278
434
|
importDecl.value.specifiers = [...importDecl.value.specifiers, ...[...importSpecifiers].filter(specifier => {
|
|
279
|
-
|
|
280
|
-
|
|
435
|
+
if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) {
|
|
436
|
+
if (specifier.imported.name === 'Item') {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
let importedName = specifier.imported.name;
|
|
440
|
+
let localName = specifier.local?.name || importedName;
|
|
441
|
+
return !existingSpecifiers.find((s) => t.isImportSpecifier(s)
|
|
442
|
+
&& t.isIdentifier(s.imported)
|
|
443
|
+
&& s.imported.name === importedName
|
|
444
|
+
&& (s.local?.name || s.imported.name) === localName);
|
|
445
|
+
}
|
|
446
|
+
if (t.isImportNamespaceSpecifier(specifier)) {
|
|
447
|
+
return !existingSpecifiers.find((s) => t.isImportNamespaceSpecifier(s) && s.local.name === specifier.local.name);
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
281
450
|
})];
|
|
282
451
|
}
|
|
283
452
|
else {
|
|
@@ -305,6 +474,9 @@ function transformer(file, api, options) {
|
|
|
305
474
|
}
|
|
306
475
|
});
|
|
307
476
|
}
|
|
477
|
+
if (programPath) {
|
|
478
|
+
(0, utils_1.removeUnusedImports)(programPath, ['@react-spectrum/s2']);
|
|
479
|
+
}
|
|
308
480
|
root.find(j.Program).get('body', 0).node.comments = leadingComments;
|
|
309
481
|
return root.toSource().replace(/assert\s*\{\s*type:\s*"macro"\s*\}/g, 'with { type: "macro" }');
|
|
310
482
|
}
|
|
@@ -51,6 +51,7 @@ let availableComponents = (0, getComponents_1.getComponents)();
|
|
|
51
51
|
* - Convert dynamic collections render function to items.map.
|
|
52
52
|
*/
|
|
53
53
|
function transformActionGroup(path) {
|
|
54
|
+
let program = path.findParent((p) => t.isProgram(p.node));
|
|
54
55
|
// Comment out overflowMode
|
|
55
56
|
(0, transforms_1.commentOutProp)(path, { propName: 'overflowMode' });
|
|
56
57
|
// Comment out buttonLabelBehavior
|
|
@@ -73,12 +74,10 @@ function transformActionGroup(path) {
|
|
|
73
74
|
}
|
|
74
75
|
let localName = newComponentName;
|
|
75
76
|
if (availableComponents.has(newComponentName)) {
|
|
76
|
-
let program = path.findParent((p) => t.isProgram(p.node));
|
|
77
77
|
localName = (0, utils_1.addComponentImport)(program, newComponentName);
|
|
78
78
|
}
|
|
79
79
|
let localChildName = childComponentName;
|
|
80
80
|
if (availableComponents.has(childComponentName)) {
|
|
81
|
-
let program = path.findParent((p) => t.isProgram(p.node));
|
|
82
81
|
localChildName = (0, utils_1.addComponentImport)(program, childComponentName);
|
|
83
82
|
}
|
|
84
83
|
// Convert dynamic collection to an array.map.
|
|
@@ -161,4 +160,5 @@ function transformActionGroup(path) {
|
|
|
161
160
|
if (path.node.closingElement) {
|
|
162
161
|
path.node.closingElement.name = t.jsxIdentifier(localName);
|
|
163
162
|
}
|
|
163
|
+
(0, utils_1.removeComponentImportIfUnused)(program, 'Item');
|
|
164
164
|
}
|
|
@@ -4,12 +4,12 @@ exports.default = transformActionMenu;
|
|
|
4
4
|
const transforms_1 = require("../../shared/transforms");
|
|
5
5
|
/**
|
|
6
6
|
* Transforms ActionMenu:
|
|
7
|
-
* -
|
|
7
|
+
* - Rename `closeOnSelect` to `shouldCloseOnSelect`.
|
|
8
8
|
* - Comment out trigger (it has not been implemented yet).
|
|
9
9
|
*/
|
|
10
10
|
function transformActionMenu(path) {
|
|
11
|
-
//
|
|
12
|
-
(0, transforms_1.
|
|
11
|
+
// Rename `closeOnSelect` to `shouldCloseOnSelect`
|
|
12
|
+
(0, transforms_1.updatePropName)(path, { oldPropName: 'closeOnSelect', newPropName: 'shouldCloseOnSelect' });
|
|
13
13
|
// Comment out trigger
|
|
14
14
|
(0, transforms_1.commentOutProp)(path, { propName: 'trigger' });
|
|
15
15
|
}
|
|
@@ -49,6 +49,7 @@ let availableComponents = (0, getComponents_1.getComponents)();
|
|
|
49
49
|
* - When `type="fullscreenTakeover"`, replaces Dialog with `<FullscreenDialog variant="fullscreenTakeover">`.
|
|
50
50
|
*/
|
|
51
51
|
function updateDialogChild(path) {
|
|
52
|
+
let program = path.findParent((p) => t.isProgram(p.node));
|
|
52
53
|
let typePath = path.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === 'type');
|
|
53
54
|
let type = typePath?.node.value?.type === 'StringLiteral' ? typePath.node.value?.value : 'modal';
|
|
54
55
|
let newComponentName = 'Dialog';
|
|
@@ -72,7 +73,6 @@ function updateDialogChild(path) {
|
|
|
72
73
|
typePath?.remove();
|
|
73
74
|
let localName = newComponentName;
|
|
74
75
|
if (newComponentName !== 'Dialog' && availableComponents.has(newComponentName)) {
|
|
75
|
-
let program = path.findParent((p) => t.isProgram(p.node));
|
|
76
76
|
localName = (0, utils_1.addComponentImport)(program, newComponentName);
|
|
77
77
|
}
|
|
78
78
|
path.traverse({
|
|
@@ -87,6 +87,20 @@ function updateDialogChild(path) {
|
|
|
87
87
|
dialog.node.openingElement.attributes.push(...props);
|
|
88
88
|
}
|
|
89
89
|
});
|
|
90
|
+
path.traverse({
|
|
91
|
+
JSXElement(childPath) {
|
|
92
|
+
if (t.isJSXIdentifier(childPath.node.openingElement.name)
|
|
93
|
+
&& (0, utils_1.getName)(childPath, childPath.node.openingElement.name) === 'Divider'
|
|
94
|
+
&& t.isJSXElement(childPath.parentPath.node)
|
|
95
|
+
&& t.isJSXIdentifier(childPath.parentPath.node.openingElement.name)) {
|
|
96
|
+
let parentName = (0, utils_1.getName)(childPath, childPath.parentPath.node.openingElement.name);
|
|
97
|
+
if (parentName === 'Dialog' || parentName === 'Popover' || parentName === 'FullscreenDialog') {
|
|
98
|
+
childPath.remove();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
(0, utils_1.removeComponentImportIfUnused)(program, 'Divider');
|
|
90
104
|
}
|
|
91
105
|
/**
|
|
92
106
|
* Transforms DialogTrigger:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = transformMenuTrigger;
|
|
4
|
+
const transforms_1 = require("../../shared/transforms");
|
|
5
|
+
/**
|
|
6
|
+
* Transforms MenuTrigger:
|
|
7
|
+
* - Rename `closeOnSelect` to `shouldCloseOnSelect` and move it to the child `Menu`.
|
|
8
|
+
*/
|
|
9
|
+
function transformMenuTrigger(path) {
|
|
10
|
+
(0, transforms_1.updatePropName)(path, { oldPropName: 'closeOnSelect', newPropName: 'shouldCloseOnSelect' });
|
|
11
|
+
(0, transforms_1.movePropToChildComponent)(path, {
|
|
12
|
+
parentComponentName: 'MenuTrigger',
|
|
13
|
+
childComponentName: 'Menu',
|
|
14
|
+
propName: 'shouldCloseOnSelect'
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -9,6 +9,9 @@ const transforms_1 = require("../../shared/transforms");
|
|
|
9
9
|
* - Change validationState="invalid" to isInvalid.
|
|
10
10
|
* - Remove validationState="valid" (it is no longer supported in Spectrum 2).
|
|
11
11
|
* - Replace isLoading with loadingState.
|
|
12
|
+
* - Rename onSelectionChange to onChange.
|
|
13
|
+
* - Rename selectedKey to value.
|
|
14
|
+
* - Rename defaultSelectedKey to defaultValue.
|
|
12
15
|
*/
|
|
13
16
|
function transformPicker(path) {
|
|
14
17
|
// Change menuWidth value from a DimensionValue to a pixel value
|
|
@@ -30,4 +33,19 @@ function transformPicker(path) {
|
|
|
30
33
|
newPropName: 'loadingState',
|
|
31
34
|
comment: 'Replace boolean passed to isLoading with appropriate loadingState.'
|
|
32
35
|
});
|
|
36
|
+
// Rename onSelectionChange to onChange
|
|
37
|
+
(0, transforms_1.updatePropName)(path, {
|
|
38
|
+
oldPropName: 'onSelectionChange',
|
|
39
|
+
newPropName: 'onChange'
|
|
40
|
+
});
|
|
41
|
+
// Rename selectedKey to value
|
|
42
|
+
(0, transforms_1.updatePropName)(path, {
|
|
43
|
+
oldPropName: 'selectedKey',
|
|
44
|
+
newPropName: 'value'
|
|
45
|
+
});
|
|
46
|
+
// Rename defaultSelectedKey to defaultValue
|
|
47
|
+
(0, transforms_1.updatePropName)(path, {
|
|
48
|
+
oldPropName: 'defaultSelectedKey',
|
|
49
|
+
newPropName: 'defaultValue'
|
|
50
|
+
});
|
|
33
51
|
}
|
|
@@ -38,11 +38,12 @@ const utils_1 = require("../../shared/utils");
|
|
|
38
38
|
const transforms_1 = require("../../shared/transforms");
|
|
39
39
|
const t = __importStar(require("@babel/types"));
|
|
40
40
|
function transformTabList(tabListPath) {
|
|
41
|
-
tabListPath.
|
|
42
|
-
|
|
43
|
-
t.isJSXIdentifier(itemPath.node.openingElement.name) &&
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
tabListPath.traverse({
|
|
42
|
+
JSXElement(itemPath) {
|
|
43
|
+
if (t.isJSXIdentifier(itemPath.node.openingElement.name) &&
|
|
44
|
+
(0, utils_1.getName)(itemPath, itemPath.node.openingElement.name) === 'Item') {
|
|
45
|
+
(0, transforms_1.updateComponentWithinCollection)(itemPath, { parentComponentName: 'TabList', newComponentName: 'Tab' });
|
|
46
|
+
}
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
49
|
return tabListPath.node;
|
|
@@ -113,4 +114,5 @@ function transformTabs(path) {
|
|
|
113
114
|
(0, transforms_1.removeProp)(path, { propName: 'isEmphasized' });
|
|
114
115
|
// Remove isQuiet
|
|
115
116
|
(0, transforms_1.removeProp)(path, { propName: 'isQuiet' });
|
|
117
|
+
(0, utils_1.removeComponentImportIfUnused)(program, 'Item');
|
|
116
118
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.illustrationMap = void 0;
|
|
4
|
+
exports.illustrationMap = new Map([
|
|
5
|
+
['Error', 'Error'],
|
|
6
|
+
['File', 'Document'],
|
|
7
|
+
['Folder', 'FolderClose'],
|
|
8
|
+
['NoSearchResults', 'NoSearchResults'],
|
|
9
|
+
['NotFound', 'NoSearchResults'],
|
|
10
|
+
['Timeout', 'Clock'],
|
|
11
|
+
['Unauthorized', 'LockClose'],
|
|
12
|
+
['Unavailable', 'Error'],
|
|
13
|
+
['Upload', 'Upload']
|
|
14
|
+
]);
|
|
@@ -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;
|
|
@@ -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
|
-
|
|
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
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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 (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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/
|
|
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/
|
|
55
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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,228 @@
|
|
|
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 moduleAugmentsRouterConfig(body) {
|
|
84
|
+
for (let stmt of body.body) {
|
|
85
|
+
if (stmt.type === 'TSInterfaceDeclaration' && stmt.id.type === 'Identifier' && stmt.id.name === 'RouterConfig') {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (stmt.type === 'ExportNamedDeclaration' &&
|
|
89
|
+
stmt.declaration?.type === 'TSInterfaceDeclaration' &&
|
|
90
|
+
stmt.declaration.id.type === 'Identifier' &&
|
|
91
|
+
stmt.declaration.id.name === 'RouterConfig') {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
function transformer(file, api) {
|
|
98
|
+
let specifiersByPackage = (0, specifiers_1.getSpecifiersByPackage)(file.path);
|
|
99
|
+
let j = api.jscodeshift.withParser({
|
|
100
|
+
parse(source) {
|
|
101
|
+
return (0, recast_1.parse)(source, {
|
|
102
|
+
parser: {
|
|
103
|
+
parse(innerSource) {
|
|
104
|
+
return (0, parser_1.parse)(innerSource, {
|
|
105
|
+
sourceType: 'module',
|
|
106
|
+
plugins: [
|
|
107
|
+
'jsx',
|
|
108
|
+
'typescript',
|
|
109
|
+
'importAssertions',
|
|
110
|
+
'dynamicImport',
|
|
111
|
+
'decorators-legacy',
|
|
112
|
+
'classProperties',
|
|
113
|
+
'classPrivateProperties',
|
|
114
|
+
'classPrivateMethods',
|
|
115
|
+
'exportDefaultFrom',
|
|
116
|
+
'exportNamespaceFrom',
|
|
117
|
+
'objectRestSpread',
|
|
118
|
+
'optionalChaining',
|
|
119
|
+
'nullishCoalescingOperator',
|
|
120
|
+
'topLevelAwait'
|
|
121
|
+
],
|
|
122
|
+
tokens: true,
|
|
123
|
+
errorRecovery: true
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
let root = j(file.source);
|
|
131
|
+
let program = root.get().node.program;
|
|
132
|
+
let uniqueSources = new Set();
|
|
133
|
+
let existingImports = new Map();
|
|
134
|
+
let didChange = false;
|
|
135
|
+
for (let node of program.body) {
|
|
136
|
+
if (node.type === 'ImportDeclaration') {
|
|
137
|
+
let source = node.source.value;
|
|
138
|
+
if (typeof source !== 'string') {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
existingImports.set(getImportDeclarationKey(node), node);
|
|
142
|
+
if (source in specifiersByPackage) {
|
|
143
|
+
let sourceMap = specifiersByPackage[source];
|
|
144
|
+
for (let specifier of node.specifiers ?? []) {
|
|
145
|
+
if (specifier.type !== 'ImportSpecifier') {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
let importedName = getImportedName(specifier);
|
|
149
|
+
let candidates = sourceMap[importedName];
|
|
150
|
+
if (candidates && (candidates.length === 1 || candidates[0] === `${source}/${importedName}`)) {
|
|
151
|
+
let importKind = node.importKind || 'value';
|
|
152
|
+
uniqueSources.add(importKind + ':' + candidates[0]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (node.type === 'TSModuleDeclaration' && node.declare && node.id.type === 'StringLiteral') {
|
|
158
|
+
let mod = node.id.value;
|
|
159
|
+
if (!specifiers_1.MONOPACKAGE_ROOTS.includes(mod) || node.body?.type !== 'TSModuleBlock' || !moduleAugmentsRouterConfig(node.body)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
let target = `${mod}/Provider`;
|
|
163
|
+
let candidates = specifiersByPackage[mod]?.Provider;
|
|
164
|
+
if (!candidates?.includes(target)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
node.id = t.stringLiteral(target);
|
|
168
|
+
didChange = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
program.body = program.body.flatMap(node => {
|
|
172
|
+
if (node.type !== 'ImportDeclaration') {
|
|
173
|
+
return [node];
|
|
174
|
+
}
|
|
175
|
+
let source = node.source.value;
|
|
176
|
+
if (typeof source !== 'string' || !(source in specifiersByPackage)) {
|
|
177
|
+
return [node];
|
|
178
|
+
}
|
|
179
|
+
let importDeclaration = node;
|
|
180
|
+
let sourceMap = specifiersByPackage[node.source.value];
|
|
181
|
+
let movedSpecifiersBySource = new Map();
|
|
182
|
+
importDeclaration.specifiers = importDeclaration.specifiers.filter(specifier => {
|
|
183
|
+
if (specifier.type !== 'ImportSpecifier') {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
let importedName = getImportedName(specifier);
|
|
187
|
+
let candidates = sourceMap[importedName];
|
|
188
|
+
if (!candidates || candidates.length === 0) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
let importKind = node.importKind || 'value';
|
|
192
|
+
let targetSource = candidates.length === 1
|
|
193
|
+
? importKind + ':' + candidates[0]
|
|
194
|
+
: resolveTargetSource(candidates.map(c => importKind + ':' + c), uniqueSources, existingImports);
|
|
195
|
+
let movedSpecifiers = movedSpecifiersBySource.get(targetSource) ?? [];
|
|
196
|
+
movedSpecifiers.push(t.cloneNode(specifier, true));
|
|
197
|
+
movedSpecifiersBySource.set(targetSource, movedSpecifiers);
|
|
198
|
+
didChange = true;
|
|
199
|
+
return false;
|
|
200
|
+
});
|
|
201
|
+
if (movedSpecifiersBySource.size === 0) {
|
|
202
|
+
return [node];
|
|
203
|
+
}
|
|
204
|
+
let newDeclarations = [];
|
|
205
|
+
if (importDeclaration.specifiers.length > 0) {
|
|
206
|
+
newDeclarations.push(node);
|
|
207
|
+
}
|
|
208
|
+
for (let [targetSource, movedSpecifiers] of movedSpecifiersBySource) {
|
|
209
|
+
let destinationImport = existingImports.get(targetSource);
|
|
210
|
+
if (!destinationImport) {
|
|
211
|
+
destinationImport = createImportDeclaration(targetSource.slice(targetSource.indexOf(':') + 1), importDeclaration.importKind, []);
|
|
212
|
+
newDeclarations.push(destinationImport);
|
|
213
|
+
existingImports.set(targetSource, destinationImport);
|
|
214
|
+
}
|
|
215
|
+
let existingSpecifierKeys = getNamedSpecifierKeys(destinationImport);
|
|
216
|
+
for (let movedSpecifier of movedSpecifiers) {
|
|
217
|
+
let key = getImportKey(movedSpecifier);
|
|
218
|
+
if (!existingSpecifierKeys.has(key)) {
|
|
219
|
+
destinationImport.specifiers.push(movedSpecifier);
|
|
220
|
+
existingSpecifierKeys.add(key);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return newDeclarations;
|
|
225
|
+
});
|
|
226
|
+
return didChange ? root.toSource({ quote: 'single' }) : file.source;
|
|
227
|
+
}
|
|
228
|
+
;
|
|
@@ -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,75 @@
|
|
|
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.MONOPACKAGE_ROOTS = void 0;
|
|
7
|
+
exports.getSpecifiersByPackage = getSpecifiersByPackage;
|
|
8
|
+
/* eslint-disable max-depth */
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const module_1 = __importDefault(require("module"));
|
|
11
|
+
const parser_1 = require("@babel/parser");
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const url_1 = __importDefault(require("url"));
|
|
14
|
+
exports.MONOPACKAGE_ROOTS = [
|
|
15
|
+
'@adobe/react-spectrum',
|
|
16
|
+
'@react-spectrum/s2',
|
|
17
|
+
'react-aria-components',
|
|
18
|
+
'react-aria',
|
|
19
|
+
'react-stately'
|
|
20
|
+
];
|
|
21
|
+
const specifiersByPackage = {};
|
|
22
|
+
/** Builds a mapping of monopackage -> export -> subpaths that contain the export. */
|
|
23
|
+
function getSpecifiersByPackage(from) {
|
|
24
|
+
for (let pkg of exports.MONOPACKAGE_ROOTS) {
|
|
25
|
+
if (specifiersByPackage[pkg]) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
let dir;
|
|
29
|
+
try {
|
|
30
|
+
let pkgPath = path_1.default.dirname(module_1.default.findPackageJSON(pkg, url_1.default.pathToFileURL(from || `${process.cwd()}/index`)));
|
|
31
|
+
dir = `${pkgPath}/dist/types/exports`;
|
|
32
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
33
|
+
dir = `${pkgPath}/exports`;
|
|
34
|
+
}
|
|
35
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
let exports = {};
|
|
43
|
+
for (let entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
44
|
+
if (entry.name === 'index.ts' || entry.name === 'index.d.ts' || !entry.isFile()) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
let contents = fs_1.default.readFileSync(`${dir}/${entry.name}`, 'utf8');
|
|
48
|
+
let ast = (0, parser_1.parse)(contents, {
|
|
49
|
+
sourceType: 'module',
|
|
50
|
+
plugins: ['typescript']
|
|
51
|
+
});
|
|
52
|
+
let subpath = entry.name.replace(/(\.d)?\.ts$/, '');
|
|
53
|
+
let importSpecifier = `${pkg}/${subpath}`;
|
|
54
|
+
for (let node of ast.program.body) {
|
|
55
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
56
|
+
for (let specifier of node.specifiers) {
|
|
57
|
+
if (specifier.type !== 'ExportSpecifier') {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
let exported = specifier.exported.type === 'Identifier' ? specifier.exported.name : specifier.exported.value;
|
|
61
|
+
exports[exported] ?? (exports[exported] = []);
|
|
62
|
+
if (exported.startsWith(subpath)) {
|
|
63
|
+
exports[exported].unshift(importSpecifier);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
exports[exported].push(importSpecifier);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
specifiersByPackage[pkg] = exports;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return specifiersByPackage;
|
|
75
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-spectrum/codemods",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"source": "src/index.ts",
|
|
6
6
|
"bin": "dist/index.js",
|
|
7
7
|
"targets": {
|
|
8
8
|
"main": false
|
|
9
9
|
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22.14.0"
|
|
12
|
+
},
|
|
10
13
|
"scripts": {
|
|
11
14
|
"build": "tsc",
|
|
12
15
|
"prepublishOnly": "yarn build"
|
|
@@ -21,11 +24,12 @@
|
|
|
21
24
|
"url": "https://github.com/adobe/react-spectrum"
|
|
22
25
|
},
|
|
23
26
|
"dependencies": {
|
|
27
|
+
"@adobe/react-spectrum": "3.47.0",
|
|
24
28
|
"@babel/parser": "^7.24.5",
|
|
25
29
|
"@babel/traverse": "^7.24.5",
|
|
26
30
|
"@babel/types": "^7.24.5",
|
|
27
|
-
"@react-spectrum/s2": "
|
|
28
|
-
"@react-types/shared": "^3.
|
|
31
|
+
"@react-spectrum/s2": "1.3.0",
|
|
32
|
+
"@react-types/shared": "^3.34.0",
|
|
29
33
|
"@types/node": "^24",
|
|
30
34
|
"boxen": "^5.1.2",
|
|
31
35
|
"chalk": "^4.0.0",
|
|
@@ -49,5 +53,5 @@
|
|
|
49
53
|
"publishConfig": {
|
|
50
54
|
"access": "public"
|
|
51
55
|
},
|
|
52
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "36fdd8bca2df281fa955117d946e6dd9718241e4"
|
|
53
57
|
}
|