@atlaskit/eslint-plugin-design-system 10.17.3 → 10.18.1

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 (45) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -1
  3. package/dist/cjs/rules/no-legacy-icons/checks.js +29 -137
  4. package/dist/cjs/rules/no-legacy-icons/helpers.js +247 -16
  5. package/dist/cjs/rules/no-legacy-icons/index.js +1 -0
  6. package/dist/cjs/rules/use-latest-xcss-syntax-typography/config/index.js +2 -1
  7. package/dist/cjs/rules/use-latest-xcss-syntax-typography/index.js +22 -5
  8. package/dist/cjs/rules/use-latest-xcss-syntax-typography/linters/common.js +5 -0
  9. package/dist/cjs/rules/use-latest-xcss-syntax-typography/linters/index.js +10 -3
  10. package/dist/cjs/rules/use-latest-xcss-syntax-typography/linters/{banned-property/index.js → restricted-property.js} +30 -10
  11. package/dist/cjs/rules/use-latest-xcss-syntax-typography/linters/wrapped-token-value.js +50 -0
  12. package/dist/es2019/rules/no-legacy-icons/checks.js +30 -108
  13. package/dist/es2019/rules/no-legacy-icons/helpers.js +200 -15
  14. package/dist/es2019/rules/no-legacy-icons/index.js +1 -0
  15. package/dist/es2019/rules/use-latest-xcss-syntax-typography/config/index.js +2 -1
  16. package/dist/es2019/rules/use-latest-xcss-syntax-typography/index.js +19 -6
  17. package/dist/es2019/rules/use-latest-xcss-syntax-typography/linters/common.js +1 -0
  18. package/dist/es2019/rules/use-latest-xcss-syntax-typography/linters/index.js +2 -1
  19. package/dist/es2019/rules/use-latest-xcss-syntax-typography/linters/restricted-property.js +61 -0
  20. package/dist/es2019/rules/use-latest-xcss-syntax-typography/linters/wrapped-token-value.js +44 -0
  21. package/dist/esm/rules/no-legacy-icons/checks.js +30 -137
  22. package/dist/esm/rules/no-legacy-icons/helpers.js +246 -15
  23. package/dist/esm/rules/no-legacy-icons/index.js +1 -0
  24. package/dist/esm/rules/use-latest-xcss-syntax-typography/config/index.js +2 -1
  25. package/dist/esm/rules/use-latest-xcss-syntax-typography/index.js +23 -6
  26. package/dist/esm/rules/use-latest-xcss-syntax-typography/linters/common.js +1 -0
  27. package/dist/esm/rules/use-latest-xcss-syntax-typography/linters/index.js +2 -1
  28. package/dist/esm/rules/use-latest-xcss-syntax-typography/linters/{banned-property/index.js → restricted-property.js} +29 -9
  29. package/dist/esm/rules/use-latest-xcss-syntax-typography/linters/wrapped-token-value.js +44 -0
  30. package/dist/types/rules/no-legacy-icons/helpers.d.ts +31 -60
  31. package/dist/types/rules/use-latest-xcss-syntax-typography/config/index.d.ts +3 -0
  32. package/dist/types/rules/use-latest-xcss-syntax-typography/linters/common.d.ts +6 -0
  33. package/dist/types/rules/use-latest-xcss-syntax-typography/linters/index.d.ts +2 -1
  34. package/dist/types/rules/use-latest-xcss-syntax-typography/linters/restricted-property.d.ts +6 -0
  35. package/dist/types/rules/use-latest-xcss-syntax-typography/linters/wrapped-token-value.d.ts +7 -0
  36. package/dist/types-ts4.5/rules/no-legacy-icons/helpers.d.ts +31 -60
  37. package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/config/index.d.ts +3 -0
  38. package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/linters/common.d.ts +6 -0
  39. package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/linters/index.d.ts +2 -1
  40. package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/linters/restricted-property.d.ts +6 -0
  41. package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/linters/wrapped-token-value.d.ts +7 -0
  42. package/package.json +2 -6
  43. package/dist/es2019/rules/use-latest-xcss-syntax-typography/linters/banned-property/index.js +0 -40
  44. package/dist/types/rules/use-latest-xcss-syntax-typography/linters/banned-property/index.d.ts +0 -7
  45. package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/linters/banned-property/index.d.ts +0 -7
