@atlaskit/codemod-cli 0.32.2 → 0.33.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,373 @@
1
+ import { getImportDeclaration } from '@hypermod/utils';
2
+ const TAG_ENTRY_POINT = '@atlaskit/tag';
3
+ const TAG_REMOVABLE_ENTRY_POINT = '@atlaskit/tag/removable-tag';
4
+ const TAG_SIMPLE_ENTRY_POINT = '@atlaskit/tag/simple-tag';
5
+ const AVATAR_ENTRY_POINT = '@atlaskit/avatar';
6
+ const PRINT_SETTINGS = {
7
+ quote: 'single'
8
+ };
9
+
10
+ // Color mapping from Light versions to non-light versions
11
+ const COLOR_MAP = {
12
+ limeLight: 'lime',
13
+ orangeLight: 'orange',
14
+ magentaLight: 'magenta',
15
+ greenLight: 'green',
16
+ blueLight: 'blue',
17
+ redLight: 'red',
18
+ purpleLight: 'purple',
19
+ greyLight: 'gray',
20
+ // Note: grey -> gray spelling change
21
+ tealLight: 'teal',
22
+ yellowLight: 'yellow',
23
+ grey: 'gray' // Also handle non-light grey -> gray
24
+ };
25
+
26
+ // Valid target color values that don't need migration
27
+ const VALID_COLORS = new Set(['lime', 'orange', 'magenta', 'green', 'blue', 'red', 'purple', 'gray', 'teal', 'yellow', 'standard']);
28
+ /**
29
+ * Check if a JSX expression is an Avatar component from '@atlaskit/avatar'
30
+ */
31
+ function isAvatarComponent(expression) {
32
+ // Handle <Avatar ... />
33
+ if (expression.type === 'JSXElement') {
34
+ var _openingElement$name;
35
+ const openingElement = expression.openingElement;
36
+ if (((_openingElement$name = openingElement.name) === null || _openingElement$name === void 0 ? void 0 : _openingElement$name.type) === 'JSXIdentifier') {
37
+ return openingElement.name.name === 'Avatar';
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+
43
+ /**
44
+ * Check if elemBefore contains only an Avatar component (directly or via render props)
45
+ */
46
+ function hasOnlyAvatarInElemBefore(attr) {
47
+ var _attr$value;
48
+ if (((_attr$value = attr.value) === null || _attr$value === void 0 ? void 0 : _attr$value.type) === 'JSXExpressionContainer') {
49
+ const expression = attr.value.expression;
50
+
51
+ // Direct Avatar: elemBefore={<Avatar />}
52
+ if (isAvatarComponent(expression)) {
53
+ return {
54
+ isAvatar: true,
55
+ avatarJSX: expression
56
+ };
57
+ }
58
+
59
+ // Render props: elemBefore={(props) => <Avatar {...props} />}
60
+ if ((expression.type === 'ArrowFunctionExpression' || expression.type === 'FunctionExpression') && expression.body) {
61
+ // Check if the function body is an Avatar component
62
+ const body = expression.body;
63
+ if (isAvatarComponent(body)) {
64
+ return {
65
+ isAvatar: true,
66
+ avatarJSX: body
67
+ };
68
+ }
69
+ }
70
+ }
71
+ return {
72
+ isAvatar: false
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Extract string value from a JSX attribute value
78
+ */
79
+ function extractStringValue(attrValue) {
80
+ if (!attrValue) {
81
+ return null;
82
+ }
83
+ if (attrValue.type === 'StringLiteral') {
84
+ return attrValue.value;
85
+ }
86
+ if (attrValue.type === 'JSXExpressionContainer') {
87
+ const expression = attrValue.expression;
88
+ if (expression.type === 'StringLiteral') {
89
+ return expression.value;
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+
95
+ /**
96
+ * Find an attribute by name in the attributes array
97
+ */
98
+ function findAttribute(attributes, attrName) {
99
+ return attributes === null || attributes === void 0 ? void 0 : attributes.find(attr => {
100
+ var _attr$name;
101
+ return attr.type === 'JSXAttribute' && ((_attr$name = attr.name) === null || _attr$name === void 0 ? void 0 : _attr$name.type) === 'JSXIdentifier' && attr.name.name === attrName;
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Codemod to migrate Tag components to new Tag/AvatarTag API.
107
+ *
108
+ * This codemod:
109
+ * 1. Identifies all Tag imports from various entry points
110
+ * 2. For tags with elemBefore containing only Avatar:
111
+ * - Migrates to AvatarTag from '@atlaskit/tag'
112
+ * - Renames elemBefore to avatar
113
+ * - Converts avatar to render props function
114
+ * - Removes color prop
115
+ * - Adds isRemovable={false} if original was SimpleTag
116
+ * 3. For other tags:
117
+ * - Migrates to default import from '@atlaskit/tag'
118
+ * - Removes appearance prop
119
+ * - Migrates color values using COLOR_MAP
120
+ * - Adds isRemovable={false} if original was SimpleTag
121
+ * - Adds comment for manual migration if color can't be mapped
122
+ */
123
+ export default function transformer(file, api) {
124
+ const j = api.jscodeshift;
125
+ const source = j(file.source);
126
+
127
+ // Track all Tag identifiers and whether they were SimpleTag
128
+ const tagIdentifiers = new Map();
129
+
130
+ // Find all Tag imports from main entry point
131
+ const mainTagImports = getImportDeclaration(j, source, TAG_ENTRY_POINT);
132
+ mainTagImports.forEach(importPath => {
133
+ var _importPath$value$spe;
134
+ (_importPath$value$spe = importPath.value.specifiers) === null || _importPath$value$spe === void 0 ? void 0 : _importPath$value$spe.forEach(specifier => {
135
+ var _specifier$imported;
136
+ if (specifier.type === 'ImportDefaultSpecifier') {
137
+ var _specifier$local, _specifier$local2;
138
+ // import Tag from '@atlaskit/tag' -> RemovableTag (default)
139
+ tagIdentifiers.set(((_specifier$local = specifier.local) === null || _specifier$local === void 0 ? void 0 : _specifier$local.name) || 'Tag', {
140
+ localName: ((_specifier$local2 = specifier.local) === null || _specifier$local2 === void 0 ? void 0 : _specifier$local2.name) || 'Tag',
141
+ isSimpleTag: false
142
+ });
143
+ } else if (specifier.type === 'ImportSpecifier' && ((_specifier$imported = specifier.imported) === null || _specifier$imported === void 0 ? void 0 : _specifier$imported.type) === 'Identifier') {
144
+ var _specifier$local3;
145
+ const localName = ((_specifier$local3 = specifier.local) === null || _specifier$local3 === void 0 ? void 0 : _specifier$local3.name) || specifier.imported.name;
146
+ if (specifier.imported.name === 'SimpleTag') {
147
+ // import { SimpleTag } from '@atlaskit/tag'
148
+ tagIdentifiers.set(localName, {
149
+ localName,
150
+ isSimpleTag: true
151
+ });
152
+ } else if (specifier.imported.name === 'RemovableTag') {
153
+ // import { RemovableTag } from '@atlaskit/tag'
154
+ tagIdentifiers.set(localName, {
155
+ localName,
156
+ isSimpleTag: false
157
+ });
158
+ }
159
+ }
160
+ });
161
+ });
162
+
163
+ // Find imports from removable-tag entry point
164
+ const removableTagImports = getImportDeclaration(j, source, TAG_REMOVABLE_ENTRY_POINT);
165
+ removableTagImports.forEach(importPath => {
166
+ var _importPath$value$spe2;
167
+ (_importPath$value$spe2 = importPath.value.specifiers) === null || _importPath$value$spe2 === void 0 ? void 0 : _importPath$value$spe2.forEach(specifier => {
168
+ if (specifier.type === 'ImportDefaultSpecifier') {
169
+ var _specifier$local4, _specifier$local5;
170
+ // import RemovableTag from '@atlaskit/tag/removable-tag'
171
+ tagIdentifiers.set(((_specifier$local4 = specifier.local) === null || _specifier$local4 === void 0 ? void 0 : _specifier$local4.name) || 'RemovableTag', {
172
+ localName: ((_specifier$local5 = specifier.local) === null || _specifier$local5 === void 0 ? void 0 : _specifier$local5.name) || 'RemovableTag',
173
+ isSimpleTag: false
174
+ });
175
+ }
176
+ });
177
+ });
178
+
179
+ // Find imports from simple-tag entry point
180
+ const simpleTagImports = getImportDeclaration(j, source, TAG_SIMPLE_ENTRY_POINT);
181
+ simpleTagImports.forEach(importPath => {
182
+ var _importPath$value$spe3;
183
+ (_importPath$value$spe3 = importPath.value.specifiers) === null || _importPath$value$spe3 === void 0 ? void 0 : _importPath$value$spe3.forEach(specifier => {
184
+ if (specifier.type === 'ImportDefaultSpecifier') {
185
+ var _specifier$local6, _specifier$local7;
186
+ // import SimpleTag from '@atlaskit/tag/simple-tag'
187
+ tagIdentifiers.set(((_specifier$local6 = specifier.local) === null || _specifier$local6 === void 0 ? void 0 : _specifier$local6.name) || 'SimpleTag', {
188
+ localName: ((_specifier$local7 = specifier.local) === null || _specifier$local7 === void 0 ? void 0 : _specifier$local7.name) || 'SimpleTag',
189
+ isSimpleTag: true
190
+ });
191
+ }
192
+ });
193
+ });
194
+
195
+ // If no Tag imports found, exit early
196
+ if (tagIdentifiers.size === 0) {
197
+ return file.source;
198
+ }
199
+
200
+ // Check if Avatar is imported
201
+ const avatarImports = getImportDeclaration(j, source, AVATAR_ENTRY_POINT);
202
+ const hasAvatarImport = avatarImports.length > 0;
203
+
204
+ // Track which tags need AvatarTag vs regular Tag
205
+ const tagsToMigrateToAvatarTag = new Set();
206
+ const tagsNeedingManualMigration = new Set();
207
+ let hasRegularTagUsage = false; // Track if any tag remains as regular Tag
208
+
209
+ // Find and process all Tag JSX elements
210
+ source.find(j.JSXElement).forEach(path => {
211
+ var _openingElement$name2;
212
+ const openingElement = path.value.openingElement;
213
+ if (((_openingElement$name2 = openingElement.name) === null || _openingElement$name2 === void 0 ? void 0 : _openingElement$name2.type) === 'JSXIdentifier' && tagIdentifiers.has(openingElement.name.name)) {
214
+ const componentName = openingElement.name.name;
215
+ const tagInfo = tagIdentifiers.get(componentName);
216
+ const attributes = openingElement.attributes || [];
217
+
218
+ // Check for elemBefore with Avatar
219
+ const elemBeforeAttr = findAttribute(attributes, 'elemBefore');
220
+ const colorAttr = findAttribute(attributes, 'color');
221
+ let shouldMigrateToAvatarTag = false;
222
+ if (elemBeforeAttr && hasAvatarImport) {
223
+ const {
224
+ isAvatar,
225
+ avatarJSX
226
+ } = hasOnlyAvatarInElemBefore(elemBeforeAttr);
227
+ if (isAvatar && avatarJSX) {
228
+ var _path$value$closingEl, _path$value$closingEl2, _elemBeforeAttr$name, _elemBeforeAttr$value;
229
+ shouldMigrateToAvatarTag = true;
230
+ tagsToMigrateToAvatarTag.add(componentName);
231
+
232
+ // Rename component to AvatarTag
233
+ openingElement.name.name = 'AvatarTag';
234
+ if (((_path$value$closingEl = path.value.closingElement) === null || _path$value$closingEl === void 0 ? void 0 : (_path$value$closingEl2 = _path$value$closingEl.name) === null || _path$value$closingEl2 === void 0 ? void 0 : _path$value$closingEl2.type) === 'JSXIdentifier') {
235
+ path.value.closingElement.name.name = 'AvatarTag';
236
+ }
237
+
238
+ // Rename elemBefore to avatar
239
+ if (((_elemBeforeAttr$name = elemBeforeAttr.name) === null || _elemBeforeAttr$name === void 0 ? void 0 : _elemBeforeAttr$name.type) === 'JSXIdentifier') {
240
+ elemBeforeAttr.name.name = 'avatar';
241
+ }
242
+
243
+ // Convert avatar to render props function if not already
244
+ if (((_elemBeforeAttr$value = elemBeforeAttr.value) === null || _elemBeforeAttr$value === void 0 ? void 0 : _elemBeforeAttr$value.type) === 'JSXExpressionContainer') {
245
+ const expression = elemBeforeAttr.value.expression;
246
+
247
+ // If it's a direct Avatar component, convert to render props
248
+ if (isAvatarComponent(expression)) {
249
+ // Create: (avatarProps) => <Avatar {...avatarProps} originalProps... />
250
+ const avatarElement = expression;
251
+ const avatarAttributes = avatarElement.openingElement.attributes || [];
252
+
253
+ // Build new attributes with spread first, then existing props
254
+ const newAttributes = [j.jsxSpreadAttribute(j.identifier('avatarProps')), ...avatarAttributes];
255
+ avatarElement.openingElement.attributes = newAttributes;
256
+
257
+ // Wrap in arrow function
258
+ const renderPropsFunc = j.arrowFunctionExpression([j.identifier('avatarProps')], avatarElement);
259
+ elemBeforeAttr.value.expression = renderPropsFunc;
260
+ }
261
+ // If it's already a function, leave it as is
262
+ }
263
+
264
+ // Remove color prop
265
+ openingElement.attributes = (openingElement.attributes || []).filter(attr => {
266
+ var _attr$name2;
267
+ if (attr.type === 'JSXAttribute' && ((_attr$name2 = attr.name) === null || _attr$name2 === void 0 ? void 0 : _attr$name2.type) === 'JSXIdentifier') {
268
+ return attr.name.name !== 'color';
269
+ }
270
+ return true;
271
+ });
272
+ }
273
+ }
274
+ if (!shouldMigrateToAvatarTag) {
275
+ var _path$value$closingEl3, _path$value$closingEl4;
276
+ // Keep as regular Tag, rename to "Tag" if needed
277
+ hasRegularTagUsage = true;
278
+ openingElement.name.name = 'Tag';
279
+ if (((_path$value$closingEl3 = path.value.closingElement) === null || _path$value$closingEl3 === void 0 ? void 0 : (_path$value$closingEl4 = _path$value$closingEl3.name) === null || _path$value$closingEl4 === void 0 ? void 0 : _path$value$closingEl4.type) === 'JSXIdentifier') {
280
+ path.value.closingElement.name.name = 'Tag';
281
+ }
282
+
283
+ // Migrate color if present
284
+ if (colorAttr) {
285
+ const colorValue = extractStringValue(colorAttr.value);
286
+ if (colorValue && COLOR_MAP[colorValue]) {
287
+ var _colorAttr$value, _colorAttr$value2;
288
+ // Update the color value
289
+ if (((_colorAttr$value = colorAttr.value) === null || _colorAttr$value === void 0 ? void 0 : _colorAttr$value.type) === 'StringLiteral') {
290
+ colorAttr.value.value = COLOR_MAP[colorValue];
291
+ } else if (((_colorAttr$value2 = colorAttr.value) === null || _colorAttr$value2 === void 0 ? void 0 : _colorAttr$value2.type) === 'JSXExpressionContainer') {
292
+ const expression = colorAttr.value.expression;
293
+ if (expression.type === 'StringLiteral') {
294
+ expression.value = COLOR_MAP[colorValue];
295
+ }
296
+ }
297
+ } else if (colorValue && !COLOR_MAP[colorValue] && !VALID_COLORS.has(colorValue)) {
298
+ // Color value is unknown/custom - mark for manual migration
299
+ tagsNeedingManualMigration.add(path);
300
+ }
301
+ }
302
+ }
303
+
304
+ // Remove appearance prop (for both AvatarTag and Tag)
305
+ openingElement.attributes = (openingElement.attributes || []).filter(attr => {
306
+ var _attr$name3;
307
+ if (attr.type === 'JSXAttribute' && ((_attr$name3 = attr.name) === null || _attr$name3 === void 0 ? void 0 : _attr$name3.type) === 'JSXIdentifier') {
308
+ return attr.name.name !== 'appearance';
309
+ }
310
+ return true;
311
+ });
312
+
313
+ // Add isRemovable={false} if original was SimpleTag and prop doesn't exist
314
+ if (tagInfo.isSimpleTag && !findAttribute(openingElement.attributes || [], 'isRemovable')) {
315
+ openingElement.attributes = openingElement.attributes || [];
316
+ openingElement.attributes.push(j.jsxAttribute(j.jsxIdentifier('isRemovable'), j.jsxExpressionContainer(j.booleanLiteral(false))));
317
+ }
318
+ }
319
+ });
320
+
321
+ // Add comments for manual migration
322
+ tagsNeedingManualMigration.forEach(path => {
323
+ const comment = ' TODO: Manual migration needed - color prop value could not be automatically migrated ';
324
+ j(path).forEach(p => {
325
+ const node = p.value;
326
+ node.comments = node.comments || [];
327
+ node.comments.push(j.commentBlock(comment));
328
+ });
329
+ });
330
+
331
+ // Update imports
332
+ const needsAvatarTagImport = tagsToMigrateToAvatarTag.size > 0;
333
+ // We need default Tag import if there are any regular tag usages
334
+ const needsDefaultTagImport = hasRegularTagUsage;
335
+
336
+ // Create new imports
337
+ const newImports = [];
338
+ if (needsDefaultTagImport) {
339
+ newImports.push(j.importDeclaration([j.importDefaultSpecifier(j.identifier('Tag'))], j.literal(TAG_ENTRY_POINT)));
340
+ }
341
+ if (needsAvatarTagImport) {
342
+ newImports.push(j.importDeclaration([j.importSpecifier(j.identifier('AvatarTag'))], j.literal(TAG_ENTRY_POINT)));
343
+ }
344
+
345
+ // Remove old Tag imports first
346
+ mainTagImports.forEach(importPath => j(importPath).remove());
347
+ removableTagImports.forEach(importPath => j(importPath).remove());
348
+ simpleTagImports.forEach(importPath => j(importPath).remove());
349
+
350
+ // Insert new imports after Avatar import (if exists) or at the beginning
351
+ if (newImports.length > 0) {
352
+ const program = source.find(j.Program);
353
+ if (program.length > 0) {
354
+ const body = program.at(0).get('body').value;
355
+ if (avatarImports.length > 0) {
356
+ // Find the Avatar import in the body
357
+ const avatarImportNode = avatarImports.at(0).get().value;
358
+ const avatarIndex = body.findIndex(node => node === avatarImportNode);
359
+ if (avatarIndex !== -1) {
360
+ // Insert all new imports right after Avatar import
361
+ body.splice(avatarIndex + 1, 0, ...newImports);
362
+ } else {
363
+ // Fallback: add at beginning
364
+ body.unshift(...newImports.reverse());
365
+ }
366
+ } else {
367
+ // No Avatar import, add at beginning
368
+ body.unshift(...newImports.reverse());
369
+ }
370
+ }
371
+ }
372
+ return source.toSource(PRINT_SETTINGS);
373
+ }
@@ -0,0 +1,18 @@
1
+ import tagToNewTagMigrationTransformer from './codemods/tag-to-newTag-migration';
2
+ export default async function transformer(file, api) {
3
+ const transformers = [tagToNewTagMigrationTransformer];
4
+ let src = file.source;
5
+ transformers.forEach(transformer => {
6
+ if (typeof src === 'undefined') {
7
+ return;
8
+ }
9
+ const nextSrc = transformer({
10
+ ...file,
11
+ source: src
12
+ }, api);
13
+ if (nextSrc) {
14
+ src = nextSrc;
15
+ }
16
+ });
17
+ return src;
18
+ }
package/dist/esm/main.js CHANGED
@@ -355,7 +355,7 @@ function _main() {
355
355
  case 4:
356
356
  _yield$parseArgs = _context6.sent;
357
357
  packages = _yield$parseArgs.packages;
358
- _process$env$_PACKAGE = "0.32.1", _PACKAGE_VERSION_ = _process$env$_PACKAGE === void 0 ? '0.0.0-dev' : _process$env$_PACKAGE;
358
+ _process$env$_PACKAGE = "0.33.0", _PACKAGE_VERSION_ = _process$env$_PACKAGE === void 0 ? '0.0.0-dev' : _process$env$_PACKAGE;
359
359
  logger.log(chalk.bgBlue(chalk.black("\uD83D\uDCDA Atlassian-Frontend codemod library @ ".concat(_PACKAGE_VERSION_, " \uD83D\uDCDA"))));
360
360
  if (packages && packages.length > 0) {
361
361
  logger.log(chalk.gray("Searching for codemods for newer versions of the following packages: ".concat(packages.map(function (pkg) {
@@ -15,7 +15,8 @@ import './remove-token-fallbacks/remove-token-fallbacks';
15
15
  import './lozenge-appearance-semantic-migration/lozenge-appearance-semantic-migration';
16
16
  import './lozenge-to-tag-migration/lozenge-to-tag-migration';
17
17
  import './badge-appearance-semantic-migration/badge-appearance-semantic-migration';
18
- var presets = ['styled-to-emotion', 'theme-remove-deprecated-mixins', 'migrate-to-link', 'migrate-to-new-buttons', 'migrate-icon-object-to-object', 'upgrade-pragmatic-drag-and-drop-to-stable', 'remove-dark-theme-vr-options', 'remove-token-fallbacks', 'lozenge-appearance-semantic-migration', 'lozenge-to-tag-migration', 'badge-appearance-semantic-migration'].map(function (preset) {
18
+ import './tag-to-newTag-migration/tag-to-newTag-migration';
19
+ var presets = ['styled-to-emotion', 'theme-remove-deprecated-mixins', 'migrate-to-link', 'migrate-to-new-buttons', 'migrate-icon-object-to-object', 'upgrade-pragmatic-drag-and-drop-to-stable', 'remove-dark-theme-vr-options', 'remove-token-fallbacks', 'lozenge-appearance-semantic-migration', 'lozenge-to-tag-migration', 'badge-appearance-semantic-migration', 'tag-to-newTag-migration'].map(function (preset) {
19
20
  return path.join(__dirname, preset, "".concat(preset, ".@(ts|js|tsx)"));
20
21
  });
21
22
  export default presets;