@atlaskit/eslint-plugin-design-system 8.32.2 → 8.34.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 (82) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -0
  3. package/constellation/ensure-design-token-usage/usage.mdx +2 -2
  4. package/constellation/index/usage.mdx +1 -0
  5. package/constellation/use-tokens-typography/usage.mdx +42 -0
  6. package/dist/cjs/ast-nodes/index.js +7 -0
  7. package/dist/cjs/ast-nodes/object-entry.js +27 -0
  8. package/dist/cjs/ast-nodes/object.js +1 -1
  9. package/dist/cjs/presets/all.codegen.js +2 -1
  10. package/dist/cjs/rules/consistent-css-prop-usage/index.js +7 -0
  11. package/dist/cjs/rules/ensure-design-token-usage/index.js +5 -4
  12. package/dist/cjs/rules/ensure-design-token-usage/rule-meta.js +1 -1
  13. package/dist/cjs/rules/ensure-design-token-usage/spacing.js +5 -1
  14. package/dist/cjs/rules/ensure-design-token-usage/utils.js +52 -42
  15. package/dist/cjs/rules/index.codegen.js +3 -1
  16. package/dist/cjs/rules/use-tokens-typography/config/index.js +26 -0
  17. package/dist/cjs/rules/use-tokens-typography/error-boundary.js +24 -0
  18. package/dist/cjs/rules/use-tokens-typography/index.js +44 -0
  19. package/dist/cjs/rules/use-tokens-typography/transformers/style-object.js +212 -0
  20. package/dist/cjs/rules/use-tokens-typography/utils.js +146 -0
  21. package/dist/es2019/ast-nodes/index.js +1 -0
  22. package/dist/es2019/ast-nodes/object-entry.js +22 -0
  23. package/dist/es2019/ast-nodes/object.js +1 -1
  24. package/dist/es2019/presets/all.codegen.js +2 -1
  25. package/dist/es2019/rules/consistent-css-prop-usage/index.js +5 -0
  26. package/dist/es2019/rules/ensure-design-token-usage/index.js +6 -5
  27. package/dist/es2019/rules/ensure-design-token-usage/rule-meta.js +1 -1
  28. package/dist/es2019/rules/ensure-design-token-usage/spacing.js +5 -1
  29. package/dist/es2019/rules/ensure-design-token-usage/utils.js +42 -38
  30. package/dist/es2019/rules/index.codegen.js +3 -1
  31. package/dist/es2019/rules/use-tokens-typography/config/index.js +20 -0
  32. package/dist/es2019/rules/use-tokens-typography/error-boundary.js +19 -0
  33. package/dist/es2019/rules/use-tokens-typography/index.js +36 -0
  34. package/dist/es2019/rules/use-tokens-typography/transformers/style-object.js +209 -0
  35. package/dist/es2019/rules/use-tokens-typography/utils.js +99 -0
  36. package/dist/esm/ast-nodes/index.js +1 -0
  37. package/dist/esm/ast-nodes/object-entry.js +22 -0
  38. package/dist/esm/ast-nodes/object.js +1 -1
  39. package/dist/esm/presets/all.codegen.js +2 -1
  40. package/dist/esm/rules/consistent-css-prop-usage/index.js +7 -0
  41. package/dist/esm/rules/ensure-design-token-usage/index.js +6 -5
  42. package/dist/esm/rules/ensure-design-token-usage/rule-meta.js +1 -1
  43. package/dist/esm/rules/ensure-design-token-usage/spacing.js +5 -1
  44. package/dist/esm/rules/ensure-design-token-usage/utils.js +46 -38
  45. package/dist/esm/rules/index.codegen.js +3 -1
  46. package/dist/esm/rules/use-tokens-typography/config/index.js +20 -0
  47. package/dist/esm/rules/use-tokens-typography/error-boundary.js +18 -0
  48. package/dist/esm/rules/use-tokens-typography/index.js +38 -0
  49. package/dist/esm/rules/use-tokens-typography/transformers/style-object.js +206 -0
  50. package/dist/esm/rules/use-tokens-typography/utils.js +129 -0
  51. package/dist/types/ast-nodes/index.d.ts +1 -0
  52. package/dist/types/ast-nodes/object-entry.d.ts +6 -0
  53. package/dist/types/ast-nodes/object.d.ts +1 -1
  54. package/dist/types/index.codegen.d.ts +1 -0
  55. package/dist/types/presets/all.codegen.d.ts +2 -1
  56. package/dist/types/rules/ensure-design-token-usage/types.d.ts +1 -1
  57. package/dist/types/rules/ensure-design-token-usage/utils.d.ts +22 -22
  58. package/dist/types/rules/index.codegen.d.ts +1 -0
  59. package/dist/types/rules/use-tokens-typography/config/index.d.ts +6 -0
  60. package/dist/types/rules/use-tokens-typography/error-boundary.d.ts +11 -0
  61. package/dist/types/rules/use-tokens-typography/index.d.ts +3 -0
  62. package/dist/types/rules/use-tokens-typography/transformers/style-object.d.ts +31 -0
  63. package/dist/types/rules/use-tokens-typography/utils.d.ts +161 -0
  64. package/dist/types-ts4.5/ast-nodes/index.d.ts +1 -0
  65. package/dist/types-ts4.5/ast-nodes/object-entry.d.ts +6 -0
  66. package/dist/types-ts4.5/ast-nodes/object.d.ts +1 -1
  67. package/dist/types-ts4.5/index.codegen.d.ts +1 -0
  68. package/dist/types-ts4.5/presets/all.codegen.d.ts +2 -1
  69. package/dist/types-ts4.5/rules/ensure-design-token-usage/types.d.ts +1 -1
  70. package/dist/types-ts4.5/rules/ensure-design-token-usage/utils.d.ts +22 -22
  71. package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -0
  72. package/dist/types-ts4.5/rules/use-tokens-typography/config/index.d.ts +6 -0
  73. package/dist/types-ts4.5/rules/use-tokens-typography/error-boundary.d.ts +11 -0
  74. package/dist/types-ts4.5/rules/use-tokens-typography/index.d.ts +3 -0
  75. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/style-object.d.ts +31 -0
  76. package/dist/types-ts4.5/rules/use-tokens-typography/utils.d.ts +161 -0
  77. package/package.json +1 -1
  78. package/dist/cjs/rules/ensure-design-token-usage/typography.js +0 -39
  79. package/dist/es2019/rules/ensure-design-token-usage/typography.js +0 -19
  80. package/dist/esm/rules/ensure-design-token-usage/typography.js +0 -33
  81. package/dist/types/rules/ensure-design-token-usage/typography.d.ts +0 -9
  82. package/dist/types-ts4.5/rules/ensure-design-token-usage/typography.d.ts +0 -9
