@atlaskit/eslint-plugin-design-system 8.38.0 → 9.1.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 (23) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/constellation/consistent-css-prop-usage/usage.mdx +3 -19
  3. package/dist/cjs/rules/consistent-css-prop-usage/index.js +53 -85
  4. package/dist/cjs/rules/use-tokens-typography/transformers/style-object.js +47 -6
  5. package/dist/cjs/rules/use-tokens-typography/utils.js +39 -3
  6. package/dist/cjs/rules/utils/is-supported-import.js +12 -3
  7. package/dist/es2019/rules/consistent-css-prop-usage/index.js +21 -48
  8. package/dist/es2019/rules/use-tokens-typography/transformers/style-object.js +49 -8
  9. package/dist/es2019/rules/use-tokens-typography/utils.js +37 -5
  10. package/dist/es2019/rules/utils/is-supported-import.js +15 -2
  11. package/dist/esm/rules/consistent-css-prop-usage/index.js +53 -85
  12. package/dist/esm/rules/use-tokens-typography/transformers/style-object.js +49 -8
  13. package/dist/esm/rules/use-tokens-typography/utils.js +37 -4
  14. package/dist/esm/rules/utils/is-supported-import.js +11 -2
  15. package/dist/types/rules/consistent-css-prop-usage/types.d.ts +1 -2
  16. package/dist/types/rules/use-tokens-typography/transformers/style-object.d.ts +4 -0
  17. package/dist/types/rules/use-tokens-typography/utils.d.ts +8 -4
  18. package/dist/types/rules/utils/is-supported-import.d.ts +5 -4
  19. package/dist/types-ts4.5/rules/consistent-css-prop-usage/types.d.ts +1 -2
  20. package/dist/types-ts4.5/rules/use-tokens-typography/transformers/style-object.d.ts +4 -0
  21. package/dist/types-ts4.5/rules/use-tokens-typography/utils.d.ts +8 -4
  22. package/dist/types-ts4.5/rules/utils/is-supported-import.d.ts +5 -4
  23. package/package.json +3 -3
@@ -2,9 +2,9 @@
2
2
 
3
3
  import { isNodeOfType } from 'eslint-codemod-utils';
4
4
  import { Object as ASTObject, ObjectEntry, Root } from '../../../ast-nodes';
5
- import { getValueForPropertyNode, insertTokensImport, normaliseValue } from '../../ensure-design-token-usage/utils';
5
+ import { getValueForPropertyNode, normaliseValue } from '../../ensure-design-token-usage/utils';
6
6
  import { isDecendantOfGlobalToken, isDecendantOfStyleBlock, isDecendantOfType } from '../../utils/is-node';
