@atlaskit/eslint-plugin-design-system 13.14.2 → 13.15.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 (24) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +63 -63
  3. package/dist/cjs/rules/no-legacy-icons/checks.js +22 -5
  4. package/dist/cjs/rules/no-legacy-icons/helpers.js +122 -46
  5. package/dist/cjs/rules/utils/get-deprecated-config.js +1 -1
  6. package/dist/es2019/rules/no-legacy-icons/checks.js +22 -6
  7. package/dist/es2019/rules/no-legacy-icons/helpers.js +125 -44
  8. package/dist/es2019/rules/utils/get-deprecated-config.js +3 -2
  9. package/dist/esm/rules/no-legacy-icons/checks.js +22 -5
  10. package/dist/esm/rules/no-legacy-icons/helpers.js +121 -45
  11. package/dist/esm/rules/utils/get-deprecated-config.js +2 -2
  12. package/dist/types/rules/no-legacy-icons/helpers.d.ts +12 -4
  13. package/dist/types/rules/use-tokens-typography/transformers/banned-properties.d.ts +1 -1
  14. package/dist/types/rules/use-tokens-typography/transformers/font-family.d.ts +1 -1
  15. package/dist/types/rules/use-tokens-typography/transformers/font-weight.d.ts +1 -1
  16. package/dist/types/rules/use-tokens-typography/transformers/restricted-capitalisation.d.ts +1 -1
  17. package/dist/types/rules/use-tokens-typography/transformers/untokenized-properties.d.ts +1 -1
  18. package/dist/types-ts4.5/rules/no-legacy-icons/helpers.d.ts +12 -4
  19. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/banned-properties.d.ts +1 -1
  20. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/font-family.d.ts +1 -1
  21. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/font-weight.d.ts +1 -1
  22. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/restricted-capitalisation.d.ts +1 -1
  23. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/untokenized-properties.d.ts +1 -1
  24. package/package.json +4 -4
@@ -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);
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { deprecatedCore as deprecatedIconLabCore } from '@atlaskit/icon-lab/deprecated-map';
3
+ import { deprecatedCore as deprecatedIconLabCore, deprecatedUtility as deprecatedIconLabUtility } from '@atlaskit/icon-lab/deprecated-map';
4
4
  import { deprecatedCore as deprecatedIconCore, deprecatedUtility as deprecatedIconUtility } from '@atlaskit/icon/deprecated-map';
5
5
  export const getConfig = specifier => {
6
6
  const configPath = path.resolve(__dirname, '..', '..', '..', 'configs', 'deprecated.json');
@@ -12,7 +12,8 @@ export const getConfig = specifier => {
12
12
  ...parsedConfig.imports,
13
13
  ...deprecatedIconCore,
14
14
  ...deprecatedIconUtility,
15
- ...deprecatedIconLabCore
15
+ ...deprecatedIconLabCore,
16
+ ...deprecatedIconLabUtility
16
17
  }
17
18
  };
18
19
  return combinedConfig[specifier];
