@atlaskit/eslint-plugin-design-system 13.30.1 → 13.32.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +75 -75
  3. package/dist/cjs/presets/all-flat.codegen.js +3 -3
  4. package/dist/cjs/presets/all.codegen.js +3 -3
  5. package/dist/cjs/presets/recommended-flat.codegen.js +2 -2
  6. package/dist/cjs/presets/recommended.codegen.js +2 -2
  7. package/dist/cjs/rules/index.codegen.js +5 -5
  8. package/dist/cjs/rules/lozenge-badge-tag-labelling-system-migration/index.js +712 -0
  9. package/dist/cjs/rules/no-placeholder/index.js +162 -0
  10. package/dist/es2019/presets/all-flat.codegen.js +3 -3
  11. package/dist/es2019/presets/all.codegen.js +3 -3
  12. package/dist/es2019/presets/recommended-flat.codegen.js +2 -2
  13. package/dist/es2019/presets/recommended.codegen.js +2 -2
  14. package/dist/es2019/rules/index.codegen.js +5 -5
  15. package/dist/es2019/rules/lozenge-badge-tag-labelling-system-migration/index.js +702 -0
  16. package/dist/es2019/rules/no-placeholder/index.js +142 -0
  17. package/dist/esm/presets/all-flat.codegen.js +3 -3
  18. package/dist/esm/presets/all.codegen.js +3 -3
  19. package/dist/esm/presets/recommended-flat.codegen.js +2 -2
  20. package/dist/esm/presets/recommended.codegen.js +2 -2
  21. package/dist/esm/rules/index.codegen.js +5 -5
  22. package/dist/esm/rules/lozenge-badge-tag-labelling-system-migration/index.js +705 -0
  23. package/dist/esm/rules/no-placeholder/index.js +154 -0
  24. package/dist/types/presets/all-flat.codegen.d.ts +1 -1
  25. package/dist/types/presets/all.codegen.d.ts +1 -1
  26. package/dist/types/presets/recommended-flat.codegen.d.ts +1 -1
  27. package/dist/types/presets/recommended.codegen.d.ts +1 -1
  28. package/dist/types/rules/index.codegen.d.ts +1 -1
  29. package/dist/types/rules/no-placeholder/index.d.ts +6 -0
  30. package/dist/types-ts4.5/presets/all-flat.codegen.d.ts +1 -1
  31. package/dist/types-ts4.5/presets/all.codegen.d.ts +1 -1
  32. package/dist/types-ts4.5/presets/recommended-flat.codegen.d.ts +1 -1
  33. package/dist/types-ts4.5/presets/recommended.codegen.d.ts +1 -1
  34. package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -1
  35. package/dist/types-ts4.5/rules/no-placeholder/index.d.ts +6 -0
  36. package/package.json +2 -2
  37. package/dist/cjs/rules/lozenge-isBold-and-lozenge-badge-appearance-migration/index.js +0 -332
  38. package/dist/cjs/rules/no-utility-icons/checks.js +0 -246
  39. package/dist/cjs/rules/no-utility-icons/index.js +0 -49
  40. package/dist/es2019/rules/lozenge-isBold-and-lozenge-badge-appearance-migration/index.js +0 -324
  41. package/dist/es2019/rules/no-utility-icons/checks.js +0 -177
  42. package/dist/es2019/rules/no-utility-icons/index.js +0 -44
  43. package/dist/esm/rules/lozenge-isBold-and-lozenge-badge-appearance-migration/index.js +0 -326
  44. package/dist/esm/rules/no-utility-icons/checks.js +0 -239
  45. package/dist/esm/rules/no-utility-icons/index.js +0 -43
  46. package/dist/types/rules/lozenge-isBold-and-lozenge-badge-appearance-migration/index.d.ts +0 -3
  47. package/dist/types/rules/no-utility-icons/checks.d.ts +0 -10
  48. package/dist/types/rules/no-utility-icons/index.d.ts +0 -3
  49. package/dist/types-ts4.5/rules/no-utility-icons/checks.d.ts +0 -10
  50. /package/dist/{types-ts4.5/rules/no-utility-icons → types/rules/lozenge-badge-tag-labelling-system-migration}/index.d.ts +0 -0
  51. /package/dist/types-ts4.5/rules/{lozenge-isBold-and-lozenge-badge-appearance-migration → lozenge-badge-tag-labelling-system-migration}/index.d.ts +0 -0
