@atlaskit/eslint-plugin-design-system 13.14.2 → 13.16.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 (66) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +2 -0
  3. package/dist/cjs/presets/all-flat.codegen.js +3 -1
  4. package/dist/cjs/presets/all.codegen.js +3 -1
  5. package/dist/cjs/presets/recommended-flat.codegen.js +2 -1
  6. package/dist/cjs/presets/recommended.codegen.js +2 -1
  7. package/dist/cjs/rules/ensure-proper-xcss-usage/index.js +157 -0
  8. package/dist/cjs/rules/index.codegen.js +5 -1
  9. package/dist/cjs/rules/no-legacy-icons/checks.js +22 -5
  10. package/dist/cjs/rules/no-legacy-icons/helpers.js +122 -46
  11. package/dist/cjs/rules/no-utility-icons/checks.js +233 -0
  12. package/dist/cjs/rules/no-utility-icons/index.js +40 -0
  13. package/dist/cjs/rules/utils/get-deprecated-config.js +1 -1
  14. package/dist/es2019/presets/all-flat.codegen.js +3 -1
  15. package/dist/es2019/presets/all.codegen.js +3 -1
  16. package/dist/es2019/presets/recommended-flat.codegen.js +2 -1
  17. package/dist/es2019/presets/recommended.codegen.js +2 -1
  18. package/dist/es2019/rules/ensure-proper-xcss-usage/index.js +149 -0
  19. package/dist/es2019/rules/index.codegen.js +5 -1
  20. package/dist/es2019/rules/no-legacy-icons/checks.js +22 -6
  21. package/dist/es2019/rules/no-legacy-icons/helpers.js +125 -44
  22. package/dist/es2019/rules/no-utility-icons/checks.js +164 -0
  23. package/dist/es2019/rules/no-utility-icons/index.js +35 -0
  24. package/dist/es2019/rules/utils/get-deprecated-config.js +3 -2
  25. package/dist/esm/presets/all-flat.codegen.js +3 -1
  26. package/dist/esm/presets/all.codegen.js +3 -1
  27. package/dist/esm/presets/recommended-flat.codegen.js +2 -1
  28. package/dist/esm/presets/recommended.codegen.js +2 -1
  29. package/dist/esm/rules/ensure-proper-xcss-usage/index.js +151 -0
  30. package/dist/esm/rules/index.codegen.js +5 -1
  31. package/dist/esm/rules/no-legacy-icons/checks.js +22 -5
  32. package/dist/esm/rules/no-legacy-icons/helpers.js +121 -45
  33. package/dist/esm/rules/no-utility-icons/checks.js +226 -0
  34. package/dist/esm/rules/no-utility-icons/index.js +34 -0
  35. package/dist/esm/rules/utils/get-deprecated-config.js +2 -2
  36. package/dist/types/index.codegen.d.ts +14 -0
  37. package/dist/types/presets/all-flat.codegen.d.ts +2 -0
  38. package/dist/types/presets/all.codegen.d.ts +2 -0
  39. package/dist/types/presets/recommended-flat.codegen.d.ts +1 -0
  40. package/dist/types/presets/recommended.codegen.d.ts +1 -0
  41. package/dist/types/rules/ensure-proper-xcss-usage/index.d.ts +3 -0
  42. package/dist/types/rules/index.codegen.d.ts +2 -0
  43. package/dist/types/rules/no-legacy-icons/helpers.d.ts +12 -4
  44. package/dist/types/rules/no-utility-icons/checks.d.ts +10 -0
  45. package/dist/types/rules/no-utility-icons/index.d.ts +3 -0
  46. package/dist/types/rules/use-tokens-typography/transformers/banned-properties.d.ts +1 -1
  47. package/dist/types/rules/use-tokens-typography/transformers/font-family.d.ts +1 -1
  48. package/dist/types/rules/use-tokens-typography/transformers/font-weight.d.ts +1 -1
  49. package/dist/types/rules/use-tokens-typography/transformers/restricted-capitalisation.d.ts +1 -1
  50. package/dist/types/rules/use-tokens-typography/transformers/untokenized-properties.d.ts +1 -1
  51. package/dist/types-ts4.5/index.codegen.d.ts +14 -0
  52. package/dist/types-ts4.5/presets/all-flat.codegen.d.ts +2 -0
  53. package/dist/types-ts4.5/presets/all.codegen.d.ts +2 -0
  54. package/dist/types-ts4.5/presets/recommended-flat.codegen.d.ts +1 -0
  55. package/dist/types-ts4.5/presets/recommended.codegen.d.ts +1 -0
  56. package/dist/types-ts4.5/rules/ensure-proper-xcss-usage/index.d.ts +3 -0
  57. package/dist/types-ts4.5/rules/index.codegen.d.ts +2 -0
  58. package/dist/types-ts4.5/rules/no-legacy-icons/helpers.d.ts +12 -4
  59. package/dist/types-ts4.5/rules/no-utility-icons/checks.d.ts +10 -0
  60. package/dist/types-ts4.5/rules/no-utility-icons/index.d.ts +3 -0
  61. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/banned-properties.d.ts +1 -1
  62. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/font-family.d.ts +1 -1
  63. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/font-weight.d.ts +1 -1
  64. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/restricted-capitalisation.d.ts +1 -1
  65. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/untokenized-properties.d.ts +1 -1
  66. package/package.json +4 -4
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
3
- * @codegen <<SignedSource::4bb65ab8e5c1cad968c6e74d7179e58b>>
3
+ * @codegen <<SignedSource::608f46f9cc8653226152a8edc34849d6>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -35,6 +35,7 @@ export default {
35
35
  '@atlaskit/design-system/no-unsafe-design-token-usage': 'error',
36
36
  '@atlaskit/design-system/no-unsafe-style-overrides': 'warn',
37
37
  '@atlaskit/design-system/no-unsupported-drag-and-drop-libraries': 'error',
38
+ '@atlaskit/design-system/no-utility-icons': 'warn',
38
39
  '@atlaskit/design-system/use-button-group-label': 'warn',
39
40
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
40
41
  '@atlaskit/design-system/use-datetime-picker-calendar-button': 'warn',
@@ -0,0 +1,149 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import { createLintRule } from '../utils/create-rule';
3
+ import { errorBoundary } from '../utils/error-boundary';
4
+ const rule = createLintRule({
5
+ meta: {
6
+ name: 'ensure-proper-xcss-usage',
7
+ docs: {
8
+ description: 'Enforces proper xcss usage: migrate from xcss() to cssMap() and use cssMap objects with specific keys.',
9
+ recommended: false,
10
+ severity: 'error'
11
+ },
12
+ messages: {
13
+ missingCssMapKey: 'xcss prop should use a specific key from cssMap (e.g., {{identifier}}.root) instead of the entire cssMap object.',
14
+ noXcssWithCompiled: 'Cannot use `xcss()` function with `@atlaskit/primitives/compiled`. Use `cssMap()` from `@atlaskit/css or `@compiled/react` instead.'
15
+ }
16
+ },
17
+ create(context) {
18
+ const tracker = {
19
+ // Components tracking
20
+ compiledComponents: new Set(),
21
+ // Function tracking
22
+ cssMapFunction: new Set(),
23
+ xcssFunction: new Set(),
24
+ // Variables tracking
25
+ cssMapVariables: new Map(),
26
+ xcssVariables: new Set()
27
+ };
28
+ return errorBoundary({
29
+ // Track all imports in a single handler
30
+ ImportDeclaration(node) {
31
+ const source = node.source.value;
32
+ node.specifiers.forEach(specifier => {
33
+ if (specifier.type !== 'ImportSpecifier') {
34
+ return;
35
+ }
36
+
37
+ // Handle different import sources
38
+ switch (source) {
39
+ case '@atlaskit/primitives/compiled':
40
+ tracker.compiledComponents.add(specifier.imported.name);
41
+ break;
42
+ case '@atlaskit/primitives':
43
+ if (specifier.imported.name === 'xcss') {
44
+ tracker.xcssFunction.add(specifier.local.name);
45
+ }
46
+ break;
47
+ case '@atlaskit/css':
48
+ case '@compiled/react':
49
+ if (specifier.imported.name === 'cssMap') {
50
+ tracker.cssMapFunction.add(specifier.local.name);
51
+ }
52
+ break;
53
+ }
54
+ });
55
+ },
56
+ // Track variable declarations
57
+ VariableDeclarator(node) {
58
+ if (!node.init || node.init.type !== 'CallExpression' || node.init.callee.type !== 'Identifier' || node.id.type !== 'Identifier') {
59
+ return;
60
+ }
61
+ const calleeName = node.init.callee.name;
62
+ const variableName = node.id.name;
63
+
64
+ // Track cssMap variables and extract their keys
65
+ if (tracker.cssMapFunction.has(calleeName)) {
66
+ const keys = new Set();
67
+
68
+ // Extract keys from the cssMap object argument
69
+ if (node.init.arguments.length > 0 && node.init.arguments[0].type === 'ObjectExpression') {
70
+ node.init.arguments[0].properties.forEach(prop => {
71
+ if (prop.type === 'Property' && prop.key.type === 'Identifier') {
72
+ keys.add(prop.key.name);
73
+ }
74
+ });
75
+ }
76
+ tracker.cssMapVariables.set(variableName, keys);
77
+ }
78
+
79
+ // Track xcss variables
80
+ if (tracker.xcssFunction.has(calleeName)) {
81
+ tracker.xcssVariables.add(variableName);
82
+ }
83
+ },
84
+ // Check JSX elements for xcss prop usage
85
+ JSXElement(node) {
86
+ var _xcssAttribute$value;
87
+ if (!isNodeOfType(node, 'JSXElement')) {
88
+ return;
89
+ }
90
+ const elementName = node.openingElement.name;
91
+ if (elementName.type !== 'JSXIdentifier') {
92
+ return;
93
+ }
94
+ const componentName = elementName.name;
95
+ if (!tracker.compiledComponents.has(componentName)) {
96
+ return;
97
+ }
98
+
99
+ // Find xcss attribute
100
+ const xcssAttribute = node.openingElement.attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.name === 'xcss');
101
+ if ((xcssAttribute === null || xcssAttribute === void 0 ? void 0 : (_xcssAttribute$value = xcssAttribute.value) === null || _xcssAttribute$value === void 0 ? void 0 : _xcssAttribute$value.type) !== 'JSXExpressionContainer') {
102
+ return;
103
+ }
104
+ const expression = xcssAttribute.value.expression;
105
+
106
+ // Check for direct xcss function calls
107
+ if (expression.type === 'CallExpression' && expression.callee.type === 'Identifier' && tracker.xcssFunction.has(expression.callee.name)) {
108
+ context.report({
109
+ node: expression,
110
+ messageId: 'noXcssWithCompiled'
111
+ });
112
+ return;
113
+ }
114
+
115
+ // Check for variables
116
+ if (expression.type === 'Identifier') {
117
+ const identifierName = expression.name;
118
+ if (tracker.xcssVariables.has(identifierName)) {
119
+ context.report({
120
+ node: expression,
121
+ messageId: 'noXcssWithCompiled'
122
+ });
123
+ } else if (tracker.cssMapVariables.has(identifierName)) {
124
+ context.report({
125
+ node: expression,
126
+ messageId: 'missingCssMapKey',
127
+ data: {
128
+ identifier: identifierName
129
+ }
130
+ });
131
+ }
132
+ return;
133
+ }
134
+
135
+ // Check member expressions (e.g., styles.root)
136
+ if (expression.type === 'MemberExpression' && expression.object.type === 'Identifier') {
137
+ const objectName = expression.object.name;
138
+ if (tracker.xcssVariables.has(objectName)) {
139
+ context.report({
140
+ node: expression,
141
+ messageId: 'noXcssWithCompiled'
142
+ });
143
+ }
144
+ }
145
+ }
146
+ });
147
+ }
148
+ });
149
+ export default rule;
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
3
- * @codegen <<SignedSource::d42e0ce93d45d02a5c30dcfdb7110c17>>
3
+ * @codegen <<SignedSource::3de5d5b44aa01faabdb840bc7fb43d04>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import consistentCssPropUsage from './consistent-css-prop-usage';
7
7
  import ensureDesignTokenUsage from './ensure-design-token-usage';