@@ -54,7 +54,7 @@ export const canAutoMigrateNewIconBasedOnSize = guidance => {
54
54
  * @param iconPackage string
55
55
  * @returns object of new icon name and import path
56
56
  */
57
- export const getNewIconNameAndImportPath = (iconPackage, shouldUseMigrationPath) => {
57
+ const getNewIconNameAndImportPath = (iconPackage, shouldUseMigrationPath) => {
58
58
  const legacyIconName = getIconKey(iconPackage);
59
59
  const migrationMapObject = getMigrationMapObject(iconPackage);
60
60
  if (!migrationMapObject || !migrationMapObject.newIcon) {
@@ -284,7 +284,7 @@ const pushManualError = (key, errors, myError, importSource, iconName) => {
284
284
  };
285
285
  }
286
286
  };
287
- export const getLiteralStringValue = value => {
287
+ const getLiteralStringValue = value => {
288
288
  if (!value) {
289
289
  return;
290
290
  }
@@ -354,7 +354,7 @@ export const addToListOfRanges = (node, sortedListOfRangesForErrors) => {
354
354
  });
355
355
  }
356
356
  };
357
- export const isInRangeList = (node, sortedListOfRangesForErrors) => {
357
+ const isInRangeList = (node, sortedListOfRangesForErrors) => {
358
358
  const {
359
359
  range
360
360
  } = node;
@@ -395,6 +395,18 @@ export const isInsideLegacyButton = (node, legacyButtonImports) => {
395
395
  return insideLegacyButton;
396
396
  };
397
397
  const findProp = (attributes, propName) => attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.name === propName);
398
+ const getNewIconNameForRenaming = (isInManualArray, importSource, importSpecifier) => {
399
+ let newIconName;
400
+ if (isInManualArray) {
401
+ newIconName = getNewIconNameAndImportPath(importSource).iconName;
402
+ const keyToName = newIconName ? newIconName[0].toUpperCase() + newIconName.slice(1) + 'Icon' : undefined;
403
+ newIconName = keyToName;
404
+ if (newIconName === undefined || importSpecifier === keyToName) {
405
+ newIconName = `${keyToName}New`;
406
+ }
407
+ }
408
+ return newIconName;
409
+ };
398
410
 
399
411
  /**
400
412
  *
@@ -406,25 +418,25 @@ const findProp = (attributes, propName) => attributes.find(attr => attr.type ===
406
418
  * @param migrationImportNode The migration import declaration node to replace, only present if shouldUseMigrationPath is false
407
419
  * @returns A list of fixers to migrate the icon
408
420
  */
409
- export const createImportFix = ({
421
+ const createImportFix = ({
410
422
  fixer,
411
423
  legacyImportNode,
412
424
  metadata,
413
425
  shouldUseMigrationPath,
414
- migrationImportNode
426
+ migrationImportNode,
427
+ newIconName
415
428
  }) => {
416
429
  const fixes = [];
417
430
  const {
418
431
  importSource
419
432
  } = metadata;
420
433
  const importPath = migrationImportNode ? importSource.replace('/migration', '').split('--')[0] : getNewIconNameAndImportPath(importSource, shouldUseMigrationPath).importPath;
421
-
422
- // replace old icon import with icon import
423
- if (legacyImportNode && importPath) {
424
- fixes.push(fixer.replaceText(legacyImportNode.source, `'${literal(importPath)}'`));
425
- }
426
- if (migrationImportNode && !shouldUseMigrationPath && importPath) {
427
- fixes.push(fixer.replaceText(migrationImportNode.source, `'${literal(importPath)}'`));
434
+ const useMigrationPath = legacyImportNode && importPath;
435
+ const useFinalPath = migrationImportNode && !shouldUseMigrationPath && importPath;
436
+ if (useMigrationPath) {
437
+ fixes.push(newIconName ? fixer.insertTextBefore(legacyImportNode, `import ${newIconName} from '${importPath}';\n`) : fixer.replaceText(legacyImportNode.source, `'${literal(importPath)}'`));
438
+ } else if (useFinalPath) {
439
+ fixes.push(newIconName ? fixer.insertTextBefore(migrationImportNode, `import ${newIconName} from '${importPath}';\n`) : fixer.replaceText(migrationImportNode.source, `'${literal(importPath)}'`));
428
440
  }
429
441
  return fixes;
430
442
  };
@@ -439,13 +451,14 @@ export const createImportFix = ({
439
451
  * @param migrationImportNode The migration import declaration node to replace, only present if shouldUseMigrationPath is false
440
452
  * @returns A list of fixers to migrate the icon
441
453
  */
442
- export const createPropFixes = ({
454
+ const createPropFixes = ({
443
455
  node,
444
456
  fixer,
445
457
  legacyImportNode,
446
458
  metadata,
447
459
  shouldUseMigrationPath,
448
- migrationImportNode
460
+ migrationImportNode,
461
+ newIconName
449
462
  }) => {
450
463
  const fixes = [];
451
464
  const {
@@ -462,6 +475,9 @@ export const createPropFixes = ({
462
475
  const {
463
476
  openingElement
464
477
  } = node;
478
+ if (newIconName) {
479
+ fixes.push(fixer.replaceText(openingElement.name, newIconName));
480
+ }
465
481
  const {
466
482
  attributes
467
483
  } = openingElement;
@@ -520,6 +536,8 @@ export const createPropFixes = ({
520
536
  }
521
537
  });
522
538
  }
539
+ } else if (node.type === 'Identifier' && newIconName) {
540
+ fixes.push(fixer.replaceText(node, newIconName));
523
541
  }
524
542
  return fixes;
525
543
  };
@@ -527,7 +545,7 @@ export const createPropFixes = ({
527
545
  /**
528
546
  * Check if the new icon exists in the migration map
529
547
  */
530
- export const checkIfNewIconExist = error => {
548
+ const checkIfNewIconExist = error => {
531
549
  var _error$data;
532
550
  if (!((_error$data = error.data) !== null && _error$data !== void 0 && _error$data.importSource)) {
533
551
  return false;
@@ -537,4 +555,171 @@ export const checkIfNewIconExist = error => {
537
555
  newIcon
538
556
  } = baseMigrationMap[iconKey] || {};
539
557
  return Boolean(newIcon);
558
+ };
559
+ export const throwManualErrors = ({
560
+ errorsManual,
561
+ errorRanges,
562
+ guidance,
563
+ context,
564
+ isQuietMode
565
+ }) => {
566
+ for (const [key, errorList] of Object.entries(errorsManual)) {
567
+ const node = 'node' in errorList.errors[0] ? errorList.errors[0].node : null;
568
+ if (!node) {
569
+ return;
570
+ }
571
+ const cantMigrateIdentifierError = errorList.errors.find(x => 'messageId' in x && x.messageId === 'cantMigrateIdentifier');
572
+ let isInRange = false;
573
+ if (cantMigrateIdentifierError && isInRangeList(node, errorRanges)) {
574
+ isInRange = true;
575
+ }
576
+ if (isInRange && errorList.errors.length - 1 > 0 || !isInRange && errorList.errors.length > 0) {
577
+ const guidanceMessage = Object.keys(guidance).includes(key) ? guidance[key] : '';
578
+ context.report({
579
+ node,
580
+ messageId: 'noLegacyIconsManualMigration',
581
+ data: {
582
+ iconName: errorList.iconName,
583
+ importSource: errorList.importSource,
584
+ guidance: isQuietMode ? guidanceMessage : `${guidanceMessage}For more information see the below errors.\n`
585
+ }
586
+ });
587
+ if (!isQuietMode) {
588
+ for (const error of errorList.errors) {
589
+ if ('messageId' in error && (error.messageId !== 'cantMigrateIdentifier' || error.messageId === 'cantMigrateIdentifier' && !isInRange)) {
590
+ context.report(error);
591
+ }
592
+ }
593
+ }
594
+ }
595
+ }
596
+ };
597
+
598
+ // Loops through automatic errors and them after adding the required suggestion/fix
599
+ export const throwAutoErrors = ({
600
+ errorsManual,
601
+ errorsAuto,
602
+ legacyIconImports,
603
+ guidance,
604
+ migrationIconImports,
605
+ shouldUseMigrationPath,
606
+ context
607
+ }) => {
608
+ // Set of all the import sources that have manual errors (required later to check if a source has both manual and auto
609
+ // errors in one file making it impossible to just remove the legacy import)
610
+ const allManualErrorSources = Object.entries(errorsManual).reduce((result, option) => {
611
+ const [key, errorInfo] = option;
612
+ if (!errorsAuto.hasOwnProperty(key)) {
613
+ result.add(errorInfo.importSource);
614
+ }
615
+ return result;
616
+ }, new Set());
617
+ //group errors by import source and remove any unwanted errors
618
+ const groupedErrorList = Object.entries(errorsAuto).reduce((result, option) => {
619
+ const [key, error] = option;
620
+ //return early if no data
621
+ if (!error.data) {
622
+ return result;
623
+ }
624
+ if (Object.keys(errorsManual).includes(key)) {
625
+ const cantMigrateIdentifierError = errorsManual[key].errors.find(x => 'messageId' in x && x.messageId === 'cantMigrateIdentifier');
626
+ // If cantMigrateIdentifier is the only manual error found we still want to throw the auto error
627
+ if (!(cantMigrateIdentifierError && errorsManual[key].errors.length === 1)) {
628
+ return result;
629
+ }
630
+ }
631
+ const importSource = error.data.importSource;
632
+ if (!result.hasOwnProperty(importSource)) {
633
+ result[importSource] = [];
634
+ }
635
+ result[importSource].push({
636
+ key,
637
+ ...error
638
+ });
639
+ return result;
640
+ }, {});
641
+ for (const [importSource, errorList] of Object.entries(groupedErrorList)) {
642
+ const autoFixers = [];
643
+ // appliedErrorsForImport will contain all the errors FOR A SINGLE IMPORT and will be merged into errorListForReport
644
+ const appliedErrorsForImport = [];
645
+ // Loop over auto errors for a single import source
646
+ for (const [_, error] of errorList.entries()) {
647
+ var _legacyIconImports$er, _legacyIconImports$er2, _migrationIconImports;
648
+ const {
649
+ key
650
+ } = error;
651
+ const node = 'node' in error ? error.node : null;
652
+ // Check if there is a manual error for the same import source somewhere else in the same file
653
+ // If that is the case we'll need to provide a suggestion instead of auto-fixing as the suggestion will
654
+ // add another import without removing the old import and this needs to be validated
655
+ const isInManualArray = allManualErrorSources.has(importSource);
656
+ // New icon name for renaming if the icon is in the manual array
657
+ 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);
658
+ if (!node) {
659
+ continue;
660
+ }
661
+ const guidanceMessage = guidance.hasOwnProperty(key) ? guidance[key] : '';
662
+ if (Object.keys(error).includes('data') && error.data) {
663
+ error.data.guidance = guidanceMessage;
664
+ }
665
+ const fixArguments = error.data ? {
666
+ metadata: error.data,
667
+ legacyImportNode: (_legacyIconImports$er2 = legacyIconImports[error.data.iconName]) === null || _legacyIconImports$er2 === void 0 ? void 0 : _legacyIconImports$er2.importNode,
668
+ migrationImportNode: (_migrationIconImports = migrationIconImports[error.data.iconName]) === null || _migrationIconImports === void 0 ? void 0 : _migrationIconImports.importNode,
669
+ shouldUseMigrationPath,
670
+ newIconName: isInManualArray ? newIconName : undefined
671
+ } : null;
672
+ if (!error.data || shouldUseMigrationPath && !checkIfNewIconExist(error) || !fixArguments) {
673
+ continue;
674
+ }
675
+ if (isInManualArray) {
676
+ // provide suggestion if there is a manual error for the same import source and thus the legacy import can't be removed
677
+ error.suggest = [{
678
+ desc: 'Rename icon import, import from the new package, and update props.',
679
+ fix: fixer => {
680
+ return [...createPropFixes({
681
+ ...fixArguments,
682
+ node,
683
+ fixer
684
+ }), ...createImportFix({
685
+ ...fixArguments,
686
+ fixer
687
+ })];
688
+ }
689
+ }];
690
+ } else {
691
+ // Update Guidance message for auto-fixing
692
+ if (error.data) {
693
+ 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.`;
694
+ }
695
+ // There should only be 1 import fix for each import source and thus only add this at the start of the list
696
+ if (autoFixers.length === 0) {
697
+ autoFixers.push(fixer => createImportFix({
698
+ ...fixArguments,
699
+ fixer
700
+ }));
701
+ }
702
+ // Push the prop fix regardless
703
+ autoFixers.push(fixer => createPropFixes({
704
+ ...fixArguments,
705
+ node,
706
+ fixer
707
+ }));
708
+ }
709
+ // Add the error to the appliedErrorsForImport, ready to be thrown later
710
+ appliedErrorsForImport.push(error);
711
+ }
712
+ // We want to have only 1 fix for each import source that is not in the manual array
713
+ // NOTE: If in the manual array, suggestions have been applied above and autoFixers.length will be 0 which will mean no fix is added
714
+ if (autoFixers.length > 0) {
715
+ // Add the fix to only one of the errors in the list of errors from the current import source
716
+ appliedErrorsForImport[0].fix = fixer => {
717
+ return autoFixers.flatMap(autoFixer => autoFixer(fixer));
718
+ };
719
+ }
720
+ // throw errors
721
+ appliedErrorsForImport.forEach(error => {
722
+ context.report(error);
723
+ });
724
+ }
540
725
  };
@@ -6,6 +6,7 @@ const rule = createLintRule({
6
6
  meta: {
7
7
  name: 'no-legacy-icons',
8
8
  fixable: 'code',
9
+ hasSuggestions: true,
9
10
  type: 'problem',
10
11
  docs: {
11
12
  description: 'Enforces no legacy icons are used.',
@@ -1,5 +1,6 @@
1
1
  const defaults = {
2
- failSilently: false
2
+ failSilently: false,
3
+ patterns: ['restricted-property', 'wrapped-token-value']
3
4
  };
4
5
  export const getConfig = overrides => {
5
6
  return Object.assign({}, defaults, overrides);
@@ -1,7 +1,8 @@
1
1
  import { createLintRule } from '../utils/create-rule';
2
2
  import { errorBoundary } from '../utils/error-boundary';
3
3
  import { getConfig } from './config';
4
- import { BannedProperty } from './linters';
4
+ import { RestrictedProperty, WrappedTokenValue } from './linters';
5
+ const typescriptErrorMessage = 'There is ongoing work to make this a TypeScript error. Once that happens, you will have to delete/refactor anyway.';
5
6
  const rule = createLintRule({
6
7
  meta: {
7
8
  name: 'use-latest-xcss-syntax-typography',
@@ -14,17 +15,29 @@ const rule = createLintRule({
14
15
  severity: 'warn'
15
16
  },
16
17
  messages: {
17
- noUnsafeTypographyProperties: `Don't set '{{ property }}' on xcss. They are unsafe as they allow invalid combinations of typography tokens. There is ongoing work to make this a TypeScript error. Once that happens, you will have to delete/refactor anyway.`
18
+ noRestrictedTypographyProperties: `Don't set '{{ property }}' on xcss as it allows invalid combinations of typography tokens. ${typescriptErrorMessage}`,
19
+ noRestrictedTypographyPropertiesHeading: `Don't set '{{ property }}' on xcss in combination with 'font' heading tokens. ${typescriptErrorMessage}`,
20
+ noWrappedTokenTypographyValues: `Don't wrap typography tokens in xcss. ${typescriptErrorMessage}`
18
21
  }
19
22
  },
20
23
  create(context) {
21
24
  const config = getConfig(context.options[0]);
22
25
  return errorBoundary({
23
- 'CallExpression[callee.name="xcss"] ObjectExpression > Property > Identifier[name=/(fontSize|lineHeight|fontWeight|letterSpacing)/]': node => BannedProperty.lint(node, {
24
- context
26
+ 'CallExpression[callee.name="xcss"] ObjectExpression > Property > Identifier[name=/(fontSize|lineHeight|fontWeight|letterSpacing)/]': node => RestrictedProperty.lint(node, {
27
+ context,
28
+ config
25
29
  }),
26
- 'CallExpression[callee.name="xcss"] ObjectExpression > Property > Literal[value=/(fontSize|lineHeight|fontWeight|letterSpacing)/]': node => BannedProperty.lint(node, {
27
- context
30
+ 'CallExpression[callee.name="xcss"] ObjectExpression > Property > Literal[value=/(fontSize|lineHeight|fontWeight|letterSpacing)/]': node => RestrictedProperty.lint(node, {
31
+ context,
32
+ config
33
+ }),
34
+ 'CallExpression[callee.name="xcss"] ObjectExpression > Property > Identifier[name=/(font|fontFamily|fontWeight)/]': node => WrappedTokenValue.lint(node, {
35
+ context,
36
+ config
37
+ }),
38
+ 'CallExpression[callee.name="xcss"] ObjectExpression > Property > Literal[value=/(font|fontFamily|fontWeight)/]': node => WrappedTokenValue.lint(node, {
39
+ context,
40
+ config
28
41
  })
29
42
  }, config);
30
43
  }
@@ -1 +1,2 @@
1
- export { BannedProperty } from './banned-property';
1
+ export { RestrictedProperty } from './restricted-property';
2
+ export { WrappedTokenValue } from './wrapped-token-value';
@@ -0,0 +1,61 @@
1
+ /* eslint-disable @repo/internal/react/require-jsdoc */
2
+
3
+ import { isNodeOfType } from 'eslint-codemod-utils';
4
+ export const RestrictedProperty = {
5
+ lint(node, {
6
+ context,
7
+ config
8
+ }) {
9
+ if (RestrictedProperty._check(node, {
10
+ context,
11
+ config
12
+ })) {
13
+ let property = 'fontSize, lineHeight, fontWeight or letterSpacing';
14
+ if (isNodeOfType(node, 'Identifier')) {
15
+ property = node.name;
16
+ } else if (isNodeOfType(node, 'Literal')) {
17
+ property = String(node.value);
18
+ }
19
+ context.report({
20
+ node,
21
+ messageId: property === 'fontWeight' ? 'noRestrictedTypographyPropertiesHeading' : 'noRestrictedTypographyProperties',
22
+ data: {
23
+ property
24
+ }
25
+ });
26
+ }
27
+ },
28
+ _check(node, {
29
+ config
30
+ }) {
31
+ if (!config.patterns.includes('restricted-property')) {
32
+ return false;
33
+ }
34
+
35
+ // Prevent font weight being used in combination with heading tokens
36
+ if (isNodeOfType(node, 'Identifier') && node.name === 'fontWeight' || isNodeOfType(node, 'Literal') && node.value === 'fontWeight') {
37
+ if (isNodeOfType(node.parent.parent, 'ObjectExpression')) {
38
+ for (const property of node.parent.parent.properties) {
39
+ var _property$value$value;
40
+ // Only looking for heading token on `font` property
41
+ const isFontProperty = isNodeOfType(property, 'Property') && (isNodeOfType(property.key, 'Literal') && property.key.value === 'font' || isNodeOfType(property.key, 'Identifier') && property.key.name === 'font');
42
+ if (!isFontProperty) {
43
+ continue;
44
+ }
45
+
46
+ // Checking for heading token string, for example xcss({ font: 'font.heading.medium' })
47
+ if (isNodeOfType(property.value, 'Literal') && typeof property.value.value === 'string' && (_property$value$value = property.value.value) !== null && _property$value$value !== void 0 && _property$value$value.startsWith('font.heading')) {
48
+ return true;
49
+ }
50
+
51
+ // Checking for wrapped heading token, for example xcss({ font: token('font.heading.medium') })
52
+ if (isNodeOfType(property.value, 'CallExpression') && isNodeOfType(property.value.callee, 'Identifier') && property.value.callee.name === 'token' && isNodeOfType(property.value.arguments[0], 'Literal') && typeof property.value.arguments[0].value === 'string' && property.value.arguments[0].value.startsWith('font.heading')) {
53
+ return true;
54
+ }
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+ return true;
60
+ }
61
+ };
@@ -0,0 +1,44 @@
1
+ /* eslint-disable @repo/internal/react/require-jsdoc */
2
+
3
+ import { isNodeOfType } from 'eslint-codemod-utils';
4
+ const messageId = 'noWrappedTokenTypographyValues';
5
+ export const WrappedTokenValue = {
6
+ lint(node, {
7
+ context,
8
+ config
9
+ }) {
10
+ if (WrappedTokenValue._check(node, {
11
+ context,
12
+ config
13
+ })) {
14
+ context.report({
15
+ node,
16
+ messageId,
17
+ fix: WrappedTokenValue._fix(node)
18
+ });
19
+ }
20
+ },
21
+ _check(node, {
22
+ config
23
+ }) {
24
+ if (!config.patterns.includes('wrapped-token-value')) {
25
+ return false;
26
+ }
27
+ if (isNodeOfType(node.parent, 'Property') && isNodeOfType(node.parent.value, 'CallExpression') && isNodeOfType(node.parent.value.callee, 'Identifier') && node.parent.value.callee.name === 'token' && node.parent.value.arguments.length >= 1) {
28
+ return true;
29
+ }
30
+ return false;
31
+ },
32
+ _fix(node) {
33
+ return fixer => {
34
+ let wrappedTokenFix;
35
+ if (isNodeOfType(node.parent, 'Property') && isNodeOfType(node.parent.value, 'CallExpression') && node.parent.value.arguments.length >= 1) {
36
+ const firstArg = node.parent.value.arguments[0];
37
+ if (isNodeOfType(firstArg, 'Literal') && typeof firstArg.value === 'string') {
38
+ wrappedTokenFix = fixer.replaceText(node.parent.value, `'${firstArg.value}'`);
39
+ }
40
+ }
41
+ return [wrappedTokenFix].filter(fix => Boolean(fix)); // Some of the transformers can return arrays with undefined, so filter them out
42
+ };
43
+ }
44
+ };