@@ -0,0 +1,702 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import { createLintRule } from '../utils/create-rule';
3
+ const rule = createLintRule({
4
+ meta: {
5
+ name: 'lozenge-badge-tag-labelling-system-migration',
6
+ fixable: 'code',
7
+ type: 'suggestion',
8
+ docs: {
9
+ description: 'Helps migrate Lozenge isBold prop, Badge appearance values, and SimpleTag/RemovableTag components as part of the Labelling System Phase 1 migration.',
10
+ recommended: false,
11
+ severity: 'warn'
12
+ },
13
+ messages: {
14
+ updateAppearance: 'Update appearance value to new semantic value.',
15
+ migrateTag: 'Non-bold <Lozenge> variants should migrate to <Tag> component.',
16
+ manualReview: "Dynamic 'isBold' props require manual review before migration.",
17
+ dynamicLozengeAppearance: "Dynamic 'appearance' prop values require manual review before migrating to Tag. Please verify the appearance value and manually convert it to the appropriate color prop value.",
18
+ updateBadgeAppearance: 'Update Badge appearance value "{{oldValue}}" to new semantic value "{{newValue}}".',
19
+ dynamicBadgeAppearance: 'Dynamic appearance prop values require manual review to ensure they use the new semantic values: neutral, information, inverse, danger, success.'
20
+ }
21
+ },
22
+ create(context) {
23
+ /**
24
+ * Contains a map of imported Lozenge components.
25
+ */
26
+ const lozengeImports = {}; // local name -> import source
27
+
28
+ /**
29
+ * Contains a map of imported Badge components.
30
+ */
31
+ const badgeImports = {}; // local name -> import source
32
+
33
+ /**
34
+ * Contains a map of imported Tag components (SimpleTag, RemovableTag, or default Tag imports).
35
+ * Maps local name to { type: 'SimpleTag' | 'RemovableTag' | 'Tag', source: string, node: ImportNode }
36
+ */
37
+ const tagImports = {};
38
+
39
+ /**
40
+ * Tracks which tag imports need to migrate to Tag (default) or AvatarTag (named)
41
+ * Maps local name to migration target: 'Tag' | 'AvatarTag'
42
+ */
43
+ const tagMigrationTargets = {};
44
+
45
+ /**
46
+ * Tracks import declaration nodes that need to be updated
47
+ */
48
+ const importDeclarationsToUpdate = new Set();
49
+
50
+ /**
51
+ * Contains a map of imported Avatar components from @atlaskit/avatar.
52
+ * Maps local name to import source
53
+ */
54
+ const avatarImports = {};
55
+
56
+ /**
57
+ * Contains a map of imported Tag and AvatarTag components from @atlaskit/tag.
58
+ * These are the new components that should not be migrated.
59
+ * Maps local name to import source
60
+ */
61
+ const newTagImports = {};
62
+
63
+ /**
64
+ * Check if a JSX attribute value is a literal false
65
+ */
66
+ function isLiteralFalse(node) {
67
+ return node && node.type === 'JSXExpressionContainer' && node.expression && node.expression.type === 'Literal' && node.expression.value === false;
68
+ }
69
+
70
+ /**
71
+ * Check if a JSX attribute value is dynamic (not a static literal value)
72
+ * Can be used for any prop type (boolean, string, etc.)
73
+ */
74
+ function isDynamicExpression(node) {
75
+ if (!node) {
76
+ return false;
77
+ }
78
+
79
+ // If it's a plain literal (e.g., appearance="value"), it's not dynamic
80
+ if (node.type === 'Literal') {
81
+ return false;
82
+ }
83
+
84
+ // If it's an expression container with a non-literal expression, it's dynamic
85
+ if (node.type === 'JSXExpressionContainer') {
86
+ const expr = node.expression;
87
+ return expr && expr.type !== 'Literal';
88
+ }
89
+ return false;
90
+ }
91
+
92
+ /**
93
+ * Get all attributes as an object for easier manipulation
94
+ */
95
+ function getAttributesMap(attributes) {
96
+ const map = {};
97
+ attributes.forEach(attr => {
98
+ if (attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier') {
99
+ map[attr.name.name] = attr;
100
+ }
101
+ });
102
+ return map;
103
+ }
104
+
105
+ /**
106
+ * Map old appearance values to new semantic appearance values
107
+ * Both Lozenge and Tag now use the same appearance prop with new semantic values
108
+ */
109
+ function mapToNewAppearanceValue(oldValue) {
110
+ const mapping = {
111
+ success: 'success',
112
+ default: 'default',
113
+ removed: 'removed',
114
+ inprogress: 'inprogress',
115
+ new: 'new',
116
+ moved: 'moved'
117
+ };
118
+ return mapping[oldValue] || oldValue;
119
+ }
120
+
121
+ /**
122
+ * Map Lozenge appearance values to Tag color values
123
+ * Used when migrating Lozenge to Tag component
124
+ */
125
+ function mapLozengeAppearanceToTagColor(appearanceValue) {
126
+ const mapping = {
127
+ success: 'lime',
128
+ default: 'gray',
129
+ removed: 'red',
130
+ inprogress: 'blue',
131
+ new: 'purple',
132
+ moved: 'yellow'
133
+ };
134
+ return mapping[appearanceValue] || appearanceValue;
135
+ }
136
+
137
+ /**
138
+ * Map Badge old appearance values to new semantic appearance values
139
+ */
140
+ function mapBadgeToNewAppearanceValue(oldValue) {
141
+ const mapping = {
142
+ added: 'success',
143
+ removed: 'danger',
144
+ default: 'neutral',
145
+ primary: 'information',
146
+ primaryInverted: 'inverse',
147
+ important: 'danger'
148
+ };
149
+ return mapping[oldValue] || oldValue;
150
+ }
151
+
152
+ /**
153
+ * Map Tag color light variants to semantic color values
154
+ */
155
+ function mapTagColorValue(oldValue) {
156
+ const mapping = {
157
+ limeLight: 'lime',
158
+ orangeLight: 'orange',
159
+ magentaLight: 'magenta',
160
+ greenLight: 'green',
161
+ blueLight: 'blue',
162
+ redLight: 'red',
163
+ purpleLight: 'purple',
164
+ greyLight: 'gray',
165
+ tealLight: 'teal',
166
+ yellowLight: 'yellow',
167
+ grey: 'gray'
168
+ };
169
+ return mapping[oldValue] || oldValue;
170
+ }
171
+
172
+ /**
173
+ * Check if elemBefore prop contains only an Avatar component from @atlaskit/avatar
174
+ * Returns the Avatar component name if it's from the avatar package, null otherwise
175
+ */
176
+ function getAvatarComponentName(elemBeforeProp) {
177
+ if (!elemBeforeProp || !elemBeforeProp.value) {
178
+ return null;
179
+ }
180
+ const value = elemBeforeProp.value;
181
+
182
+ // Check for JSX element: <Avatar ... />
183
+ if (value.type === 'JSXElement' && value.openingElement.name.name === 'Avatar') {
184
+ const avatarName = value.openingElement.name.name;
185
+ if (avatarImports[avatarName]) {
186
+ return avatarName;
187
+ }
188
+ }
189
+
190
+ // Check for JSX expression container: {<Avatar ... />}
191
+ if (value.type === 'JSXExpressionContainer' && value.expression) {
192
+ // Direct JSX element: {<Avatar ... />}
193
+ if (value.expression.type === 'JSXElement' && value.expression.openingElement.name.name === 'Avatar') {
194
+ const avatarName = value.expression.openingElement.name.name;
195
+ if (avatarImports[avatarName]) {
196
+ return avatarName;
197
+ }
198
+ }
199
+
200
+ // Arrow function: {() => <Avatar ... />}
201
+ if (value.expression.type === 'ArrowFunctionExpression') {
202
+ const body = value.expression.body;
203
+ if (body.type === 'JSXElement' && body.openingElement.name.name === 'Avatar') {
204
+ const avatarName = body.openingElement.name.name;
205
+ if (avatarImports[avatarName]) {
206
+ return avatarName;
207
+ }
208
+ }
209
+ }
210
+ }
211
+ return null;
212
+ }
213
+
214
+ /**
215
+ * Check if color prop value needs mapping
216
+ */
217
+ function colorNeedsMapping(colorProp) {
218
+ if (!(colorProp !== null && colorProp !== void 0 && colorProp.value)) {
219
+ return false;
220
+ }
221
+ const stringValue = extractStringValue(colorProp.value);
222
+ return stringValue !== null && typeof stringValue === 'string' && mapTagColorValue(stringValue) !== stringValue;
223
+ }
224
+
225
+ /**
226
+ * Extract the string value from a JSX attribute value
227
+ */
228
+ function extractStringValue(attrValue) {
229
+ if (!attrValue) {
230
+ return null;
231
+ }
232
+ if (attrValue.type === 'Literal') {
233
+ return attrValue.value;
234
+ }
235
+ if (attrValue.type === 'JSXExpressionContainer' && attrValue.expression && attrValue.expression.type === 'Literal') {
236
+ return attrValue.expression.value;
237
+ }
238
+ return null;
239
+ }
240
+
241
+ /**
242
+ * Create a fixer function to replace an appearance prop value
243
+ * Handles both Literal and JSXExpressionContainer with Literal
244
+ */
245
+ function createAppearanceFixer(attrValue, newValue) {
246
+ return fixer => {
247
+ if (!attrValue) {
248
+ return null;
249
+ }
250
+ if (attrValue.type === 'Literal') {
251
+ return fixer.replaceText(attrValue, `"${newValue}"`);
252
+ }
253
+ if (attrValue.type === 'JSXExpressionContainer' && 'expression' in attrValue && attrValue.expression && attrValue.expression.type === 'Literal') {
254
+ return fixer.replaceText(attrValue.expression, `"${newValue}"`);
255
+ }
256
+ return null;
257
+ };
258
+ }
259
+
260
+ /**
261
+ * Generate the replacement JSX element text for Tag migration
262
+ * Handles both regular Tag and avatarTag migrations
263
+ */
264
+ function generateTagReplacement(node, options = {}) {
265
+ const sourceCode = context.getSourceCode();
266
+ const attributes = node.openingElement.attributes;
267
+
268
+ // Build new attributes array
269
+ const newAttributes = [];
270
+ attributes.forEach(attr => {
271
+ if (attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier') {
272
+ const attrName = attr.name.name;
273
+ if (attrName === 'isBold') {
274
+ // Skip isBold attribute
275
+ return;
276
+ }
277
+ if (attrName === 'appearance') {
278
+ // For Lozenge migrations, convert appearance to color prop
279
+ // For SimpleTag/RemovableTag migrations, delete appearance prop
280
+ if (options.isLozengeMigration) {
281
+ // Map Lozenge appearance value to Tag color value and change prop name from appearance to color
282
+ const stringValue = extractStringValue(attr.value);
283
+ if (stringValue && typeof stringValue === 'string') {
284
+ const mappedColor = mapLozengeAppearanceToTagColor(stringValue);
285
+ newAttributes.push(`color="${mappedColor}"`);
286
+ }
287
+ // If we can't extract the string value (dynamic expression), skip it
288
+ // Dynamic expressions should be caught earlier and require manual review
289
+ // This code path shouldn't be reached, but we skip to be safe
290
+ }
291
+ // For SimpleTag/RemovableTag migrations, skip appearance prop (delete it)
292
+ return;
293
+ }
294
+ if (attrName === 'color') {
295
+ // For avatar tag, skip color prop; for regular tag, map color value
296
+ // Note: Lozenge doesn't have a color prop, but Tag/SimpleTag/RemovableTag do
297
+ if (options.isAvatarTag) {
298
+ return;
299
+ }
300
+ const stringValue = extractStringValue(attr.value);
301
+ if (stringValue && typeof stringValue === 'string') {
302
+ const mappedColor = mapTagColorValue(stringValue);
303
+ newAttributes.push(`color="${mappedColor}"`);
304
+ } else {
305
+ // If we can't extract the string value, keep as-is
306
+ const value = attr.value ? sourceCode.getText(attr.value) : '';
307
+ newAttributes.push(`color${value ? `=${value}` : ''}`);
308
+ }
309
+ return;
310
+ }
311
+ if (attrName === 'elemBefore') {
312
+ // For avatar tag, rename elemBefore to avatar and use render props
313
+ if (options.isAvatarTag) {
314
+ const elemBeforeValue = attr.value;
315
+ let avatarElement = null;
316
+
317
+ // Extract Avatar element from various formats
318
+ if (elemBeforeValue.type === 'JSXElement') {
319
+ avatarElement = elemBeforeValue;
320
+ } else if (elemBeforeValue.type === 'JSXExpressionContainer') {
321
+ const expr = elemBeforeValue.expression;
322
+ // Direct JSX element: {<Avatar ... />}
323
+ if (expr.type === 'JSXElement') {
324
+ avatarElement = expr;
325
+ }
326
+ // Arrow function: {() => <Avatar ... />}
327
+ else if (expr.type === 'ArrowFunctionExpression' && expr.body.type === 'JSXElement') {
328
+ avatarElement = expr.body;
329
+ }
330
+ }
331
+ if (avatarElement) {
332
+ // Generate render props: avatar={(props) => <Avatar {...props} ... />}
333
+ const avatarElementText = sourceCode.getText(avatarElement);
334
+ // Add {...props} spread to the Avatar element attributes
335
+ const avatarWithProps = avatarElementText.replace(/<Avatar\s/, '<Avatar {...props} ');
336
+ newAttributes.push(`avatar={(props) => ${avatarWithProps}}`);
337
+ }
338
+ return;
339
+ }
340
+ // For regular tag, keep elemBefore as-is
341
+ newAttributes.push(sourceCode.getText(attr));
342
+ return;
343
+ }
344
+
345
+ // Keep all other attributes
346
+ newAttributes.push(sourceCode.getText(attr));
347
+ } else if (attr.type === 'JSXSpreadAttribute') {
348
+ // Keep spread attributes
349
+ newAttributes.push(sourceCode.getText(attr));
350
+ }
351
+ });
352
+
353
+ // Add isRemovable={false} for SimpleTag migrations and Lozenge migrations
354
+ if (options.isSimpleTag || options.isLozengeMigration) {
355
+ newAttributes.push('isRemovable={false}');
356
+ }
357
+ const attributesText = newAttributes.length > 0 ? ` ${newAttributes.join(' ')}` : '';
358
+ const children = node.children.length > 0 ? sourceCode.getText().slice(node.openingElement.range[1], node.closingElement ? node.closingElement.range[0] : node.range[1]) : '';
359
+ const componentName = options.preserveComponentName ? node.openingElement.name.name : options.isAvatarTag ? 'AvatarTag' : 'Tag';
360
+ if (node.closingElement) {
361
+ return `<${componentName}${attributesText}>${children}</${componentName}>`;
362
+ } else {
363
+ return `<${componentName}${attributesText} />`;
364
+ }
365
+ }
366
+ return {
367
+ ImportDeclaration(node) {
368
+ const moduleSource = node.source.value;
369
+ if (typeof moduleSource === 'string') {
370
+ // Track Lozenge imports
371
+ if (moduleSource === '@atlaskit/lozenge' || moduleSource.startsWith('@atlaskit/lozenge')) {
372
+ node.specifiers.forEach(spec => {
373
+ if (spec.type === 'ImportDefaultSpecifier') {
374
+ lozengeImports[spec.local.name] = moduleSource;
375
+ } else if (spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier') {
376
+ if (spec.imported.name === 'Lozenge') {
377
+ lozengeImports[spec.local.name] = moduleSource;
378
+ }
379
+ }
380
+ });
381
+ }
382
+ // Track Badge imports
383
+ if (moduleSource === '@atlaskit/badge' || moduleSource.startsWith('@atlaskit/badge')) {
384
+ node.specifiers.forEach(spec => {
385
+ if (spec.type === 'ImportDefaultSpecifier') {
386
+ badgeImports[spec.local.name] = moduleSource;
387
+ } else if (spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier') {
388
+ if (spec.imported.name === 'Badge' || spec.imported.name === 'default') {
389
+ badgeImports[spec.local.name] = moduleSource;
390
+ }
391
+ }
392
+ });
393
+ }
394
+ // Track Tag imports (SimpleTag, RemovableTag only - not the new Tag component)
395
+ if (moduleSource === '@atlaskit/tag' || moduleSource.startsWith('@atlaskit/tag')) {
396
+ node.specifiers.forEach(spec => {
397
+ if (spec.type === 'ImportDefaultSpecifier') {
398
+ // Check for default imports from subpaths and main package
399
+ if (moduleSource === '@atlaskit/tag/simple-tag') {
400
+ // Default import from @atlaskit/tag/simple-tag is a SimpleTag
401
+ tagImports[spec.local.name] = {
402
+ type: 'SimpleTag',
403
+ source: moduleSource,
404
+ node: {
405
+ ...spec,
406
+ parent: node
407
+ }
408
+ };
409
+ importDeclarationsToUpdate.add(node);
410
+ } else if (moduleSource === '@atlaskit/tag/removable-tag' || moduleSource === '@atlaskit/tag') {
411
+ // Default import from @atlaskit/tag/removable-tag or @atlaskit/tag is a RemovableTag
412
+ tagImports[spec.local.name] = {
413
+ type: 'RemovableTag',
414
+ source: moduleSource,
415
+ node: {
416
+ ...spec,
417
+ parent: node
418
+ }
419
+ };
420
+ importDeclarationsToUpdate.add(node);
421
+ }
422
+ } else if (spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier') {
423
+ const importName = spec.imported.name;
424
+ if (importName === 'SimpleTag' || importName === 'RemovableTag') {
425
+ tagImports[spec.local.name] = {
426
+ type: importName,
427
+ source: moduleSource,
428
+ node: {
429
+ ...spec,
430
+ parent: node
431
+ }
432
+ };
433
+ // Mark this import declaration for potential updates
434
+ importDeclarationsToUpdate.add(node);
435
+ } else if (importName === 'AvatarTag') {
436
+ // Track new AvatarTag component - it should not be migrated
437
+ newTagImports[spec.local.name] = moduleSource;
438
+ }
439
+ // Note: Tag from named imports is not skipped - it may still need migration
440
+ // (e.g., if it has appearance prop or other old props)
441
+ }
442
+ });
443
+ }
444
+ // Track Avatar imports
445
+ if (moduleSource === '@atlaskit/avatar' || moduleSource.startsWith('@atlaskit/avatar')) {
446
+ node.specifiers.forEach(spec => {
447
+ if (spec.type === 'ImportDefaultSpecifier') {
448
+ avatarImports[spec.local.name] = moduleSource;
449
+ } else if (spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier') {
450
+ if (spec.imported.name === 'Avatar') {
451
+ avatarImports[spec.local.name] = moduleSource;
452
+ }
453
+ }
454
+ });
455
+ }
456
+ }
457
+ },
458
+ JSXElement(node) {
459
+ if (!isNodeOfType(node, 'JSXElement')) {
460
+ return;
461
+ }
462
+ if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier')) {
463
+ return;
464
+ }
465
+ const elementName = node.openingElement.name.name;
466
+
467
+ // Skip new AvatarTag component - it should not be migrated
468
+ if (newTagImports[elementName]) {
469
+ return;
470
+ }
471
+
472
+ // Handle SimpleTag, RemovableTag, and Tag migrations
473
+ if (tagImports[elementName]) {
474
+ const tagImportInfo = tagImports[elementName];
475
+ const attributesMap = getAttributesMap(node.openingElement.attributes);
476
+ const {
477
+ elemBefore: elemBeforeProp,
478
+ avatar: avatarProp,
479
+ appearance: appearanceProp,
480
+ color: colorProp
481
+ } = attributesMap;
482
+
483
+ // For default import from @atlaskit/tag, check if it's already the new Tag
484
+ if (tagImportInfo.type === 'RemovableTag' && tagImportInfo.source === '@atlaskit/tag') {
485
+ // If using avatar prop, it's already the new Tag
486
+ if (avatarProp) {
487
+ return;
488
+ }
489
+
490
+ // Check if component name is already correct and nothing needs migration
491
+ if (elementName === 'Tag' || elementName === 'AvatarTag') {
492
+ const needsNameChange = false;
493
+ const needsMigration = needsNameChange || appearanceProp || colorNeedsMapping(colorProp);
494
+ if (!needsMigration) {
495
+ // Still need to check elemBefore for Avatar
496
+ if (elemBeforeProp) {
497
+ const hasAvatarInElemBefore = getAvatarComponentName(elemBeforeProp) !== null;
498
+ if (hasAvatarInElemBefore) {
499
+ // Has Avatar in elemBefore, needs migration to AvatarTag
500
+ } else {
501
+ // No Avatar, nothing to migrate
502
+ return;
503
+ }
504
+ } else {
505
+ // No elemBefore, nothing to migrate
506
+ return;
507
+ }
508
+ }
509
+ // If we get here, something needs migration
510
+ }
511
+ }
512
+
513
+ // Determine migration target based on elemBefore containing Avatar
514
+ const hasAvatarInElemBefore = elemBeforeProp ? getAvatarComponentName(elemBeforeProp) !== null : false;
515
+ const migrationTarget = hasAvatarInElemBefore ? 'AvatarTag' : 'Tag';
516
+
517
+ // Record the migration target for this import
518
+ tagMigrationTargets[elementName] = migrationTarget;
519
+
520
+ // Migrate the JSX element
521
+ context.report({
522
+ node: node,
523
+ messageId: 'migrateTag',
524
+ fix: fixer => {
525
+ var _tagImportInfo$node, _tagImportInfo$node$p, _tagImportInfo$node$p2, _tagImportInfo$node2, _tagImportInfo$node2$, _tagImportInfo$node2$2, _tagImportInfo$node3, _tagImportInfo$node3$, _tagImportInfo$node3$2, _tagImportInfo$node4, _tagImportInfo$node5, _tagImportInfo$node5$, _tagImportInfo$node5$2, _tagImportInfo$node6;
526
+ const fixes = [];
527
+
528
+ // Fix the JSX element
529
+ const replacement = generateTagReplacement(node, {
530
+ isAvatarTag: hasAvatarInElemBefore,
531
+ isSimpleTag: !hasAvatarInElemBefore && tagImportInfo.type === 'SimpleTag'
532
+ });
533
+ fixes.push(fixer.replaceText(node, replacement));
534
+
535
+ // Fix the import statement for named imports, subpath default imports, and main package default imports
536
+ const isSubpathImport = ((_tagImportInfo$node = tagImportInfo.node) === null || _tagImportInfo$node === void 0 ? void 0 : (_tagImportInfo$node$p = _tagImportInfo$node.parent) === null || _tagImportInfo$node$p === void 0 ? void 0 : (_tagImportInfo$node$p2 = _tagImportInfo$node$p.source) === null || _tagImportInfo$node$p2 === void 0 ? void 0 : _tagImportInfo$node$p2.value) === '@atlaskit/tag/simple-tag' || ((_tagImportInfo$node2 = tagImportInfo.node) === null || _tagImportInfo$node2 === void 0 ? void 0 : (_tagImportInfo$node2$ = _tagImportInfo$node2.parent) === null || _tagImportInfo$node2$ === void 0 ? void 0 : (_tagImportInfo$node2$2 = _tagImportInfo$node2$.source) === null || _tagImportInfo$node2$2 === void 0 ? void 0 : _tagImportInfo$node2$2.value) === '@atlaskit/tag/removable-tag';
537
+ const isMainPackageDefaultImport = ((_tagImportInfo$node3 = tagImportInfo.node) === null || _tagImportInfo$node3 === void 0 ? void 0 : (_tagImportInfo$node3$ = _tagImportInfo$node3.parent) === null || _tagImportInfo$node3$ === void 0 ? void 0 : (_tagImportInfo$node3$2 = _tagImportInfo$node3$.source) === null || _tagImportInfo$node3$2 === void 0 ? void 0 : _tagImportInfo$node3$2.value) === '@atlaskit/tag' && ((_tagImportInfo$node4 = tagImportInfo.node) === null || _tagImportInfo$node4 === void 0 ? void 0 : _tagImportInfo$node4.type) === 'ImportDefaultSpecifier';
538
+ if (isSubpathImport || isMainPackageDefaultImport || ((_tagImportInfo$node5 = tagImportInfo.node) === null || _tagImportInfo$node5 === void 0 ? void 0 : (_tagImportInfo$node5$ = _tagImportInfo$node5.parent) === null || _tagImportInfo$node5$ === void 0 ? void 0 : (_tagImportInfo$node5$2 = _tagImportInfo$node5$.source) === null || _tagImportInfo$node5$2 === void 0 ? void 0 : _tagImportInfo$node5$2.value) === '@atlaskit/tag' && ((_tagImportInfo$node6 = tagImportInfo.node) === null || _tagImportInfo$node6 === void 0 ? void 0 : _tagImportInfo$node6.type) === 'ImportSpecifier') {
539
+ var _tagImportInfo$node7;
540
+ const importNode = (_tagImportInfo$node7 = tagImportInfo.node) === null || _tagImportInfo$node7 === void 0 ? void 0 : _tagImportInfo$node7.parent;
541
+ if (importNode) {
542
+ const sourceCode = context.getSourceCode();
543
+ const mainModuleSource = '@atlaskit/tag';
544
+
545
+ // Get all other specifiers that are not SimpleTag or RemovableTag
546
+ // For subpath imports and main package default imports, exclude the default specifier itself
547
+ const otherSpecifiers = importNode.specifiers.filter(spec => {
548
+ // Skip default specifiers from subpath imports and main package - they're being replaced
549
+ if (spec.type === 'ImportDefaultSpecifier' && (isSubpathImport || isMainPackageDefaultImport)) {
550
+ return false;
551
+ }
552
+ if (spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier') {
553
+ const importName = spec.imported.name;
554
+ return importName !== 'SimpleTag' && importName !== 'RemovableTag';
555
+ }
556
+ return false;
557
+ }).map(spec => sourceCode.getText(spec));
558
+ let newImportText = '';
559
+ if (migrationTarget === 'Tag') {
560
+ if (otherSpecifiers.length > 0) {
561
+ newImportText = `import Tag, { ${otherSpecifiers.join(', ')} } from '${mainModuleSource}';`;
562
+ } else {
563
+ newImportText = `import Tag from '${mainModuleSource}';`;
564
+ }
565
+ } else if (migrationTarget === 'AvatarTag') {
566
+ if (otherSpecifiers.length > 0) {
567
+ newImportText = `import { AvatarTag, ${otherSpecifiers.join(', ')} } from '${mainModuleSource}';`;
568
+ } else {
569
+ newImportText = `import { AvatarTag } from '${mainModuleSource}';`;
570
+ }
571
+ }
572
+ if (newImportText) {
573
+ fixes.push(fixer.replaceText(importNode, newImportText));
574
+ }
575
+ }
576
+ }
577
+ return fixes.length === 1 ? fixes[0] : fixes;
578
+ }
579
+ });
580
+ return;
581
+ }
582
+
583
+ // Handle Badge components
584
+ if (badgeImports[elementName]) {
585
+ // Find the appearance prop
586
+ const appearanceProp = node.openingElement.attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === 'appearance');
587
+ if (!appearanceProp || appearanceProp.type !== 'JSXAttribute') {
588
+ // No appearance prop or it's a spread attribute, nothing to migrate
589
+ return;
590
+ }
591
+
592
+ // Check if it's a dynamic expression
593
+ if (isDynamicExpression(appearanceProp.value)) {
594
+ context.report({
595
+ node: appearanceProp,
596
+ messageId: 'dynamicBadgeAppearance'
597
+ });
598
+ return;
599
+ }
600
+
601
+ // Extract the string value
602
+ const stringValue = extractStringValue(appearanceProp.value);
603
+ if (stringValue && typeof stringValue === 'string') {
604
+ const mappedValue = mapBadgeToNewAppearanceValue(stringValue);
605
+ if (mappedValue !== stringValue) {
606
+ context.report({
607
+ node: appearanceProp,
608
+ messageId: 'updateBadgeAppearance',
609
+ data: {
610
+ oldValue: stringValue,
611
+ newValue: mappedValue
612
+ },
613
+ fix: createAppearanceFixer(appearanceProp.value, mappedValue)
614
+ });
615
+ }
616
+ }
617
+ return;
618
+ }
619
+
620
+ // Only process if this is a Lozenge component we've imported
621
+ if (!lozengeImports[elementName]) {
622
+ return;
623
+ }
624
+ const attributesMap = getAttributesMap(node.openingElement.attributes);
625
+ const appearanceProp = attributesMap.appearance;
626
+ const isBoldProp = attributesMap.isBold;
627
+
628
+ // Handle appearance prop value migration
629
+ if (appearanceProp) {
630
+ const shouldMigrateToTag = !isBoldProp || isLiteralFalse(isBoldProp.value);
631
+ if (!shouldMigrateToTag) {
632
+ // Only update appearance values for Lozenge components that stay as Lozenge
633
+ const stringValue = extractStringValue(appearanceProp.value);
634
+ if (stringValue && typeof stringValue === 'string') {
635
+ const mappedValue = mapToNewAppearanceValue(stringValue);
636
+ if (mappedValue !== stringValue) {
637
+ context.report({
638
+ node: appearanceProp,
639
+ messageId: 'updateAppearance',
640
+ fix: createAppearanceFixer(appearanceProp.value, mappedValue)
641
+ });
642
+ }
643
+ }
644
+ }
645
+ }
646
+
647
+ // Handle isBold prop and Tag migration
648
+ if (isBoldProp) {
649
+ if (isLiteralFalse(isBoldProp.value)) {
650
+ // isBold={false} should migrate to Tag
651
+ // Check if appearance is dynamic - if so, require manual review
652
+ if (appearanceProp && isDynamicExpression(appearanceProp.value)) {
653
+ context.report({
654
+ node: appearanceProp,
655
+ messageId: 'dynamicLozengeAppearance'
656
+ });
657
+ return;
658
+ }
659
+ context.report({
660
+ node: node,
661
+ messageId: 'migrateTag',
662
+ fix: fixer => {
663
+ const replacement = generateTagReplacement(node, {
664
+ isLozengeMigration: true
665
+ });
666
+ return fixer.replaceText(node, replacement);
667
+ }
668
+ });
669
+ } else if (isDynamicExpression(isBoldProp.value)) {
670
+ // Dynamic isBold requires manual review
671
+ context.report({
672
+ node: isBoldProp,
673
+ messageId: 'manualReview'
674
+ });
675
+ }
676
+ // isBold={true} or isBold (implicit true) - no action needed
677
+ } else {
678
+ // No isBold prop means implicit false, should migrate to Tag
679
+ // Check if appearance is dynamic - if so, require manual review
680
+ if (appearanceProp && isDynamicExpression(appearanceProp.value)) {
681
+ context.report({
682
+ node: appearanceProp,
683
+ messageId: 'dynamicLozengeAppearance'
684
+ });
685
+ return;
686
+ }
687
+ context.report({
688
+ node: node,
689
+ messageId: 'migrateTag',
690
+ fix: fixer => {
691
+ const replacement = generateTagReplacement(node, {
692
+ isLozengeMigration: true
693
+ });
694
+ return fixer.replaceText(node, replacement);
695
+ }
696
+ });
697
+ }
698
+ }
699
+ };
700
+ }
701
+ });
702
+ export default rule;