@atlaskit/eslint-plugin-design-system 8.15.4 → 8.16.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 (102) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/constellation/index/usage.mdx +62 -16
  3. package/dist/cjs/rules/ensure-design-token-usage/typography.js +2 -2
  4. package/dist/cjs/rules/prefer-primitives/index.js +2 -3
  5. package/dist/cjs/rules/prefer-primitives/utils.js +16 -5
  6. package/dist/cjs/rules/use-primitives/index.js +89 -58
  7. package/dist/cjs/rules/use-primitives/transformers/css-to-xcss.js +95 -0
  8. package/dist/cjs/rules/use-primitives/transformers/index.js +31 -0
  9. package/dist/cjs/rules/use-primitives/transformers/jsx-element-to-box.js +26 -0
  10. package/dist/cjs/rules/use-primitives/utils/contains-only-supported-attrs.js +19 -0
  11. package/dist/cjs/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +31 -0
  12. package/dist/cjs/rules/use-primitives/utils/get-attribute-value-identifier.js +37 -0
  13. package/dist/cjs/rules/use-primitives/utils/get-function-argument-at-pos.js +10 -0
  14. package/dist/cjs/rules/use-primitives/utils/get-jsx-attribute-by-name.js +16 -0
  15. package/dist/cjs/rules/use-primitives/utils/get-variable-definition-value.js +29 -0
  16. package/dist/cjs/rules/use-primitives/utils/get-variable-usage-count.js +21 -0
  17. package/dist/cjs/rules/use-primitives/utils/index.js +89 -0
  18. package/dist/cjs/rules/use-primitives/utils/is-function-named.js +19 -0
  19. package/dist/cjs/rules/use-primitives/utils/is-valid-tag-name.js +13 -0
  20. package/dist/cjs/rules/use-primitives/utils/update-jsx-attribute-by-name.js +31 -0
  21. package/dist/cjs/rules/use-primitives/utils/update-jsx-element-name.js +16 -0
  22. package/dist/cjs/rules/use-primitives/utils/upsert-import-declaration.js +80 -0
  23. package/dist/es2019/rules/ensure-design-token-usage/typography.js +1 -1
  24. package/dist/es2019/rules/prefer-primitives/index.js +1 -2
  25. package/dist/es2019/rules/prefer-primitives/utils.js +11 -2
  26. package/dist/es2019/rules/use-primitives/index.js +91 -60
  27. package/dist/es2019/rules/use-primitives/transformers/css-to-xcss.js +91 -0
  28. package/dist/es2019/rules/use-primitives/transformers/index.js +2 -0
  29. package/dist/es2019/rules/use-primitives/transformers/jsx-element-to-box.js +16 -0
  30. package/dist/es2019/rules/use-primitives/utils/contains-only-supported-attrs.js +13 -0
  31. package/dist/es2019/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +26 -0
  32. package/dist/es2019/rules/use-primitives/utils/get-attribute-value-identifier.js +32 -0
  33. package/dist/es2019/rules/use-primitives/utils/get-function-argument-at-pos.js +4 -0
  34. package/dist/es2019/rules/use-primitives/utils/get-jsx-attribute-by-name.js +10 -0
  35. package/dist/es2019/rules/use-primitives/utils/get-variable-definition-value.js +23 -0
  36. package/dist/es2019/rules/use-primitives/utils/get-variable-usage-count.js +15 -0
  37. package/dist/es2019/rules/use-primitives/utils/index.js +12 -0
  38. package/dist/es2019/rules/use-primitives/utils/is-function-named.js +13 -0
  39. package/dist/es2019/rules/use-primitives/utils/is-valid-tag-name.js +7 -0
  40. package/dist/es2019/rules/use-primitives/utils/update-jsx-attribute-by-name.js +26 -0
  41. package/dist/es2019/rules/use-primitives/utils/update-jsx-element-name.js +12 -0
  42. package/dist/es2019/rules/use-primitives/utils/upsert-import-declaration.js +76 -0
  43. package/dist/esm/rules/ensure-design-token-usage/typography.js +1 -1
  44. package/dist/esm/rules/prefer-primitives/index.js +1 -2
  45. package/dist/esm/rules/prefer-primitives/utils.js +13 -2
  46. package/dist/esm/rules/use-primitives/index.js +91 -60
  47. package/dist/esm/rules/use-primitives/transformers/css-to-xcss.js +88 -0
  48. package/dist/esm/rules/use-primitives/transformers/index.js +2 -0
  49. package/dist/esm/rules/use-primitives/transformers/jsx-element-to-box.js +19 -0
  50. package/dist/esm/rules/use-primitives/utils/contains-only-supported-attrs.js +13 -0
  51. package/dist/esm/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +26 -0
  52. package/dist/esm/rules/use-primitives/utils/get-attribute-value-identifier.js +32 -0
  53. package/dist/esm/rules/use-primitives/utils/get-function-argument-at-pos.js +4 -0
  54. package/dist/esm/rules/use-primitives/utils/get-jsx-attribute-by-name.js +10 -0
  55. package/dist/esm/rules/use-primitives/utils/get-variable-definition-value.js +23 -0
  56. package/dist/esm/rules/use-primitives/utils/get-variable-usage-count.js +15 -0
  57. package/dist/esm/rules/use-primitives/utils/index.js +12 -0
  58. package/dist/esm/rules/use-primitives/utils/is-function-named.js +13 -0
  59. package/dist/esm/rules/use-primitives/utils/is-valid-tag-name.js +7 -0
  60. package/dist/esm/rules/use-primitives/utils/update-jsx-attribute-by-name.js +24 -0
  61. package/dist/esm/rules/use-primitives/utils/update-jsx-element-name.js +10 -0
  62. package/dist/esm/rules/use-primitives/utils/upsert-import-declaration.js +75 -0
  63. package/dist/types/rules/prefer-primitives/utils.d.ts +2 -1
  64. package/dist/types/rules/use-primitives/transformers/css-to-xcss.d.ts +9 -0
  65. package/dist/types/rules/use-primitives/transformers/index.d.ts +2 -0
  66. package/dist/types/rules/use-primitives/transformers/jsx-element-to-box.d.ts +3 -0
  67. package/dist/types/rules/use-primitives/utils/contains-only-supported-attrs.d.ts +2 -0
  68. package/dist/types/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.d.ts +9 -0
  69. package/dist/types/rules/use-primitives/utils/get-attribute-value-identifier.d.ts +14 -0
  70. package/dist/types/rules/use-primitives/utils/get-function-argument-at-pos.d.ts +2 -0
  71. package/dist/types/rules/use-primitives/utils/get-jsx-attribute-by-name.d.ts +2 -0
  72. package/dist/types/rules/use-primitives/utils/get-variable-definition-value.d.ts +2 -0
  73. package/dist/types/rules/use-primitives/utils/get-variable-usage-count.d.ts +6 -0
  74. package/dist/types/rules/use-primitives/utils/index.d.ts +12 -0
  75. package/dist/types/rules/use-primitives/utils/is-function-named.d.ts +2 -0
  76. package/dist/types/rules/use-primitives/utils/is-valid-tag-name.d.ts +2 -0
  77. package/dist/types/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +3 -0
  78. package/dist/types/rules/use-primitives/utils/update-jsx-element-name.d.ts +3 -0
  79. package/dist/types/rules/use-primitives/utils/upsert-import-declaration.d.ts +11 -0
  80. package/dist/types-ts4.5/rules/prefer-primitives/utils.d.ts +2 -1
  81. package/dist/types-ts4.5/rules/use-primitives/transformers/css-to-xcss.d.ts +9 -0
  82. package/dist/types-ts4.5/rules/use-primitives/transformers/index.d.ts +2 -0
  83. package/dist/types-ts4.5/rules/use-primitives/transformers/jsx-element-to-box.d.ts +3 -0
  84. package/dist/types-ts4.5/rules/use-primitives/utils/contains-only-supported-attrs.d.ts +2 -0
  85. package/dist/types-ts4.5/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.d.ts +9 -0
  86. package/dist/types-ts4.5/rules/use-primitives/utils/get-attribute-value-identifier.d.ts +14 -0
  87. package/dist/types-ts4.5/rules/use-primitives/utils/get-function-argument-at-pos.d.ts +2 -0
  88. package/dist/types-ts4.5/rules/use-primitives/utils/get-jsx-attribute-by-name.d.ts +2 -0
  89. package/dist/types-ts4.5/rules/use-primitives/utils/get-variable-definition-value.d.ts +2 -0
  90. package/dist/types-ts4.5/rules/use-primitives/utils/get-variable-usage-count.d.ts +6 -0
  91. package/dist/types-ts4.5/rules/use-primitives/utils/index.d.ts +12 -0
  92. package/dist/types-ts4.5/rules/use-primitives/utils/is-function-named.d.ts +2 -0
  93. package/dist/types-ts4.5/rules/use-primitives/utils/is-valid-tag-name.d.ts +2 -0
  94. package/dist/types-ts4.5/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +3 -0
  95. package/dist/types-ts4.5/rules/use-primitives/utils/update-jsx-element-name.d.ts +3 -0
  96. package/dist/types-ts4.5/rules/use-primitives/utils/upsert-import-declaration.d.ts +11 -0
  97. package/package.json +3 -1
  98. package/dist/cjs/rules/use-primitives/utils.js +0 -364
  99. package/dist/es2019/rules/use-primitives/utils.js +0 -353
  100. package/dist/esm/rules/use-primitives/utils.js +0 -359
  101. package/dist/types/rules/use-primitives/utils.d.ts +0 -13
  102. package/dist/types-ts4.5/rules/use-primitives/utils.d.ts +0 -13
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getVariableUsagesCount = void 0;
7
+ /**
8
+ * Using Regex here because otherwise we'd need to traverse the entire AST
9
+ * We should harden this logic as we go.
10
+ */
11
+ var getVariableUsagesCount = exports.getVariableUsagesCount = function getVariableUsagesCount(variableName, context) {
12
+ if (!variableName) {
13
+ return 0;
14
+ }
15
+ var source = context.getSourceCode().text;
16
+ var matches = Array.from(source.matchAll(new RegExp("[^a-z]".concat(variableName, "[^a-z]"), 'g')));
17
+
18
+ // subtract 1 because one of the matches is the variable definition:
19
+ // e.g. a regex will find two `beep`s in this: `const beep = 'hello'; console.log(beep)`
20
+ return matches.length - 1;
21
+ };
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "containsOnlySupportedAttrs", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _containsOnlySupportedAttrs.containsOnlySupportedAttrs;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "convertASTObjectExpressionToJSObject", {
13
+ enumerable: true,
14
+ get: function get() {
15
+ return _convertAstObjectExpressionToJsObject.convertASTObjectExpressionToJSObject;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "getAttributeValueIdentifier", {
19
+ enumerable: true,
20
+ get: function get() {
21
+ return _getAttributeValueIdentifier.getAttributeValueIdentifier;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "getFunctionArgumentAtPos", {
25
+ enumerable: true,
26
+ get: function get() {
27
+ return _getFunctionArgumentAtPos.getFunctionArgumentAtPos;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "getJSXAttributeByName", {
31
+ enumerable: true,
32
+ get: function get() {
33
+ return _getJsxAttributeByName.getJSXAttributeByName;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "getVariableDefinitionValue", {
37
+ enumerable: true,
38
+ get: function get() {
39
+ return _getVariableDefinitionValue.getVariableDefinitionValue;
40
+ }
41
+ });
42
+ Object.defineProperty(exports, "getVariableUsagesCount", {
43
+ enumerable: true,
44
+ get: function get() {
45
+ return _getVariableUsageCount.getVariableUsagesCount;
46
+ }
47
+ });
48
+ Object.defineProperty(exports, "isFunctionNamed", {
49
+ enumerable: true,
50
+ get: function get() {
51
+ return _isFunctionNamed.isFunctionNamed;
52
+ }
53
+ });
54
+ Object.defineProperty(exports, "isValidTagName", {
55
+ enumerable: true,
56
+ get: function get() {
57
+ return _isValidTagName.isValidTagName;
58
+ }
59
+ });
60
+ Object.defineProperty(exports, "updateJSXAttributeByName", {
61
+ enumerable: true,
62
+ get: function get() {
63
+ return _updateJsxAttributeByName.updateJSXAttributeByName;
64
+ }
65
+ });
66
+ Object.defineProperty(exports, "updateJSXElementName", {
67
+ enumerable: true,
68
+ get: function get() {
69
+ return _updateJsxElementName.updateJSXElementName;
70
+ }
71
+ });
72
+ Object.defineProperty(exports, "upsertImportDeclaration", {
73
+ enumerable: true,
74
+ get: function get() {
75
+ return _upsertImportDeclaration.upsertImportDeclaration;
76
+ }
77
+ });
78
+ var _containsOnlySupportedAttrs = require("./contains-only-supported-attrs");
79
+ var _convertAstObjectExpressionToJsObject = require("./convert-ast-object-expression-to-js-object");
80
+ var _getAttributeValueIdentifier = require("./get-attribute-value-identifier");
81
+ var _getFunctionArgumentAtPos = require("./get-function-argument-at-pos");
82
+ var _getJsxAttributeByName = require("./get-jsx-attribute-by-name");
83
+ var _getVariableDefinitionValue = require("./get-variable-definition-value");
84
+ var _getVariableUsageCount = require("./get-variable-usage-count");
85
+ var _isFunctionNamed = require("./is-function-named");
86
+ var _isValidTagName = require("./is-valid-tag-name");
87
+ var _updateJsxAttributeByName = require("./update-jsx-attribute-by-name");
88
+ var _updateJsxElementName = require("./update-jsx-element-name");
89
+ var _upsertImportDeclaration = require("./upsert-import-declaration");
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isFunctionNamed = void 0;
7
+ var _eslintCodemodUtils = require("eslint-codemod-utils");
8
+ var isFunctionNamed = exports.isFunctionNamed = function isFunctionNamed(node, name) {
9
+ if (!node) {
10
+ return false;
11
+ }
12
+ if (!(0, _eslintCodemodUtils.isNodeOfType)(node.node.init, 'CallExpression')) {
13
+ return false;
14
+ }
15
+ if (!(0, _eslintCodemodUtils.isNodeOfType)(node.node.init.callee, 'Identifier')) {
16
+ return false;
17
+ }
18
+ return node.node.init.callee.name === name;
19
+ };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isValidTagName = void 0;
7
+ var validPrimitiveElements = new Set(['div']);
8
+ var isValidTagName = exports.isValidTagName = function isValidTagName(node) {
9
+ if (node.openingElement.name.type !== 'JSXIdentifier') {
10
+ return false;
11
+ }
12
+ return validPrimitiveElements.has(node.openingElement.name.name);
13
+ };
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.updateJSXAttributeByName = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _eslintCodemodUtils = require("eslint-codemod-utils");
10
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
11
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
12
+ var updateJSXAttributeByName = exports.updateJSXAttributeByName = function updateJSXAttributeByName(oldName, newName, node, fixer) {
13
+ var openingElement = node.openingElement;
14
+ var attributes = openingElement.attributes;
15
+ var existingAttribute = attributes.find(function (attr) {
16
+ if (attr.type !== 'JSXAttribute') {
17
+ return false;
18
+ }
19
+ if (attr.name.type !== 'JSXIdentifier') {
20
+ return false;
21
+ }
22
+ return attr.name.name === oldName;
23
+ });
24
+ if (!existingAttribute || existingAttribute.type !== 'JSXAttribute') {
25
+ return [];
26
+ }
27
+ var newAttribute = (0, _eslintCodemodUtils.jsxAttribute)(_objectSpread(_objectSpread({}, existingAttribute), {}, {
28
+ name: (0, _eslintCodemodUtils.jsxIdentifier)(newName)
29
+ }));
30
+ return fixer.replaceText(existingAttribute, newAttribute.toString());
31
+ };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.updateJSXElementName = void 0;
7
+ var _eslintCodemodUtils = require("eslint-codemod-utils");
8
+ var updateJSXElementName = exports.updateJSXElementName = function updateJSXElementName(node, newName, fixer) {
9
+ var openingElement = node.openingElement,
10
+ closingElement = node.closingElement;
11
+ var newOpeningElement = fixer.replaceText(openingElement.name, (0, _eslintCodemodUtils.jsxIdentifier)(newName).toString());
12
+ var newClosingElement = closingElement &&
13
+ // Self closing tags, like `<div />` don't need to have the closing tag updated
14
+ fixer.replaceText(closingElement.name, (0, _eslintCodemodUtils.jsxIdentifier)(newName).toString());
15
+ return [newOpeningElement, newClosingElement];
16
+ };
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.upsertImportDeclaration = void 0;
7
+ var _eslintCodemodUtils = require("eslint-codemod-utils");
8
+ /**
9
+ * Note: Very naive implementation. Does not handle default imports, namespace imports, or aliased imports.
10
+ * Add that functionality when needed.
11
+ *
12
+ * However, does handle duplicate identifiers.
13
+ */
14
+ var upsertImportDeclaration = exports.upsertImportDeclaration = function upsertImportDeclaration(_ref, context, fixer) {
15
+ var packageName = _ref.packageName,
16
+ specifiers = _ref.specifiers;
17
+ // Find any imports that match the packageName
18
+ var body = context.getSourceCode().ast.body;
19
+ var existingImports = body.filter(function (node) {
20
+ if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'ImportDeclaration')) {
21
+ return false;
22
+ }
23
+ if (!(0, _eslintCodemodUtils.hasImportDeclaration)(node, packageName)) {
24
+ return false;
25
+ }
26
+ return true;
27
+ });
28
+
29
+ /**
30
+ * This can happen for cases like:
31
+ * ```
32
+ * import { Stack } from '@atlaskit/primitives'
33
+ * import type { StackProps } from '@atlaskit/primitives'
34
+ * ```
35
+ *
36
+ * Ignore these cases for now to reduce scope creep
37
+ *
38
+ * TODO: Support multiple imports
39
+ */
40
+ if (existingImports.length > 1) {
41
+ return;
42
+ }
43
+
44
+ // The import doesn't exist yet, we can just insert a whole new one
45
+ if (existingImports.length === 0) {
46
+ return fixer.insertTextBefore(body[0], "".concat((0, _eslintCodemodUtils.insertImportDeclaration)(packageName, specifiers), ";\n"));
47
+ }
48
+
49
+ // The import exists so, modify the existing one
50
+ var existingImport = existingImports[0]; // We have already validated that only one exists
51
+
52
+ /**
53
+ * `insertImportSpecifier()` has the unfortunate implementation detail of naively adding duplicate specifiers.
54
+ * e.g. calling
55
+ * `insertImportSpecifier(importDecl, 'xcss')`
56
+ * on
57
+ * `import { Inline, xcss } from '@atlaskit/primitives'`
58
+ * will result in:
59
+ * `import { Inline, xcss, xcss } from '@atlaskit/primitives'`.
60
+ * So, we need to filter out specifiers that are already imported.
61
+ */
62
+ var uniqueImportSpecifiers = specifiers.filter(function (specifier) {
63
+ return !existingImport.specifiers.find(function (existingSpecifier) {
64
+ if (existingSpecifier.type !== 'ImportSpecifier') {
65
+ return false;
66
+ }
67
+ return existingSpecifier.imported.name === specifier;
68
+ });
69
+ });
70
+
71
+ // Ensures the import doesn't end up like: `import { Box, xcss, } from '@atlaskit/primitives';`
72
+ // which can happen if the import decl already contains all import specifiers
73
+ if (uniqueImportSpecifiers.length === 0) {
74
+ return;
75
+ }
76
+ return fixer.replaceText(existingImport, "".concat((0, _eslintCodemodUtils.insertImportSpecifier)(existingImport,
77
+ // `insertImportSpecifier` only accepts one specifier, and we can't call it multiple times on the same import, because fixes can't overlap
78
+ // So, join the array into a string literal, and hope for the best 🤞
79
+ uniqueImportSpecifiers.join(', ')), ";\n"));
80
+ };
@@ -1,5 +1,5 @@
1
1
  import { isNodeOfType } from 'eslint-codemod-utils';
2
- import { typography as typographyTokens } from '@atlaskit/tokens/tokens-raw';
2
+ import { typographyAdg3 as typographyTokens } from '@atlaskit/tokens/tokens-raw';
3
3
  const typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
4
4
  export const isTypographyProperty = propertyName => {
5
5
  return typographyProperties.includes(propertyName);
@@ -1,9 +1,8 @@
1
1
  // eslint-disable-next-line import/no-extraneous-dependencies
2
2
 
3
3
  import { isNodeOfType } from 'eslint-codemod-utils';
4
- import { validPrimitiveElements } from '../use-primitives/utils';
5
4
  import { createLintRule } from '../utils/create-rule';
6
- import { shouldSuggest } from './utils';
5
+ import { shouldSuggest, validPrimitiveElements } from './utils';
7
6
  const primitiveDocsUrl = 'https://go.atlassian.com/dst-prefer-primitives';
8
7
  const rule = createLintRule({
9
8
  meta: {
@@ -1,4 +1,13 @@
1
- import { getChildrenByType, isValidPrimitiveElement, isWhiteSpace } from '../use-primitives/utils';
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ export const validPrimitiveElements = new Set(['div', 'span', 'article', 'aside', 'dialog', 'footer', 'header', 'li', 'main', 'nav', 'ol', 'section', 'ul']);
3
+ const getChildrenByType = (node, types) => {
4
+ return node.children.filter(child => {
5
+ return types.find(type => isNodeOfType(child, type));
6
+ });
7
+ };
8
+ const isValidPrimitiveElement = node => {
9
+ return validPrimitiveElements.has(node.openingElement.name.name);
10
+ };
2
11
  export const shouldSuggest = node => {
3
12
  if (!node) {
4
13
  return false;
@@ -16,7 +25,7 @@ export const shouldSuggest = node => {
16
25
  * </div>
17
26
  * ```
18
27
  */
19
- const nonWhiteSpaceTextChildren = getChildrenByType(node, ['JSXText']).filter(child => !isWhiteSpace(child.value));
28
+ const nonWhiteSpaceTextChildren = getChildrenByType(node, ['JSXText']).filter(child => child.value.trim() !== '');
20
29
  if (nonWhiteSpaceTextChildren.length > 0) {
21
30
  return false;
22
31
  }
@@ -1,11 +1,8 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
2
-
3
- import { isNodeOfType } from 'eslint-codemod-utils';
1
+ import { getIdentifierInParentScope, isNodeOfType } from 'eslint-codemod-utils';
4
2
  import { createLintRule } from '../utils/create-rule';
5
- import { primitiveFixer, shouldSuggestBox, shouldSuggestInline, shouldSuggestStack } from './utils';
6
- const boxDocsUrl = 'https://staging.atlassian.design/components/primitives/box';
7
- const inlineDocsUrl = 'https://staging.atlassian.design/components/primitives/inline';
8
- const stackDocsUrl = 'https://staging.atlassian.design/components/primitives/stack';
3
+ import { jsxElementToBoxTransformer, supportedStylesMap } from './transformers';
4
+ import { containsOnlySupportedAttrs, convertASTObjectExpressionToJSObject, getAttributeValueIdentifier, getFunctionArgumentAtPos, getJSXAttributeByName, getVariableDefinitionValue, getVariableUsagesCount, isFunctionNamed, isValidTagName } from './utils';
5
+ const boxDocsUrl = 'https://atlassian.design/components/primitives/box';
9
6
  const rule = createLintRule({
10
7
  meta: {
11
8
  name: 'use-primitives',
@@ -18,21 +15,11 @@ const rule = createLintRule({
18
15
  severity: 'warn'
19
16
  },
20
17
  messages: {
21
- preferPrimitivesBox: `This "{{element}}" may be able to be replaced with a "Box". See ${boxDocsUrl} for guidance.`,
22
- preferPrimitivesInline: `This "{{element}}" may be able to be replaced with an "Inline". See ${inlineDocsUrl} for guidance.`,
23
- preferPrimitivesStack: `This "{{element}}" may be able to be replaced with a "Stack". See ${stackDocsUrl} for guidance.`
18
+ preferPrimitivesBox: `This "{{element}}" may be able to be replaced with a "Box". See ${boxDocsUrl} for guidance.`
24
19
  }
25
20
  },
26
21
  create(context) {
27
22
  return {
28
- /**
29
- * Traverse file
30
- * Look for any JSX opening element and check what it is
31
- * a) it's already a component
32
- * b) it's native HTML
33
- *
34
- * if b) suggest alternative use of primitives
35
- */
36
23
  JSXOpeningElement(node) {
37
24
  if (!isNodeOfType(node, 'JSXOpeningElement')) {
38
25
  return;
@@ -40,15 +27,10 @@ const rule = createLintRule({
40
27
  if (!isNodeOfType(node.name, 'JSXIdentifier')) {
41
28
  return;
42
29
  }
43
- const suggestBox = shouldSuggestBox(node === null || node === void 0 ? void 0 : node.parent);
44
- const suggestInline = shouldSuggestInline(node === null || node === void 0 ? void 0 : node.parent, context);
45
- const suggestStack = shouldSuggestStack(node === null || node === void 0 ? void 0 : node.parent, context);
46
-
47
- // const suggestText = shouldSuggestText(
48
- // node?.parent as any,
49
- // // context.getScope(),
50
- // );
51
-
30
+ if (!isNodeOfType(node.parent, 'JSXElement')) {
31
+ return;
32
+ }
33
+ const suggestBox = shouldSuggestBox(node.parent, context);
52
34
  if (suggestBox) {
53
35
  context.report({
54
36
  node: node,
@@ -58,39 +40,7 @@ const rule = createLintRule({
58
40
  },
59
41
  suggest: [{
60
42
  desc: `Convert to Box`,
61
- fix: primitiveFixer(node, 'Box', context)
62
- }]
63
- });
64
- }
65
- if (suggestInline) {
66
- context.report({
67
- node: node,
68
- messageId: 'preferPrimitivesInline',
69
- data: {
70
- element: node.name.name
71
- },
72
- suggest: [{
73
- desc: `Convert to Inline`,
74
- fix: primitiveFixer(node, 'Inline', context)
75
- }, {
76
- desc: `Convert to Flex`,
77
- fix: primitiveFixer(node, 'Flex', context)
78
- }]
79
- });
80
- }
81
- if (suggestStack) {
82
- context.report({
83
- node: node,
84
- messageId: 'preferPrimitivesStack',
85
- data: {
86
- element: node.name.name
87
- },
88
- suggest: [{
89
- desc: `Convert to Stack`,
90
- fix: primitiveFixer(node, 'Stack', context)
91
- }, {
92
- desc: `Convert to Flex`,
93
- fix: primitiveFixer(node, 'Flex', context)
43
+ fix: jsxElementToBoxTransformer(node.parent, context)
94
44
  }]
95
45
  });
96
46
  }
@@ -98,4 +48,85 @@ const rule = createLintRule({
98
48
  };
99
49
  }
100
50
  });
51
+ const shouldSuggestBox = (node, context
52
+ // scope: Scope.Scope,
53
+ ) => {
54
+ if (!node) {
55
+ return false;
56
+ }
57
+
58
+ // Currently we only support `div`, but possibly more in future
59
+ if (!isValidTagName(node)) {
60
+ return false;
61
+ }
62
+
63
+ // Currently we only support elements that contain only a `css` attr, possibly more in future
64
+ if (!containsOnlySupportedAttrs(node)) {
65
+ return false;
66
+ }
67
+
68
+ // Get the `css` attr
69
+ const cssAttr = getJSXAttributeByName(node.openingElement, 'css');
70
+
71
+ // Get the prop value, e.g. `myStyles` in `css={myStyles}`
72
+ const cssVariableName = getAttributeValueIdentifier(cssAttr);
73
+
74
+ // `cssVariableName` could be undefined if the maker has
75
+ // done something like `css={[styles1, styles2]}`, `css={...styles}`, etc
76
+ if (!cssVariableName) {
77
+ return false;
78
+ }
79
+
80
+ /**
81
+ * Make sure the variable is not used in multiple JSX elements:
82
+ * ```
83
+ * <div css={paddingStyles}></div>
84
+ * <input css={paddingStyles}></input>
85
+ * ```
86
+ */
87
+ if (getVariableUsagesCount(cssVariableName, context) !== 1) {
88
+ return false;
89
+ }
90
+
91
+ // Find where `cssVariableName` is defined. We're looking for `const myStyles = css({...})`
92
+ const cssVariableDefinition = getIdentifierInParentScope(context.getScope(), cssVariableName);
93
+ const cssVariableValue = getVariableDefinitionValue(cssVariableDefinition);
94
+
95
+ // Check if `cssVariableValue` is a function called `css()`
96
+ if (!isFunctionNamed(cssVariableValue, 'css')) {
97
+ return false;
98
+ }
99
+
100
+ // get the `{}` in `css({})`
101
+ // Zero indexed
102
+ const cssObjectExpression = getFunctionArgumentAtPos(cssVariableValue, 0);
103
+
104
+ // Bail on empty `css()` calls
105
+ if (!cssObjectExpression) {
106
+ return false;
107
+ }
108
+ const cssObject = convertASTObjectExpressionToJSObject(cssObjectExpression);
109
+
110
+ // Bail if there are less or more than 1 styles defined
111
+ if (Object.keys(cssObject).length !== 1) {
112
+ return false;
113
+ }
114
+
115
+ // NOTE: Our approach with this lint rule is to strictly whitelist css properties we can map.
116
+ // It means we have to provide mappings for everything (e.g. `display: block`).
117
+ // However, from a maker's experience, it's much better that the rule doesn't report (if we miss a mapping)
118
+ // than the rule reporting on things that can't be mapped.
119
+ const containsOnlyValidStyles = Object.keys(cssObject).every(styleProperty => {
120
+ const styleValue = cssObject[styleProperty];
121
+ return supportedStylesMap[styleProperty] &&
122
+ // Is the key something we can map
123
+ supportedStylesMap[styleProperty][styleValue] // Is the value something we can map
124
+ ;
125
+ });
126
+
127
+ if (!containsOnlyValidStyles) {
128
+ return false;
129
+ }
130
+ return true;
131
+ };
101
132
  export default rule;
@@ -0,0 +1,91 @@
1
+ import { getIdentifierInParentScope, identifier, isNodeOfType, literal } from 'eslint-codemod-utils';
2
+ import { getAttributeValueIdentifier, getFunctionArgumentAtPos, getJSXAttributeByName, getVariableDefinitionValue } from '../utils';
3
+ export const cssToXcssTransformer = (node, context, fixer) => {
4
+ /**
5
+ * Note: The logic here is very similar to the logic in `shouldSuggestBox`. i.e.
6
+ * 1. Find the `css` attr
7
+ * 2. Find the variableName (`myStyles` in `css={myStyles}`)
8
+ * 3. Find the `const myStyles = css({ padding: '8px' })`
9
+ * 4. Find the style object `{ padding: '8px' }`
10
+ *
11
+ * The only difference is, we've already performed very defensive checks for these steps in `shouldSuggestBox`,
12
+ * so there's no need to do those checks here.
13
+ *
14
+ * The repetition could be avoided by combining the 'shouldSuggest' and 'fixCode' steps. However, there are tradeoffs
15
+ * to that approach (mainly poor separation of concerns). I'm un-opinionated about which strategy we use. I just opted
16
+ * for this because the original `use-primitives` rule did this.
17
+ */
18
+ const cssAttr = getJSXAttributeByName(node.openingElement, 'css');
19
+ const cssVariableName = getAttributeValueIdentifier(cssAttr);
20
+ if (!cssVariableName) {
21
+ return [];
22
+ }
23
+ const cssVariableDefinition = getIdentifierInParentScope(context.getScope(), cssVariableName);
24
+ const cssVariableValue = getVariableDefinitionValue(cssVariableDefinition);
25
+ if (!cssVariableValue) {
26
+ return [];
27
+ }
28
+ const cssObjectExpression = getFunctionArgumentAtPos(cssVariableValue, 0);
29
+ return [
30
+ // Update `css` function name to `xcss`.
31
+ fixer.replaceText(cssVariableValue.node.init.callee, identifier('xcss').toString()),
32
+ // Update css object values to xcss values. e.g. `'8px'` -> `'space.100'`
33
+ // Note: `properties` in this context is a group of AST nodes that make up a key/value pair in an object.
34
+ // e.g. `padding: '8px'`. For clarity, it's renamed to `entry` inside the `.map()`.
35
+ ...cssObjectExpression.properties.map(entry => {
36
+ if (!isNodeOfType(entry, 'Property')) {
37
+ return;
38
+ }
39
+ if (!isNodeOfType(entry.key, 'Identifier')) {
40
+ return;
41
+ }
42
+ if (!isNodeOfType(entry.value, 'Literal')) {
43
+ return;
44
+ }
45
+ const value = entry.value.value;
46
+ if (typeof value !== 'string') {
47
+ return;
48
+ }
49
+ return fixer.replaceText(entry.value, literal(`'${supportedStylesMap[entry.key.name][value]}'`).toString());
50
+ })];
51
+ };
52
+ export const spaceTokenMap = {
53
+ '0px': 'space.0',
54
+ '2px': 'space.025',
55
+ '4px': 'space.050',
56
+ '6px': 'space.075',
57
+ '8px': 'space.100',
58
+ '12px': 'space.150',
59
+ '16px': 'space.200',
60
+ '20px': 'space.250',
61
+ '24px': 'space.300',
62
+ '32px': 'space.400',
63
+ '40px': 'space.500',
64
+ '48px': 'space.600',
65
+ '64px': 'space.800',
66
+ '80px': 'space.1000'
67
+ };
68
+ export const supportedStylesMap = {
69
+ padding: spaceTokenMap,
70
+ paddingBlock: spaceTokenMap,
71
+ paddingBlockEnd: spaceTokenMap,
72
+ paddingBlockStart: spaceTokenMap,
73
+ paddingBottom: spaceTokenMap,
74
+ paddingInline: spaceTokenMap,
75
+ paddingInlineEnd: spaceTokenMap,
76
+ paddingInlineStart: spaceTokenMap,
77
+ paddingLeft: spaceTokenMap,
78
+ paddingRight: spaceTokenMap,
79
+ paddingTop: spaceTokenMap,
80
+ margin: spaceTokenMap,
81
+ marginBlock: spaceTokenMap,
82
+ marginBlockEnd: spaceTokenMap,
83
+ marginBlockStart: spaceTokenMap,
84
+ marginBottom: spaceTokenMap,
85
+ marginInline: spaceTokenMap,
86
+ marginInlineEnd: spaceTokenMap,
87
+ marginInlineStart: spaceTokenMap,
88
+ marginLeft: spaceTokenMap,
89
+ marginRight: spaceTokenMap,
90
+ marginTop: spaceTokenMap
91
+ };
@@ -0,0 +1,2 @@
1
+ export { cssToXcssTransformer, supportedStylesMap, spaceTokenMap } from './css-to-xcss';
2
+ export { jsxElementToBoxTransformer } from './jsx-element-to-box';
@@ -0,0 +1,16 @@
1
+ import { updateJSXAttributeByName, updateJSXElementName, upsertImportDeclaration } from '../utils';
2
+ import { cssToXcssTransformer } from './css-to-xcss';
3
+ export const jsxElementToBoxTransformer = (node, context) => {
4
+ return fixer => {
5
+ // Insert `import { Box, xcss } from '@atlaskit/primitives'`
6
+ // Or, if the import exists already, update it to include `Box`, and `xcss`
7
+ const importFixes = upsertImportDeclaration({
8
+ packageName: '@atlaskit/primitives',
9
+ specifiers: ['Box', 'xcss']
10
+ }, context, fixer);
11
+ const elementNameFixes = updateJSXElementName(node, 'Box', fixer);
12
+ const attributeFix = updateJSXAttributeByName('css', 'xcss', node, fixer);
13
+ const cssToXcssTransform = cssToXcssTransformer(node, context, fixer);
14
+ return [importFixes, attributeFix, ...elementNameFixes, ...cssToXcssTransform].filter(fix => Boolean(fix)); // Some of the transformers can return arrays with undefined, so filter them out
15
+ };
16
+ };
@@ -0,0 +1,13 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ const supportedAttributes = ['css'];
3
+ export const containsOnlySupportedAttrs = node => {
4
+ return node.openingElement.attributes.every(attr => {
5
+ if (!isNodeOfType(attr, 'JSXAttribute')) {
6
+ return false;
7
+ }
8
+ if (!isNodeOfType(attr.name, 'JSXIdentifier')) {
9
+ return false;
10
+ }
11
+ return supportedAttributes.includes(attr.name.name);
12
+ });
13
+ };
@@ -0,0 +1,26 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+
3
+ // FIXME: This is only loosely typed
4
+
5
+ /**
6
+ * Note: Not recursive. Only handles top level key/value pairs
7
+ */
8
+ export const convertASTObjectExpressionToJSObject = styles => {
9
+ const styleObj = {};
10
+ styles.properties.forEach(prop => {
11
+ if (!isNodeOfType(prop, 'Property')) {
12
+ return;
13
+ }
14
+ if (!isNodeOfType(prop.key, 'Identifier')) {
15
+ return;
16
+ }
17
+ if (!isNodeOfType(prop.value, 'Literal')) {
18
+ return;
19
+ }
20
+ if (typeof prop.value.value !== 'string') {
21
+ return;
22
+ }
23
+ styleObj[prop.key.name] = prop.value.value;
24
+ });
25
+ return styleObj;
26
+ };