@@ -391,6 +391,13 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
391
391
  case 'CallExpression':
392
392
  case 'TaggedTemplateExpression':
393
393
  case 'TemplateLiteral':
394
+ if (expression.type === 'CallExpression' && expression.callee.type === 'Identifier' && expression.callee.name === 'cx') {
395
+ expression.arguments.forEach(function (exp) {
396
+ return exp && _this3.traverseExpression(exp);
397
+ });
398
+ return;
399
+ }
400
+
394
401
  // We've found elements that shouldn't be here! Report an error.
395
402
  this.context.report({
396
403
  node: expression,
@@ -9,7 +9,7 @@ import { lintJSXIdentifierForColor, lintJSXLiteralForColor, lintJSXMemberForColo
9
9
  import { errorBoundary } from './error-boundary';
10
10
  import ruleMeta from './rule-meta';
11
11
  import { lintObjectForSpacing } from './spacing';
12
- import { convertHyphenatedNameToCamelCase, emToPixels, getDomainsForProperty, getFontSizeFromNode, getFontSizeValueInScope, getTokenReplacement, getValueFromShorthand, getValueFromTemplateLiteralRaw, includesTokenString, insertTokensImport, isAuto, isCalc, isTokenValueString, isValidSpacingValue, isZero, processCssNode, splitShorthandValues } from './utils';
12
+ import { convertHyphenatedNameToCamelCase, emToPixels, getDomainsForProperty, getFontSizeValueInScope, getPropertyNodeFromParent, getTokenReplacement, getValueForPropertyNode, getValueFromShorthand, getValueFromTemplateLiteralRaw, includesTokenString, insertTokensImport, isAuto, isCalc, isTokenValueString, isValidSpacingValue, isZero, processCssNode, splitShorthandValues } from './utils';
13
13
  var defaultConfig = {
14
14
  domains: ['color', 'spacing'],
15
15
  applyImport: true,
@@ -104,11 +104,12 @@ var createWithConfig = function createWithConfig(initialConfig) {
104
104
  if (domains.includes('color')) {
105
105
  return lintObjectForColor(node, context, config);
106
106
  }
107
- if (domains.includes('spacing') || domains.includes('shape') || domains.includes('typography')) {
107
+ if (domains.includes('spacing') || domains.includes('shape')) {
108
108
  /**
109
- * We do this in case the fontSize for a style object is declared alongside the `em` or `lineHeight` declaration
109
+ * We do this in case the fontSize for a style object is declared alongside the `em` or `lineHeight` declaration.
110
110
  */
111
- var fontSize = getFontSizeFromNode(parentNode, context);
111
+ var fontSizeNode = getPropertyNodeFromParent('fontSize', parentNode);
112
+ var fontSize = fontSizeNode && getValueForPropertyNode(fontSizeNode, context);
112
113
  return lintObjectForSpacing(node, context, config, fontSize, tokenNode);
113
114
  }
114
115
  }
@@ -166,7 +167,7 @@ var createWithConfig = function createWithConfig(initialConfig) {
166
167
  return currentSource;
167
168
  }
168
169
  }
169
- if (domains.includes('spacing') || domains.includes('typography') || domains.includes('shape')) {
170
+ if (domains.includes('spacing') || domains.includes('shape')) {
170
171
  if (!isValidSpacingValue(resolvedCssValues, globalFontSize)) {
171
172
  // no changes should be made to the current property
172
173
  return currentSource;
@@ -1,4 +1,4 @@
1
- var domainsToLint = ['color', 'spacing', 'typography', 'shape'];
1
+ var domainsToLint = ['color', 'spacing', 'shape'];
2
2
  var ruleMeta = {
3
3
  name: 'ensure-design-token-usage',
4
4
  hasSuggestions: true,
@@ -69,7 +69,8 @@ export var lintObjectForSpacing = function lintObjectForSpacing(node, context, r
69
69
  payload: "".concat(propertyName, ":").concat(pixelValue)
70
70
  },
71
71
  fix: function fix(fixer) {
72
- var replacementNode = getTokenReplacement(propertyName, pixelValue);
72
+ // Casting due to possibility of pixelValue being string | number from emToPixels
73
+ var replacementNode = pixelValue && getTokenReplacement(propertyName, pixelValue);
73
74
  if (!replacementNode) {
74
75
  return null;
75
76
  }
@@ -116,6 +117,8 @@ export var lintObjectForSpacing = function lintObjectForSpacing(node, context, r
116
117
  if (!allResolvableValues) {
117
118
  return null;
118
119
  }
120
+
121
+ // Casting due to possibility of value being string | number
119
122
  var valuesWithTokenReplacement = valuesForProperty.filter(function (value) {
120
123
  return findTokenNameByPropertyValue(propertyName, value);
121
124
  }).filter(function (value) {
@@ -149,6 +152,7 @@ export var lintObjectForSpacing = function lintObjectForSpacing(node, context, r
149
152
  var pixelValueString = "".concat(pixelValue, "px");
150
153
  // if there is a token we take it, otherwise we go with the original value
151
154
 
155
+ // Casting due to possibility of value being string | number
152
156
  return findTokenNameByPropertyValue(propertyName, value) ? "${".concat(getTokenNodeForValue(propertyName, pixelValueString), "}") : originalValues[index];
153
157
  }).join(' '), "`"))]);
154
158
  }
@@ -6,7 +6,6 @@ import { spacing as spacingScale } from '@atlaskit/tokens/tokens-raw';
6
6
  import { findIdentifierInParentScope } from '../utils/find-in-parent';
7
7
  import { isColorCssPropertyName, isCurrentSurfaceCustomPropertyName } from '../utils/is-color';
8
8
  import { borderWidthValueToToken, isBorderRadius, isBorderSizeProperty, isShapeProperty, radiusValueToToken } from './shape';
9
- import { isCodeFontFamily, isFontFamily, isFontSize, isFontSizeSmall, isTypographyProperty, typographyValueToToken } from './typography';
10
9
  var properties = ['padding', 'paddingBlock', 'paddingInline', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingInline', 'paddingInlineStart', 'paddingInlineEnd', 'paddingBlock', 'paddingBlockStart', 'paddingBlockEnd', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 'marginInline', 'marginInlineStart', 'marginInlineEnd', 'marginBlock', 'marginBlockStart', 'marginBlockEnd', 'margin', 'gap', 'rowGap', 'gridRowGap', 'columnGap', 'gridColumnGap', 'top', 'left', 'right', 'bottom', 'inlineStart', 'inlineEnd', 'blockStart', 'blockEnd', 'outline-offset'];
11
10
  var spacingValueToToken = Object.fromEntries(spacingScale.map(function (token) {
12
11
  return [token.value, token.cleanName];
@@ -34,8 +33,10 @@ export var splitShorthandValues = function splitShorthandValues(str) {
34
33
  };
35
34
  export var getValueFromShorthand = function getValueFromShorthand(str) {
36
35
  var valueString = String(str);
37
- var fontFamily = /(sans-serif$)|(monospace$)/;
38
- if (fontFamily.test(valueString)) {
36
+ var fontFamily = /(Charlie)|(sans-serif$)|(monospace$)/;
37
+ var fontWeightString = /(regular$)|(medium$)|(semibold$)|(bold$)/;
38
+ var fontStyleString = /(inherit$)|(normal$)|(italic$)/;
39
+ if (fontFamily.test(valueString) || fontWeightString.test(valueString) || fontStyleString.test(valueString)) {
39
40
  return [valueString];
40
41
  }
41
42
  // If we want to filter out NaN just add .filter(Boolean)
@@ -64,6 +65,12 @@ var getRawExpressionForToken = function getRawExpressionForToken(node, context)
64
65
  }).join(', '), ")}");
65
66
  return call;
66
67
  };
68
+ var isFontSize = function isFontSize(node) {
69
+ return isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && (node.callee.name === 'fontSize' || node.callee.name === 'getFontSize');
70
+ };
71
+ var isFontSizeSmall = function isFontSizeSmall(node) {
72
+ return isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && node.callee.name === 'fontSizeSmall';
73
+ };
67
74
  var getValueFromCallExpression = function getValueFromCallExpression(node, context) {
68
75
  if (!isNodeOfType(node, 'CallExpression')) {
69
76
  return null;
@@ -80,12 +87,6 @@ var getValueFromCallExpression = function getValueFromCallExpression(node, conte
80
87
  if (isFontSizeSmall(node)) {
81
88
  return 11;
82
89
  }
83
- if (isFontFamily(node)) {
84
- return "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif";
85
- }
86
- if (isCodeFontFamily(node)) {
87
- return "'SFMono-Medium', 'SF Mono', 'Segoe UI Mono', 'Roboto Mono', 'Ubuntu Mono', Menlo, Consolas, Courier, monospace";
88
- }
89
90
  if (isToken(node)) {
90
91
  return getRawExpressionForToken(node, context);
91
92
  }
@@ -290,7 +291,7 @@ export var convertHyphenatedNameToCamelCase = function convertHyphenatedNameToCa
290
291
 
291
292
  /**
292
293
  * @param node
293
- * @returns The furthest parent node that is on the same line as the input node
294
+ * @returns The furthest parent node that is on the same line as the input node.
294
295
  */
295
296
  export var findParentNodeForLine = function findParentNodeForLine(node) {
296
297
  var _node$loc, _node$parent$loc;
@@ -305,14 +306,13 @@ export var findParentNodeForLine = function findParentNodeForLine(node) {
305
306
  };
306
307
 
307
308
  /**
308
- * Returns an array of domains that are relevant to the provided property based on the rule options
309
+ * Returns an array of domains that are relevant to the provided property based on the rule options.
309
310
  * @param propertyName camelCase CSS property
310
- * @param targetOptions Array containing the types of properties that should be included in the rule
311
+ * @param targetOptions Array containing the types of properties that should be included in the rule.
311
312
  * @example
312
313
  * ```
313
314
  * propertyName: padding, targetOptions: ['spacing'] -> returns ['spacing']
314
- * propertyName: fontWeight, targetOptions: ['spacing', 'typography'] -> returns ['typography']
315
- * propertyName: backgroundColor, targetOptions: ['spacing', 'typography'] -> returns []
315
+ * propertyName: backgroundColor, targetOptions: ['spacing'] -> returns []
316
316
  * propertyName: backgroundColor, targetOptions: ['color', 'spacing'] -> returns ['color']
317
317
  * ```
318
318
  */
@@ -327,15 +327,12 @@ export function getDomainsForProperty(propertyName, targetOptions) {
327
327
  if (isShapeProperty(propertyName) && targetOptions.includes('shape')) {
328
328
  domains.push('shape');
329
329
  }
330
- if (isTypographyProperty(propertyName) && targetOptions.includes('typography')) {
331
- domains.push('typography');
332
- }
333
330
  return domains;
334
331
  }
335
332
 
336
333
  /**
337
334
  * Function that removes JS comments from a string of code,
338
- * sometimes makers will have single or multiline comments in their tagged template literals styles, this can mess with our parsing logic
335
+ * sometimes makers will have single or multiline comments in their tagged template literals styles, this can mess with our parsing logic.
339
336
  */
340
337
  export function cleanComments(str) {
341
338
  return str.replace(/\/\*([\s\S]*?)\*\//g, '').replace(/\/\/(.*)/g, '');
@@ -343,11 +340,11 @@ export function cleanComments(str) {
343
340
 
344
341
  /**
345
342
  * Returns an array of tuples representing a processed css within `TaggedTemplateExpression` node.
346
- * each element of the array is a tuple `[string, string]`,
343
+ * Each element of the array is a tuple `[string, string]`,
347
344
  * where the first element is the processed css line with computed values
348
- * and the second element of the tuple is the original css line from source
349
- * @param node TaggedTemplateExpression node
350
- * @param context Rule.RuleContext
345
+ * and the second element of the tuple is the original css line from source.
346
+ * @param node TaggedTemplateExpression node.
347
+ * @param context Rule.RuleContext.
351
348
  * @example
352
349
  * ```
353
350
  * `[['padding: 8', 'padding: ${gridSize()}'], ['margin: 6', 'margin: 6px' ]]`
@@ -422,7 +419,7 @@ export function getFontSizeValueInScope(cssProperties) {
422
419
 
423
420
  /**
424
421
  * Attempts to remove all non-essential words & characters from a style block.
425
- * Including selectors and queries
422
+ * Including selectors and queries.
426
423
  * @param styleString string of css properties
427
424
  */
428
425
  export function splitCssProperties(styleString) {
@@ -447,8 +444,8 @@ export function splitCssProperties(styleString) {
447
444
  }
448
445
 
449
446
  /**
450
- * returns whether the current string is a token value
451
- * @param originalVaue string representing a css property value e.g 1em, 12px
447
+ * Returns whether the current string is a token value.
448
+ * @param originalVaue string representing a css property value e.g 1em, 12px.
452
449
  */
453
450
  export function isTokenValueString(originalValue) {
454
451
  return originalValue.startsWith('${token(') && originalValue.endsWith('}');
@@ -462,19 +459,27 @@ export function includesTokenString(originalValue) {
462
459
  *
463
460
  * -> for pixels this '8px'
464
461
  * -> for weights '400'
465
- * -> for family 'Arial'
462
+ * -> for family 'Arial'.
466
463
  *
467
464
  * @internal
468
465
  */
469
466
  export function normaliseValue(propertyName, value) {
470
- var isFontWeightOrFamily = /fontWeight|fontFamily/.test(propertyName);
467
+ var isFontStringProperty = /fontWeight|fontFamily|fontStyle/.test(propertyName);
468
+ var isLineHeight = /lineHeight/.test(propertyName);
471
469
  var propertyValue = typeof value === 'string' ? value.trim() : value;
472
- var lookupValue = isFontWeightOrFamily ? propertyValue : typeof propertyValue === 'string' ? propertyValue : "".concat(propertyValue, "px");
470
+ var lookupValue;
471
+ if (isFontStringProperty) {
472
+ lookupValue = "".concat(propertyValue);
473
+ } else if (isLineHeight) {
474
+ lookupValue = value === 1 ? "".concat(propertyValue) : "".concat(propertyValue, "px");
475
+ } else {
476
+ lookupValue = typeof propertyValue === 'string' ? propertyValue : "".concat(propertyValue, "px");
477
+ }
473
478
  return lookupValue;
474
479
  }
475
480
  export function findTokenNameByPropertyValue(propertyName, value) {
476
481
  var lookupValue = normaliseValue(propertyName, value);
477
- var tokenName = isShapeProperty(propertyName) ? isBorderSizeProperty(propertyName) ? borderWidthValueToToken[lookupValue] : radiusValueToToken[lookupValue] : isTypographyProperty(propertyName) ? typographyValueToToken[propertyName][lookupValue] : spacingValueToToken[lookupValue];
482
+ var tokenName = isShapeProperty(propertyName) ? isBorderSizeProperty(propertyName) ? borderWidthValueToToken[lookupValue] : radiusValueToToken[lookupValue] : spacingValueToToken[lookupValue];
478
483
  if (!tokenName) {
479
484
  return undefined;
480
485
  }
@@ -483,9 +488,9 @@ export function findTokenNameByPropertyValue(propertyName, value) {
483
488
 
484
489
  /**
485
490
  * Returns a stringifiable node with the token expression corresponding to its matching token.
486
- * if no token found for the pair the function returns undefined
487
- * @param propertyName string camelCased css property
488
- * @param value the computed value e.g '8px' -> '8'
491
+ * If no token found for the pair the function returns undefined.
492
+ * @param propertyName string camelCased css property.
493
+ * @param value The computed value e.g '8px' -> '8'.
489
494
  */
490
495
  export function getTokenReplacement(propertyName, value) {
491
496
  var tokenName = findTokenNameByPropertyValue(propertyName, value);
@@ -495,17 +500,20 @@ export function getTokenReplacement(propertyName, value) {
495
500
  var fallbackValue = normaliseValue(propertyName, value);
496
501
  return getTokenNodeForValue(propertyName, fallbackValue);
497
502
  }
498
- export function getFontSizeFromNode(parentNode, context) {
499
- var fontSizeNode = parentNode.properties.find(function (node) {
503
+ export function getPropertyNodeFromParent(property, parentNode) {
504
+ var propertyNode = parentNode.properties.find(function (node) {
500
505
  if (!isNodeOfType(node, 'Property')) {
501
506
  return;
502
507
  }
503
508
  if (!isNodeOfType(node.key, 'Identifier')) {
504
509
  return;
505
510
  }
506
- return node.key.name === 'fontSize';
511
+ return node.key.name === property;
507
512
  });
508
- var fontSizeValue = isNodeOfType(fontSizeNode, 'Property') ? getValue(fontSizeNode.value, context) : null;
509
- var fontSize = Array.isArray(fontSizeValue) ? fontSizeValue[0] : fontSizeValue;
510
- return fontSize;
513
+ return propertyNode;
514
+ }
515
+ export function getValueForPropertyNode(propertyNode, context) {
516
+ var propertyValueRaw = isNodeOfType(propertyNode, 'Property') ? getValue(propertyNode.value, context) : null;
517
+ var propertyValue = Array.isArray(propertyValueRaw) ? propertyValueRaw[0] : propertyValueRaw;
518
+ return propertyValue;
511
519
  }
@@ -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::23062a8759ba919facf30a402e5546bd>>
3
+ * @codegen <<SignedSource::c283cd7ede5e813a9119cd707d339273>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import consistentCssPropUsage from './consistent-css-prop-usage';
@@ -32,6 +32,7 @@ import useHeadingLevelInSpotlightCard from './use-heading-level-in-spotlight-car
32
32
  import useHrefInLinkItem from './use-href-in-link-item';
33
33
  import usePrimitives from './use-primitives';
34
34
  import usePrimitivesText from './use-primitives-text';
35
+ import useTokensTypography from './use-tokens-typography';
35
36
  import useVisuallyHidden from './use-visually-hidden';
36
37
  export default {
37
38
  'consistent-css-prop-usage': consistentCssPropUsage,
@@ -63,5 +64,6 @@ export default {
63
64
  'use-href-in-link-item': useHrefInLinkItem,
64
65
  'use-primitives': usePrimitives,
65
66
  'use-primitives-text': usePrimitivesText,
67
+ 'use-tokens-typography': useTokensTypography,
66
68
  'use-visually-hidden': useVisuallyHidden
67
69
  };
@@ -0,0 +1,20 @@
1
+ export var ruleSchema = {
2
+ type: 'array',
3
+ items: {
4
+ type: 'object',
5
+ properties: {
6
+ failSilently: {
7
+ type: 'boolean'
8
+ }
9
+ }
10
+ }
11
+ };
12
+ var defaultConfig = {
13
+ failSilently: false
14
+ };
15
+ export var getConfig = function getConfig(overrides) {
16
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
17
+ // start with an empty object, then merge in the defaults, then merge in overrides.
18
+ // The empty object is returned, as well as modified in place
19
+ return Object.assign({}, defaultConfig, overrides);
20
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ESLint rules should NEVER throw exceptions, because that breaks the VSCode ESLint server
3
+ * (and probably the IntelliJ one too), which causes linting to fail in a file.
4
+ *
5
+ * It also breaks CI, which was the reason this error boundary was added. It's a final
6
+ * catch all.
7
+ */
8
+ export var errorBoundary = function errorBoundary(func, _ref) {
9
+ var config = _ref.config;
10
+ try {
11
+ func();
12
+ } catch (err) {
13
+ if (!config.failSilently) {
14
+ // eslint-disable-next-line no-console
15
+ console.warn(err);
16
+ }
17
+ }
18
+ };
@@ -0,0 +1,38 @@
1
+ import { createLintRule } from '../utils/create-rule';
2
+ import { getConfig, ruleSchema } from './config';
3
+ import { errorBoundary } from './error-boundary';
4
+ import { StyleObject } from './transformers/style-object';
5
+ var create = function create(context) {
6
+ var config = getConfig(context.options[0]);
7
+ return {
8
+ // const styles = css({ fontSize: '14px, ... }), styled.div({ fontSize: 14, ... })
9
+ ObjectExpression: function ObjectExpression(node) {
10
+ return errorBoundary(function () {
11
+ return StyleObject.lint(node, {
12
+ context: context
13
+ });
14
+ }, {
15
+ config: config
16
+ });
17
+ }
18
+ };
19
+ };
20
+ var rule = createLintRule({
21
+ meta: {
22
+ name: 'use-tokens-typography',
23
+ type: 'problem',
24
+ fixable: 'code',
25
+ hasSuggestions: true,
26
+ docs: {
27
+ description: 'Enforces usage of design tokens for typography properties rather than hard-coded values.',
28
+ recommended: false,
29
+ severity: 'warn'
30
+ },
31
+ messages: {
32
+ noRawTypographyValues: 'Typography primitives or tokens should be used instead of hard-coded values.\n\n@meta <<{{payload}}>>.'
33
+ },
34
+ schema: ruleSchema
35
+ },
36
+ create: create
37
+ });
38
+ export default rule;
@@ -0,0 +1,206 @@
1
+ /* eslint-disable @repo/internal/react/require-jsdoc */
2
+
3
+ import { isNodeOfType } from 'eslint-codemod-utils';
4
+ import { Object as ASTObject, ObjectEntry, Root } from '../../../ast-nodes';
5
+ import { getValueForPropertyNode, insertTokensImport, normaliseValue } from '../../ensure-design-token-usage/utils';
6
+ import { isDecendantOfGlobalToken, isDecendantOfStyleBlock, isDecendantOfType } from '../../utils/is-node';
7
+ import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, isValidPropertyNode, notUndefined } from '../utils';
8
+ export var StyleObject = {
9
+ lint: function lint(node, _ref) {
10
+ var context = _ref.context;
11
+ // To force the correct node type
12
+ if (!isNodeOfType(node, 'ObjectExpression')) {
13
+ return {
14
+ success: false
15
+ };
16
+ }
17
+
18
+ // Check whether all criteria needed to make a transformation are met
19
+ var _StyleObject$_check = StyleObject._check(node, {
20
+ context: context
21
+ }),
22
+ success = _StyleObject$_check.success,
23
+ refs = _StyleObject$_check.refs;
24
+ if (!success || !refs) {
25
+ return;
26
+ }
27
+ var fontSizeNode = refs.fontSizeNode,
28
+ fontSizeRaw = refs.fontSizeRaw,
29
+ tokensImportNode = refs.tokensImportNode;
30
+ var fontSizeValue = normaliseValue('fontSize', fontSizeRaw);
31
+
32
+ // -- Font weight --
33
+ var fontWeightNode = ASTObject.getEntryByPropertyName(node, 'fontWeight');
34
+ var fontWeightRaw = fontWeightNode && getValueForPropertyNode(fontWeightNode, context);
35
+
36
+ // If no fontWeight value exists, default to 400 to avoid matching with a bolder token resulting in a visual change
37
+ var fontWeightValue = fontWeightRaw && normaliseValue('fontWeight', fontWeightRaw) || defaultFontWeight;
38
+ fontWeightValue = fontWeightValue.length === 3 ? fontWeightValue : fontWeightMap[fontWeightValue] || defaultFontWeight;
39
+
40
+ // -- Line height --
41
+ var lineHeightNode = ASTObject.getEntryByPropertyName(node, 'lineHeight');
42
+ var lineHeightRaw = lineHeightNode && getValueForPropertyNode(lineHeightNode, context);
43
+ var shouldAddFontWeight = false;
44
+ var lineHeightValue = lineHeightRaw && normaliseValue('lineHeight', lineHeightRaw) || undefined;
45
+ if (lineHeightValue === fontSizeValue) {
46
+ lineHeightValue = '1';
47
+ }
48
+
49
+ // -- Match tokens --
50
+ var matchingTokens = findTypographyTokenForValues(fontSizeValue, lineHeightValue);
51
+ if (matchingTokens.length) {
52
+ // If we have multiple matching tokens, try matching fontWeight
53
+ var matchingTokensWithWeight = matchingTokens.filter(function (token) {
54
+ return fontWeightValue ? token.values.fontWeight === fontWeightValue : token;
55
+ });
56
+ if (matchingTokensWithWeight.length) {
57
+ // Possibly narrowed down tokens
58
+ matchingTokens = matchingTokensWithWeight;
59
+ } else {
60
+ // Ended up with 0 matches by matching fontWeight
61
+ // return body token and add fontWeight manually
62
+ matchingTokens = matchingTokens.filter(function (token) {
63
+ return token.tokenName.includes('.body');
64
+ });
65
+ shouldAddFontWeight = true;
66
+ }
67
+ }
68
+
69
+ // Get other font-* nodes that we can replace/remove.
70
+ // These aren't needed for token matching.
71
+
72
+ // -- Font family --
73
+ var fontFamilyNode = ASTObject.getEntryByPropertyName(node, 'fontFamily');
74
+ var fontFamilyRaw = fontFamilyNode && getValueForPropertyNode(fontFamilyNode, context);
75
+ var fontFamilyValue = fontFamilyRaw && normaliseValue('fontFamily', fontFamilyRaw) || undefined;
76
+ var fontFamilyToAdd;
77
+ // If font family uses the Charlie font we can't replace; exit
78
+ if (fontFamilyValue) {
79
+ if (fontFamilyValue.toLowerCase().includes('charlie display')) {
80
+ fontFamilyToAdd = 'heading';
81
+ } else if (fontFamilyValue.toLowerCase().includes('charlie text')) {
82
+ fontFamilyToAdd = 'body';
83
+ }
84
+ } else {
85
+ // Font family node exists but we can't resolve its value
86
+ // Will need to re-add it below the font property to ensure it still applies
87
+ fontFamilyToAdd = fontFamilyNode ? 'original' : undefined;
88
+ }
89
+
90
+ // -- Font style --
91
+ var fontStyleNode = ASTObject.getEntryByPropertyName(node, 'fontStyle');
92
+ var fontStyleRaw = fontStyleNode && getValueForPropertyNode(fontStyleNode, context);
93
+ var fontStyleValue = fontStyleRaw && normaliseValue('fontStyle', fontStyleRaw) || undefined;
94
+ var fontStyleToAdd;
95
+ if (fontStyleValue === 'italic') {
96
+ fontStyleToAdd = 'italic';
97
+ }
98
+
99
+ // -- Letter spacing --
100
+ var letterSpacingNode = ASTObject.getEntryByPropertyName(node, 'letterSpacing');
101
+
102
+ // A single matching token
103
+ // TOOD: Maybe suggest options if > 1 matching token
104
+ if (matchingTokens.length === 1) {
105
+ var matchingToken = matchingTokens[0];
106
+
107
+ // fontSize node is always first
108
+ var nodesToReplace = [fontSizeNode, fontWeightNode, lineHeightNode, fontFamilyNode, fontStyleNode, letterSpacingNode].filter(notUndefined);
109
+ var fontFamilyTokenName = fontFamilyToAdd ? "font.family.brand.".concat(fontFamilyToAdd) : '';
110
+ var fontWeightReplacementToken = shouldAddFontWeight ? findFontWeightTokenForValue(fontWeightValue) : undefined;
111
+ var fontWeightReplacement = fontWeightReplacementToken && getTokenProperty('fontWeight', fontWeightReplacementToken.tokenName, fontWeightValue);
112
+ var fontFamilyReplacement = fontFamilyToAdd && (fontFamilyToAdd === 'original' ? convertPropertyNodeToStringableNode(
113
+ // This will always exist if fontFamilyToAdd === 'original', TS can't figure that out.
114
+ fontFamilyNode) : getTokenProperty('fontFamily', fontFamilyTokenName, findFontFamilyValueForToken(fontFamilyTokenName)));
115
+ var fontStyleReplacement = fontStyleToAdd && getLiteralProperty('fontStyle', fontStyleToAdd);
116
+ var fixerRefs = {
117
+ matchingToken: matchingToken,
118
+ nodesToReplace: nodesToReplace,
119
+ tokensImportNode: tokensImportNode,
120
+ fontWeightReplacement: fontWeightReplacement,
121
+ fontFamilyReplacement: fontFamilyReplacement,
122
+ fontStyleReplacement: fontStyleReplacement
123
+ };
124
+ context.report({
125
+ node: fontSizeNode,
126
+ messageId: 'noRawTypographyValues',
127
+ data: {
128
+ payload: "fontSize:".concat(fontSizeRaw)
129
+ },
130
+ fix: StyleObject._fix(fixerRefs, context)
131
+ });
132
+ } else if (!matchingTokens.length) {
133
+ context.report({
134
+ node: fontSizeNode,
135
+ messageId: 'noRawTypographyValues',
136
+ data: {
137
+ payload: "fontSize:".concat(fontSizeRaw)
138
+ }
139
+ });
140
+ }
141
+ return;
142
+ },
143
+ _check: function _check(node, _ref2) {
144
+ var context = _ref2.context;
145
+ if (!isDecendantOfStyleBlock(node) && !isDecendantOfType(node, 'JSXExpressionContainer')) {
146
+ return {
147
+ success: false
148
+ };
149
+ }
150
+
151
+ // -- Font size --
152
+ var fontSizeNode = ASTObject.getEntryByPropertyName(node, 'fontSize');
153
+ if (!fontSizeNode || !isValidPropertyNode(fontSizeNode) || isDecendantOfGlobalToken(fontSizeNode.value)) {
154
+ return {
155
+ success: false
156
+ };
157
+ }
158
+ var fontSizeRaw = getValueForPropertyNode(fontSizeNode, context);
159
+
160
+ // Without a valid fontSize value we can't be certain what token should be used; exit
161
+ if (!fontSizeRaw) {
162
+ return {
163
+ success: false
164
+ };
165
+ }
166
+ var importDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/tokens');
167
+
168
+ // If there is more than one `@atlaskit/tokens` import, then it becomes difficult to determine which import to transform
169
+ if (importDeclaration.length > 1) {
170
+ return {
171
+ success: false
172
+ };
173
+ }
174
+ return {
175
+ success: true,
176
+ refs: {
177
+ fontSizeNode: fontSizeNode,
178
+ fontSizeRaw: fontSizeRaw,
179
+ tokensImportNode: importDeclaration[0]
180
+ }
181
+ };
182
+ },
183
+ _fix: function _fix(refs, context) {
184
+ return function (fixer) {
185
+ var matchingToken = refs.matchingToken,
186
+ nodesToReplace = refs.nodesToReplace,
187
+ tokensImportNode = refs.tokensImportNode,
188
+ fontWeightReplacement = refs.fontWeightReplacement,
189
+ fontFamilyReplacement = refs.fontFamilyReplacement,
190
+ fontStyleReplacement = refs.fontStyleReplacement;
191
+ var fontSizeNode = nodesToReplace[0];
192
+ return (!tokensImportNode ? [insertTokensImport(fixer)] : []).concat(nodesToReplace.map(function (node, index) {
193
+ // Replace first node with token, delete remaining nodes. Guaranteed to be fontSize
194
+ if (index === 0) {
195
+ return fixer.replaceText(node, "".concat(getTokenProperty('font', matchingToken.tokenName, matchingToken.tokenValue)));
196
+ }
197
+
198
+ // We don't replace fontWeight/fontFamily/fontStyle here in case it occurs before the font property.
199
+ // Instead delete the original property and add below
200
+ return ObjectEntry.deleteEntry(node, context, fixer);
201
+ }),
202
+ // Make sure font weight/family/style properties are added AFTER font property to ensure they override corectly
203
+ fontWeightReplacement ? [fixer.insertTextAfter(fontSizeNode, ",\n".concat(fontWeightReplacement))] : [], fontFamilyReplacement ? [fixer.insertTextAfter(fontSizeNode, ",\n".concat(fontFamilyReplacement))] : [], fontStyleReplacement ? [fixer.insertTextAfter(fontSizeNode, ",\n".concat(fontStyleReplacement))] : []);
204
+ };
205
+ }
206
+ };