@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atlaskit/eslint-plugin-design-system
2
2
 
3
+ ## 9.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#84334](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/84334) [`b2134858ba58`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/b2134858ba58) - The `use-tokens-typography` rule now applies token fallbacks that reference the constants exported via `@atlaskit/theme/typography`, rather than applying the fallback string inline.
8
+
9
+ ## 9.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - [#83454](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/83454) [`be8b7ad6ff8e`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/be8b7ad6ff8e) - Remove name autofixer from `consistent-css-prop-usage`. Variables for css / xcss / cssMap functions will no longer be required to end with "Styles".
14
+
15
+ [BREAKING] Some rule options have been changed:
16
+
17
+ - `fixNamesOnly` and `autoFixNames` have been removed, as there is no longer an autofixer that enforces variable names.
18
+ - If you use `fixNamesOnly: true`, we recommend switching to using `autoFix: false`.
19
+ - Users of the `autoFixNames` option should remove this from their configuration.
20
+ - `autoFix` option has been added. This controls whether the remaining autofixers should run or not (e.g. hoisting `css` function calls, wrapping objects in `css` function calls), and is `true` by default.
21
+
3
22
  ## 8.38.0
4
23
 
5
24
  ### Minor Changes
@@ -16,7 +16,6 @@ This rule checks for the following cases:
16
16
  - Styles must be defined in the same file as their usage, and not be imported.
17
17
  - Styles should not contain spread operators (e.g. `css({ ...spreadStyles })`).
18
18
  - Styles must all be defined at the top of the file, or at the bottom of the file.
19
- - Styles must be in a variable whose name ends in `styles` (or `Styles`).
20
19
 
21
20
  This rule also has an autofixer that enforces and fixes the code (where practical) to meet the above requirements.
22
21
 
@@ -81,7 +80,7 @@ function Button({ children }) {
81
80
 
82
81
  ### Correct
83
82
 
84
- **Using the css() function to create a style object that follows the naming convention (ends in Styles) and passing it as a variable into the css={...} JSX attribute.**
83
+ **Using the css() function to create a style object and passing it as a variable into the css={...} JSX attribute.**
85
84
 
86
85
  With the following options turned on:
87
86
 
@@ -181,23 +180,8 @@ Whether to exclude `css` attributes of React components from being affected by t
181
180
 
182
181
  This is `false` by default.
183
182
 
184
- ### autoFixNames
183
+ ### autoFix
185
184
 
186
- When set to `true`, this rule will _turn on_ an autofixer that adds `Styles` at the end of existing style variables.
187
-
188
- ```tsx
189
- // vvvvv will be renamed to `myCssStyles`
190
- const myCss = { color: 'blue' };
191
- ```
192
-
193
- Note that when hoisting styles, the name of the newly created style variable follows its own naming scheme (`styles`, `styles_1`, `styles_2`, ...) and is not affected by the value of `autoFixNames`.
185
+ When set to `true`, this rule will turn on the autofixer. Set this to `false` if you do not want the autofixers to run.
194
186
 
195
187
  This is `true` by default.
196
-
197
- ### fixNamesOnly
198
-
199
- When set to `true`, this rule will _turn off_ all autofixers other than the autofixer adding `Styles` to the end of existing style variables (which is controlled by `autoFixNames`). In other words, styles will no longer be hoisted and styles will no longer be wrapped in a `css`/`xcss` function call.
200
-
201
- This is `false` by default.
202
-
203
- If `autoFixNames` is `false` and `fixNamesOnly` is `true`, all autofixers will be turned off.
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.default = void 0;
8
- var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
8
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10
9
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
11
10
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
@@ -40,6 +39,18 @@ var getProgramNode = function getProgramNode(expression) {
40
39
  }
41
40
  return expression.parent;
42
41
  };
