@atlaskit/eslint-plugin-design-system 8.33.0 → 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 (79) hide show
  1. package/CHANGELOG.md +6 -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/ensure-design-token-usage/index.js +5 -4
  11. package/dist/cjs/rules/ensure-design-token-usage/rule-meta.js +1 -1
  12. package/dist/cjs/rules/ensure-design-token-usage/spacing.js +5 -1
  13. package/dist/cjs/rules/ensure-design-token-usage/utils.js +52 -42
  14. package/dist/cjs/rules/index.codegen.js +3 -1
  15. package/dist/cjs/rules/use-tokens-typography/config/index.js +26 -0
  16. package/dist/cjs/rules/use-tokens-typography/error-boundary.js +24 -0
  17. package/dist/cjs/rules/use-tokens-typography/index.js +44 -0
  18. package/dist/cjs/rules/use-tokens-typography/transformers/style-object.js +212 -0
  19. package/dist/cjs/rules/use-tokens-typography/utils.js +146 -0
  20. package/dist/es2019/ast-nodes/index.js +1 -0
  21. package/dist/es2019/ast-nodes/object-entry.js +22 -0
  22. package/dist/es2019/ast-nodes/object.js +1 -1
  23. package/dist/es2019/presets/all.codegen.js +2 -1
  24. package/dist/es2019/rules/ensure-design-token-usage/index.js +6 -5
  25. package/dist/es2019/rules/ensure-design-token-usage/rule-meta.js +1 -1
  26. package/dist/es2019/rules/ensure-design-token-usage/spacing.js +5 -1
  27. package/dist/es2019/rules/ensure-design-token-usage/utils.js +42 -38
  28. package/dist/es2019/rules/index.codegen.js +3 -1
  29. package/dist/es2019/rules/use-tokens-typography/config/index.js +20 -0
  30. package/dist/es2019/rules/use-tokens-typography/error-boundary.js +19 -0
  31. package/dist/es2019/rules/use-tokens-typography/index.js +36 -0
  32. package/dist/es2019/rules/use-tokens-typography/transformers/style-object.js +209 -0
  33. package/dist/es2019/rules/use-tokens-typography/utils.js +99 -0
  34. package/dist/esm/ast-nodes/index.js +1 -0
  35. package/dist/esm/ast-nodes/object-entry.js +22 -0
  36. package/dist/esm/ast-nodes/object.js +1 -1
  37. package/dist/esm/presets/all.codegen.js +2 -1
  38. package/dist/esm/rules/ensure-design-token-usage/index.js +6 -5
  39. package/dist/esm/rules/ensure-design-token-usage/rule-meta.js +1 -1
  40. package/dist/esm/rules/ensure-design-token-usage/spacing.js +5 -1
  41. package/dist/esm/rules/ensure-design-token-usage/utils.js +46 -38
  42. package/dist/esm/rules/index.codegen.js +3 -1
  43. package/dist/esm/rules/use-tokens-typography/config/index.js +20 -0
  44. package/dist/esm/rules/use-tokens-typography/error-boundary.js +18 -0
  45. package/dist/esm/rules/use-tokens-typography/index.js +38 -0
  46. package/dist/esm/rules/use-tokens-typography/transformers/style-object.js +206 -0
  47. package/dist/esm/rules/use-tokens-typography/utils.js +129 -0
  48. package/dist/types/ast-nodes/index.d.ts +1 -0
  49. package/dist/types/ast-nodes/object-entry.d.ts +6 -0
  50. package/dist/types/ast-nodes/object.d.ts +1 -1
  51. package/dist/types/index.codegen.d.ts +1 -0
  52. package/dist/types/presets/all.codegen.d.ts +2 -1
  53. package/dist/types/rules/ensure-design-token-usage/types.d.ts +1 -1
  54. package/dist/types/rules/ensure-design-token-usage/utils.d.ts +22 -22
  55. package/dist/types/rules/index.codegen.d.ts +1 -0
  56. package/dist/types/rules/use-tokens-typography/config/index.d.ts +6 -0
  57. package/dist/types/rules/use-tokens-typography/error-boundary.d.ts +11 -0
  58. package/dist/types/rules/use-tokens-typography/index.d.ts +3 -0
  59. package/dist/types/rules/use-tokens-typography/transformers/style-object.d.ts +31 -0
  60. package/dist/types/rules/use-tokens-typography/utils.d.ts +161 -0
  61. package/dist/types-ts4.5/ast-nodes/index.d.ts +1 -0
  62. package/dist/types-ts4.5/ast-nodes/object-entry.d.ts +6 -0
  63. package/dist/types-ts4.5/ast-nodes/object.d.ts +1 -1
  64. package/dist/types-ts4.5/index.codegen.d.ts +1 -0
  65. package/dist/types-ts4.5/presets/all.codegen.d.ts +2 -1
  66. package/dist/types-ts4.5/rules/ensure-design-token-usage/types.d.ts +1 -1
  67. package/dist/types-ts4.5/rules/ensure-design-token-usage/utils.d.ts +22 -22
  68. package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -0
  69. package/dist/types-ts4.5/rules/use-tokens-typography/config/index.d.ts +6 -0
  70. package/dist/types-ts4.5/rules/use-tokens-typography/error-boundary.d.ts +11 -0
  71. package/dist/types-ts4.5/rules/use-tokens-typography/index.d.ts +3 -0
  72. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/style-object.d.ts +31 -0
  73. package/dist/types-ts4.5/rules/use-tokens-typography/utils.d.ts +161 -0
  74. package/package.json +1 -1
  75. package/dist/cjs/rules/ensure-design-token-usage/typography.js +0 -39
  76. package/dist/es2019/rules/ensure-design-token-usage/typography.js +0 -19
  77. package/dist/esm/rules/ensure-design-token-usage/typography.js +0 -33
  78. package/dist/types/rules/ensure-design-token-usage/typography.d.ts +0 -9
  79. package/dist/types-ts4.5/rules/ensure-design-token-usage/typography.d.ts +0 -9
