@react-spectrum/codemods 0.1.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.
@@ -0,0 +1,685 @@
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 (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.functionMap = void 0;
27
+ const utils_1 = require("./utils");
28
+ const dimensions_1 = require("./dimensions");
29
+ const getComponents_1 = require("../getComponents");
30
+ const t = __importStar(require("@babel/types"));
31
+ let availableComponents = (0, getComponents_1.getComponents)();
32
+ /**
33
+ * Update prop name and value to new prop name and value.
34
+ *
35
+ * Example:
36
+ * - Button: Change variant="cta" to variant="accent".
37
+ * - Link: Change `variant="overBackground"` to `staticColor="white"`.
38
+ */
39
+ function updatePropNameAndValue(path, options) {
40
+ const { oldProp, oldValue, newProp, newValue } = options;
41
+ let attrPath = path.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === oldProp);
42
+ if (attrPath && t.isJSXAttribute(attrPath.node) && attrPath.node.name.name === oldProp) {
43
+ if (t.isStringLiteral(attrPath.node.value) &&
44
+ attrPath.node.value.value === oldValue) {
45
+ // Update old prop name to new prop name
46
+ attrPath.node.name.name = newProp;
47
+ // If prop value is a string and matches the old value, replace it with the new value
48
+ if (typeof newValue === 'string') {
49
+ attrPath.node.value = t.stringLiteral(newValue);
50
+ }
51
+ else if (typeof newValue === 'boolean') {
52
+ if (!newValue) {
53
+ attrPath.node.value = t.jsxExpressionContainer(t.booleanLiteral(newValue));
54
+ }
55
+ else {
56
+ attrPath.node.value = null;
57
+ }
58
+ }
59
+ }
60
+ else if (t.isJSXExpressionContainer(attrPath.node.value)) {
61
+ if (t.isIdentifier(attrPath.node.value.expression)) {
62
+ // @ts-ignore
63
+ if (attrPath.node.comments && [...attrPath.node.comments].some((comment) => comment.value.includes('could not be automatically'))) {
64
+ return;
65
+ }
66
+ (0, utils_1.addComment)(attrPath.node, ` TODO(S2-upgrade): Prop ${oldProp} could not be automatically updated because ${attrPath.node.value.expression.name} could not be followed.`);
67
+ }
68
+ else {
69
+ // If prop value is an expression, traverse to find a string literal that matches the old and replace it with the new value
70
+ attrPath.traverse({
71
+ StringLiteral(stringPath) {
72
+ if (t.isStringLiteral(stringPath.node) &&
73
+ stringPath.node.value === oldValue) {
74
+ // Update old prop name to new prop name
75
+ attrPath.node.name.name = newProp;
76
+ if (typeof newValue === 'string') {
77
+ stringPath.replaceWith(t.stringLiteral(newValue));
78
+ }
79
+ else if (typeof newValue === 'boolean') {
80
+ if (!newValue) {
81
+ stringPath.replaceWith(t.booleanLiteral(newValue));
82
+ }
83
+ else {
84
+ attrPath.node.value = null;
85
+ }
86
+ }
87
+ }
88
+ }
89
+ });
90
+ }
91
+ }
92
+ }
93
+ }
94
+ /**
95
+ * Updates a prop name and value to new prop name and value, and adds an additional prop.
96
+ *
97
+ * Example:
98
+ * - Button: Change `variant="overBackground"` to `variant="primary" staticColor="white"`.
99
+ */
100
+ function updatePropValueAndAddNewProp(path, options) {
101
+ const { oldProp, oldValue, newProp, newValue, additionalProp, additionalValue } = options;
102
+ let attrPath = path.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === oldProp);
103
+ if (attrPath && t.isStringLiteral(attrPath.node.value) && attrPath.node.value.value === oldValue) {
104
+ // Update old prop name to new prop name
105
+ attrPath.node.name.name = newProp;
106
+ // If prop value is a string and matches the old value, replace it with the new value
107
+ if (typeof newValue === 'string') {
108
+ attrPath.node.value = t.stringLiteral(newValue);
109
+ }
110
+ else if (typeof newValue === 'boolean') {
111
+ attrPath.node.value = t.jsxExpressionContainer(t.booleanLiteral(newValue));
112
+ }
113
+ if (additionalProp && additionalValue) {
114
+ attrPath.insertAfter(t.jsxAttribute(t.jsxIdentifier(additionalProp), t.stringLiteral(additionalValue)));
115
+ }
116
+ }
117
+ else if (attrPath && t.isJSXExpressionContainer(attrPath.node.value)) {
118
+ // If prop value is an expression, traverse to find a string literal that matches the old and replace it with the new value
119
+ attrPath.traverse({
120
+ StringLiteral(stringPath) {
121
+ if (t.isStringLiteral(stringPath.node) &&
122
+ stringPath.node.value === oldValue) {
123
+ // Update old prop name to new prop name
124
+ attrPath.node.name.name = newProp;
125
+ if (typeof newValue === 'string') {
126
+ stringPath.replaceWith(t.stringLiteral(newValue));
127
+ }
128
+ else if (typeof newValue === 'boolean') {
129
+ stringPath.replaceWith(t.booleanLiteral(newValue));
130
+ }
131
+ if (additionalProp && additionalValue) {
132
+ attrPath.insertAfter(t.jsxAttribute(t.jsxIdentifier(additionalProp), t.stringLiteral(additionalValue)));
133
+ }
134
+ }
135
+ }
136
+ });
137
+ }
138
+ }
139
+ /**
140
+ * Updates a prop name to new prop name.
141
+ *
142
+ * Example:
143
+ * - Button: Change style to fillStyle.
144
+ */
145
+ function updatePropName(path, options) {
146
+ const { oldProp, newProp } = options;
147
+ let attrPath = path.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === oldProp);
148
+ if (attrPath && t.isJSXAttribute(attrPath.node) && attrPath.node.name.name === oldProp) {
149
+ attrPath.node.name.name = newProp;
150
+ }
151
+ }
152
+ /**
153
+ * Removes a prop.
154
+ *
155
+ * Example:
156
+ * - Button: Remove isQuiet (it is no longer supported).
157
+ */
158
+ function removeProp(path, options) {
159
+ const { propToRemove, propValue } = options;
160
+ let attrPath = path.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === propToRemove);
161
+ if (attrPath && t.isJSXAttribute(attrPath.node) && attrPath.node.name.name === propToRemove) {
162
+ if (propValue) {
163
+ // If prop value is provided, remove prop only if it matches the value
164
+ if (t.isStringLiteral(attrPath.node.value) && attrPath.node.value.value === propValue) {
165
+ attrPath.remove();
166
+ }
167
+ else if (t.isJSXExpressionContainer(attrPath.node.value)) {
168
+ if (t.isIdentifier(attrPath.node.value.expression)) {
169
+ // @ts-ignore
170
+ // eslint-disable-next-line max-depth
171
+ if (attrPath.node.comments && [...attrPath.node.comments].some((comment) => comment.value.includes('could not be automatically'))) {
172
+ return;
173
+ }
174
+ (0, utils_1.addComment)(attrPath.node, ` TODO(S2-upgrade): Prop ${propToRemove} could not be automatically removed because ${attrPath.node.value.expression.name} could not be followed.`);
175
+ }
176
+ else {
177
+ attrPath.traverse({
178
+ StringLiteral(stringPath) {
179
+ if (t.isStringLiteral(stringPath.node) &&
180
+ stringPath.node.value === propValue) {
181
+ // Invalid prop value was found inside expression.
182
+ (0, utils_1.addComment)(attrPath.node, ` TODO(S2-upgrade): ${propToRemove}="${propValue}" is no longer supported. You'll need to update this manually.`);
183
+ }
184
+ }
185
+ });
186
+ }
187
+ }
188
+ }
189
+ else {
190
+ // No prop value provided, so remove prop regardless of value
191
+ attrPath.remove();
192
+ }
193
+ }
194
+ }
195
+ /**
196
+ * Comments out a prop.
197
+ *
198
+ * Example:
199
+ * - Button: Comment out isPending (it has not been implemented yet).
200
+ */
201
+ function commentOutProp(path, options) {
202
+ const { propToComment, propValue } = options;
203
+ let attrPath = path.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === propToComment);
204
+ if (attrPath && t.isJSXAttribute(attrPath.node) && attrPath.node.name.name === propToComment) {
205
+ if (propValue) {
206
+ // If prop value is provided, comment out prop only if it matches the value
207
+ if (t.isStringLiteral(attrPath.node.value) && attrPath.node.value.value === propValue) {
208
+ (0, utils_1.addComment)(attrPath.parentPath.node, ` TODO(S2-upgrade): ${propToComment}="${propValue}" has not been implemented yet.`);
209
+ attrPath.remove();
210
+ }
211
+ else {
212
+ attrPath.traverse({
213
+ StringLiteral(stringPath) {
214
+ if (t.isStringLiteral(stringPath.node) &&
215
+ stringPath.node.value === propValue) {
216
+ (0, utils_1.addComment)(attrPath.parentPath.node, ` TODO(S2-upgrade): ${propToComment}="${propValue}" has not been implemented yet.`);
217
+ attrPath.remove();
218
+ }
219
+ }
220
+ });
221
+ }
222
+ }
223
+ else {
224
+ (0, utils_1.addComment)(attrPath.parentPath.node, ` TODO(S2-upgrade): ${propToComment} has not been implemented yet.`);
225
+ attrPath.remove();
226
+ }
227
+ }
228
+ }
229
+ /**
230
+ * Add a comment above an element.
231
+ *
232
+ * Example:
233
+ * - Breadcrumbs: Check if nav needs to be added around Bre.
234
+ */
235
+ function addCommentToElement(path, options) {
236
+ const { comment } = options;
237
+ (0, utils_1.addComment)(path.node, ` TODO(S2-upgrade): ${comment}`);
238
+ }
239
+ /**
240
+ * If a prop is present, updates to use a new component.
241
+ *
242
+ * Example:
243
+ * - Button: If `href` is present, Button should be converted to `LinkButton`.
244
+ */
245
+ function updateComponentIfPropPresent(path, options) {
246
+ const { newComponent, propToCheck } = options;
247
+ let attrPath = path.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === propToCheck);
248
+ if (attrPath && t.isJSXAttribute(attrPath.node) && attrPath.node.name.name === propToCheck) {
249
+ let node = attrPath.findParent((p) => t.isJSXElement(p.node))?.node;
250
+ if (node && t.isJSXElement(node)) {
251
+ let localName = newComponent;
252
+ if (availableComponents.has(newComponent)) {
253
+ let program = path.findParent((p) => t.isProgram(p.node));
254
+ localName = (0, utils_1.addComponentImport)(program, newComponent);
255
+ }
256
+ node.openingElement.name = t.jsxIdentifier(localName);
257
+ if (node.closingElement) {
258
+ node.closingElement.name = t.jsxIdentifier(localName);
259
+ }
260
+ }
261
+ }
262
+ }
263
+ /**
264
+ * Remove render props, and move usage to a child component.
265
+ *
266
+ * Example:
267
+ * - DialogTrigger: Update children to remove render props usage, and note that `close` function moved from `DialogTrigger` to `Dialog`.
268
+ */
269
+ function moveRenderPropsToChild(path, options) {
270
+ const { newChildComponent } = options;
271
+ const renderFunctionIndex = path.node.children.findIndex((child) => t.isJSXExpressionContainer(child) &&
272
+ t.isArrowFunctionExpression(child.expression) &&
273
+ t.isJSXElement(child.expression.body) &&
274
+ t.isJSXIdentifier(child.expression.body.openingElement.name));
275
+ const renderFunction = path.node.children[renderFunctionIndex];
276
+ if (t.isJSXExpressionContainer(renderFunction) &&
277
+ t.isArrowFunctionExpression(renderFunction.expression) &&
278
+ t.isJSXElement(renderFunction.expression.body) &&
279
+ t.isJSXIdentifier(renderFunction.expression.body.openingElement.name) &&
280
+ (0, utils_1.getName)(path, renderFunction.expression.body.openingElement.name) !== newChildComponent) {
281
+ (0, utils_1.addComment)(renderFunction, ' TODO(S2-upgrade): update this dialog to move the close function inside');
282
+ return;
283
+ }
284
+ if (renderFunction &&
285
+ t.isArrowFunctionExpression(renderFunction.expression) &&
286
+ t.isJSXElement(renderFunction.expression.body)) {
287
+ const dialogElement = renderFunction.expression.body;
288
+ const newRenderFunction = t.jsxExpressionContainer(t.arrowFunctionExpression(renderFunction.expression.params, t.jsxFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), dialogElement.children)));
289
+ let removedOnDismiss = false;
290
+ const attributes = dialogElement.openingElement.attributes.filter((attr) => {
291
+ if (t.isJSXAttribute(attr) && attr.name.name === 'onDismiss') {
292
+ removedOnDismiss = true;
293
+ return false;
294
+ }
295
+ return true;
296
+ });
297
+ path.node.children[renderFunctionIndex] = t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(newChildComponent), attributes), t.jsxClosingElement(t.jsxIdentifier(newChildComponent)), [newRenderFunction]);
298
+ if (removedOnDismiss) {
299
+ (0, utils_1.addComment)(path.node.children[renderFunctionIndex], ' onDismiss was removed from Dialog. Use onOpenChange on the DialogTrigger, or onDismiss on the DialogContainer instead');
300
+ }
301
+ }
302
+ }
303
+ /**
304
+ * If within a collection component, updates to use a new component.
305
+ *
306
+ * Example:
307
+ * - Item: If within `Menu`, update name from `Item` to `MenuItem`.
308
+ */
309
+ function updateComponentWithinCollection(path, options) {
310
+ const { parentComponent, newComponent } = options;
311
+ // Collections currently implemented
312
+ // TODO: Add 'ActionGroup', 'ListBox', 'ListView' once implemented
313
+ const collectionItemParents = new Set(['Menu', 'ActionMenu', 'TagGroup', 'Breadcrumbs', 'Picker', 'ComboBox', 'ListBox', 'TabList', 'TabPanels', 'Collection']);
314
+ if (t.isJSXElement(path.node) &&
315
+ t.isJSXIdentifier(path.node.openingElement.name)) {
316
+ // Find closest parent collection component
317
+ let closestParentCollection = path.findParent((p) => t.isJSXElement(p.node) &&
318
+ t.isJSXIdentifier(p.node.openingElement.name) &&
319
+ collectionItemParents.has((0, utils_1.getName)(path, p.node.openingElement.name)));
320
+ if (closestParentCollection &&
321
+ t.isJSXElement(closestParentCollection.node) &&
322
+ t.isJSXIdentifier(closestParentCollection.node.openingElement.name) &&
323
+ (0, utils_1.getName)(path, closestParentCollection.node.openingElement.name) === parentComponent) {
324
+ // If closest parent collection component matches parentComponent, replace with newComponent
325
+ let attributes = path.node.openingElement.attributes;
326
+ let keyProp = attributes.find((attr) => t.isJSXAttribute(attr) && attr.name.name === 'key');
327
+ if (keyProp &&
328
+ t.isJSXAttribute(keyProp)) {
329
+ // Update key prop to be id
330
+ keyProp.name = t.jsxIdentifier('id');
331
+ }
332
+ if (t.isArrowFunctionExpression(path.parentPath.node) &&
333
+ path.parentPath.parentPath &&
334
+ t.isCallExpression(path.parentPath.parentPath.node) &&
335
+ path.parentPath.parentPath.node.callee.type === 'MemberExpression' &&
336
+ path.parentPath.parentPath.node.callee.property.type === 'Identifier' &&
337
+ path.parentPath.parentPath.node.callee.property.name === 'map') {
338
+ // If Array.map is used, keep the key prop
339
+ if (keyProp &&
340
+ t.isJSXAttribute(keyProp)) {
341
+ let newKeyProp = t.jsxAttribute(t.jsxIdentifier('key'), keyProp.value);
342
+ attributes.push(newKeyProp);
343
+ }
344
+ }
345
+ let localName = newComponent;
346
+ if (availableComponents.has(newComponent)) {
347
+ let program = path.findParent((p) => t.isProgram(p.node));
348
+ localName = (0, utils_1.addComponentImport)(program, newComponent);
349
+ }
350
+ let newNode = t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(localName), attributes), t.jsxClosingElement(t.jsxIdentifier(localName)), path.node.children);
351
+ path.replaceWith(newNode);
352
+ }
353
+ }
354
+ }
355
+ /**
356
+ * If no parent collection detected, leave a comment for the user to check manually.
357
+ *
358
+ * Example: If they're declaring declaring Items somewhere above the collection.
359
+ */
360
+ function commentIfParentCollectionNotDetected(path) {
361
+ const collectionItemParents = new Set(['Menu', 'ActionMenu', 'TagGroup', 'Breadcrumbs', 'Picker', 'ComboBox', 'ListBox', 'TabList', 'TabPanels', 'ActionGroup', 'ListBox', 'ListView', 'Collection', 'SearchAutocomplete', 'Accordion', 'ActionBar', 'StepList']);
362
+ if (t.isJSXElement(path.node)) {
363
+ // Find closest parent collection component
364
+ let closestParentCollection = path.findParent((p) => t.isJSXElement(p.node) &&
365
+ t.isJSXIdentifier(p.node.openingElement.name) &&
366
+ collectionItemParents.has((0, utils_1.getName)(path, p.node.openingElement.name)));
367
+ if (!closestParentCollection) {
368
+ // If we couldn't find a parent collection parent, leave a comment for them to check manually
369
+ (0, utils_1.addComment)(path.node, ' TODO(S2-upgrade): Couldn\'t automatically detect what type of collection component this is rendered in. You\'ll need to update this manually.');
370
+ }
371
+ }
372
+ }
373
+ /**
374
+ * Updates Tabs to the new API.
375
+ *
376
+ * Example:
377
+ * - Tabs: Remove TabPanels components and keep individual TabPanel components inside.
378
+ */
379
+ function updateTabs(path) {
380
+ function transformTabs(path) {
381
+ let tabListNode = null;
382
+ let tabPanelsNodes = [];
383
+ let itemsProp = null;
384
+ path.node.openingElement.attributes = path.node.openingElement.attributes.filter(attr => {
385
+ if (t.isJSXAttribute(attr) && attr.name.name === 'items') {
386
+ itemsProp = attr;
387
+ return false;
388
+ }
389
+ return true;
390
+ });
391
+ path.get('children').forEach(childPath => {
392
+ if (t.isJSXElement(childPath.node)) {
393
+ if (t.isJSXIdentifier(childPath.node.openingElement.name) &&
394
+ (0, utils_1.getName)(childPath, childPath.node.openingElement.name) === 'TabList') {
395
+ tabListNode = transformTabList(childPath);
396
+ if (itemsProp) {
397
+ tabListNode.openingElement.attributes.push(itemsProp);
398
+ }
399
+ }
400
+ else if (t.isJSXIdentifier(childPath.node.openingElement.name) &&
401
+ (0, utils_1.getName)(childPath, childPath.node.openingElement.name) === 'TabPanels') {
402
+ tabPanelsNodes = transformTabPanels(childPath, itemsProp);
403
+ }
404
+ }
405
+ });
406
+ if (tabListNode) {
407
+ path.node.children = [tabListNode, ...tabPanelsNodes];
408
+ }
409
+ }
410
+ function transformTabList(tabListPath) {
411
+ tabListPath.get('children').forEach(itemPath => {
412
+ if (t.isJSXElement(itemPath.node) &&
413
+ t.isJSXIdentifier(itemPath.node.openingElement.name) &&
414
+ (0, utils_1.getName)(itemPath, itemPath.node.openingElement.name) === 'Item') {
415
+ updateComponentWithinCollection(itemPath, { parentComponent: 'TabList', newComponent: 'Tab' });
416
+ }
417
+ });
418
+ return tabListPath.node;
419
+ }
420
+ function transformTabPanels(tabPanelsPath, itemsProp) {
421
+ // Dynamic case
422
+ let dynamicRender = tabPanelsPath.get('children').find(path => t.isJSXExpressionContainer(path.node));
423
+ if (dynamicRender) {
424
+ updateToNewComponent(tabPanelsPath, { newComponent: 'Collection' });
425
+ let itemPath = dynamicRender.get('expression').get('body');
426
+ updateComponentWithinCollection(itemPath, { parentComponent: 'Collection', newComponent: 'TabPanel' });
427
+ if (itemsProp) {
428
+ tabPanelsPath.node.openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier('items'), itemsProp.value));
429
+ }
430
+ return [tabPanelsPath.node];
431
+ }
432
+ // Static case
433
+ return tabPanelsPath.get('children').map(itemPath => {
434
+ if (t.isJSXElement(itemPath.node) &&
435
+ t.isJSXIdentifier(itemPath.node.openingElement.name) &&
436
+ (0, utils_1.getName)(itemPath, itemPath.node.openingElement.name) === 'Item') {
437
+ updateComponentWithinCollection(itemPath, { parentComponent: 'TabPanels', newComponent: 'TabPanel' });
438
+ return itemPath.node;
439
+ }
440
+ return null;
441
+ }).filter(Boolean);
442
+ }
443
+ let program = path.findParent((p) => t.isProgram(p.node));
444
+ (0, utils_1.removeComponentImport)(program, 'TabPanels');
445
+ transformTabs(path);
446
+ }
447
+ /**
448
+ * If within a component, moves prop to new child component.
449
+ *
450
+ * Example:
451
+ * - Section: If within `Menu`, move `title` prop string to be a child of new `Heading` within a `Header`.
452
+ */
453
+ function movePropToNewChildComponent(path, options) {
454
+ const { parentComponent, childComponent, propToMove, newChildComponent } = options;
455
+ if (t.isJSXElement(path.node) &&
456
+ t.isJSXElement(path.parentPath.node) &&
457
+ t.isJSXIdentifier(path.node.openingElement.name) &&
458
+ t.isJSXIdentifier(path.parentPath.node.openingElement.name) &&
459
+ (0, utils_1.getName)(path, path.node.openingElement.name) === childComponent &&
460
+ (0, utils_1.getName)(path, path.parentPath.node.openingElement.name) === parentComponent) {
461
+ let propValue;
462
+ path.node.openingElement.attributes =
463
+ path.node.openingElement.attributes.filter((attr) => {
464
+ if (t.isJSXAttribute(attr) && attr.name.name === propToMove) {
465
+ propValue = attr.value;
466
+ return false;
467
+ }
468
+ return true;
469
+ });
470
+ if (propValue) {
471
+ path.node.children.unshift(t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(newChildComponent), []), t.jsxClosingElement(t.jsxIdentifier(newChildComponent)), [t.isStringLiteral(propValue) ? t.jsxText(propValue.value) : propValue]));
472
+ // TODO: handle dynamic collections. Need to wrap function child in <Collection> and move `items` prop down.
473
+ }
474
+ }
475
+ }
476
+ /**
477
+ * Updates prop to be on parent component.
478
+ *
479
+ * Example:
480
+ * - Tooltip: Remove `placement` and add to the parent `TooltipTrigger` instead.
481
+ */
482
+ function movePropToParentComponent(path, options) {
483
+ const { parentComponent, childComponent, propToMove } = options;
484
+ path.traverse({
485
+ JSXAttribute(attributePath) {
486
+ if (t.isJSXElement(path.parentPath.node) &&
487
+ t.isJSXIdentifier(path.node.openingElement.name) &&
488
+ t.isJSXIdentifier(path.parentPath.node.openingElement.name) &&
489
+ attributePath.node.name.name === propToMove &&
490
+ (0, utils_1.getName)(path, path.node.openingElement.name) === childComponent &&
491
+ (0, utils_1.getName)(path, path.parentPath.node.openingElement.name) === parentComponent) {
492
+ path.parentPath.node.openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier(propToMove), attributePath.node.value));
493
+ attributePath.remove();
494
+ }
495
+ }
496
+ });
497
+ }
498
+ /**
499
+ * Update to use a new component.
500
+ *
501
+ * Example:
502
+ * - Flex: Update `Flex` to be a `div` and apply flex styles using the style macro.
503
+ */
504
+ function updateToNewComponent(path, options) {
505
+ const { newComponent } = options;
506
+ let localName = newComponent;
507
+ if (availableComponents.has(newComponent)) {
508
+ let program = path.findParent((p) => t.isProgram(p.node));
509
+ localName = (0, utils_1.addComponentImport)(program, newComponent);
510
+ }
511
+ path.node.openingElement.name = t.jsxIdentifier(localName);
512
+ if (path.node.closingElement) {
513
+ path.node.closingElement.name = t.jsxIdentifier(localName);
514
+ }
515
+ }
516
+ const conversions = {
517
+ 'cm': 37.8,
518
+ 'mm': 3.78,
519
+ 'in': 96,
520
+ 'pt': 1.33,
521
+ 'pc': 16
522
+ };
523
+ /**
524
+ * Updates prop to use pixel value instead.
525
+ *
526
+ * Example:
527
+ * - ComboBox: Update `menuWidth` to a pixel value.
528
+ */
529
+ function convertDimensionValueToPx(path, options) {
530
+ const { propToConvertValue } = options;
531
+ let attrPath = path.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === propToConvertValue);
532
+ if (attrPath && t.isJSXAttribute(attrPath.node) && attrPath.node.name.name === propToConvertValue) {
533
+ if (t.isStringLiteral(attrPath.node.value)) {
534
+ try {
535
+ let value = (0, dimensions_1.convertDimension)(attrPath.node.value.value);
536
+ if (value && typeof value === 'number') {
537
+ attrPath.node.value = t.jsxExpressionContainer(t.numericLiteral(value));
538
+ }
539
+ else if (value && typeof value === 'string') {
540
+ // eslint-disable-next-line max-depth
541
+ if ((/%|vw|vh|auto|ex|ch|rem|vmin|vmax/).test(value)) {
542
+ (0, utils_1.addComment)(attrPath.node, ' TODO(S2-upgrade): Unable to convert CSS unit to a pixel value');
543
+ }
544
+ else if ((/cm|mm|in|pt|pc/).test(value)) {
545
+ let unit = value.replace(/\[|\]|\d+/g, '');
546
+ let conversion = conversions[unit];
547
+ value = Number(value.replace(/\[|\]|cm|mm|in|pt|pc/g, ''));
548
+ // eslint-disable-next-line max-depth
549
+ if (!isNaN(value)) {
550
+ let pixelValue = Math.round(conversion * value);
551
+ attrPath.node.value = t.jsxExpressionContainer(t.numericLiteral(pixelValue));
552
+ }
553
+ }
554
+ else if ((/px/).test(value)) {
555
+ let pixelValue = Number(value.replace(/\[|\]|px/g, ''));
556
+ // eslint-disable-next-line max-depth
557
+ if (!isNaN(pixelValue)) {
558
+ attrPath.node.value = t.jsxExpressionContainer(t.numericLiteral(pixelValue));
559
+ }
560
+ }
561
+ }
562
+ }
563
+ catch (error) {
564
+ (0, utils_1.addComment)(attrPath.node, ` TODO(S2-upgrade): Prop ${propToConvertValue} could not be automatically updated due to error: ${error}`);
565
+ }
566
+ }
567
+ else if (t.isJSXExpressionContainer(attrPath.node.value)) {
568
+ if (t.isIdentifier(attrPath.node.value.expression)) {
569
+ (0, utils_1.addComment)(attrPath.node, ` TODO(S2-upgrade): Prop ${propToConvertValue} could not be automatically updated because ${attrPath.node.value.expression.name} could not be followed.`);
570
+ }
571
+ }
572
+ }
573
+ }
574
+ /**
575
+ * Updates double placement values to a single value.
576
+ *
577
+ * Example:
578
+ * - TooltipTrigger: Update `placement="bottom left"` to `placement="bottom"`.
579
+ */
580
+ function updatePlacementToSingleValue(path, options) {
581
+ const { propToUpdate, childComponent } = options;
582
+ const doublePlacementValues = new Set([
583
+ 'bottom left',
584
+ 'bottom right',
585
+ 'bottom start',
586
+ 'bottom end',
587
+ 'top left',
588
+ 'top right',
589
+ 'top start',
590
+ 'top end',
591
+ 'left top',
592
+ 'left bottom',
593
+ 'start top',
594
+ 'start bottom',
595
+ 'right top',
596
+ 'right bottom',
597
+ 'end top',
598
+ 'end bottom'
599
+ ]);
600
+ let elementPath = childComponent ?
601
+ path.get('children').find((child) => t.isJSXElement(child.node) &&
602
+ t.isJSXIdentifier(child.node.openingElement.name) &&
603
+ (0, utils_1.getName)(path, child.node.openingElement.name) === childComponent) : path;
604
+ let attrPath = elementPath.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === propToUpdate);
605
+ if (attrPath && t.isJSXAttribute(attrPath.node) && attrPath.node.name.name === propToUpdate) {
606
+ if (t.isStringLiteral(attrPath.node.value) && doublePlacementValues.has(attrPath.node.value.value)) {
607
+ attrPath.replaceWith(t.jsxAttribute(t.jsxIdentifier(propToUpdate), t.stringLiteral(attrPath.node.value.value.split(' ')[0])));
608
+ return;
609
+ }
610
+ else if (t.isJSXExpressionContainer(attrPath.node.value)) {
611
+ attrPath.traverse({
612
+ StringLiteral(stringPath) {
613
+ if (t.isStringLiteral(stringPath.node) &&
614
+ doublePlacementValues.has(stringPath.node.value)) {
615
+ stringPath.replaceWith(t.stringLiteral(stringPath.node.value.split(' ')[0]));
616
+ return;
617
+ }
618
+ }
619
+ });
620
+ }
621
+ }
622
+ }
623
+ /**
624
+ * Remove component if within a parent component.
625
+ *
626
+ * Example:
627
+ * - Divider: Remove `Divider` if used within a `Dialog`.
628
+ */
629
+ function removeComponentIfWithinParent(path, options) {
630
+ const { parentComponent } = options;
631
+ if (t.isJSXElement(path.node) &&
632
+ t.isJSXElement(path.parentPath.node) &&
633
+ t.isJSXIdentifier(path.node.openingElement.name) &&
634
+ t.isJSXIdentifier(path.parentPath.node.openingElement.name) &&
635
+ (0, utils_1.getName)(path, path.parentPath.node.openingElement.name) === parentComponent) {
636
+ path.remove();
637
+ }
638
+ }
639
+ function updateAvatarSize(path) {
640
+ if (t.isJSXElement(path.node) &&
641
+ t.isJSXIdentifier(path.node.openingElement.name) &&
642
+ (0, utils_1.getName)(path, path.node.openingElement.name) === 'Avatar') {
643
+ let sizeAttrPath = path.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === 'size');
644
+ if (sizeAttrPath) {
645
+ let value = sizeAttrPath.node.value;
646
+ if (value?.type === 'StringLiteral') {
647
+ const avatarDimensions = {
648
+ 'avatar-size-50': 16,
649
+ 'avatar-size-75': 18,
650
+ 'avatar-size-100': 20,
651
+ 'avatar-size-200': 22,
652
+ 'avatar-size-300': 26,
653
+ 'avatar-size-400': 28,
654
+ 'avatar-size-500': 32,
655
+ 'avatar-size-600': 36,
656
+ 'avatar-size-700': 40
657
+ };
658
+ let val = avatarDimensions[value.value];
659
+ if (val != null) {
660
+ sizeAttrPath.node.value = t.jsxExpressionContainer(t.numericLiteral(val));
661
+ }
662
+ }
663
+ }
664
+ }
665
+ }
666
+ exports.functionMap = {
667
+ updatePropNameAndValue,
668
+ updatePropValueAndAddNewProp,
669
+ updatePropName,
670
+ removeProp,
671
+ commentOutProp,
672
+ addCommentToElement,
673
+ updateComponentIfPropPresent,
674
+ moveRenderPropsToChild,
675
+ updateComponentWithinCollection,
676
+ commentIfParentCollectionNotDetected,
677
+ updateTabs,
678
+ movePropToNewChildComponent,
679
+ movePropToParentComponent,
680
+ updateToNewComponent,
681
+ convertDimensionValueToPx,
682
+ updatePlacementToSingleValue,
683
+ removeComponentIfWithinParent,
684
+ updateAvatarSize
685
+ };