8
8
  import ensureDesignTokenUsagePreview from './ensure-design-token-usage-preview';
9
9
  import ensureIconColor from './ensure-icon-color';
10
+ import ensureProperXcssUsage from './ensure-proper-xcss-usage';
10
11
  import iconLabel from './icon-label';
11
12
  import noBannedImports from './no-banned-imports';
12
13
  import noBooleanAutofocusOnModalDialog from './no-boolean-autofocus-on-modal-dialog';
@@ -43,6 +44,7 @@ import noStyledTaggedTemplateExpression from './no-styled-tagged-template-expres
43
44
  import noUnsafeDesignTokenUsage from './no-unsafe-design-token-usage';
44
45
  import noUnsafeStyleOverrides from './no-unsafe-style-overrides';
45
46
  import noUnsupportedDragAndDropLibraries from './no-unsupported-drag-and-drop-libraries';
47
+ import noUtilityIcons from './no-utility-icons';
46
48
  import preferPrimitives from './prefer-primitives';
47
49
  import useButtonGroupLabel from './use-button-group-label';
48
50
  import useCxFunctionInXcss from './use-cx-function-in-xcss';
@@ -69,6 +71,7 @@ export const rules = {
69
71
  'ensure-design-token-usage': ensureDesignTokenUsage,
70
72
  'ensure-design-token-usage/preview': ensureDesignTokenUsagePreview,
71
73
  'ensure-icon-color': ensureIconColor,
74
+ 'ensure-proper-xcss-usage': ensureProperXcssUsage,
72
75
  'icon-label': iconLabel,
73
76
  'no-banned-imports': noBannedImports,
74
77
  'no-boolean-autofocus-on-modal-dialog': noBooleanAutofocusOnModalDialog,
@@ -105,6 +108,7 @@ export const rules = {
105
108
  'no-unsafe-design-token-usage': noUnsafeDesignTokenUsage,
106
109
  'no-unsafe-style-overrides': noUnsafeStyleOverrides,
107
110
  'no-unsupported-drag-and-drop-libraries': noUnsupportedDragAndDropLibraries,
111
+ 'no-utility-icons': noUtilityIcons,
108
112
  'prefer-primitives': preferPrimitives,
109
113
  'use-button-group-label': useButtonGroupLabel,
110
114
  'use-cx-function-in-xcss': useCxFunctionInXcss,
@@ -12,6 +12,8 @@ export const createChecks = context => {
12
12
  const legacyButtonImports = new Set();
13
13
  const errorsManual = {};
14
14
  const errorsAuto = {};
15
+ const iconSizesInfo = {}; //Import source key, locations as value
16
+
15
17
  let guidance = {};
16
18
 
17
19
  // Extract parameters
@@ -316,7 +318,7 @@ export const createChecks = context => {
316
318
 
317
319
  // Legacy icons rendered as JSX elements
318
320
  if (Object.keys(legacyIconImports).includes(name)) {
319
- var _size, _size2;
321
+ var _size, _size2, _sizeProp$value2;
320
322
  // Determine if inside a new button - if so:
321
323
  // - Assume spread props are safe - still error if props explicitly set to unmigratable values
322
324
  const insideNewButton = isInsideNewButton(node, newButtonImports);
@@ -402,7 +404,7 @@ export const createChecks = context => {
402
404
  const isNewIconMigratable = canAutoMigrateNewIconBasedOnSize(upcomingIcon ? upcomingIcon.sizeGuidance[(_size = size) !== null && _size !== void 0 ? _size : 'medium'] : migrationMapObject === null || migrationMapObject === void 0 ? void 0 : migrationMapObject.sizeGuidance[(_size2 = size) !== null && _size2 !== void 0 ? _size2 : 'medium']);
403
405
 
404
406
  // Add spacing if:
405
- // 1. size is medium for core/utility icons or small for utility icons, or not set (default is medium for core and small for utility icons)
407
+ // 1. size is medium for core/utility icons or not set (default is medium for core and small for utility icons)
406
408
  // 2. not inside a new or legacy button (except for icon-only legacy buttons)
407
409
  const sizeProp = node.openingElement.attributes.find(attribute => attribute.type === 'JSXAttribute' && (attribute.name.name === 'size' || attribute.name.name === 'LEGACY_size'));
408
410
  let spacing;
@@ -411,13 +413,24 @@ export const createChecks = context => {
411
413
  if (sizeProp && sizeProp.type === 'JSXAttribute' && ((_sizeProp$value = sizeProp.value) === null || _sizeProp$value === void 0 ? void 0 : _sizeProp$value.type) === 'Literal') {
412
414
  if (sizeProp.value.value === 'medium') {
413
415
  spacing = 'spacious';
414
- } else if (sizeProp.value.value === 'small' && (newIcon === null || newIcon === void 0 ? void 0 : newIcon.type) === 'utility') {
415
- spacing = 'compact';
416
416
  }
417
417
  } else if (!sizeProp) {
418
418
  spacing = 'spacious';
419
419
  }
420
420
  }
421
+ if (!iconSizesInfo[legacyIconImports[name].packageName]) {
422
+ iconSizesInfo[legacyIconImports[name].packageName] = {
423
+ small: [],
424
+ usageCount: 0
425
+ };
426
+ }
427
+
428
+ // Do not automatically migration if size is small as we cannot determine if a core icon or a scaled down utility icon should be used
429
+ if (sizeProp && sizeProp.type === 'JSXAttribute' && ((_sizeProp$value2 = sizeProp.value) === null || _sizeProp$value2 === void 0 ? void 0 : _sizeProp$value2.type) === 'Literal' && sizeProp.value.value === 'small') {
430
+ iconSizesInfo[legacyIconImports[name].packageName].small.push(locToString(node));
431
+ }
432
+ iconSizesInfo[legacyIconImports[name].packageName].usageCount++;
433
+ const shouldForceSmallIcon = newIcon === null || newIcon === void 0 ? void 0 : newIcon.shouldForceSmallIcon;
421
434
  if (shouldUseSafeMigrationMode && !hasManualMigration && (newIcon !== null && newIcon !== void 0 && newIcon.isMigrationUnsafe || size !== 'medium' || hasSecondaryColorProp)) {
422
435
  createCantFindSuitableReplacementError(node, legacyIconImports[name].packageName, name, errorsManual, upcomingIcon ? true : migrationMapObject ? true : false);
423
436
  } else if (!hasManualMigration && (newIcon || upcomingIcon) && isNewIconMigratable) {
@@ -427,7 +440,8 @@ export const createChecks = context => {
427
440
  iconName: name,
428
441
  errors: errorsAuto,
429
442
  spacing,
430
- insideNewButton
443
+ insideNewButton,
444
+ shouldForceSmallIcon
431
445
  });
432
446
  } else if ((!newIcon && !upcomingIcon || !isNewIconMigratable) && size) {
433
447
  createCantFindSuitableReplacementError(node, legacyIconImports[name].packageName, name, errorsManual, upcomingIcon ? true : migrationMapObject ? true : false);
@@ -437,7 +451,8 @@ export const createChecks = context => {
437
451
  iconPackage: legacyIconImports[name].packageName,
438
452
  insideNewButton,
439
453
  size: size && isSize(size) ? size : undefined,
440
- shouldUseMigrationPath
454
+ shouldUseMigrationPath,
455
+ shouldForceSmallIcon
441
456
  });
442
457
  }
443
458
  };
@@ -479,6 +494,7 @@ export const createChecks = context => {
479
494
  throwAutoErrors({
480
495
  errorsManual,
481
496
  errorsAuto,
497
+ iconSizesInfo,
482
498
  legacyIconImports,
483
499
  guidance,
484
500
  migrationIconImports,
@@ -46,7 +46,7 @@ const getIconKey = iconPackage => {
46
46
  * Checks if a new icon can be auto-migrated based on guidance from the migration map
47
47
  */
48
48
  export const canAutoMigrateNewIconBasedOnSize = guidance => {
49
- return ['swap', 'swap-slight-visual-change', 'swap-visual-change', 'swap-size-shift-utility'].includes(guidance || '');
49
+ return guidance ? ['swap', 'swap-slight-visual-change', 'swap-visual-change'].includes(guidance) : false;
50
50
  };
51
51
 
52
52
  /**
@@ -76,9 +76,11 @@ const getNewIconNameAndImportPath = (iconPackage, shouldUseMigrationPath) => {
76
76
  export const createGuidance = ({
77
77
  iconPackage,
78
78
  insideNewButton,
79
- size,
80
- shouldUseMigrationPath
79
+ size: initialSize,
80
+ shouldUseMigrationPath,
81
+ shouldForceSmallIcon
81
82
  }) => {
83
+ const size = shouldForceSmallIcon ? 'small' : initialSize;
82
84
  const migrationMapObject = getMigrationMapObject(iconPackage);
83
85
  const upcomingIcon = getUpcomingIcons(iconPackage);
84
86
  if (upcomingIcon) {
@@ -133,8 +135,18 @@ export const createGuidance = ({
133
135
  }
134
136
  if (insideNewButton) {
135
137
  guidance += buttonGuidanceStr;
136
- } else if (size && size === 'medium') {
137
- guidance += "Setting the spacing property to 'spacious' will maintain the icon's box dimensions - but consider setting spacing='none' as it allows for easier control of spacing by parent elements.\n";
138
+ } else if (size === 'medium') {
139
+ guidance += "Setting the spacing='spacious' will maintain the icon's box dimensions - but consider setting spacing='none' as it allows for easier control of spacing by parent elements.\n";
140
+ } else if (size === 'small') {
141
+ if (initialSize !== 'small' && shouldForceSmallIcon) {
142
+ guidance += "For this icon, it's recommended to use a smaller size using size='small'. Alternatively, for special cases where a larger version is needed size='medium' can be used, but it is generally discouraged for this icon.\n";
143
+ } else if (initialSize === 'small') {
144
+ if (shouldForceSmallIcon) {
145
+ guidance += "Setting spacing='compact' will maintain the icon's box dimensions - but consider setting spacing='none' as it allows for easier control of spacing by parent elements.\n";
146
+ } else {
147
+ guidance += "It's recommended to upscale to a medium icon with no spacing. Alternatively for special cases where smaller icons are required, the original icon size and dimensions can be maintained by using size='small' and spacing='compact'.\n";
148
+ }
149
+ }
138
150
  } else if (size) {
139
151
  guidance += "In the new icon, please use spacing='none'.\n";
140
152
  }
@@ -258,7 +270,8 @@ export const createAutoMigrationError = ({
258
270
  iconName,
259
271
  errors,
260
272
  spacing,
261
- insideNewButton
273
+ insideNewButton,
274
+ shouldForceSmallIcon
262
275
  }) => {
263
276
  const myError = {
264
277
  node,
@@ -268,7 +281,8 @@ export const createAutoMigrationError = ({
268
281
  iconName,
269
282
  spacing: spacing !== null && spacing !== void 0 ? spacing : '',
270
283
  // value type need to be a string in Rule.ReportDescriptor
271
- insideNewButton: String(insideNewButton)
284
+ insideNewButton: String(insideNewButton),
285
+ shouldForceSmallIcon: String(shouldForceSmallIcon)
272
286
  }
273
287
  };
274
288
  errors[locToString(node)] = myError;
@@ -421,7 +435,7 @@ const getNewIconNameForRenaming = (isInManualArray, importSource, importSpecifie
421
435
  let newIconName;
422
436
  if (isInManualArray) {
423
437
  newIconName = getNewIconNameAndImportPath(importSource).iconName;
424
- const keyToName = newIconName ? newIconName[0].toUpperCase() + newIconName.slice(1) + 'Icon' : undefined;
438
+ const keyToName = newIconName ? getComponentName(newIconName) : undefined;
425
439
  newIconName = keyToName;
426
440
  if (newIconName === undefined || importSpecifier === keyToName) {
427
441
  newIconName = `${keyToName}New`;
@@ -429,6 +443,9 @@ const getNewIconNameForRenaming = (isInManualArray, importSource, importSpecifie
429
443
  }
430
444
  return newIconName;
431
445
  };
446
+ export const getComponentName = name => {
447
+ return name.split(/\W/).map(part => `${part[0].toUpperCase()}${part.slice(1)}`).join('').concat('Icon');
448
+ };
432
449
 
433
450
  /**
434
451
  *
@@ -499,7 +516,8 @@ const createPropFixes = ({
499
516
  }) => {
500
517
  const fixes = [];
501
518
  const {
502
- spacing
519
+ spacing,
520
+ size
503
521
  } = metadata;
504
522
  if (shouldUseMigrationPath && !legacyImportNode) {
505
523
  return fixes;
@@ -520,24 +538,31 @@ const createPropFixes = ({
520
538
  if (primaryColor && primaryColor.type === 'JSXAttribute') {
521
539
  fixes.push(fixer.replaceText(primaryColor.name, 'color'));
522
540
  }
523
-
524
- // rename or remove size prop based on shouldUseMigrationPath,
525
- // add spacing="spacious" if
526
- // 1. it's in error metadata, which means size is medium
527
- // 2. no existing spacing prop
528
- // 3. iconType is "core"
529
- // 4. icon is not imported from migration entrypoint
530
541
  const sizeProp = findProp(attributes, 'size');
531
542
  const spacingProp = findProp(attributes, 'spacing');
532
- if (spacing && !spacingProp && !migrationImportNode) {
533
- fixes.push(fixer.insertTextAfter(sizeProp || openingElement.name, ` spacing="${spacing}"`));
534
- }
535
543
  if (sizeProp && sizeProp.type === 'JSXAttribute') {
536
- fixes.push(shouldUseMigrationPath ?
537
- // replace size prop with LEGACY_size,
538
- fixer.replaceText(sizeProp.name, 'LEGACY_size') :
539
- // remove size prop if shouldUseMigrationPath is false
540
- fixer.remove(sizeProp));
544
+ if (shouldUseMigrationPath) {
545
+ // Rename existing size prop to LEGACY_size and add new size prop if applicable
546
+ fixes.push(fixer.replaceText(sizeProp.name, 'LEGACY_size'));
547
+ if (size) {
548
+ fixes.push(fixer.insertTextAfter(sizeProp, ` size="${size}"`));
549
+ }
550
+ } else {
551
+ if (size && sizeProp.value) {
552
+ // update size prop with new replacement size
553
+ fixes.push(fixer.replaceText(sizeProp.value, `"${size}"`));
554
+ } else {
555
+ // remove size prop if no new replacement size is specified
556
+ fixes.push(fixer.remove(sizeProp));
557
+ }
558
+ }
559
+ } else if (size) {
560
+ fixes.push(fixer.insertTextAfter(openingElement.name, ` size="${size}"`));
561
+ }
562
+
563
+ // Add spacing prop if no existing spacing prop and icon is not imported from migration entrypoint
564
+ if (spacing && spacing !== 'none' && !spacingProp && !migrationImportNode) {
565
+ fixes.push(fixer.insertTextAfter(sizeProp || openingElement.name, ` spacing="${spacing}"`));
541
566
  }
542
567
 
543
568
  // rename or remove secondaryColor prop based on shouldUseMigrationPath
@@ -622,6 +647,7 @@ export const throwManualErrors = ({
622
647
  export const throwAutoErrors = ({
623
648
  errorsManual,
624
649
  errorsAuto,
650
+ iconSizesInfo,
625
651
  legacyIconImports,
626
652
  guidance,
627
653
  migrationIconImports,
@@ -667,7 +693,7 @@ export const throwAutoErrors = ({
667
693
  const appliedErrorsForImport = [];
668
694
  // Loop over auto errors for a single import source
669
695
  for (const [_, error] of errorList.entries()) {
670
- var _legacyIconImports$er, _legacyIconImports$er2, _migrationIconImports;
696
+ var _iconSizesInfo$import, _iconSizesInfo$import2, _iconSizesInfo$import3, _legacyIconImports$er, _error$data2, _legacyIconImports$er2, _migrationIconImports;
671
697
  const {
672
698
  key
673
699
  } = error;
@@ -676,8 +702,20 @@ export const throwAutoErrors = ({
676
702
  // If that is the case we'll need to provide a suggestion instead of auto-fixing as the suggestion will
677
703
  // add another import without removing the old import and this needs to be validated
678
704
  const isInManualArray = allManualErrorSources.has(importSource);
705
+
706
+ // Check if the icon has size of small, if so it cannot be automatically migrated. Two suggestions will be provided
707
+ // 1. Use core icon with no spacing
708
+ // 2. Use utility icon with compact spacing
709
+ const isSizeSmall = (_iconSizesInfo$import = iconSizesInfo[importSource]) === null || _iconSizesInfo$import === void 0 ? void 0 : _iconSizesInfo$import.small.includes(key);
710
+ const isMixedSizeUsage = ((_iconSizesInfo$import2 = iconSizesInfo[importSource]) === null || _iconSizesInfo$import2 === void 0 ? void 0 : _iconSizesInfo$import2.small.length) > 0 && ((_iconSizesInfo$import3 = iconSizesInfo[importSource]) === null || _iconSizesInfo$import3 === void 0 ? void 0 : _iconSizesInfo$import3.small.length) < iconSizesInfo[importSource].usageCount;
711
+
712
+ // Icon should be renamed
713
+ // 1. If the icon is in the manual array OR
714
+ // 2. If there is mixed size usages of this icon with size small
715
+ const shouldRenameIcon = isInManualArray || isMixedSizeUsage;
716
+
679
717
  // New icon name for renaming if the icon is in the manual array
680
- const newIconName = getNewIconNameForRenaming(isInManualArray, importSource, errorList[0].data ? (_legacyIconImports$er = legacyIconImports[errorList[0].data.iconName]) === null || _legacyIconImports$er === void 0 ? void 0 : _legacyIconImports$er.importSpecifier : undefined);
718
+ const newIconName = getNewIconNameForRenaming(shouldRenameIcon, importSource, errorList[0].data ? (_legacyIconImports$er = legacyIconImports[errorList[0].data.iconName]) === null || _legacyIconImports$er === void 0 ? void 0 : _legacyIconImports$er.importSpecifier : undefined);
681
719
  if (!node) {
682
720
  continue;
683
721
  }
@@ -685,23 +723,49 @@ export const throwAutoErrors = ({
685
723
  if (Object.keys(error).includes('data') && error.data) {
686
724
  error.data.guidance = guidanceMessage;
687
725
  }
726
+ const shouldForceSmallIcon = ((_error$data2 = error.data) === null || _error$data2 === void 0 ? void 0 : _error$data2.shouldForceSmallIcon) === 'true';
688
727
  const fixArguments = error.data ? {
689
- metadata: error.data,
728
+ metadata: {
729
+ ...error.data,
730
+ spacing: error.data.isInNewButton ? 'none' : error.data.spacing,
731
+ size: shouldForceSmallIcon ? 'small' : error.data.size
732
+ },
690
733
  legacyImportNode: (_legacyIconImports$er2 = legacyIconImports[error.data.iconName]) === null || _legacyIconImports$er2 === void 0 ? void 0 : _legacyIconImports$er2.importNode,
691
734
  migrationImportNode: (_migrationIconImports = migrationIconImports[error.data.iconName]) === null || _migrationIconImports === void 0 ? void 0 : _migrationIconImports.importNode,
692
735
  shouldUseMigrationPath,
693
- newIconName: isInManualArray ? newIconName : undefined
736
+ newIconName: shouldRenameIcon ? newIconName : undefined
694
737
  } : null;
695
738
  if (!error.data || shouldUseMigrationPath && !checkIfNewIconExist(error) || !fixArguments) {
696
739
  continue;
697
740
  }
698
- if (isInManualArray) {
699
- // provide suggestion if there is a manual error for the same import source and thus the legacy import can't be removed
741
+ const isInNewButton = fixArguments.metadata.insideNewButton === 'true';
742
+ if (isSizeSmall && !shouldForceSmallIcon) {
700
743
  error.suggest = [{
701
- desc: 'Rename icon import, import from the new package, and update props.',
744
+ desc: isInNewButton ? 'Replace with medium core icon (Recommended)' : 'Replace with medium core icon and no spacing (Recommended)',
702
745
  fix: fixer => {
703
746
  return [...createPropFixes({
704
747
  ...fixArguments,
748
+ metadata: {
749
+ ...fixArguments.metadata,
750
+ spacing: 'none'
751
+ },
752
+ node,
753
+ fixer
754
+ }), ...createImportFix({
755
+ ...fixArguments,
756
+ fixer
757
+ })];
758
+ }
759
+ }, {
760
+ desc: isInNewButton ? 'Replace with small core icon' : 'Replace with small core icon and compact spacing',
761
+ fix: fixer => {
762
+ return [...createPropFixes({
763
+ ...fixArguments,
764
+ metadata: {
765
+ ...fixArguments.metadata,
766
+ spacing: 'compact',
767
+ size: 'small'
768
+ },
705
769
  node,
706
770
  fixer
707
771
  }), ...createImportFix({
@@ -711,23 +775,40 @@ export const throwAutoErrors = ({
711
775
  }
712
776
  }];
713
777
  } else {
714
- // Update Guidance message for auto-fixing
715
- if (error.data) {
716
- error.data.guidance = error.data.guidance + `\nTo automatically fix this icon, run the auto-fixer attached to the first use of ${importSource} in this file - either manually, or by saving this file.`;
717
- }
718
- // There should only be 1 import fix for each import source and thus only add this at the start of the list
719
- if (autoFixers.length === 0) {
720
- autoFixers.push(fixer => createImportFix({
778
+ if (isInManualArray) {
779
+ // provide suggestion if there is a manual error for the same import source and thus the legacy import can't be removed
780
+ error.suggest = [{
781
+ desc: 'Rename icon import, import from the new package, and update props.',
782
+ fix: fixer => {
783
+ return [...createPropFixes({
784
+ ...fixArguments,
785
+ node,
786
+ fixer
787
+ }), ...createImportFix({
788
+ ...fixArguments,
789
+ fixer
790
+ })];
791
+ }
792
+ }];
793
+ } else {
794
+ // Update Guidance message for auto-fixing
795
+ if (error.data) {
796
+ error.data.guidance = error.data.guidance + `\nTo automatically fix this icon, run the auto-fixer attached to the first use of ${importSource} in this file - either manually, or by saving this file.`;
797
+ }
798
+ // There should only be 1 import fix for each import source and thus only add this at the start of the list
799
+ if (autoFixers.length === 0) {
800
+ autoFixers.push(fixer => createImportFix({
801
+ ...fixArguments,
802
+ fixer
803
+ }));
804
+ }
805
+ // Push the prop fix regardless
806
+ autoFixers.push(fixer => createPropFixes({
721
807
  ...fixArguments,
808
+ node,
722
809
  fixer
723
810
  }));
724
811
  }
725
- // Push the prop fix regardless
726
- autoFixers.push(fixer => createPropFixes({
727
- ...fixArguments,
728
- node,
729
- fixer
730
- }));
731
812
  }
732
813
  // Add the error to the appliedErrorsForImport, ready to be thrown later
733
814
  appliedErrorsForImport.push(error);