@@ -5,7 +5,6 @@ import { spacing as spacingScale } from '@atlaskit/tokens/tokens-raw';
5
5
  import { findIdentifierInParentScope } from '../utils/find-in-parent';
6
6
  import { isColorCssPropertyName, isCurrentSurfaceCustomPropertyName } from '../utils/is-color';
7
7
  import { borderWidthValueToToken, isBorderRadius, isBorderSizeProperty, isShapeProperty, radiusValueToToken } from './shape';
8
- import { isCodeFontFamily, isFontFamily, isFontSize, isFontSizeSmall, isTypographyProperty, typographyValueToToken } from './typography';
9
8
  const 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'];
10
9
  const spacingValueToToken = Object.fromEntries(spacingScale.map(token => [token.value, token.cleanName]));
11
10
  export function insertTokensImport(fixer) {
@@ -31,8 +30,10 @@ export const splitShorthandValues = str => {
31
30
  };
32
31
  export const getValueFromShorthand = str => {
33
32
  const valueString = String(str);
34
- const fontFamily = /(sans-serif$)|(monospace$)/;
35
- if (fontFamily.test(valueString)) {
33
+ const fontFamily = /(Charlie)|(sans-serif$)|(monospace$)/;
34
+ const fontWeightString = /(regular$)|(medium$)|(semibold$)|(bold$)/;
35
+ const fontStyleString = /(inherit$)|(normal$)|(italic$)/;
36
+ if (fontFamily.test(valueString) || fontWeightString.test(valueString) || fontStyleString.test(valueString)) {
36
37
  return [valueString];
37
38
  }
38
39
  // If we want to filter out NaN just add .filter(Boolean)
@@ -57,6 +58,8 @@ const getRawExpressionForToken = (node, context) => {
57
58
  }).join(', ')})}`;
58
59
  return call;
59
60
  };
61
+ const isFontSize = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && (node.callee.name === 'fontSize' || node.callee.name === 'getFontSize');
62
+ const isFontSizeSmall = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && node.callee.name === 'fontSizeSmall';
60
63
  const getValueFromCallExpression = (node, context) => {
61
64
  if (!isNodeOfType(node, 'CallExpression')) {
62
65
  return null;
@@ -73,12 +76,6 @@ const getValueFromCallExpression = (node, context) => {
73
76
  if (isFontSizeSmall(node)) {
74
77
  return 11;
75
78
  }
76
- if (isFontFamily(node)) {
77
- return `-apple-system, BlinkMacSystemFont, \'Segoe UI\', \'Roboto\', \'Oxygen\', \'Ubuntu\', \'Fira Sans\', \'Droid Sans\', \'Helvetica Neue\', sans-serif`;
78
- }
79
- if (isCodeFontFamily(node)) {
80
- return `\'SFMono-Medium\', \'SF Mono\', \'Segoe UI Mono\', \'Roboto Mono\', \'Ubuntu Mono\', Menlo, Consolas, Courier, monospace`;
81
- }
82
79
  if (isToken(node)) {
83
80
  return getRawExpressionForToken(node, context);
84
81
  }