7
- import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, isValidPropertyNode, notUndefined } from '../utils';
7
+ import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, insertFallbackImportFull, insertFallbackImportSpecifier, insertTokensImport, isValidPropertyNode, notUndefined } from '../utils';
8
8
  export const StyleObject = {
9
9
  lint(node, {
10
10
  context
@@ -29,7 +29,9 @@ export const StyleObject = {
29
29
  const {
30
30
  fontSizeNode,
31
31
  fontSizeRaw,
32
- tokensImportNode
32
+ tokensImportNode,
33
+ themeImportNode,
34
+ shouldAddFallbackImport
33
35
  } = refs;
34
36
  const fontSizeValue = normaliseValue('fontSize', fontSizeRaw);
35
37
 
@@ -117,6 +119,8 @@ export const StyleObject = {
117
119
  matchingToken,
118
120
  nodesToReplace,
119
121
  tokensImportNode,
122
+ themeImportNode,
123
+ shouldAddFallbackImport,
120
124
  fontWeightReplacement,
121
125
  fontFamilyReplacement,
122
126
  fontStyleReplacement
@@ -164,20 +168,47 @@ export const StyleObject = {
164
168
  success: false
165
169
  };
166
170
  }
167
- const importDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/tokens');
171
+ const tokensImportDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/tokens');
168
172
 
169
173
  // If there is more than one `@atlaskit/tokens` import, then it becomes difficult to determine which import to transform
170
- if (importDeclaration.length > 1) {
174
+ if (tokensImportDeclaration.length > 1) {
171
175
  return {
172
176
  success: false
173
177
  };
174
178
  }
179
+
180
+ // This exists purely because we're not inlining the fallback values
181
+ // and instead referencing a `fontFallback` object that exists in @atlaskit/theme/typography.
182
+ // This is a temporary measure until fallbacks are no longer required
183
+ let shouldAddFallbackImport = 'full';
184
+ const themeImportDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/theme/typography');
185
+ if (themeImportDeclaration.length) {
186
+ // Import exists, check if specifier exists
187
+ shouldAddFallbackImport = 'specifier';
188
+ const fallbackImport = themeImportDeclaration[0].specifiers.find(specifier => {
189
+ // @atlaskit/theme/typography has no default export so we can safely narrow this type
190
+ if (!isNodeOfType(specifier, 'ImportSpecifier')) {
191
+ return false;
192
+ }
193
+ if (specifier.imported.name === 'fontFallback') {
194
+ return true;
195
+ }
196
+ return false;
197
+ });
198
+
199
+ // Exact import already exists, no need to add
200
+ if (fallbackImport) {
201
+ shouldAddFallbackImport = false;
202
+ }
203
+ }
175
204
  return {
176
205
  success: true,
177
206
  refs: {
178
207
  fontSizeNode,
179
208
  fontSizeRaw,
180
- tokensImportNode: importDeclaration[0]
209
+ tokensImportNode: tokensImportDeclaration[0],
210
+ themeImportNode: themeImportDeclaration[0],
211
+ shouldAddFallbackImport
181
212
  }
182
213
  };
183
214
  },
@@ -187,15 +218,25 @@ export const StyleObject = {
187
218
  matchingToken,
188
219
  nodesToReplace,
189
220
  tokensImportNode,
221
+ themeImportNode,
222
+ shouldAddFallbackImport,
190
223
  fontWeightReplacement,
191
224
  fontFamilyReplacement,
192
225
  fontStyleReplacement
193
226
  } = refs;
194
227
  const fontSizeNode = nodesToReplace[0];
195
- return (!tokensImportNode ? [insertTokensImport(fixer)] : []).concat(nodesToReplace.map((node, index) => {
228
+ const root = context.getSourceCode().ast.body;
229
+ let fallbackImport;
230
+ if (shouldAddFallbackImport === 'full') {
231
+ fallbackImport = insertFallbackImportFull(root, fixer);
232
+ } else if (shouldAddFallbackImport === 'specifier') {
233
+ fallbackImport = insertFallbackImportSpecifier(fixer, themeImportNode);
234
+ }
235
+ const fallbackName = (matchingToken.tokenName === 'font.body' ? 'font.body.medium' : matchingToken.tokenName).replace('font', 'fontFallback');
236
+ return (!tokensImportNode ? [insertTokensImport(root, fixer)] : []).concat(fallbackImport ? [fallbackImport] : [], nodesToReplace.map((node, index) => {
196
237
  // Replace first node with token, delete remaining nodes. Guaranteed to be fontSize
197
238
  if (index === 0) {
198
- return fixer.replaceText(node, `${getTokenProperty('font', matchingToken.tokenName, matchingToken.tokenValue)}`);
239
+ return fixer.replaceText(node, `${getTokenProperty('font', matchingToken.tokenName, fallbackName, true)}`);
199
240
  }
200
241
 
201
242
  // We don't replace fontWeight/fontFamily/fontStyle here in case it occurs before the font property.
@@ -1,6 +1,7 @@
1
- import { callExpression, identifier, isNodeOfType, literal, property } from 'eslint-codemod-utils';
1
+ import { callExpression, identifier, isNodeOfType, literal, memberExpression, property } from 'eslint-codemod-utils';
2
2
  import { typographyPalette } from '@atlaskit/tokens/palettes-raw';
3
3
  import { typographyAdg3 as typographyTokens } from '@atlaskit/tokens/tokens-raw';
4
+ import { Import, Root } from '../../ast-nodes';
4
5
  export const typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
5
6
  export const isTypographyProperty = propertyName => {
6
7
  return typographyProperties.includes(propertyName);
@@ -68,21 +69,37 @@ export function isValidPropertyNode(node) {
68
69
  }
69
70
  return true;
70
71
  }
71
- function getTokenNode(tokenName, tokenValue) {
72
+ function getTokenNode(tokenName, tokenValue, isFallbackMember) {
73
+ let fallback;
74
+ if (isFallbackMember) {
75
+ fallback = createMemberExpressionFromArray(tokenValue.split('.'));
76
+ } else {
77
+ fallback = literal(tokenValue);
78
+ }
72
79
  return callExpression({
73
80
  callee: identifier({
74
81
  name: 'token'
75
82
  }),
76
83
  arguments: [literal({
77
84
  value: `'${tokenName}'`
78
- }), literal(tokenValue)],
85
+ }), fallback],
79
86
  optional: false
80
87
  });
81
88
  }
82
- export function getTokenProperty(propertyName, tokenName, tokenFallback) {
89
+ function createMemberExpressionFromArray(array) {
90
+ if (array.length === 1) {
91
+ return identifier(array[0]);
92
+ }
93
+ const property = array.pop();
94
+ return memberExpression({
95
+ object: createMemberExpressionFromArray(array),
96
+ property: identifier(property)
97
+ });
98
+ }
99
+ export function getTokenProperty(propertyName, tokenName, tokenFallback, isFallbackMember = false) {
83
100
  return property({
84
101
  key: identifier(propertyName),
85
- value: getTokenNode(tokenName, tokenFallback)
102
+ value: getTokenNode(tokenName, tokenFallback, isFallbackMember)
86
103
  });
87
104
  }
88
105
  export function getLiteralProperty(propertyName, propertyValue) {
@@ -96,4 +113,19 @@ export function convertPropertyNodeToStringableNode(node) {
96
113
  key: node.key,
97
114
  value: node.value
98
115
  });
116
+ }
117
+ export function insertTokensImport(root, fixer) {
118
+ return Root.insertImport(root, {
119
+ module: '@atlaskit/tokens',
120
+ specifiers: ['token']
121
+ }, fixer);
122
+ }
123
+ export function insertFallbackImportFull(root, fixer) {
124
+ return Root.insertImport(root, {
125
+ module: '@atlaskit/theme/typography',
126
+ specifiers: ['fontFallback']
127
+ }, fixer);
128
+ }
129
+ export function insertFallbackImportSpecifier(fixer, themeImportNode) {
130
+ return Import.insertNamedSpecifiers(themeImportNode, ['fontFallback'], fixer);
99
131
  }
@@ -1,3 +1,10 @@
1
+ // This should be kept in sync with
2
+ // packages/design-system/eslint-plugin-ui-styling-standard/src/rules/utils/is-supported-import.tsx
3
+ // whenever possible.
4
+ //
5
+ // TODO: would having an @atlassian/eslint-plugin-design-system-common
6
+ // package be useful?
7
+
1
8
  // eslint-disable-next-line import/no-extraneous-dependencies
2
9
 
3
10
  export const CSS_IN_JS_IMPORTS = {
@@ -109,8 +116,14 @@ export const isCssMap = isSupportedImportWrapper('cssMap');
109
116
  export const isKeyframes = isSupportedImportWrapper('keyframes');
110
117
  // `styled` is also the explicit default of `styled-components` and `@emotion/styled`, so we also match on default imports generally
111
118
  export const isStyled = isSupportedImportWrapper('styled', ['styled-components', '@emotion/styled']);
112
- export const isImportedFrom = (moduleName, exactMatch = true) => (nodeToCheck, referencesInScope, importSources) => {
113
- if (!importSources.some(importSource => importSource === moduleName || !exactMatch && importSource.startsWith(moduleName))) {
119
+ export const isXcss = isSupportedImportWrapper('xcss');
120
+ export const isImportedFrom = (moduleName, exactMatch = true) => (nodeToCheck, referencesInScope,
121
+ /**
122
+ * If we strictly have specific import sources in the config scope, pass them to make this more performant.
123
+ * Pass `null` if you don't care if its configured or not.
124
+ */
125
+ importSources = null) => {
126
+ if (importSources && !importSources.some(importSource => importSource === moduleName || !exactMatch && importSource.startsWith(moduleName))) {
114
127
  // Don't go through the trouble of checking the import sources does not include this
115
128
  // We'll assume this is skipped elsewhere.
116
129
  return false;
@@ -1,4 +1,3 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
1
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
2
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
4
3
  import _createClass from "@babel/runtime/helpers/createClass";
@@ -33,6 +32,18 @@ var getProgramNode = function getProgramNode(expression) {
33
32
  }
34
33
  return expression.parent;
35
34
  };
35
+ var isDeclaredInsideComponent = function isDeclaredInsideComponent(expression) {
36
+ // These nodes imply that there is a distinct own scope (function scope / block scope),
37
+ // and so the presence of them means that expression was not defined in the module scope.
38
+ var NOT_MODULE_SCOPE = ['ArrowFunctionExpression', 'BlockStatement', 'ClassDeclaration', 'FunctionExpression'];
39
+ while (expression.type !== 'Program') {
40
+ if (NOT_MODULE_SCOPE.includes(expression.type)) {
41
+ return true;
42
+ }
43
+ expression = expression.parent;
44
+ }
45
+ return false;
46
+ };
36
47
  var JSXExpressionLinter = /*#__PURE__*/function () {
37
48
  // File-level tracking of styles hoisted from the cssAtTopOfModule/cssAtBottomOfModule fixers.
38
49
 
@@ -112,13 +123,13 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
112
123
  });
113
124
  return;
114
125
  }
115
- if (identifier.parent.parent.parent.type !== 'Program') {
126
+ if (isDeclaredInsideComponent(identifier)) {
116
127
  // When variable is declared inside the component
117
128
  this.context.report({
118
129
  node: sourceIdentifier,
119
130
  messageId: this.configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
120
131
  fix: function fix(fixer) {
121
- if (_this.configuration.fixNamesOnly) {
132
+ if (!_this.configuration.autoFix) {
122
133
  return [];
123
134
  }
124
135
  return _this.fixCssNotInModuleScope(fixer, identifier, false);
@@ -140,7 +151,7 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
140
151
  node: identifier,
141
152
  messageId: 'cssObjectTypeOnly',
142
153
  fix: function fix(fixer) {
143
- if (_this.configuration.fixNamesOnly) {
154
+ if (!_this.configuration.autoFix) {
144
155
  return [];
145
156
  }
146
157
  return _this.addCssFunctionCall(fixer, identifier.parent);
@@ -316,10 +327,11 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
316
327
  // The last value is the bottom of the file
317
328
  fixerNodePlacement = programNode.body[programNode.body.length - 1];
318
329
  } else {
330
+ var _ref3;
319
331
  // Place after the last ImportDeclaration
320
- fixerNodePlacement = programNode.body.length === 1 ? programNode.body[0] : programNode.body.find(function (node) {
332
+ fixerNodePlacement = (_ref3 = programNode.body.length === 1 ? programNode.body[0] : programNode.body.find(function (node) {
321
333
  return node.type !== 'ImportDeclaration';
322
- });
334
+ })) !== null && _ref3 !== void 0 ? _ref3 : fixerNodePlacement;
323
335
  }
324
336
  var moduleString;
325
337
  var fixes = [];
@@ -403,7 +415,7 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
403
415
  node: expression,
404
416
  messageId: this.configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
405
417
  fix: function fix(fixer) {
406
- if (_this3.configuration.fixNamesOnly) {
418
+ if (!_this3.configuration.autoFix) {
407
419
  return [];
408
420
  }
409
421
 
@@ -448,8 +460,7 @@ var defaultConfig = {
448
460
  cssImportSource: CSS_IN_JS_IMPORTS.compiled,
449
461
  xcssImportSource: CSS_IN_JS_IMPORTS.atlaskitPrimitives,
450
462
  excludeReactComponents: false,
451
- fixNamesOnly: false,
452
- autoFixNames: true
463
+ autoFix: true
453
464
  };
454
465
  var rule = createLintRule({
455
466
  meta: {
@@ -468,8 +479,7 @@ var rule = createLintRule({
468
479
  cssObjectTypeOnly: "Create styles using objects passed to a css function call, e.g. `css({ textAlign: 'center'; })`.",
469
480
  cssInModule: "Imported styles should not be used; instead define in the module, import a component, or use a design token.",
470
481
  cssArrayStylesOnly: "Compose styles with an array on the css prop instead of using object spread.",
471
- noMemberExpressions: "Styles should be a regular variable (e.g. 'buttonStyles'), not a member of an object (e.g. 'myObject.styles').",
472
- shouldEndInStyles: 'Declared styles should end in "styles".'
482
+ noMemberExpressions: "Styles should be a regular variable (e.g. 'buttonStyles'), not a member of an object (e.g. 'myObject.styles')."
473
483
  },
474
484
  schema: [{
475
485
  type: 'object',
@@ -493,10 +503,7 @@ var rule = createLintRule({
493
503
  excludeReactComponents: {
494
504
  type: 'boolean'
495
505
  },
496
- autoFixNames: {
497
- type: 'boolean'
498
- },
499
- fixNamesOnly: {
506
+ autoFix: {
500
507
  type: 'boolean'
501
508
  }
502
509
  },
@@ -504,80 +511,41 @@ var rule = createLintRule({
504
511
  }]
505
512
  },
506
513
  create: function create(context) {
507
- var _ref3;
508
514
  var mergedConfig = assign({}, defaultConfig, context.options[0]);
509
- var declarationSuffix = 'Styles';
510
- var callSelectorFunctions = [].concat(_toConsumableArray(mergedConfig.cssFunctions), ['cssMap']);
511
- var callSelector = callSelectorFunctions.map(function (fn) {
512
- return "CallExpression[callee.name=".concat(fn, "]");
513
- }).join(',');
514
- return _ref3 = {}, _defineProperty(_ref3, callSelector, function (node) {
515
- if (node.parent.type !== 'VariableDeclarator') {
516
- // We aren't interested in these that don't have a parent.
517
- return;
518
- }
519
- var identifier = node.parent.id;
520
- if (identifier.type === 'Identifier' && (identifier.name.endsWith(declarationSuffix) || identifier.name.startsWith(declarationSuffix.toLowerCase()) || identifier.name === declarationSuffix.toLowerCase())) {
521
- // Already prefixed! Nothing to do.
522
- return;
523
- }
524
- if (!mergedConfig.autoFixNames) {
525
- return;
526
- }
527
- context.report({
528
- node: identifier,
529
- messageId: 'shouldEndInStyles',
530
- fix: function fix(fixer) {
531
- var _context$getScope$var;
532
- var identifierName = identifier.type === 'Identifier' ? identifier.name : '';
533
- var references = ((_context$getScope$var = context.getScope().variables.find(function (varb) {
534
- return varb.name === identifierName;
535
- })) === null || _context$getScope$var === void 0 ? void 0 : _context$getScope$var.references) || [];
536
- var newIdentifierName = "".concat(identifierName
537
- // Remove "Style" if it is already defined.
538
- .replace(/Style$/, '')).concat(declarationSuffix);
539
- return references.filter(function (ref) {
540
- return ref.identifier.name === identifierName;
541
- }).map(function (ref) {
542
- if (ref.identifier.parent && ref.identifier.parent.shorthand) {
543
- return fixer.replaceText(ref.identifier, "".concat(identifierName, ": ").concat(newIdentifierName));
544
- }
545
- return fixer.replaceText(ref.identifier, newIdentifierName);
546
- });
547
- }
548
- });
549
- }), _defineProperty(_ref3, "JSXAttribute", function JSXAttribute(nodeOriginal) {
550
- var node = nodeOriginal;
551
- var name = node.name,
552
- value = node.value;
553
- if (mergedConfig.excludeReactComponents && node.parent.type === 'JSXOpeningElement') {
554
- // e.g. <item.before />
555
- if (node.parent.name.type === 'JSXMemberExpression') {
556
- return;
557
- }
558
- // e.g. <div />, <MenuItem />
559
- if (node.parent.name.type === 'JSXIdentifier' && !isDOMElementName(node.parent.name.name)) {
560
- return;
561
- }
562
- }
563
- if (name.type === 'JSXIdentifier' && mergedConfig.cssFunctions.includes(name.name)) {
564
- // When not a jsx expression. For eg. css=""
565
- if ((value === null || value === void 0 ? void 0 : value.type) !== 'JSXExpressionContainer') {
566
- context.report({
567
- node: node,
568
- messageId: mergedConfig.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule'
569
- });
570
- return;
515
+ return {
516
+ JSXAttribute: function JSXAttribute(nodeOriginal) {
517
+ var node = nodeOriginal;
518
+ var name = node.name,
519
+ value = node.value;
520
+ if (mergedConfig.excludeReactComponents && node.parent.type === 'JSXOpeningElement') {
521
+ // e.g. <item.before />
522
+ if (node.parent.name.type === 'JSXMemberExpression') {
523
+ return;
524
+ }
525
+ // e.g. <div />, <MenuItem />
526
+ if (node.parent.name.type === 'JSXIdentifier' && !isDOMElementName(node.parent.name.name)) {
527
+ return;
528
+ }
571
529
  }
572
- if (value.expression.type === 'JSXEmptyExpression') {
573
- // e.g. the comment in
574
- // <div css={/* Hello there */} />
575
- return;
530
+ if (name.type === 'JSXIdentifier' && mergedConfig.cssFunctions.includes(name.name)) {
531
+ // When not a jsx expression. For eg. css=""
532
+ if ((value === null || value === void 0 ? void 0 : value.type) !== 'JSXExpressionContainer') {
533
+ context.report({
534
+ node: node,
535
+ messageId: mergedConfig.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule'
536
+ });
537
+ return;
538
+ }
539
+ if (value.expression.type === 'JSXEmptyExpression') {
540
+ // e.g. the comment in
541
+ // <div css={/* Hello there */} />
542
+ return;
543
+ }
544
+ var linter = new JSXExpressionLinter(context, name.name, mergedConfig, value.expression);
545
+ linter.run();
576
546
  }
577
- var linter = new JSXExpressionLinter(context, name.name, mergedConfig, value.expression);
578
- linter.run();
579
547
  }
580
- }), _ref3;
548
+ };
581
549
  }
582
550
  });
583
551
  export default rule;
@@ -2,9 +2,9 @@
2
2
 
3
3
  import { isNodeOfType } from 'eslint-codemod-utils';
4
4
  import { Object as ASTObject, ObjectEntry, Root } from '../../../ast-nodes';
5
- import { getValueForPropertyNode, insertTokensImport, normaliseValue } from '../../ensure-design-token-usage/utils';
5
+ import { getValueForPropertyNode, normaliseValue } from '../../ensure-design-token-usage/utils';
6
6
  import { isDecendantOfGlobalToken, isDecendantOfStyleBlock, isDecendantOfType } from '../../utils/is-node';
7
- import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, isValidPropertyNode, notUndefined } from '../utils';
7
+ import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, insertFallbackImportFull, insertFallbackImportSpecifier, insertTokensImport, isValidPropertyNode, notUndefined } from '../utils';
8
8
  export var StyleObject = {
9
9
  lint: function lint(node, _ref) {
10
10
  var context = _ref.context;
@@ -26,7 +26,9 @@ export var StyleObject = {
26
26
  }
27
27
  var fontSizeNode = refs.fontSizeNode,
28
28
  fontSizeRaw = refs.fontSizeRaw,
29
- tokensImportNode = refs.tokensImportNode;
29
+ tokensImportNode = refs.tokensImportNode,
30
+ themeImportNode = refs.themeImportNode,
31
+ shouldAddFallbackImport = refs.shouldAddFallbackImport;
30
32
  var fontSizeValue = normaliseValue('fontSize', fontSizeRaw);
31
33
 
32
34
  // -- Font weight --
@@ -117,6 +119,8 @@ export var StyleObject = {
117
119
  matchingToken: matchingToken,
118
120
  nodesToReplace: nodesToReplace,
119
121
  tokensImportNode: tokensImportNode,
122
+ themeImportNode: themeImportNode,
123
+ shouldAddFallbackImport: shouldAddFallbackImport,
120
124
  fontWeightReplacement: fontWeightReplacement,
121
125
  fontFamilyReplacement: fontFamilyReplacement,
122
126
  fontStyleReplacement: fontStyleReplacement
@@ -163,20 +167,47 @@ export var StyleObject = {
163
167
  success: false
164
168
  };
165
169
  }
166
- var importDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/tokens');
170
+ var tokensImportDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/tokens');
167
171
 
168
172
  // If there is more than one `@atlaskit/tokens` import, then it becomes difficult to determine which import to transform
169
- if (importDeclaration.length > 1) {
173
+ if (tokensImportDeclaration.length > 1) {
170
174
  return {
171
175
  success: false
172
176
  };
173
177
  }
178
+
179
+ // This exists purely because we're not inlining the fallback values
180
+ // and instead referencing a `fontFallback` object that exists in @atlaskit/theme/typography.
181
+ // This is a temporary measure until fallbacks are no longer required
182
+ var shouldAddFallbackImport = 'full';
183
+ var themeImportDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/theme/typography');
184
+ if (themeImportDeclaration.length) {
185
+ // Import exists, check if specifier exists
186
+ shouldAddFallbackImport = 'specifier';
187
+ var fallbackImport = themeImportDeclaration[0].specifiers.find(function (specifier) {
188
+ // @atlaskit/theme/typography has no default export so we can safely narrow this type
189
+ if (!isNodeOfType(specifier, 'ImportSpecifier')) {
190
+ return false;
191
+ }
192
+ if (specifier.imported.name === 'fontFallback') {
193
+ return true;
194
+ }
195
+ return false;
196
+ });
197
+
198
+ // Exact import already exists, no need to add
199
+ if (fallbackImport) {
200
+ shouldAddFallbackImport = false;
201
+ }
202
+ }
174
203
  return {
175
204
  success: true,
176
205
  refs: {
177
206
  fontSizeNode: fontSizeNode,
178
207
  fontSizeRaw: fontSizeRaw,
179
- tokensImportNode: importDeclaration[0]
208
+ tokensImportNode: tokensImportDeclaration[0],
209
+ themeImportNode: themeImportDeclaration[0],
210
+ shouldAddFallbackImport: shouldAddFallbackImport
180
211
  }
181
212
  };
182
213
  },
@@ -185,14 +216,24 @@ export var StyleObject = {
185
216
  var matchingToken = refs.matchingToken,
186
217
  nodesToReplace = refs.nodesToReplace,
187
218
  tokensImportNode = refs.tokensImportNode,
219
+ themeImportNode = refs.themeImportNode,
220
+ shouldAddFallbackImport = refs.shouldAddFallbackImport,
188
221
  fontWeightReplacement = refs.fontWeightReplacement,
189
222
  fontFamilyReplacement = refs.fontFamilyReplacement,
190
223
  fontStyleReplacement = refs.fontStyleReplacement;
191
224
  var fontSizeNode = nodesToReplace[0];
192
- return (!tokensImportNode ? [insertTokensImport(fixer)] : []).concat(nodesToReplace.map(function (node, index) {
225
+ var root = context.getSourceCode().ast.body;
226
+ var fallbackImport;
227
+ if (shouldAddFallbackImport === 'full') {
228
+ fallbackImport = insertFallbackImportFull(root, fixer);
229
+ } else if (shouldAddFallbackImport === 'specifier') {
230
+ fallbackImport = insertFallbackImportSpecifier(fixer, themeImportNode);
231
+ }
232
+ var fallbackName = (matchingToken.tokenName === 'font.body' ? 'font.body.medium' : matchingToken.tokenName).replace('font', 'fontFallback');
233
+ return (!tokensImportNode ? [insertTokensImport(root, fixer)] : []).concat(fallbackImport ? [fallbackImport] : [], nodesToReplace.map(function (node, index) {
193
234
  // Replace first node with token, delete remaining nodes. Guaranteed to be fontSize
194
235
  if (index === 0) {
195
- return fixer.replaceText(node, "".concat(getTokenProperty('font', matchingToken.tokenName, matchingToken.tokenValue)));
236
+ return fixer.replaceText(node, "".concat(getTokenProperty('font', matchingToken.tokenName, fallbackName, true)));
196
237
  }
197
238
 
198
239
  // We don't replace fontWeight/fontFamily/fontStyle here in case it occurs before the font property.
@@ -1,6 +1,7 @@
1
- import { callExpression, identifier, isNodeOfType, literal, property } from 'eslint-codemod-utils';
1
+ import { callExpression, identifier, isNodeOfType, literal, memberExpression, property } from 'eslint-codemod-utils';
2
2
  import { typographyPalette } from '@atlaskit/tokens/palettes-raw';
3
3
  import { typographyAdg3 as typographyTokens } from '@atlaskit/tokens/tokens-raw';
4
+ import { Import, Root } from '../../ast-nodes';
4
5
  export var typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
5
6
  export var isTypographyProperty = function isTypographyProperty(propertyName) {
6
7
  return typographyProperties.includes(propertyName);
@@ -98,21 +99,38 @@ export function isValidPropertyNode(node) {
98
99
  }
99
100
  return true;
100
101
  }
101
- function getTokenNode(tokenName, tokenValue) {
102
+ function getTokenNode(tokenName, tokenValue, isFallbackMember) {
103
+ var fallback;
104
+ if (isFallbackMember) {
105
+ fallback = createMemberExpressionFromArray(tokenValue.split('.'));
106
+ } else {
107
+ fallback = literal(tokenValue);
108
+ }
102
109
  return callExpression({
103
110
  callee: identifier({
104
111
  name: 'token'
105
112
  }),
106
113
  arguments: [literal({
107
114
  value: "'".concat(tokenName, "'")
108
- }), literal(tokenValue)],
115
+ }), fallback],
109
116
  optional: false
110
117
  });
111
118
  }
119
+ function createMemberExpressionFromArray(array) {
120
+ if (array.length === 1) {
121
+ return identifier(array[0]);
122
+ }
123
+ var property = array.pop();
124
+ return memberExpression({
125
+ object: createMemberExpressionFromArray(array),
126
+ property: identifier(property)
127
+ });
128
+ }
112
129
  export function getTokenProperty(propertyName, tokenName, tokenFallback) {
130
+ var isFallbackMember = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
113
131
  return property({
114
132
  key: identifier(propertyName),
115
- value: getTokenNode(tokenName, tokenFallback)
133
+ value: getTokenNode(tokenName, tokenFallback, isFallbackMember)
116
134
  });
117
135
  }
118
136
  export function getLiteralProperty(propertyName, propertyValue) {
@@ -126,4 +144,19 @@ export function convertPropertyNodeToStringableNode(node) {
126
144
  key: node.key,
127
145
  value: node.value
128
146
  });
147
+ }
148
+ export function insertTokensImport(root, fixer) {
149
+ return Root.insertImport(root, {
150
+ module: '@atlaskit/tokens',
151
+ specifiers: ['token']
152
+ }, fixer);
153
+ }
154
+ export function insertFallbackImportFull(root, fixer) {
155
+ return Root.insertImport(root, {
156
+ module: '@atlaskit/theme/typography',
157
+ specifiers: ['fontFallback']
158
+ }, fixer);
159
+ }
160
+ export function insertFallbackImportSpecifier(fixer, themeImportNode) {
161
+ return Import.insertNamedSpecifiers(themeImportNode, ['fontFallback'], fixer);
129
162
  }
@@ -1,3 +1,10 @@
1
+ // This should be kept in sync with
2
+ // packages/design-system/eslint-plugin-ui-styling-standard/src/rules/utils/is-supported-import.tsx
3
+ // whenever possible.
4
+ //
5
+ // TODO: would having an @atlassian/eslint-plugin-design-system-common
6
+ // package be useful?
7
+
1
8
  // eslint-disable-next-line import/no-extraneous-dependencies
2
9
 
3
10
  export var CSS_IN_JS_IMPORTS = {
@@ -112,10 +119,12 @@ export var isCssMap = isSupportedImportWrapper('cssMap');
112
119
  export var isKeyframes = isSupportedImportWrapper('keyframes');
113
120
  // `styled` is also the explicit default of `styled-components` and `@emotion/styled`, so we also match on default imports generally
114
121
  export var isStyled = isSupportedImportWrapper('styled', ['styled-components', '@emotion/styled']);
122
+ export var isXcss = isSupportedImportWrapper('xcss');
115
123
  export var isImportedFrom = function isImportedFrom(moduleName) {
116
124
  var exactMatch = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
117
- return function (nodeToCheck, referencesInScope, importSources) {
118
- if (!importSources.some(function (importSource) {
125
+ return function (nodeToCheck, referencesInScope) {
126
+ var importSources = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
127
+ if (importSources && !importSources.some(function (importSource) {
119
128
  return importSource === moduleName || !exactMatch && importSource.startsWith(moduleName);
120
129
  })) {
121
130
  // Don't go through the trouble of checking the import sources does not include this
@@ -5,6 +5,5 @@ export type RuleConfig = {
5
5
  cssImportSource: ImportSource;
6
6
  xcssImportSource: ImportSource;
7
7
  excludeReactComponents: boolean;
8
- autoFixNames: boolean;
9
- fixNamesOnly: boolean;
8
+ autoFix: boolean;
10
9
  };
@@ -8,6 +8,8 @@ interface Refs {
8
8
  fontSizeNode: Property;
9
9
  fontSizeRaw: string | number;
10
10
  tokensImportNode: ImportDeclaration | undefined;
11
+ themeImportNode: ImportDeclaration | undefined;
12
+ shouldAddFallbackImport: 'full' | 'specifier' | false;
11
13
  }
12
14
  type Check = {
13
15
  success: boolean;
@@ -17,6 +19,8 @@ interface FixerRefs {
17
19
  matchingToken: TokenValueMap;
18
20
  nodesToReplace: Property[];
19
21
  tokensImportNode: ImportDeclaration | undefined;
22
+ themeImportNode: ImportDeclaration | undefined;
23
+ shouldAddFallbackImport: Refs['shouldAddFallbackImport'];
20
24
  fontWeightReplacement: StringableASTNode<Property> | undefined;
21
25
  fontFamilyReplacement: StringableASTNode<Property> | undefined;
22
26
  fontStyleReplacement: StringableASTNode<Property> | undefined;