42
+ var isDeclaredInsideComponent = function isDeclaredInsideComponent(expression) {
43
+ // These nodes imply that there is a distinct own scope (function scope / block scope),
44
+ // and so the presence of them means that expression was not defined in the module scope.
45
+ var NOT_MODULE_SCOPE = ['ArrowFunctionExpression', 'BlockStatement', 'ClassDeclaration', 'FunctionExpression'];
46
+ while (expression.type !== 'Program') {
47
+ if (NOT_MODULE_SCOPE.includes(expression.type)) {
48
+ return true;
49
+ }
50
+ expression = expression.parent;
51
+ }
52
+ return false;
53
+ };
43
54
  var JSXExpressionLinter = /*#__PURE__*/function () {
44
55
  // File-level tracking of styles hoisted from the cssAtTopOfModule/cssAtBottomOfModule fixers.
45
56
 
@@ -119,13 +130,13 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
119
130
  });
120
131
  return;
121
132
  }
122
- if (identifier.parent.parent.parent.type !== 'Program') {
133
+ if (isDeclaredInsideComponent(identifier)) {
123
134
  // When variable is declared inside the component
124
135
  this.context.report({
125
136
  node: sourceIdentifier,
126
137
  messageId: this.configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
127
138
  fix: function fix(fixer) {
128
- if (_this.configuration.fixNamesOnly) {
139
+ if (!_this.configuration.autoFix) {
129
140
  return [];
130
141
  }
131
142
  return _this.fixCssNotInModuleScope(fixer, identifier, false);
@@ -147,7 +158,7 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
147
158
  node: identifier,
148
159
  messageId: 'cssObjectTypeOnly',
149
160
  fix: function fix(fixer) {
150
- if (_this.configuration.fixNamesOnly) {
161
+ if (!_this.configuration.autoFix) {
151
162
  return [];
152
163
  }
153
164
  return _this.addCssFunctionCall(fixer, identifier.parent);
@@ -323,10 +334,11 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
323
334
  // The last value is the bottom of the file
324
335
  fixerNodePlacement = programNode.body[programNode.body.length - 1];
325
336
  } else {
337
+ var _ref3;
326
338
  // Place after the last ImportDeclaration
327
- fixerNodePlacement = programNode.body.length === 1 ? programNode.body[0] : programNode.body.find(function (node) {
339
+ fixerNodePlacement = (_ref3 = programNode.body.length === 1 ? programNode.body[0] : programNode.body.find(function (node) {
328
340
  return node.type !== 'ImportDeclaration';
329
- });
341
+ })) !== null && _ref3 !== void 0 ? _ref3 : fixerNodePlacement;
330
342
  }
331
343
  var moduleString;
332
344
  var fixes = [];
@@ -410,7 +422,7 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
410
422
  node: expression,
411
423
  messageId: this.configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
412
424
  fix: function fix(fixer) {
413
- if (_this3.configuration.fixNamesOnly) {
425
+ if (!_this3.configuration.autoFix) {
414
426
  return [];
415
427
  }
416
428
 
@@ -455,8 +467,7 @@ var defaultConfig = {
455
467
  cssImportSource: _isSupportedImport.CSS_IN_JS_IMPORTS.compiled,
456
468
  xcssImportSource: _isSupportedImport.CSS_IN_JS_IMPORTS.atlaskitPrimitives,
457
469
  excludeReactComponents: false,
458
- fixNamesOnly: false,
459
- autoFixNames: true
470
+ autoFix: true
460
471
  };
461
472
  var rule = (0, _createRule.createLintRule)({
462
473
  meta: {
@@ -475,8 +486,7 @@ var rule = (0, _createRule.createLintRule)({
475
486
  cssObjectTypeOnly: "Create styles using objects passed to a css function call, e.g. `css({ textAlign: 'center'; })`.",
476
487
  cssInModule: "Imported styles should not be used; instead define in the module, import a component, or use a design token.",
477
488
  cssArrayStylesOnly: "Compose styles with an array on the css prop instead of using object spread.",
478
- noMemberExpressions: "Styles should be a regular variable (e.g. 'buttonStyles'), not a member of an object (e.g. 'myObject.styles').",
479
- shouldEndInStyles: 'Declared styles should end in "styles".'
489
+ noMemberExpressions: "Styles should be a regular variable (e.g. 'buttonStyles'), not a member of an object (e.g. 'myObject.styles')."
480
490
  },
481
491
  schema: [{
482
492
  type: 'object',
@@ -500,10 +510,7 @@ var rule = (0, _createRule.createLintRule)({
500
510
  excludeReactComponents: {
501
511
  type: 'boolean'
502
512
  },
503
- autoFixNames: {
504
- type: 'boolean'
505
- },
506
- fixNamesOnly: {
513
+ autoFix: {
507
514
  type: 'boolean'
508
515
  }
509
516
  },
@@ -511,80 +518,41 @@ var rule = (0, _createRule.createLintRule)({
511
518
  }]
512
519
  },
513
520
  create: function create(context) {
514
- var _ref3;
515
521
  var mergedConfig = (0, _assign.default)({}, defaultConfig, context.options[0]);
516
- var declarationSuffix = 'Styles';
517
- var callSelectorFunctions = [].concat((0, _toConsumableArray2.default)(mergedConfig.cssFunctions), ['cssMap']);
518
- var callSelector = callSelectorFunctions.map(function (fn) {
519
- return "CallExpression[callee.name=".concat(fn, "]");
520
- }).join(',');
521
- return _ref3 = {}, (0, _defineProperty2.default)(_ref3, callSelector, function (node) {
522
- if (node.parent.type !== 'VariableDeclarator') {
523
- // We aren't interested in these that don't have a parent.
524
- return;
525
- }
526
- var identifier = node.parent.id;
527
- if (identifier.type === 'Identifier' && (identifier.name.endsWith(declarationSuffix) || identifier.name.startsWith(declarationSuffix.toLowerCase()) || identifier.name === declarationSuffix.toLowerCase())) {
528
- // Already prefixed! Nothing to do.
529
- return;
530
- }
531
- if (!mergedConfig.autoFixNames) {
532
- return;
533
- }
534
- context.report({
535
- node: identifier,
536
- messageId: 'shouldEndInStyles',
537
- fix: function fix(fixer) {
538
- var _context$getScope$var;
539
- var identifierName = identifier.type === 'Identifier' ? identifier.name : '';
540
- var references = ((_context$getScope$var = context.getScope().variables.find(function (varb) {
541
- return varb.name === identifierName;
542
- })) === null || _context$getScope$var === void 0 ? void 0 : _context$getScope$var.references) || [];
543
- var newIdentifierName = "".concat(identifierName
544
- // Remove "Style" if it is already defined.
545
- .replace(/Style$/, '')).concat(declarationSuffix);
546
- return references.filter(function (ref) {
547
- return ref.identifier.name === identifierName;
548
- }).map(function (ref) {
549
- if (ref.identifier.parent && ref.identifier.parent.shorthand) {
550
- return fixer.replaceText(ref.identifier, "".concat(identifierName, ": ").concat(newIdentifierName));
551
- }
552
- return fixer.replaceText(ref.identifier, newIdentifierName);
553
- });
554
- }
555
- });
556
- }), (0, _defineProperty2.default)(_ref3, "JSXAttribute", function JSXAttribute(nodeOriginal) {
557
- var node = nodeOriginal;
558
- var name = node.name,
559
- value = node.value;
560
- if (mergedConfig.excludeReactComponents && node.parent.type === 'JSXOpeningElement') {
561
- // e.g. <item.before />
562
- if (node.parent.name.type === 'JSXMemberExpression') {
563
- return;
564
- }
565
- // e.g. <div />, <MenuItem />
566
- if (node.parent.name.type === 'JSXIdentifier' && !isDOMElementName(node.parent.name.name)) {
567
- return;
568
- }
569
- }
570
- if (name.type === 'JSXIdentifier' && mergedConfig.cssFunctions.includes(name.name)) {
571
- // When not a jsx expression. For eg. css=""
572
- if ((value === null || value === void 0 ? void 0 : value.type) !== 'JSXExpressionContainer') {
573
- context.report({
574
- node: node,
575
- messageId: mergedConfig.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule'
576
- });
577
- return;
522
+ return {
523
+ JSXAttribute: function JSXAttribute(nodeOriginal) {
524
+ var node = nodeOriginal;
525
+ var name = node.name,
526
+ value = node.value;
527
+ if (mergedConfig.excludeReactComponents && node.parent.type === 'JSXOpeningElement') {
528
+ // e.g. <item.before />
529
+ if (node.parent.name.type === 'JSXMemberExpression') {
530
+ return;
531
+ }
532
+ // e.g. <div />, <MenuItem />
533
+ if (node.parent.name.type === 'JSXIdentifier' && !isDOMElementName(node.parent.name.name)) {
534
+ return;
535
+ }
578
536
  }
579
- if (value.expression.type === 'JSXEmptyExpression') {
580
- // e.g. the comment in
581
- // <div css={/* Hello there */} />
582
- return;
537
+ if (name.type === 'JSXIdentifier' && mergedConfig.cssFunctions.includes(name.name)) {
538
+ // When not a jsx expression. For eg. css=""
539
+ if ((value === null || value === void 0 ? void 0 : value.type) !== 'JSXExpressionContainer') {
540
+ context.report({
541
+ node: node,
542
+ messageId: mergedConfig.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule'
543
+ });
544
+ return;
545
+ }
546
+ if (value.expression.type === 'JSXEmptyExpression') {
547
+ // e.g. the comment in
548
+ // <div css={/* Hello there */} />
549
+ return;
550
+ }
551
+ var linter = new JSXExpressionLinter(context, name.name, mergedConfig, value.expression);
552
+ linter.run();
583
553
  }
584
- var linter = new JSXExpressionLinter(context, name.name, mergedConfig, value.expression);
585
- linter.run();
586
554
  }
587
- }), _ref3;
555
+ };
588
556
  }
589
557
  });
590
558
  var _default = exports.default = rule;
@@ -32,7 +32,9 @@ var StyleObject = exports.StyleObject = {
32
32
  }
33
33
  var fontSizeNode = refs.fontSizeNode,
34
34
  fontSizeRaw = refs.fontSizeRaw,
35
- tokensImportNode = refs.tokensImportNode;
35
+ tokensImportNode = refs.tokensImportNode,
36
+ themeImportNode = refs.themeImportNode,
37
+ shouldAddFallbackImport = refs.shouldAddFallbackImport;
36
38
  var fontSizeValue = (0, _utils.normaliseValue)('fontSize', fontSizeRaw);
37
39
 
38
40
  // -- Font weight --
@@ -123,6 +125,8 @@ var StyleObject = exports.StyleObject = {
123
125
  matchingToken: matchingToken,
124
126
  nodesToReplace: nodesToReplace,
125
127
  tokensImportNode: tokensImportNode,
128
+ themeImportNode: themeImportNode,
129
+ shouldAddFallbackImport: shouldAddFallbackImport,
126
130
  fontWeightReplacement: fontWeightReplacement,
127
131
  fontFamilyReplacement: fontFamilyReplacement,
128
132
  fontStyleReplacement: fontStyleReplacement
@@ -169,20 +173,47 @@ var StyleObject = exports.StyleObject = {
169
173
  success: false
170
174
  };
171
175
  }
172
- var importDeclaration = _astNodes.Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/tokens');
176
+ var tokensImportDeclaration = _astNodes.Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/tokens');
173
177
 
174
178
  // If there is more than one `@atlaskit/tokens` import, then it becomes difficult to determine which import to transform
175
- if (importDeclaration.length > 1) {
179
+ if (tokensImportDeclaration.length > 1) {
176
180
  return {
177
181
  success: false
178
182
  };
179
183
  }
184
+
185
+ // This exists purely because we're not inlining the fallback values
186
+ // and instead referencing a `fontFallback` object that exists in @atlaskit/theme/typography.
187
+ // This is a temporary measure until fallbacks are no longer required
188
+ var shouldAddFallbackImport = 'full';
189
+ var themeImportDeclaration = _astNodes.Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/theme/typography');
190
+ if (themeImportDeclaration.length) {
191
+ // Import exists, check if specifier exists
192
+ shouldAddFallbackImport = 'specifier';
193
+ var fallbackImport = themeImportDeclaration[0].specifiers.find(function (specifier) {
194
+ // @atlaskit/theme/typography has no default export so we can safely narrow this type
195
+ if (!(0, _eslintCodemodUtils.isNodeOfType)(specifier, 'ImportSpecifier')) {
196
+ return false;
197
+ }
198
+ if (specifier.imported.name === 'fontFallback') {
199
+ return true;
200
+ }
201
+ return false;
202
+ });
203
+
204
+ // Exact import already exists, no need to add
205
+ if (fallbackImport) {
206
+ shouldAddFallbackImport = false;
207
+ }
208
+ }
180
209
  return {
181
210
  success: true,
182
211
  refs: {
183
212
  fontSizeNode: fontSizeNode,
184
213
  fontSizeRaw: fontSizeRaw,
185
- tokensImportNode: importDeclaration[0]
214
+ tokensImportNode: tokensImportDeclaration[0],
215
+ themeImportNode: themeImportDeclaration[0],
216
+ shouldAddFallbackImport: shouldAddFallbackImport
186
217
  }
187
218
  };
188
219
  },
@@ -191,14 +222,24 @@ var StyleObject = exports.StyleObject = {
191
222
  var matchingToken = refs.matchingToken,
192
223
  nodesToReplace = refs.nodesToReplace,
193
224
  tokensImportNode = refs.tokensImportNode,
225
+ themeImportNode = refs.themeImportNode,
226
+ shouldAddFallbackImport = refs.shouldAddFallbackImport,
194
227
  fontWeightReplacement = refs.fontWeightReplacement,
195
228
  fontFamilyReplacement = refs.fontFamilyReplacement,
196
229
  fontStyleReplacement = refs.fontStyleReplacement;
197
230
  var fontSizeNode = nodesToReplace[0];
198
- return (!tokensImportNode ? [(0, _utils.insertTokensImport)(fixer)] : []).concat(nodesToReplace.map(function (node, index) {
231
+ var root = context.getSourceCode().ast.body;
232
+ var fallbackImport;
233
+ if (shouldAddFallbackImport === 'full') {
234
+ fallbackImport = (0, _utils2.insertFallbackImportFull)(root, fixer);
235
+ } else if (shouldAddFallbackImport === 'specifier') {
236
+ fallbackImport = (0, _utils2.insertFallbackImportSpecifier)(fixer, themeImportNode);
237
+ }
238
+ var fallbackName = (matchingToken.tokenName === 'font.body' ? 'font.body.medium' : matchingToken.tokenName).replace('font', 'fontFallback');
239
+ return (!tokensImportNode ? [(0, _utils2.insertTokensImport)(root, fixer)] : []).concat(fallbackImport ? [fallbackImport] : [], nodesToReplace.map(function (node, index) {
199
240
  // Replace first node with token, delete remaining nodes. Guaranteed to be fontSize
200
241
  if (index === 0) {
201
- return fixer.replaceText(node, "".concat((0, _utils2.getTokenProperty)('font', matchingToken.tokenName, matchingToken.tokenValue)));
242
+ return fixer.replaceText(node, "".concat((0, _utils2.getTokenProperty)('font', matchingToken.tokenName, fallbackName, true)));
202
243
  }
203
244
 
204
245
  // We don't replace fontWeight/fontFamily/fontStyle here in case it occurs before the font property.
@@ -11,6 +11,9 @@ exports.findTypographyTokenForValues = findTypographyTokenForValues;
11
11
  exports.fontWeightTokens = exports.fontWeightMap = exports.fontFamilyTokens = void 0;
12
12
  exports.getLiteralProperty = getLiteralProperty;
13
13
  exports.getTokenProperty = getTokenProperty;
14
+ exports.insertFallbackImportFull = insertFallbackImportFull;
15
+ exports.insertFallbackImportSpecifier = insertFallbackImportSpecifier;
16
+ exports.insertTokensImport = insertTokensImport;
14
17
  exports.isTypographyProperty = exports.isFontSizeSmall = exports.isFontSize = exports.isFontFamily = exports.isCodeFontFamily = void 0;
15
18
  exports.isValidPropertyNode = isValidPropertyNode;
16
19
  exports.notUndefined = notUndefined;
@@ -18,6 +21,7 @@ exports.typographyValueToToken = exports.typographyProperties = void 0;
18
21
  var _eslintCodemodUtils = require("eslint-codemod-utils");
19
22
  var _palettesRaw = require("@atlaskit/tokens/palettes-raw");
20
23
  var _tokensRaw = require("@atlaskit/tokens/tokens-raw");
24
+ var _astNodes = require("../../ast-nodes");
21
25
  var typographyProperties = exports.typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
22
26
  var isTypographyProperty = exports.isTypographyProperty = function isTypographyProperty(propertyName) {
23
27
  return typographyProperties.includes(propertyName);
@@ -115,21 +119,38 @@ function isValidPropertyNode(node) {
115
119
  }
116
120
  return true;
117
121
  }
118
- function getTokenNode(tokenName, tokenValue) {
122
+ function getTokenNode(tokenName, tokenValue, isFallbackMember) {
123
+ var fallback;
124
+ if (isFallbackMember) {
125
+ fallback = createMemberExpressionFromArray(tokenValue.split('.'));
126
+ } else {
127
+ fallback = (0, _eslintCodemodUtils.literal)(tokenValue);
128
+ }
119
129
  return (0, _eslintCodemodUtils.callExpression)({
120
130
  callee: (0, _eslintCodemodUtils.identifier)({
121
131
  name: 'token'
122
132
  }),
123
133
  arguments: [(0, _eslintCodemodUtils.literal)({
124
134
  value: "'".concat(tokenName, "'")
125
- }), (0, _eslintCodemodUtils.literal)(tokenValue)],
135
+ }), fallback],
126
136
  optional: false
127
137
  });
128
138
  }
139
+ function createMemberExpressionFromArray(array) {
140
+ if (array.length === 1) {
141
+ return (0, _eslintCodemodUtils.identifier)(array[0]);
142
+ }
143
+ var property = array.pop();
144
+ return (0, _eslintCodemodUtils.memberExpression)({
145
+ object: createMemberExpressionFromArray(array),
146
+ property: (0, _eslintCodemodUtils.identifier)(property)
147
+ });
148
+ }
129
149
  function getTokenProperty(propertyName, tokenName, tokenFallback) {
150
+ var isFallbackMember = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
130
151
  return (0, _eslintCodemodUtils.property)({
131
152
  key: (0, _eslintCodemodUtils.identifier)(propertyName),
132
- value: getTokenNode(tokenName, tokenFallback)
153
+ value: getTokenNode(tokenName, tokenFallback, isFallbackMember)
133
154
  });
134
155
  }
135
156
  function getLiteralProperty(propertyName, propertyValue) {
@@ -143,4 +164,19 @@ function convertPropertyNodeToStringableNode(node) {
143
164
  key: node.key,
144
165
  value: node.value
145
166
  });
167
+ }
168
+ function insertTokensImport(root, fixer) {
169
+ return _astNodes.Root.insertImport(root, {
170
+ module: '@atlaskit/tokens',
171
+ specifiers: ['token']
172
+ }, fixer);
173
+ }
174
+ function insertFallbackImportFull(root, fixer) {
175
+ return _astNodes.Root.insertImport(root, {
176
+ module: '@atlaskit/theme/typography',
177
+ specifiers: ['fontFallback']
178
+ }, fixer);
179
+ }
180
+ function insertFallbackImportSpecifier(fixer, themeImportNode) {
181
+ return _astNodes.Import.insertNamedSpecifiers(themeImportNode, ['fontFallback'], fixer);
146
182
  }
@@ -3,7 +3,14 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.isStyledComponents = exports.isStyled = exports.isKeyframes = exports.isImportedFrom = exports.isEmotion = exports.isCxFunction = exports.isCssMap = exports.isCss = exports.isCompiled = exports.getImportSources = exports.DEFAULT_IMPORT_SOURCES = exports.CSS_IN_JS_IMPORTS = void 0;
6
+ exports.isXcss = exports.isStyledComponents = exports.isStyled = exports.isKeyframes = exports.isImportedFrom = exports.isEmotion = exports.isCxFunction = exports.isCssMap = exports.isCss = exports.isCompiled = exports.getImportSources = exports.DEFAULT_IMPORT_SOURCES = exports.CSS_IN_JS_IMPORTS = void 0;
7
+ // This should be kept in sync with
8
+ // packages/design-system/eslint-plugin-ui-styling-standard/src/rules/utils/is-supported-import.tsx
9
+ // whenever possible.
10
+ //
11
+ // TODO: would having an @atlassian/eslint-plugin-design-system-common
12
+ // package be useful?
13
+
7
14
  // eslint-disable-next-line import/no-extraneous-dependencies
8
15
 
9
16
  var CSS_IN_JS_IMPORTS = exports.CSS_IN_JS_IMPORTS = {
@@ -118,10 +125,12 @@ var isCssMap = exports.isCssMap = isSupportedImportWrapper('cssMap');
118
125
  var isKeyframes = exports.isKeyframes = isSupportedImportWrapper('keyframes');
119
126
  // `styled` is also the explicit default of `styled-components` and `@emotion/styled`, so we also match on default imports generally
120
127
  var isStyled = exports.isStyled = isSupportedImportWrapper('styled', ['styled-components', '@emotion/styled']);
128
+ var isXcss = exports.isXcss = isSupportedImportWrapper('xcss');
121
129
  var isImportedFrom = exports.isImportedFrom = function isImportedFrom(moduleName) {
122
130
  var exactMatch = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
123
- return function (nodeToCheck, referencesInScope, importSources) {
124
- if (!importSources.some(function (importSource) {
131
+ return function (nodeToCheck, referencesInScope) {
132
+ var importSources = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
133
+ if (importSources && !importSources.some(function (importSource) {
125
134
  return importSource === moduleName || !exactMatch && importSource.startsWith(moduleName);
126
135
  })) {
127
136
  // Don't go through the trouble of checking the import sources does not include this
@@ -24,6 +24,18 @@ const getProgramNode = expression => {
24
24
  }
25
25
  return expression.parent;
26
26
  };
27
+ const isDeclaredInsideComponent = expression => {
28
+ // These nodes imply that there is a distinct own scope (function scope / block scope),
29
+ // and so the presence of them means that expression was not defined in the module scope.
30
+ const NOT_MODULE_SCOPE = ['ArrowFunctionExpression', 'BlockStatement', 'ClassDeclaration', 'FunctionExpression'];
31
+ while (expression.type !== 'Program') {
32
+ if (NOT_MODULE_SCOPE.includes(expression.type)) {
33
+ return true;
34
+ }
35
+ expression = expression.parent;
36
+ }
37
+ return false;
38
+ };
27
39
  class JSXExpressionLinter {
28
40
  // File-level tracking of styles hoisted from the cssAtTopOfModule/cssAtBottomOfModule fixers.
29
41
 
@@ -92,13 +104,13 @@ class JSXExpressionLinter {
92
104
  });
93
105
  return;
94
106
  }
95
- if (identifier.parent.parent.parent.type !== 'Program') {
107
+ if (isDeclaredInsideComponent(identifier)) {
96
108
  // When variable is declared inside the component
97
109
  this.context.report({
98
110
  node: sourceIdentifier,
99
111
  messageId: this.configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
100
112
  fix: fixer => {
101
- if (this.configuration.fixNamesOnly) {
113
+ if (!this.configuration.autoFix) {
102
114
  return [];
103
115
  }
104
116
  return this.fixCssNotInModuleScope(fixer, identifier, false);
@@ -120,7 +132,7 @@ class JSXExpressionLinter {
120
132
  node: identifier,
121
133
  messageId: 'cssObjectTypeOnly',
122
134
  fix: fixer => {
123
- if (this.configuration.fixNamesOnly) {
135
+ if (!this.configuration.autoFix) {
124
136
  return [];
125
137
  }
126
138
  return this.addCssFunctionCall(fixer, identifier.parent);
@@ -285,8 +297,9 @@ class JSXExpressionLinter {
285
297
  // The last value is the bottom of the file
286
298
  fixerNodePlacement = programNode.body[programNode.body.length - 1];
287
299
  } else {
300
+ var _ref;
288
301
  // Place after the last ImportDeclaration
289
- fixerNodePlacement = programNode.body.length === 1 ? programNode.body[0] : programNode.body.find(node => node.type !== 'ImportDeclaration');
302
+ fixerNodePlacement = (_ref = programNode.body.length === 1 ? programNode.body[0] : programNode.body.find(node => node.type !== 'ImportDeclaration')) !== null && _ref !== void 0 ? _ref : fixerNodePlacement;
290
303
  }
291
304
  let moduleString;
292
305
  let fixes = [];
@@ -363,7 +376,7 @@ class JSXExpressionLinter {
363
376
  node: expression,
364
377
  messageId: this.configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
365
378
  fix: fixer => {
366
- if (this.configuration.fixNamesOnly) {
379
+ if (!this.configuration.autoFix) {
367
380
  return [];
368
381
  }
369
382
 
@@ -404,8 +417,7 @@ const defaultConfig = {
404
417
  cssImportSource: CSS_IN_JS_IMPORTS.compiled,
405
418
  xcssImportSource: CSS_IN_JS_IMPORTS.atlaskitPrimitives,
406
419
  excludeReactComponents: false,
407
- fixNamesOnly: false,
408
- autoFixNames: true
420
+ autoFix: true
409
421
  };
410
422
  const rule = createLintRule({
411
423
  meta: {
@@ -424,8 +436,7 @@ const rule = createLintRule({
424
436
  cssObjectTypeOnly: `Create styles using objects passed to a css function call, e.g. \`css({ textAlign: 'center'; })\`.`,
425
437
  cssInModule: `Imported styles should not be used; instead define in the module, import a component, or use a design token.`,
426
438
  cssArrayStylesOnly: `Compose styles with an array on the css prop instead of using object spread.`,
427
- noMemberExpressions: `Styles should be a regular variable (e.g. 'buttonStyles'), not a member of an object (e.g. 'myObject.styles').`,
428
- shouldEndInStyles: 'Declared styles should end in "styles".'
439
+ noMemberExpressions: `Styles should be a regular variable (e.g. 'buttonStyles'), not a member of an object (e.g. 'myObject.styles').`
429
440
  },
430
441
  schema: [{
431
442
  type: 'object',
@@ -449,10 +460,7 @@ const rule = createLintRule({
449
460
  excludeReactComponents: {
450
461
  type: 'boolean'
451
462
  },
452
- autoFixNames: {
453
- type: 'boolean'
454
- },
455
- fixNamesOnly: {
463
+ autoFix: {
456
464
  type: 'boolean'
457
465
  }
458
466
  },
@@ -461,42 +469,7 @@ const rule = createLintRule({
461
469
  },
462
470
  create(context) {
463
471
  const mergedConfig = assign({}, defaultConfig, context.options[0]);
464
- const declarationSuffix = 'Styles';
465
- const callSelectorFunctions = [...mergedConfig.cssFunctions, 'cssMap'];
466
- const callSelector = callSelectorFunctions.map(fn => `CallExpression[callee.name=${fn}]`).join(',');
467
472
  return {
468
- [callSelector]: node => {
469
- if (node.parent.type !== 'VariableDeclarator') {
470
- // We aren't interested in these that don't have a parent.
471
- return;
472
- }
473
- const identifier = node.parent.id;
474
- if (identifier.type === 'Identifier' && (identifier.name.endsWith(declarationSuffix) || identifier.name.startsWith(declarationSuffix.toLowerCase()) || identifier.name === declarationSuffix.toLowerCase())) {
475
- // Already prefixed! Nothing to do.
476
- return;
477
- }
478
- if (!mergedConfig.autoFixNames) {
479
- return;
480
- }
481
- context.report({
482
- node: identifier,
483
- messageId: 'shouldEndInStyles',
484
- fix: fixer => {
485
- var _context$getScope$var;
486
- const identifierName = identifier.type === 'Identifier' ? identifier.name : '';
487
- const references = ((_context$getScope$var = context.getScope().variables.find(varb => varb.name === identifierName)) === null || _context$getScope$var === void 0 ? void 0 : _context$getScope$var.references) || [];
488
- const newIdentifierName = `${identifierName
489
- // Remove "Style" if it is already defined.
490
- .replace(/Style$/, '')}${declarationSuffix}`;
491
- return references.filter(ref => ref.identifier.name === identifierName).map(ref => {
492
- if (ref.identifier.parent && ref.identifier.parent.shorthand) {
493
- return fixer.replaceText(ref.identifier, `${identifierName}: ${newIdentifierName}`);
494
- }
495
- return fixer.replaceText(ref.identifier, newIdentifierName);
496
- });
497
- }
498
- });
499
- },
500
473
  JSXAttribute(nodeOriginal) {
501
474
  const node = nodeOriginal;
502
475
  const {