@@ -14,6 +14,8 @@ export var createChecks = function createChecks(context) {
14
14
  var legacyButtonImports = new Set();
15
15
  var errorsManual = {};
16
16
  var errorsAuto = {};
17
+ var iconSizesInfo = {}; //Import source key, locations as value
18
+
17
19
  var guidance = {};
18
20
 
19
21
  // Extract parameters
@@ -385,6 +387,7 @@ export var createChecks = function createChecks(context) {
385
387
 
386
388
  // Legacy icons rendered as JSX elements
387
389
  if (Object.keys(legacyIconImports).includes(name)) {
390
+ var _sizeProp$value2;
388
391
  // Determine if inside a new button - if so:
389
392
  // - Assume spread props are safe - still error if props explicitly set to unmigratable values
390
393
  var insideNewButton = isInsideNewButton(node, newButtonImports);
@@ -483,7 +486,7 @@ export var createChecks = function createChecks(context) {
483
486
  var isNewIconMigratable = canAutoMigrateNewIconBasedOnSize(upcomingIcon ? upcomingIcon.sizeGuidance[size !== null && size !== void 0 ? size : 'medium'] : migrationMapObject === null || migrationMapObject === void 0 ? void 0 : migrationMapObject.sizeGuidance[size !== null && size !== void 0 ? size : 'medium']);
484
487
 
485
488
  // Add spacing if:
486
- // 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)
489
+ // 1. size is medium for core/utility icons or not set (default is medium for core and small for utility icons)
487
490
  // 2. not inside a new or legacy button (except for icon-only legacy buttons)
488
491
  var sizeProp = node.openingElement.attributes.find(function (attribute) {
489
492
  return attribute.type === 'JSXAttribute' && (attribute.name.name === 'size' || attribute.name.name === 'LEGACY_size');
@@ -494,13 +497,24 @@ export var createChecks = function createChecks(context) {
494
497
  if (sizeProp && sizeProp.type === 'JSXAttribute' && ((_sizeProp$value = sizeProp.value) === null || _sizeProp$value === void 0 ? void 0 : _sizeProp$value.type) === 'Literal') {
495
498
  if (sizeProp.value.value === 'medium') {
496
499
  spacing = 'spacious';
497
- } else if (sizeProp.value.value === 'small' && (newIcon === null || newIcon === void 0 ? void 0 : newIcon.type) === 'utility') {
498
- spacing = 'compact';
499
500
  }
500
501
  } else if (!sizeProp) {
501
502
  spacing = 'spacious';
502
503
  }
503
504
  }
505
+ if (!iconSizesInfo[legacyIconImports[name].packageName]) {
506
+ iconSizesInfo[legacyIconImports[name].packageName] = {
507
+ small: [],
508
+ usageCount: 0
509
+ };
510
+ }
511
+
512
+ // 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
513
+ if (sizeProp && sizeProp.type === 'JSXAttribute' && ((_sizeProp$value2 = sizeProp.value) === null || _sizeProp$value2 === void 0 ? void 0 : _sizeProp$value2.type) === 'Literal' && sizeProp.value.value === 'small') {
514
+ iconSizesInfo[legacyIconImports[name].packageName].small.push(locToString(node));
515
+ }
516
+ iconSizesInfo[legacyIconImports[name].packageName].usageCount++;
517
+ var shouldForceSmallIcon = newIcon === null || newIcon === void 0 ? void 0 : newIcon.shouldForceSmallIcon;
504
518
  if (shouldUseSafeMigrationMode && !hasManualMigration && (newIcon !== null && newIcon !== void 0 && newIcon.isMigrationUnsafe || size !== 'medium' || hasSecondaryColorProp)) {
505
519
  createCantFindSuitableReplacementError(node, legacyIconImports[name].packageName, name, errorsManual, upcomingIcon ? true : migrationMapObject ? true : false);
506
520
  } else if (!hasManualMigration && (newIcon || upcomingIcon) && isNewIconMigratable) {
@@ -510,7 +524,8 @@ export var createChecks = function createChecks(context) {
510
524
  iconName: name,
511
525
  errors: errorsAuto,
512
526
  spacing: spacing,
513
- insideNewButton: insideNewButton
527
+ insideNewButton: insideNewButton,
528
+ shouldForceSmallIcon: shouldForceSmallIcon
514
529
  });
515
530
  } else if ((!newIcon && !upcomingIcon || !isNewIconMigratable) && size) {
516
531
  createCantFindSuitableReplacementError(node, legacyIconImports[name].packageName, name, errorsManual, upcomingIcon ? true : migrationMapObject ? true : false);
@@ -520,7 +535,8 @@ export var createChecks = function createChecks(context) {
520
535
  iconPackage: legacyIconImports[name].packageName,
521
536
  insideNewButton: insideNewButton,
522
537
  size: size && isSize(size) ? size : undefined,
523
- shouldUseMigrationPath: shouldUseMigrationPath
538
+ shouldUseMigrationPath: shouldUseMigrationPath,
539
+ shouldForceSmallIcon: shouldForceSmallIcon
524
540
  });
525
541
  }
526
542
  };
@@ -571,6 +587,7 @@ export var createChecks = function createChecks(context) {
571
587
  throwAutoErrors({
572
588
  errorsManual: errorsManual,
573
589
  errorsAuto: errorsAuto,
590
+ iconSizesInfo: iconSizesInfo,
574
591
  legacyIconImports: legacyIconImports,
575
592
  guidance: guidance,
576
593
  migrationIconImports: migrationIconImports,