@@ -281,7 +278,7 @@ export const convertHyphenatedNameToCamelCase = prop => {
281
278
 
282
279
  /**
283
280
  * @param node
284
- * @returns The furthest parent node that is on the same line as the input node
281
+ * @returns The furthest parent node that is on the same line as the input node.
285
282
  */
286
283
  export const findParentNodeForLine = node => {
287
284
  var _node$loc, _node$parent$loc;
@@ -296,14 +293,13 @@ export const findParentNodeForLine = node => {
296
293
  };
297
294
 
298
295
  /**
299
- * Returns an array of domains that are relevant to the provided property based on the rule options
296
+ * Returns an array of domains that are relevant to the provided property based on the rule options.
300
297
  * @param propertyName camelCase CSS property
301
- * @param targetOptions Array containing the types of properties that should be included in the rule
298
+ * @param targetOptions Array containing the types of properties that should be included in the rule.
302
299
  * @example
303
300
  * ```
304
301
  * propertyName: padding, targetOptions: ['spacing'] -> returns ['spacing']
305
- * propertyName: fontWeight, targetOptions: ['spacing', 'typography'] -> returns ['typography']
306
- * propertyName: backgroundColor, targetOptions: ['spacing', 'typography'] -> returns []
302
+ * propertyName: backgroundColor, targetOptions: ['spacing'] -> returns []
307
303
  * propertyName: backgroundColor, targetOptions: ['color', 'spacing'] -> returns ['color']
308
304
  * ```
309
305
  */
@@ -318,15 +314,12 @@ export function getDomainsForProperty(propertyName, targetOptions) {
318
314
  if (isShapeProperty(propertyName) && targetOptions.includes('shape')) {
319
315
  domains.push('shape');
320
316
  }
321
- if (isTypographyProperty(propertyName) && targetOptions.includes('typography')) {
322
- domains.push('typography');
323
- }
324
317
  return domains;
325
318
  }
326
319
 
327
320
  /**
328
321
  * Function that removes JS comments from a string of code,
329
- * sometimes makers will have single or multiline comments in their tagged template literals styles, this can mess with our parsing logic
322
+ * sometimes makers will have single or multiline comments in their tagged template literals styles, this can mess with our parsing logic.
330
323
  */
331
324
  export function cleanComments(str) {
332
325
  return str.replace(/\/\*([\s\S]*?)\*\//g, '').replace(/\/\/(.*)/g, '');
@@ -334,11 +327,11 @@ export function cleanComments(str) {
334
327
 
335
328
  /**
336
329
  * Returns an array of tuples representing a processed css within `TaggedTemplateExpression` node.
337
- * each element of the array is a tuple `[string, string]`,
330
+ * Each element of the array is a tuple `[string, string]`,
338
331
  * where the first element is the processed css line with computed values
339
- * and the second element of the tuple is the original css line from source
340
- * @param node TaggedTemplateExpression node
341
- * @param context Rule.RuleContext
332
+ * and the second element of the tuple is the original css line from source.
333
+ * @param node TaggedTemplateExpression node.
334
+ * @param context Rule.RuleContext.
342
335
  * @example
343
336
  * ```
344
337
  * `[['padding: 8', 'padding: ${gridSize()}'], ['margin: 6', 'margin: 6px' ]]`
@@ -403,7 +396,7 @@ export function getFontSizeValueInScope(cssProperties) {
403
396
 
404
397
  /**
405
398
  * Attempts to remove all non-essential words & characters from a style block.
406
- * Including selectors and queries
399
+ * Including selectors and queries.
407
400
  * @param styleString string of css properties
408
401
  */
409
402
  export function splitCssProperties(styleString) {
@@ -418,8 +411,8 @@ export function splitCssProperties(styleString) {
418
411
  }
419
412
 
420
413
  /**
421
- * returns whether the current string is a token value
422
- * @param originalVaue string representing a css property value e.g 1em, 12px
414
+ * Returns whether the current string is a token value.
415
+ * @param originalVaue string representing a css property value e.g 1em, 12px.
423
416
  */
424
417
  export function isTokenValueString(originalValue) {
425
418
  return originalValue.startsWith('${token(') && originalValue.endsWith('}');
@@ -433,19 +426,27 @@ export function includesTokenString(originalValue) {
433
426
  *
434
427
  * -> for pixels this '8px'
435
428
  * -> for weights '400'
436
- * -> for family 'Arial'
429
+ * -> for family 'Arial'.
437
430
  *
438
431
  * @internal
439
432
  */
440
433
  export function normaliseValue(propertyName, value) {
441
- const isFontWeightOrFamily = /fontWeight|fontFamily/.test(propertyName);
434
+ const isFontStringProperty = /fontWeight|fontFamily|fontStyle/.test(propertyName);
435
+ const isLineHeight = /lineHeight/.test(propertyName);
442
436
  const propertyValue = typeof value === 'string' ? value.trim() : value;
443
- const lookupValue = isFontWeightOrFamily ? propertyValue : typeof propertyValue === 'string' ? propertyValue : `${propertyValue}px`;
437
+ let lookupValue;
438
+ if (isFontStringProperty) {
439
+ lookupValue = `${propertyValue}`;
440
+ } else if (isLineHeight) {
441
+ lookupValue = value === 1 ? `${propertyValue}` : `${propertyValue}px`;
442
+ } else {
443
+ lookupValue = typeof propertyValue === 'string' ? propertyValue : `${propertyValue}px`;
444
+ }
444
445
  return lookupValue;
445
446
  }
446
447
  export function findTokenNameByPropertyValue(propertyName, value) {
447
448
  const lookupValue = normaliseValue(propertyName, value);
448
- const tokenName = isShapeProperty(propertyName) ? isBorderSizeProperty(propertyName) ? borderWidthValueToToken[lookupValue] : radiusValueToToken[lookupValue] : isTypographyProperty(propertyName) ? typographyValueToToken[propertyName][lookupValue] : spacingValueToToken[lookupValue];
449
+ const tokenName = isShapeProperty(propertyName) ? isBorderSizeProperty(propertyName) ? borderWidthValueToToken[lookupValue] : radiusValueToToken[lookupValue] : spacingValueToToken[lookupValue];
449
450
  if (!tokenName) {
450
451
  return undefined;
451
452
  }
@@ -454,9 +455,9 @@ export function findTokenNameByPropertyValue(propertyName, value) {
454
455
 
455
456
  /**
456
457
  * Returns a stringifiable node with the token expression corresponding to its matching token.
457
- * if no token found for the pair the function returns undefined
458
- * @param propertyName string camelCased css property
459
- * @param value the computed value e.g '8px' -> '8'
458
+ * If no token found for the pair the function returns undefined.
459
+ * @param propertyName string camelCased css property.
460
+ * @param value The computed value e.g '8px' -> '8'.
460
461
  */
461
462
  export function getTokenReplacement(propertyName, value) {
462
463
  const tokenName = findTokenNameByPropertyValue(propertyName, value);
@@ -466,17 +467,20 @@ export function getTokenReplacement(propertyName, value) {
466
467
  const fallbackValue = normaliseValue(propertyName, value);
467
468
  return getTokenNodeForValue(propertyName, fallbackValue);
468
469
  }
469
- export function getFontSizeFromNode(parentNode, context) {
470
- const fontSizeNode = parentNode.properties.find(node => {
470
+ export function getPropertyNodeFromParent(property, parentNode) {
471
+ const propertyNode = parentNode.properties.find(node => {
471
472
  if (!isNodeOfType(node, 'Property')) {
472
473
  return;
473
474
  }
474
475
  if (!isNodeOfType(node.key, 'Identifier')) {
475
476
  return;
476
477
  }
477
- return node.key.name === 'fontSize';
478
+ return node.key.name === property;
478
479
  });
479
- const fontSizeValue = isNodeOfType(fontSizeNode, 'Property') ? getValue(fontSizeNode.value, context) : null;
480
- const fontSize = Array.isArray(fontSizeValue) ? fontSizeValue[0] : fontSizeValue;
481
- return fontSize;
480
+ return propertyNode;
481
+ }
482
+ export function getValueForPropertyNode(propertyNode, context) {
483
+ const propertyValueRaw = isNodeOfType(propertyNode, 'Property') ? getValue(propertyNode.value, context) : null;
484
+ const propertyValue = Array.isArray(propertyValueRaw) ? propertyValueRaw[0] : propertyValueRaw;
485
+ return propertyValue;
482
486
  }
@@ -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 const ruleSchema = {
2
+ type: 'array',
3
+ items: {
4
+ type: 'object',
5
+ properties: {
6
+ failSilently: {
7
+ type: 'boolean'
8
+ }
9
+ }
10
+ }
11
+ };
12
+ const defaultConfig = {
13
+ failSilently: false
14
+ };
15
+ export const 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,19 @@
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 const errorBoundary = (func, {
9
+ config
10
+ }) => {
11
+ try {
12
+ func();
13
+ } catch (err) {
14
+ if (!config.failSilently) {
15
+ // eslint-disable-next-line no-console
16
+ console.warn(err);
17
+ }
18
+ }
19
+ };
@@ -0,0 +1,36 @@
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
+ const create = context => {
6
+ const config = getConfig(context.options[0]);
7
+ return {
8
+ // const styles = css({ fontSize: '14px, ... }), styled.div({ fontSize: 14, ... })
9
+ ObjectExpression: node => errorBoundary(() => {
10
+ return StyleObject.lint(node, {
11
+ context
12
+ });
13
+ }, {
14
+ config
15
+ })
16
+ };
17
+ };
18
+ const rule = createLintRule({
19
+ meta: {
20
+ name: 'use-tokens-typography',
21
+ type: 'problem',
22
+ fixable: 'code',
23
+ hasSuggestions: true,
24
+ docs: {
25
+ description: 'Enforces usage of design tokens for typography properties rather than hard-coded values.',
26
+ recommended: false,
27
+ severity: 'warn'
28
+ },
29
+ messages: {
30
+ noRawTypographyValues: 'Typography primitives or tokens should be used instead of hard-coded values.\n\n@meta <<{{payload}}>>.'
31
+ },
32
+ schema: ruleSchema
33
+ },
34
+ create
35
+ });
36
+ export default rule;
@@ -0,0 +1,209 @@
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 const StyleObject = {
9
+ lint(node, {
10
+ context
11
+ }) {
12
+ // To force the correct node type
13
+ if (!isNodeOfType(node, 'ObjectExpression')) {
14
+ return {
15
+ success: false
16
+ };
17
+ }
18
+
19
+ // Check whether all criteria needed to make a transformation are met
20
+ const {
21
+ success,
22
+ refs
23
+ } = StyleObject._check(node, {
24
+ context
25
+ });
26
+ if (!success || !refs) {
27
+ return;
28
+ }
29
+ const {
30
+ fontSizeNode,
31
+ fontSizeRaw,
32
+ tokensImportNode
33
+ } = refs;
34
+ const fontSizeValue = normaliseValue('fontSize', fontSizeRaw);
35
+
36
+ // -- Font weight --
37
+ const fontWeightNode = ASTObject.getEntryByPropertyName(node, 'fontWeight');
38
+ const fontWeightRaw = fontWeightNode && getValueForPropertyNode(fontWeightNode, context);
39
+
40
+ // If no fontWeight value exists, default to 400 to avoid matching with a bolder token resulting in a visual change
41
+ let fontWeightValue = fontWeightRaw && normaliseValue('fontWeight', fontWeightRaw) || defaultFontWeight;
42
+ fontWeightValue = fontWeightValue.length === 3 ? fontWeightValue : fontWeightMap[fontWeightValue] || defaultFontWeight;
43
+
44
+ // -- Line height --
45
+ const lineHeightNode = ASTObject.getEntryByPropertyName(node, 'lineHeight');
46
+ const lineHeightRaw = lineHeightNode && getValueForPropertyNode(lineHeightNode, context);
47
+ let shouldAddFontWeight = false;
48
+ let lineHeightValue = lineHeightRaw && normaliseValue('lineHeight', lineHeightRaw) || undefined;
49
+ if (lineHeightValue === fontSizeValue) {
50
+ lineHeightValue = '1';
51
+ }
52
+
53
+ // -- Match tokens --
54
+ let matchingTokens = findTypographyTokenForValues(fontSizeValue, lineHeightValue);
55
+ if (matchingTokens.length) {
56
+ // If we have multiple matching tokens, try matching fontWeight
57
+ let matchingTokensWithWeight = matchingTokens.filter(token => fontWeightValue ? token.values.fontWeight === fontWeightValue : token);
58
+ if (matchingTokensWithWeight.length) {
59
+ // Possibly narrowed down tokens
60
+ matchingTokens = matchingTokensWithWeight;
61
+ } else {
62
+ // Ended up with 0 matches by matching fontWeight
63
+ // return body token and add fontWeight manually
64
+ matchingTokens = matchingTokens.filter(token => token.tokenName.includes('.body'));
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
+ const fontFamilyNode = ASTObject.getEntryByPropertyName(node, 'fontFamily');
74
+ const fontFamilyRaw = fontFamilyNode && getValueForPropertyNode(fontFamilyNode, context);
75
+ const fontFamilyValue = fontFamilyRaw && normaliseValue('fontFamily', fontFamilyRaw) || undefined;
76
+ let 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
+ const fontStyleNode = ASTObject.getEntryByPropertyName(node, 'fontStyle');
92
+ const fontStyleRaw = fontStyleNode && getValueForPropertyNode(fontStyleNode, context);
93
+ const fontStyleValue = fontStyleRaw && normaliseValue('fontStyle', fontStyleRaw) || undefined;
94
+ let fontStyleToAdd;
95
+ if (fontStyleValue === 'italic') {
96
+ fontStyleToAdd = 'italic';
97
+ }
98
+
99
+ // -- Letter spacing --
100
+ const 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
+ const matchingToken = matchingTokens[0];
106
+
107
+ // fontSize node is always first
108
+ const nodesToReplace = [fontSizeNode, fontWeightNode, lineHeightNode, fontFamilyNode, fontStyleNode, letterSpacingNode].filter(notUndefined);
109
+ const fontFamilyTokenName = fontFamilyToAdd ? `font.family.brand.${fontFamilyToAdd}` : '';
110
+ const fontWeightReplacementToken = shouldAddFontWeight ? findFontWeightTokenForValue(fontWeightValue) : undefined;
111
+ const fontWeightReplacement = fontWeightReplacementToken && getTokenProperty('fontWeight', fontWeightReplacementToken.tokenName, fontWeightValue);
112
+ const 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
+ const fontStyleReplacement = fontStyleToAdd && getLiteralProperty('fontStyle', fontStyleToAdd);
116
+ const fixerRefs = {
117
+ matchingToken,
118
+ nodesToReplace,
119
+ tokensImportNode,
120
+ fontWeightReplacement,
121
+ fontFamilyReplacement,
122
+ fontStyleReplacement
123
+ };
124
+ context.report({
125
+ node: fontSizeNode,
126
+ messageId: 'noRawTypographyValues',
127
+ data: {
128
+ payload: `fontSize:${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:${fontSizeRaw}`
138
+ }
139
+ });
140
+ }
141
+ return;
142
+ },
143
+ _check(node, {
144
+ context
145
+ }) {
146
+ if (!isDecendantOfStyleBlock(node) && !isDecendantOfType(node, 'JSXExpressionContainer')) {
147
+ return {
148
+ success: false
149
+ };
150
+ }
151
+
152
+ // -- Font size --
153
+ const fontSizeNode = ASTObject.getEntryByPropertyName(node, 'fontSize');
154
+ if (!fontSizeNode || !isValidPropertyNode(fontSizeNode) || isDecendantOfGlobalToken(fontSizeNode.value)) {
155
+ return {
156
+ success: false
157
+ };
158
+ }
159
+ const fontSizeRaw = getValueForPropertyNode(fontSizeNode, context);
160
+
161
+ // Without a valid fontSize value we can't be certain what token should be used; exit
162
+ if (!fontSizeRaw) {
163
+ return {
164
+ success: false
165
+ };
166
+ }
167
+ const importDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/tokens');
168
+
169
+ // If there is more than one `@atlaskit/tokens` import, then it becomes difficult to determine which import to transform
170
+ if (importDeclaration.length > 1) {
171
+ return {
172
+ success: false
173
+ };
174
+ }
175
+ return {
176
+ success: true,
177
+ refs: {
178
+ fontSizeNode,
179
+ fontSizeRaw,
180
+ tokensImportNode: importDeclaration[0]
181
+ }
182
+ };
183
+ },
184
+ _fix(refs, context) {
185
+ return fixer => {
186
+ const {
187
+ matchingToken,
188
+ nodesToReplace,
189
+ tokensImportNode,
190
+ fontWeightReplacement,
191
+ fontFamilyReplacement,
192
+ fontStyleReplacement
193
+ } = refs;
194
+ const fontSizeNode = nodesToReplace[0];
195
+ return (!tokensImportNode ? [insertTokensImport(fixer)] : []).concat(nodesToReplace.map((node, index) => {
196
+ // Replace first node with token, delete remaining nodes. Guaranteed to be fontSize
197
+ if (index === 0) {
198
+ return fixer.replaceText(node, `${getTokenProperty('font', matchingToken.tokenName, matchingToken.tokenValue)}`);
199
+ }
200
+
201
+ // We don't replace fontWeight/fontFamily/fontStyle here in case it occurs before the font property.
202
+ // Instead delete the original property and add below
203
+ return ObjectEntry.deleteEntry(node, context, fixer);
204
+ }),
205
+ // Make sure font weight/family/style properties are added AFTER font property to ensure they override corectly
206
+ fontWeightReplacement ? [fixer.insertTextAfter(fontSizeNode, `,\n${fontWeightReplacement}`)] : [], fontFamilyReplacement ? [fixer.insertTextAfter(fontSizeNode, `,\n${fontFamilyReplacement}`)] : [], fontStyleReplacement ? [fixer.insertTextAfter(fontSizeNode, `,\n${fontStyleReplacement}`)] : []);
207
+ };
208
+ }
209
+ };
@@ -0,0 +1,99 @@
1
+ import { callExpression, identifier, isNodeOfType, literal, property } from 'eslint-codemod-utils';
2
+ import { typographyPalette } from '@atlaskit/tokens/palettes-raw';
3
+ import { typographyAdg3 as typographyTokens } from '@atlaskit/tokens/tokens-raw';
4
+ export const typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
5
+ export const isTypographyProperty = propertyName => {
6
+ return typographyProperties.includes(propertyName);
7
+ };
8
+ export const isFontSize = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && (node.callee.name === 'fontSize' || node.callee.name === 'getFontSize');
9
+ export const isFontSizeSmall = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && node.callee.name === 'fontSizeSmall';
10
+ export const isFontFamily = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && (node.callee.name === 'fontFamily' || node.callee.name === 'getFontFamily');
11
+ export const isCodeFontFamily = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && (node.callee.name === 'codeFontFamily' || node.callee.name === 'getCodeFontFamily');
12
+ export const typographyValueToToken = typographyTokens
13
+ // we're filtering here to remove the `font` tokens.
14
+ .filter(t => t.attributes.group === 'typography').filter(t => t.cleanName.includes('font.heading') || t.cleanName.includes('font.body')).map(currentToken => {
15
+ var _typographyPalette$fi, _typographyPalette$fi2, _typographyPalette$fi3;
16
+ const individualValues = {
17
+ fontSize: (_typographyPalette$fi = typographyPalette.find(baseToken => baseToken.path.slice(-1)[0] ===
18
+ // @ts-expect-error token.original.value can be a string, due to the typographyTokens export including deprecated tokens
19
+ currentToken.original.value.fontSize)) === null || _typographyPalette$fi === void 0 ? void 0 : _typographyPalette$fi.value,
20
+ fontWeight: (_typographyPalette$fi2 = typographyPalette.find(baseToken => baseToken.path.slice(-1)[0] ===
21
+ // @ts-expect-error token.original.value can be a string, due to the typographyTokens export including deprecated tokens
22
+ currentToken.original.value.fontWeight)) === null || _typographyPalette$fi2 === void 0 ? void 0 : _typographyPalette$fi2.value,
23
+ lineHeight: (_typographyPalette$fi3 = typographyPalette.find(baseToken => baseToken.path.slice(-1)[0] ===
24
+ // @ts-expect-error token.original.value can be a string, due to the typographyTokens export including deprecated tokens
25
+ currentToken.original.value.lineHeight)) === null || _typographyPalette$fi3 === void 0 ? void 0 : _typographyPalette$fi3.value
26
+ };
27
+ return {
28
+ tokenName: currentToken.cleanName,
29
+ tokenValue: currentToken.value,
30
+ values: individualValues
31
+ };
32
+ });
33
+ export function findTypographyTokenForValues(fontSize, lineHeight) {
34
+ let matchingTokens = typographyValueToToken.filter(token => token.values.fontSize === fontSize)
35
+ // If lineHeight == 1, we don't match to a token
36
+ .filter(() => lineHeight === '1' ? false : true);
37
+ return matchingTokens;
38
+ }
39
+ export const fontWeightTokens = typographyTokens.filter(token => token.attributes.group === 'fontWeight').map(token => {
40
+ return {
41
+ tokenName: token.cleanName,
42
+ tokenValue: token.value,
43
+ values: {}
44
+ };
45
+ });
46
+ export function findFontWeightTokenForValue(fontWeight) {
47
+ return fontWeightTokens.find(token => token.tokenValue === fontWeight);
48
+ }
49
+ export const fontWeightMap = {
50
+ regular: '400',
51
+ medium: '500',
52
+ semibold: '600',
53
+ bold: '700'
54
+ };
55
+ export const defaultFontWeight = fontWeightMap.regular;
56
+ export const fontFamilyTokens = typographyTokens.filter(token => token.attributes.group === 'fontFamily');
57
+ export function findFontFamilyValueForToken(tokenName) {
58
+ var _fontFamilyTokens$fin;
59
+ // Note this will only ever be undefined if the tokens get renamed, and should never happen.
60
+ return ((_fontFamilyTokens$fin = fontFamilyTokens.find(token => token.cleanName === tokenName)) === null || _fontFamilyTokens$fin === void 0 ? void 0 : _fontFamilyTokens$fin.value) || '';
61
+ }
62
+ export function notUndefined(value) {
63
+ return value !== undefined;
64
+ }
65
+ export function isValidPropertyNode(node) {
66
+ if (!isNodeOfType(node.key, 'Identifier') && !isNodeOfType(node.key, 'Literal')) {
67
+ return false;
68
+ }
69
+ return true;
70
+ }
71
+ function getTokenNode(tokenName, tokenValue) {
72
+ return callExpression({
73
+ callee: identifier({
74
+ name: 'token'
75
+ }),
76
+ arguments: [literal({
77
+ value: `'${tokenName}'`
78
+ }), literal(tokenValue)],
79
+ optional: false
80
+ });
81
+ }
82
+ export function getTokenProperty(propertyName, tokenName, tokenFallback) {
83
+ return property({
84
+ key: identifier(propertyName),
85
+ value: getTokenNode(tokenName, tokenFallback)
86
+ });
87
+ }
88
+ export function getLiteralProperty(propertyName, propertyValue) {
89
+ return property({
90
+ key: identifier(propertyName),
91
+ value: literal(propertyValue)
92
+ });
93
+ }
94
+ export function convertPropertyNodeToStringableNode(node) {
95
+ return property({
96
+ key: node.key,
97
+ value: node.value
98
+ });
99
+ }
@@ -3,4 +3,5 @@ export { Import } from './import';
3
3
  export { JSXAttribute } from './jsx-attribute';
4
4
  export { JSXElement } from './jsx-element';
5
5
  export { Object } from './object';
6
+ export { ObjectEntry } from './object-entry';
6
7
  export { Root } from './root';
@@ -0,0 +1,22 @@
1
+ var ObjectEntry = {
2
+ deleteEntry: function deleteEntry(node, context, fixer) {
3
+ var _lastToken;
4
+ // context.getSourceCode() is deprecated in favour of context.sourceCode, however this returns undefined for some reason
5
+ var sourceCode = context.getSourceCode();
6
+
7
+ // fixer.remove() doesn't account for things like commas or newlines within an ObjectExpression and will result in invalid output.
8
+ // This approach specifically removes the node and trailing comma, and should work for single- and multi-line objects.
9
+ // From https://github.com/eslint/eslint/issues/9576#issuecomment-341737453
10
+ var prevToken = sourceCode.getTokenBefore(node);
11
+ while (((_prevToken = prevToken) === null || _prevToken === void 0 ? void 0 : _prevToken.value) !== ',' && ((_prevToken2 = prevToken) === null || _prevToken2 === void 0 ? void 0 : _prevToken2.value) !== '{') {
12
+ var _prevToken, _prevToken2;
13
+ prevToken = sourceCode.getTokenBefore(node);
14
+ }
15
+ var lastToken = sourceCode.getTokenAfter(node);
16
+ if (((_lastToken = lastToken) === null || _lastToken === void 0 ? void 0 : _lastToken.value) !== ',') {
17
+ lastToken = sourceCode.getTokenBefore(lastToken);
18
+ }
19
+ return fixer.removeRange([prevToken.range[1], lastToken.range[1]]);
20
+ }
21
+ };
22
+ export { ObjectEntry };
@@ -27,7 +27,7 @@ var ASTObjectExpression = {
27
27
  });
28
28
  },
29
29
  /**
30
- * Returns a key-value pair like: `padding: '8px'` from: `{ padding: '8px' }`.
30
+ * Returns the first Property node from an Object that matches the provided name.
31
31
  */
32
32
  getEntryByPropertyName: function getEntryByPropertyName(node, name) {
33
33
  return node.properties.find(function (property) {
@@ -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::bcb633b9d5c2def00d43b11139433c5c>>
3
+ * @codegen <<SignedSource::d90c2cf5e100daf98915f9467f2e5663>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  export default {
@@ -35,6 +35,7 @@ export default {
35
35
  '@atlaskit/design-system/use-href-in-link-item': 'warn',
36
36
  '@atlaskit/design-system/use-primitives': 'warn',
37
37
  '@atlaskit/design-system/use-primitives-text': 'warn',
38
+ '@atlaskit/design-system/use-tokens-typography': 'warn',
38
39
  '@atlaskit/design-system/use-visually-hidden': 'error'
39
40
  }
40
41
  };