@atlaskit/eslint-plugin-design-system 8.23.1 → 8.23.3

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 (136) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/ast-nodes/function-call.js +48 -0
  3. package/dist/cjs/ast-nodes/import.js +49 -0
  4. package/dist/cjs/ast-nodes/index.js +40 -0
  5. package/dist/cjs/ast-nodes/jsx-attribute.js +64 -0
  6. package/dist/cjs/ast-nodes/jsx-element.js +55 -0
  7. package/dist/cjs/ast-nodes/root.js +34 -0
  8. package/dist/cjs/rules/consistent-css-prop-usage/index.js +25 -5
  9. package/dist/cjs/rules/use-primitives/index.js +8 -104
  10. package/dist/cjs/rules/use-primitives/transformers/compiled-styled/convert-jsx-call-site.js +39 -0
  11. package/dist/cjs/rules/use-primitives/transformers/compiled-styled/convert-styled-component-call-to-jsx.js +44 -0
  12. package/dist/cjs/rules/use-primitives/{utils → transformers/compiled-styled}/find-valid-styled-component-call.js +5 -2
  13. package/dist/cjs/rules/use-primitives/transformers/compiled-styled/index.js +117 -0
  14. package/dist/cjs/rules/use-primitives/transformers/compiled-styled/supported.js +10 -0
  15. package/dist/cjs/rules/use-primitives/transformers/compiled-styled/upsert-import-declaration.js +37 -0
  16. package/dist/cjs/rules/use-primitives/transformers/emotion-css/contains-only-supported-attrs.js +27 -0
  17. package/dist/cjs/rules/use-primitives/transformers/emotion-css/index.js +125 -0
  18. package/dist/cjs/rules/use-primitives/transformers/emotion-css/supported.js +10 -0
  19. package/dist/cjs/rules/use-primitives/transformers/emotion-css/upsert-import-declaration.js +37 -0
  20. package/dist/cjs/rules/use-primitives/transformers/index.js +10 -10
  21. package/dist/cjs/rules/use-primitives/utils/index.js +1 -43
  22. package/dist/cjs/rules/use-primitives/utils/is-valid-css-properties-to-transform.js +3 -0
  23. package/dist/es2019/ast-nodes/function-call.js +42 -0
  24. package/dist/es2019/ast-nodes/import.js +42 -0
  25. package/dist/es2019/ast-nodes/index.js +5 -0
  26. package/dist/es2019/ast-nodes/jsx-attribute.js +59 -0
  27. package/dist/es2019/ast-nodes/jsx-element.js +50 -0
  28. package/dist/es2019/ast-nodes/root.js +28 -0
  29. package/dist/es2019/rules/consistent-css-prop-usage/index.js +23 -5
  30. package/dist/es2019/rules/use-primitives/index.js +9 -105
  31. package/dist/es2019/rules/use-primitives/transformers/compiled-styled/convert-jsx-call-site.js +29 -0
  32. package/dist/es2019/rules/use-primitives/transformers/compiled-styled/convert-styled-component-call-to-jsx.js +37 -0
  33. package/dist/es2019/rules/use-primitives/{utils → transformers/compiled-styled}/find-valid-styled-component-call.js +2 -2
  34. package/dist/es2019/rules/use-primitives/transformers/compiled-styled/index.js +107 -0
  35. package/dist/es2019/rules/use-primitives/transformers/compiled-styled/supported.js +4 -0
  36. package/dist/es2019/rules/use-primitives/transformers/compiled-styled/upsert-import-declaration.js +30 -0
  37. package/dist/es2019/rules/use-primitives/transformers/emotion-css/contains-only-supported-attrs.js +19 -0
  38. package/dist/es2019/rules/use-primitives/transformers/emotion-css/index.js +115 -0
  39. package/dist/es2019/rules/use-primitives/transformers/emotion-css/supported.js +4 -0
  40. package/dist/es2019/rules/use-primitives/transformers/emotion-css/upsert-import-declaration.js +30 -0
  41. package/dist/es2019/rules/use-primitives/transformers/index.js +2 -2
  42. package/dist/es2019/rules/use-primitives/utils/index.js +1 -7
  43. package/dist/es2019/rules/use-primitives/utils/is-valid-css-properties-to-transform.js +3 -0
  44. package/dist/esm/ast-nodes/function-call.js +42 -0
  45. package/dist/esm/ast-nodes/import.js +43 -0
  46. package/dist/esm/ast-nodes/index.js +5 -0
  47. package/dist/esm/ast-nodes/jsx-attribute.js +59 -0
  48. package/dist/esm/ast-nodes/jsx-element.js +50 -0
  49. package/dist/esm/ast-nodes/root.js +28 -0
  50. package/dist/esm/rules/consistent-css-prop-usage/index.js +25 -5
  51. package/dist/esm/rules/use-primitives/index.js +9 -105
  52. package/dist/esm/rules/use-primitives/transformers/compiled-styled/convert-jsx-call-site.js +30 -0
  53. package/dist/esm/rules/use-primitives/transformers/compiled-styled/convert-styled-component-call-to-jsx.js +38 -0
  54. package/dist/esm/rules/use-primitives/{utils → transformers/compiled-styled}/find-valid-styled-component-call.js +2 -2
  55. package/dist/esm/rules/use-primitives/transformers/compiled-styled/index.js +107 -0
  56. package/dist/esm/rules/use-primitives/transformers/compiled-styled/supported.js +4 -0
  57. package/dist/esm/rules/use-primitives/transformers/compiled-styled/upsert-import-declaration.js +29 -0
  58. package/dist/esm/rules/use-primitives/transformers/emotion-css/contains-only-supported-attrs.js +19 -0
  59. package/dist/esm/rules/use-primitives/transformers/emotion-css/index.js +115 -0
  60. package/dist/esm/rules/use-primitives/transformers/emotion-css/supported.js +4 -0
  61. package/dist/esm/rules/use-primitives/transformers/emotion-css/upsert-import-declaration.js +29 -0
  62. package/dist/esm/rules/use-primitives/transformers/index.js +2 -2
  63. package/dist/esm/rules/use-primitives/utils/index.js +1 -7
  64. package/dist/esm/rules/use-primitives/utils/is-valid-css-properties-to-transform.js +3 -0
  65. package/dist/types/ast-nodes/function-call.d.ts +21 -0
  66. package/dist/types/ast-nodes/import.d.ts +16 -0
  67. package/dist/types/ast-nodes/index.d.ts +5 -0
  68. package/dist/types/ast-nodes/jsx-attribute.d.ts +26 -0
  69. package/dist/types/ast-nodes/jsx-element.d.ts +21 -0
  70. package/dist/types/ast-nodes/root.d.ts +19 -0
  71. package/dist/types/rules/use-primitives/transformers/compiled-styled/convert-jsx-call-site.d.ts +17 -0
  72. package/dist/types/rules/use-primitives/transformers/compiled-styled/convert-styled-component-call-to-jsx.d.ts +17 -0
  73. package/dist/types/rules/use-primitives/transformers/compiled-styled/index.d.ts +25 -0
  74. package/dist/types/rules/use-primitives/transformers/compiled-styled/supported.d.ts +2 -0
  75. package/dist/types/rules/use-primitives/transformers/compiled-styled/upsert-import-declaration.d.ts +14 -0
  76. package/dist/types/rules/use-primitives/transformers/emotion-css/contains-only-supported-attrs.d.ts +7 -0
  77. package/dist/types/rules/use-primitives/transformers/emotion-css/index.d.ts +16 -0
  78. package/dist/types/rules/use-primitives/transformers/emotion-css/supported.d.ts +2 -0
  79. package/dist/types/rules/use-primitives/transformers/emotion-css/upsert-import-declaration.d.ts +14 -0
  80. package/dist/types/rules/use-primitives/transformers/index.d.ts +2 -2
  81. package/dist/types/rules/use-primitives/utils/index.d.ts +0 -6
  82. package/dist/types-ts4.5/ast-nodes/function-call.d.ts +21 -0
  83. package/dist/types-ts4.5/ast-nodes/import.d.ts +16 -0
  84. package/dist/types-ts4.5/ast-nodes/index.d.ts +5 -0
  85. package/dist/types-ts4.5/ast-nodes/jsx-attribute.d.ts +26 -0
  86. package/dist/types-ts4.5/ast-nodes/jsx-element.d.ts +21 -0
  87. package/dist/types-ts4.5/ast-nodes/root.d.ts +19 -0
  88. package/dist/types-ts4.5/rules/use-primitives/transformers/compiled-styled/convert-jsx-call-site.d.ts +17 -0
  89. package/dist/types-ts4.5/rules/use-primitives/transformers/compiled-styled/convert-styled-component-call-to-jsx.d.ts +17 -0
  90. package/dist/types-ts4.5/rules/use-primitives/transformers/compiled-styled/index.d.ts +25 -0
  91. package/dist/types-ts4.5/rules/use-primitives/transformers/compiled-styled/supported.d.ts +2 -0
  92. package/dist/types-ts4.5/rules/use-primitives/transformers/compiled-styled/upsert-import-declaration.d.ts +14 -0
  93. package/dist/types-ts4.5/rules/use-primitives/transformers/emotion-css/contains-only-supported-attrs.d.ts +7 -0
  94. package/dist/types-ts4.5/rules/use-primitives/transformers/emotion-css/index.d.ts +16 -0
  95. package/dist/types-ts4.5/rules/use-primitives/transformers/emotion-css/supported.d.ts +2 -0
  96. package/dist/types-ts4.5/rules/use-primitives/transformers/emotion-css/upsert-import-declaration.d.ts +14 -0
  97. package/dist/types-ts4.5/rules/use-primitives/transformers/index.d.ts +2 -2
  98. package/dist/types-ts4.5/rules/use-primitives/utils/index.d.ts +0 -6
  99. package/package.json +1 -1
  100. package/dist/cjs/rules/use-primitives/transformers/jsx-element-to-box.js +0 -26
  101. package/dist/cjs/rules/use-primitives/transformers/styled-component-to-primitive.js +0 -68
  102. package/dist/cjs/rules/use-primitives/utils/contains-only-supported-attrs.js +0 -19
  103. package/dist/cjs/rules/use-primitives/utils/is-valid-tag-name.js +0 -13
  104. package/dist/cjs/rules/use-primitives/utils/update-jsx-element-name.js +0 -16
  105. package/dist/cjs/rules/use-primitives/utils/upsert-import-declaration.js +0 -80
  106. package/dist/es2019/rules/use-primitives/transformers/jsx-element-to-box.js +0 -16
  107. package/dist/es2019/rules/use-primitives/transformers/styled-component-to-primitive.js +0 -59
  108. package/dist/es2019/rules/use-primitives/utils/contains-only-supported-attrs.js +0 -13
  109. package/dist/es2019/rules/use-primitives/utils/is-valid-tag-name.js +0 -7
  110. package/dist/es2019/rules/use-primitives/utils/update-jsx-element-name.js +0 -12
  111. package/dist/es2019/rules/use-primitives/utils/upsert-import-declaration.js +0 -76
  112. package/dist/esm/rules/use-primitives/transformers/jsx-element-to-box.js +0 -19
  113. package/dist/esm/rules/use-primitives/transformers/styled-component-to-primitive.js +0 -61
  114. package/dist/esm/rules/use-primitives/utils/contains-only-supported-attrs.js +0 -13
  115. package/dist/esm/rules/use-primitives/utils/is-valid-tag-name.js +0 -7
  116. package/dist/esm/rules/use-primitives/utils/update-jsx-element-name.js +0 -10
  117. package/dist/esm/rules/use-primitives/utils/upsert-import-declaration.js +0 -75
  118. package/dist/types/rules/use-primitives/transformers/jsx-element-to-box.d.ts +0 -3
  119. package/dist/types/rules/use-primitives/transformers/styled-component-to-primitive.d.ts +0 -13
  120. package/dist/types/rules/use-primitives/utils/contains-only-supported-attrs.d.ts +0 -2
  121. package/dist/types/rules/use-primitives/utils/is-valid-tag-name.d.ts +0 -3
  122. package/dist/types/rules/use-primitives/utils/update-jsx-element-name.d.ts +0 -3
  123. package/dist/types/rules/use-primitives/utils/upsert-import-declaration.d.ts +0 -11
  124. package/dist/types-ts4.5/rules/use-primitives/transformers/jsx-element-to-box.d.ts +0 -3
  125. package/dist/types-ts4.5/rules/use-primitives/transformers/styled-component-to-primitive.d.ts +0 -13
  126. package/dist/types-ts4.5/rules/use-primitives/utils/contains-only-supported-attrs.d.ts +0 -2
  127. package/dist/types-ts4.5/rules/use-primitives/utils/is-valid-tag-name.d.ts +0 -3
  128. package/dist/types-ts4.5/rules/use-primitives/utils/update-jsx-element-name.d.ts +0 -3
  129. package/dist/types-ts4.5/rules/use-primitives/utils/upsert-import-declaration.d.ts +0 -11
  130. /package/dist/cjs/rules/use-primitives/{utils → transformers/compiled-styled}/find-valid-jsx-usage-to-transform.js +0 -0
  131. /package/dist/es2019/rules/use-primitives/{utils → transformers/compiled-styled}/find-valid-jsx-usage-to-transform.js +0 -0
  132. /package/dist/esm/rules/use-primitives/{utils → transformers/compiled-styled}/find-valid-jsx-usage-to-transform.js +0 -0
  133. /package/dist/types/rules/use-primitives/{utils → transformers/compiled-styled}/find-valid-jsx-usage-to-transform.d.ts +0 -0
  134. /package/dist/types/rules/use-primitives/{utils → transformers/compiled-styled}/find-valid-styled-component-call.d.ts +0 -0
  135. /package/dist/types-ts4.5/rules/use-primitives/{utils → transformers/compiled-styled}/find-valid-jsx-usage-to-transform.d.ts +0 -0
  136. /package/dist/types-ts4.5/rules/use-primitives/{utils → transformers/compiled-styled}/find-valid-styled-component-call.d.ts +0 -0
@@ -0,0 +1,50 @@
1
+ import { isNodeOfType, jsxIdentifier } from 'eslint-codemod-utils';
2
+ export const JSXElementHelper = {
3
+ /**
4
+ * Names of JSXElements can be any of:
5
+ * `<Component></Component>` - (JSXIdentifier)
6
+ * `<MyComponents.Component></MyComponents.Component>` - `MyComponents` is a namespace (JSXNamespacedName)
7
+ * `<MyComponents.Component></MyComponents.Component>` - `MyComponents` is an object (JSXMemberExpression)
8
+ *
9
+ * Getting the name of a JSXMemberExpression is difficult, because object can contain objects, which is recursively defined in the AST.
10
+ * e.g. getting the name of `<MyComponents.PresentationLayer.LeftSideBar.Header />` would require `getName` to recursively resolve all parts of the name.
11
+ * `getName` does not currently have this functionality. Add it if you need it.
12
+ */
13
+ getName(node) {
14
+ if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier')) {
15
+ // TODO: We may want to log this
16
+ return '';
17
+ }
18
+ return node.openingElement.name.name;
19
+ },
20
+ updateName(node, newName, fixer) {
21
+ const isSelfClosing = JSXElementHelper.isSelfClosing(node);
22
+ const openingElementFix = fixer.replaceText(node.openingElement.name, jsxIdentifier(newName).toString());
23
+ if (isSelfClosing || !node.closingElement) {
24
+ return [openingElementFix];
25
+ }
26
+ const closingElementFix = fixer.replaceText(node.closingElement.name, jsxIdentifier(newName).toString());
27
+ return [openingElementFix, closingElementFix];
28
+ },
29
+ isSelfClosing(node) {
30
+ return node.openingElement.selfClosing;
31
+ },
32
+ getAttributes(node) {
33
+ return node.openingElement.attributes;
34
+ },
35
+ getAttributeByName(node, name) {
36
+ return node.openingElement.attributes.find(attr => {
37
+ // Ignore anything other than JSXAttribute
38
+ if (!isNodeOfType(attr, 'JSXAttribute')) {
39
+ return false;
40
+ }
41
+ return attr.name.name === name;
42
+ });
43
+ },
44
+ containsSpreadAttributes(node) {
45
+ return node.openingElement.attributes.some(attr => {
46
+ return isNodeOfType(attr, 'JSXSpreadAttribute');
47
+ });
48
+ }
49
+ };
50
+ export { JSXElementHelper as JSXElement };
@@ -0,0 +1,28 @@
1
+ /* eslint-disable @repo/internal/react/require-jsdoc */
2
+
3
+ import { hasImportDeclaration, insertImportDeclaration, isNodeOfType } from 'eslint-codemod-utils';
4
+ // Little bit unreadable, but better than duplicating the type
5
+
6
+ export const Root = {
7
+ /**
8
+ * Note: This can return multiple ImportDeclarations for cases like:
9
+ * ```
10
+ * import { Stack } from '@atlaskit/primitives'
11
+ * import type { StackProps } from '@atlaskit/primitives'
12
+ * ```
13
+ */
14
+ findImportsByModule(root, name) {
15
+ return root.filter(node => {
16
+ if (!isNodeOfType(node, 'ImportDeclaration')) {
17
+ return false;
18
+ }
19
+ if (!hasImportDeclaration(node, name)) {
20
+ return false;
21
+ }
22
+ return true;
23
+ });
24
+ },
25
+ insertImport(root, data, fixer) {
26
+ return fixer.insertTextBefore(root[0], `${insertImportDeclaration(data.module, data.specifiers)};\n`);
27
+ }
28
+ };
@@ -3,6 +3,8 @@
3
3
  import { getIdentifierInParentScope, isNodeOfType } from 'eslint-codemod-utils';
4
4
  import assign from 'lodash/assign';
5
5
  import { createLintRule } from '../utils/create-rule';
6
+ // File-level tracking of styles hoisted from the cssOnTopOfModule/cssAtBottomOfModule fixers
7
+ let hoistedCss = [];
6
8
  function isCssCallExpression(node, cssFunctions) {
7
9
  cssFunctions = [...cssFunctions, 'cssMap'];
8
10
  return !!(isNodeOfType(node, 'CallExpression') && node.callee && node.callee.type === 'Identifier' && cssFunctions.includes(node.callee.name) && node.arguments.length && node.arguments[0].type === 'ObjectExpression');
@@ -13,11 +15,11 @@ function findSpreadProperties(node) {
13
15
  // @ts-ignore
14
16
  property.type === 'ExperimentalSpreadProperty');
15
17
  }
16
- const getTopLevelNode = expression => {
18
+ const getProgramNode = expression => {
17
19
  while (expression.parent.type !== 'Program') {
18
20
  expression = expression.parent;
19
21
  }
20
- return expression;
22
+ return expression.parent;
21
23
  };
22
24
 
23
25
  // TODO: This can be optimised by implementing a fixer at the very end (Program:exit) and handling all validations at once
@@ -34,7 +36,7 @@ const getDeclaratorString = context => {
34
36
  var _scope;
35
37
  scope = (_scope = scope) === null || _scope === void 0 ? void 0 : _scope.upper;
36
38
  }
37
- const variables = scope.variables.map(variable => variable.name);
39
+ const variables = scope.variables.map(variable => variable.name).concat(hoistedCss);
38
40
  let count = 2;
39
41
  let declaratorName = 'styles';
40
42
 
@@ -47,6 +49,9 @@ const getDeclaratorString = context => {
47
49
  count++;
48
50
  }
49
51
  }
52
+
53
+ // Keep track of it by adding it to the hoistedCss global array
54
+ hoistedCss = [...hoistedCss, `${declaratorName}${count}`];
50
55
  return `${declaratorName}${count}`;
51
56
  };
52
57
  function analyzeIdentifier(context, sourceIdentifier, configuration) {
@@ -107,7 +112,17 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
107
112
  */
108
113
  const fixCssNotInModuleScope = (fixer, context, configuration, node, cssAttributeName) => {
109
114
  const sourceCode = context.getSourceCode();
110
- const topLevelNode = getTopLevelNode(node);
115
+
116
+ // Get the program node in order to properly position the hoisted styles
117
+ const programNode = getProgramNode(node);
118
+ let fixerNodePlacement = programNode;
119
+ if (configuration.stylesPlacement === 'bottom') {
120
+ // The last value is the bottom of the file
121
+ fixerNodePlacement = programNode.body[programNode.body.length - 1];
122
+ } else {
123
+ // Place after the last ImportDeclaration
124
+ fixerNodePlacement = programNode.body.length === 1 ? programNode.body[0] : programNode.body.find(node => node.type !== 'ImportDeclaration');
125
+ }
111
126
  let moduleString;
112
127
  let implementFixer = [];
113
128
  if (node.type === 'Identifier') {
@@ -129,7 +144,7 @@ const fixCssNotInModuleScope = (fixer, context, configuration, node, cssAttribut
129
144
  }
130
145
  return [...implementFixer,
131
146
  // Insert the node either before or after
132
- configuration.stylesPlacement === 'bottom' ? fixer.insertTextAfter(topLevelNode, '\n' + moduleString) : fixer.insertTextBefore(topLevelNode, moduleString + '\n')];
147
+ configuration.stylesPlacement === 'bottom' ? fixer.insertTextAfter(fixerNodePlacement, '\n' + moduleString) : fixer.insertTextBefore(fixerNodePlacement, moduleString + '\n')];
133
148
  };
134
149
 
135
150
  /**
@@ -255,6 +270,9 @@ const rule = createLintRule({
255
270
  name,
256
271
  value
257
272
  } = node;
273
+
274
+ // Always reset to empty array
275
+ hoistedCss = [];
258
276
  if (name.type === 'JSXIdentifier' && mergedConfig.cssFunctions.includes(name.name)) {
259
277
  // When not a jsx expression. For eg. css=""
260
278
  if ((value === null || value === void 0 ? void 0 : value.type) !== 'JSXExpressionContainer') {
@@ -1,8 +1,6 @@
1
- import { getIdentifierInParentScope, isNodeOfType } from 'eslint-codemod-utils';
2
1
  import { createLintRule } from '../utils/create-rule';
3
2
  import { getConfig } from './config';
4
- import { jsxElementToBoxTransformer, styledComponentToPrimitive } from './transformers';
5
- import { containsOnlySupportedAttrs, findValidJsxUsageToTransform, findValidStyledComponentCall, getAttributeValueIdentifier, getJSXAttributeByName, getVariableDefinitionValue, getVariableUsagesCount, isFunctionNamed, isValidCssPropertiesToTransform, isValidTagName } from './utils';
3
+ import { CompiledStyled, EmotionCSS } from './transformers';
6
4
  const boxDocsUrl = 'https://atlassian.design/components/primitives/box';
7
5
  const rule = createLintRule({
8
6
  meta: {
@@ -24,113 +22,19 @@ const rule = createLintRule({
24
22
  return {
25
23
  // transforms styled.<html>(...) usages
26
24
  CallExpression(node) {
27
- if (!config.patterns.includes('compiled-styled-object')) {
28
- return;
29
- }
30
- if (!isNodeOfType(node, 'CallExpression')) {
31
- return;
32
- }
33
- const styledComponentVariableRef = findValidStyledComponentCall(node);
34
- if (!styledComponentVariableRef || !isNodeOfType(styledComponentVariableRef.id, 'Identifier') || !isValidCssPropertiesToTransform(node, config)) {
35
- return;
36
- }
37
- const styledComponentJsxRef = findValidJsxUsageToTransform(styledComponentVariableRef.id.name, context.getScope());
38
- if (!styledComponentJsxRef) {
39
- return;
40
- }
41
-
42
- // if we have both references at this point then we can offer a fix \o/
43
- context.report({
44
- node: styledComponentVariableRef,
45
- messageId: 'preferPrimitivesBox',
46
- suggest: [{
47
- desc: `Convert ${styledComponentVariableRef.id.name} to Box`,
48
- fix: styledComponentToPrimitive({
49
- stylesRef: styledComponentVariableRef,
50
- jsxRef: styledComponentJsxRef
51
- }, context)
52
- }]
25
+ CompiledStyled.lint(node, {
26
+ context,
27
+ config
53
28
  });
54
29
  },
55
30
  // transforms <div css={...}> usages
56
- JSXOpeningElement(node) {
57
- if (!config.patterns.includes('compiled-css-function')) {
58
- return false;
59
- }
60
- if (!isNodeOfType(node, 'JSXOpeningElement')) {
61
- return;
62
- }
63
- if (!isNodeOfType(node.name, 'JSXIdentifier')) {
64
- return;
65
- }
66
- if (!isNodeOfType(node.parent, 'JSXElement')) {
67
- return;
68
- }
69
- const suggestBox = shouldSuggestBox(node.parent, context, config);
70
- if (suggestBox) {
71
- context.report({
72
- node: node,
73
- messageId: 'preferPrimitivesBox',
74
- data: {
75
- element: node.name.name
76
- },
77
- suggest: [{
78
- desc: `Convert to Box`,
79
- fix: jsxElementToBoxTransformer(node.parent, context)
80
- }]
81
- });
82
- }
31
+ JSXElement(node) {
32
+ EmotionCSS.lint(node, {
33
+ context,
34
+ config
35
+ });
83
36
  }
84
37
  };
85
38
  }
86
39
  });
87
- const shouldSuggestBox = (node, context, config
88
- // scope: Scope.Scope,
89
- ) => {
90
- if (!node) {
91
- return false;
92
- }
93
-
94
- // Currently we only support `div`, but possibly more in future
95
- if (!isValidTagName(node)) {
96
- return false;
97
- }
98
-
99
- // Currently we only support elements that contain only a `css` attr, possibly more in future
100
- if (!containsOnlySupportedAttrs(node)) {
101
- return false;
102
- }
103
-
104
- // Get the `css` attr
105
- const cssAttr = getJSXAttributeByName(node.openingElement, 'css');
106
-
107
- // Get the prop value, e.g. `myStyles` in `css={myStyles}`
108
- const cssVariableName = getAttributeValueIdentifier(cssAttr);
109
-
110
- // `cssVariableName` could be undefined if the maker has
111
- // done something like `css={[styles1, styles2]}`, `css={...styles}`, etc
112
- if (!cssVariableName) {
113
- return false;
114
- }
115
-
116
- /**
117
- * Make sure the variable is not used in multiple JSX elements:
118
- * ```
119
- * <div css={paddingStyles}></div>
120
- * <input css={paddingStyles}></input>
121
- * ```
122
- */
123
- if (getVariableUsagesCount(cssVariableName, context) !== 1) {
124
- return false;
125
- }
126
-
127
- // Find where `cssVariableName` is defined. We're looking for `const myStyles = css({...})`
128
- const cssVariableDefinition = getIdentifierInParentScope(context.getScope(), cssVariableName);
129
- const cssVariableValue = getVariableDefinitionValue(cssVariableDefinition);
130
- // Check if `cssVariableValue` is a function called `css()`
131
- if (!cssVariableValue || !isFunctionNamed(cssVariableValue, 'css')) {
132
- return false;
133
- }
134
- return isValidCssPropertiesToTransform(cssVariableValue.node.init, config);
135
- };
136
40
  export default rule;
@@ -0,0 +1,29 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import * as ast from '../../../../ast-nodes';
3
+
4
+ /**
5
+ * Transforms a JSXElement from:
6
+ * ```
7
+ * <div>
8
+ * // ...
9
+ * </div>
10
+ * ```
11
+ * to
12
+ * ```
13
+ * <Box xcss={myStyles}>
14
+ * // ...
15
+ * </Box>
16
+ * ```
17
+ */
18
+ export const convertJsxCallSite = (jsxElement, newStylesVariableName, fixer) => {
19
+ const fixes = [];
20
+
21
+ // renames the JSX call site
22
+ if (isNodeOfType(jsxElement, 'JSXElement')) {
23
+ fixes.push(...ast.JSXElement.updateName(jsxElement, 'Box', fixer));
24
+ }
25
+
26
+ // adds xcss prop
27
+ fixes.push(fixer.insertTextAfter(jsxElement.openingElement.name, ` xcss={${newStylesVariableName}}`));
28
+ return fixes;
29
+ };
@@ -0,0 +1,37 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import { styledObjectToXcssTokens } from '../css-to-xcss';
3
+
4
+ /**
5
+ * Transforms a variable like:
6
+ * ```
7
+ * const MyComponent = styled.div({
8
+ * padding: '8px',
9
+ * })
10
+ * ```
11
+ * to
12
+ * ```
13
+ * const myComponentStyles = xcss({
14
+ * padding: 'space.100',
15
+ * })
16
+ * ```
17
+ */
18
+ export const convertStyledComponentToXcss = (styles, newStylesVariableName, fixer) => {
19
+ const fixes = [];
20
+
21
+ // renames the variable from MyComponent to myComponentStyles
22
+ fixes.push(fixer.replaceText(styles.id, newStylesVariableName));
23
+
24
+ // renames the function call from styled.<tag> to xcss
25
+ if (styles.init && isNodeOfType(styles.init, 'CallExpression')) {
26
+ fixes.push(fixer.replaceText(styles.init.callee, 'xcss'));
27
+ }
28
+
29
+ // converts CSS values to XCSS-compatible tokens
30
+ if (styles.init && isNodeOfType(styles.init, 'CallExpression')) {
31
+ const objectExpression = styles.init.arguments[0];
32
+ if (isNodeOfType(objectExpression, 'ObjectExpression')) {
33
+ fixes.push(...styledObjectToXcssTokens(objectExpression, fixer));
34
+ }
35
+ }
36
+ return fixes;
37
+ };
@@ -1,5 +1,5 @@
1
1
  import { closestOfType, isNodeOfType } from 'eslint-codemod-utils';
2
- import { validPrimitiveElements } from './is-valid-tag-name';
2
+ import * as supported from './supported';
3
3
 
4
4
  /**
5
5
  * returns a variable reference if preconditions are favourable for
@@ -41,7 +41,7 @@ const isStyledCallExpression = call => {
41
41
  if (!isNodeOfType(call.callee.object, 'Identifier') || !isNodeOfType(call.callee.property, 'Identifier')) {
42
42
  return false;
43
43
  }
44
- if (/^styled2?$/.test(call.callee.object.name) && validPrimitiveElements.has(call.callee.property.name)) {
44
+ if (/^styled2?$/.test(call.callee.object.name) && supported.elements.includes(call.callee.property.name)) {
45
45
  return true;
46
46
  }
47
47
  return false;
@@ -0,0 +1,107 @@
1
+ /* eslint-disable @repo/internal/react/require-jsdoc */
2
+
3
+ import { isNodeOfType } from 'eslint-codemod-utils';
4
+ import * as ast from '../../../../ast-nodes';
5
+ import { isValidCssPropertiesToTransform } from '../../utils';
6
+ import { convertJsxCallSite } from './convert-jsx-call-site';
7
+ import { convertStyledComponentToXcss } from './convert-styled-component-call-to-jsx';
8
+ import { findValidJsxUsageToTransform } from './find-valid-jsx-usage-to-transform';
9
+ import { findValidStyledComponentCall } from './find-valid-styled-component-call';
10
+ import { upsertImportDeclaration } from './upsert-import-declaration';
11
+ export const CompiledStyled = {
12
+ lint(node, {
13
+ context,
14
+ config
15
+ }) {
16
+ if (!isNodeOfType(node, 'CallExpression')) {
17
+ return;
18
+ }
19
+
20
+ // Check whether all criteria needed to make a transformation are met
21
+ const {
22
+ success,
23
+ refs
24
+ } = CompiledStyled._check(node, {
25
+ context,
26
+ config
27
+ });
28
+ if (!success || !refs) {
29
+ return;
30
+ }
31
+ context.report({
32
+ node: refs.styles,
33
+ messageId: 'preferPrimitivesBox',
34
+ suggest: [{
35
+ desc: `Convert ${ast.JSXElement.getName(refs.jsxElement)} to Box`,
36
+ fix: CompiledStyled._fix(refs, context)
37
+ }]
38
+ });
39
+ },
40
+ _check(node, {
41
+ context,
42
+ config
43
+ }) {
44
+ if (!config.patterns.includes('compiled-styled-object')) {
45
+ return {
46
+ success: false
47
+ };
48
+ }
49
+ if (!isNodeOfType(node, 'CallExpression')) {
50
+ return {
51
+ success: false
52
+ };
53
+ }
54
+ const styledComponentVariableRef = findValidStyledComponentCall(node);
55
+ if (!styledComponentVariableRef || !isNodeOfType(styledComponentVariableRef.id, 'Identifier') || !isValidCssPropertiesToTransform(node, config)) {
56
+ return {
57
+ success: false
58
+ };
59
+ }
60
+ const styledComponentJsxRef = findValidJsxUsageToTransform(styledComponentVariableRef.id.name, context.getScope());
61
+ if (!styledComponentJsxRef) {
62
+ return {
63
+ success: false
64
+ };
65
+ }
66
+ if (!isNodeOfType(styledComponentJsxRef.parent, 'JSXElement')) {
67
+ return {
68
+ success: false
69
+ };
70
+ }
71
+ const importDeclaration = ast.Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/primitives');
72
+
73
+ // If there is more than one `@atlaskit/primitives` import, then it becomes difficult to determine which import to transform
74
+ if (importDeclaration.length > 1) {
75
+ return {
76
+ success: false
77
+ };
78
+ }
79
+ return {
80
+ success: true,
81
+ refs: {
82
+ styles: styledComponentVariableRef,
83
+ jsxElement: styledComponentJsxRef.parent
84
+ }
85
+ };
86
+ },
87
+ /**
88
+ * All required validation steps have been taken care of before this
89
+ * transformer is called, so it just goes ahead providing all necessary fixes
90
+ */
91
+ _fix(refs, context) {
92
+ return fixer => {
93
+ // generates the new variable name: MyComponent -> myComponentStyles
94
+ const calculatedStylesVariableName = isNodeOfType(refs.styles.id, 'Identifier') && `${refs.styles.id.name.replace(refs.styles.id.name[0], refs.styles.id.name[0].toLowerCase())}Styles`;
95
+ if (!calculatedStylesVariableName) {
96
+ return [];
97
+ }
98
+ const importFixes = upsertImportDeclaration({
99
+ module: '@atlaskit/primitives',
100
+ specifiers: ['Box', 'xcss']
101
+ }, context, fixer);
102
+ const stylesFixes = convertStyledComponentToXcss(refs.styles, calculatedStylesVariableName, fixer);
103
+ const jsxFixes = convertJsxCallSite(refs.jsxElement, calculatedStylesVariableName, fixer);
104
+ return [importFixes, ...stylesFixes, ...jsxFixes].filter(fix => Boolean(fix)); // Some of the transformers can return arrays with undefined, so filter them out
105
+ };
106
+ }
107
+ };
@@ -0,0 +1,4 @@
1
+ export const elements = ['div'];
2
+ export const attributes = ['css'
3
+ // 'data-testid'
4
+ ];
@@ -0,0 +1,30 @@
1
+ import * as ast from '../../../../ast-nodes';
2
+
3
+ /**
4
+ * Currently this is defined here because it's not very general purpose.
5
+ * If we were to move this to `ast-nodes`, half the implementation would be in `Root`,
6
+ * and the other half would be in `Import`.
7
+ *
8
+ * TODO: Refactor and move to `ast-nodes`
9
+ *
10
+ * Note: It does not handle default imports, namespace imports, or aliased imports.
11
+ */
12
+ export const upsertImportDeclaration = ({
13
+ module,
14
+ specifiers
15
+ }, context, fixer) => {
16
+ // Find any imports that match the packageName
17
+ const root = context.getSourceCode().ast.body;
18
+ const importDeclarations = ast.Root.findImportsByModule(root, module);
19
+
20
+ // The import doesn't exist yet, we can just insert a whole new one
21
+ if (importDeclarations.length === 0) {
22
+ return ast.Root.insertImport(root, {
23
+ module,
24
+ specifiers
25
+ }, fixer);
26
+ }
27
+
28
+ // The import exists so, modify the existing one
29
+ return ast.Import.insertNamedSpecifiers(importDeclarations[0], specifiers, fixer);
30
+ };
@@ -0,0 +1,19 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import * as ast from '../../../../ast-nodes';
3
+ import * as supported from './supported';
4
+
5
+ /**
6
+ * Check that every attribute in the JSXElement is something we support.
7
+ * We do this via a whitelist in `this.attributes`. The result is we exclude
8
+ * dangerous attrs like `id` and `style`.
9
+ */
10
+ export const containsOnlySupportedAttrs = node => {
11
+ const attrs = ast.JSXElement.getAttributes(node);
12
+ return attrs.every(attr => {
13
+ if (!isNodeOfType(attr, 'JSXAttribute')) {
14
+ return false;
15
+ }
16
+ const name = ast.JSXAttribute.getName(attr);
17
+ return supported.attributes.includes(name);
18
+ });
19
+ };
@@ -0,0 +1,115 @@
1
+ /* eslint-disable @repo/internal/react/require-jsdoc */
2
+
3
+ import { getIdentifierInParentScope, isNodeOfType } from 'eslint-codemod-utils';
4
+ import * as ast from '../../../../ast-nodes';
5
+ import { getVariableDefinitionValue, getVariableUsagesCount, isValidCssPropertiesToTransform } from '../../utils';
6
+ import { cssToXcssTransformer } from '../css-to-xcss';
7
+ import { containsOnlySupportedAttrs } from './contains-only-supported-attrs';
8
+ import * as supported from './supported';
9
+ import { upsertImportDeclaration } from './upsert-import-declaration';
10
+ export const EmotionCSS = {
11
+ lint(node, {
12
+ context,
13
+ config
14
+ }) {
15
+ if (!isNodeOfType(node, 'JSXElement')) {
16
+ return;
17
+ }
18
+
19
+ // Check whether all criteria needed to make a transformation are met
20
+ if (!EmotionCSS._check(node, {
21
+ context,
22
+ config
23
+ })) {
24
+ return;
25
+ }
26
+ context.report({
27
+ node: node.openingElement,
28
+ messageId: 'preferPrimitivesBox',
29
+ suggest: [{
30
+ desc: `Convert to Box`,
31
+ fix: EmotionCSS._fix(node, {
32
+ context
33
+ })
34
+ }]
35
+ });
36
+ },
37
+ _check(node, {
38
+ context,
39
+ config
40
+ }) {
41
+ var _cssVariableValue$nod, _cssVariableValue$nod2;
42
+ if (!config.patterns.includes('compiled-css-function')) {
43
+ return false;
44
+ }
45
+ if (!isNodeOfType(node, 'JSXElement')) {
46
+ return false;
47
+ }
48
+ const elementName = ast.JSXElement.getName(node);
49
+ if (!elementName) {
50
+ return false;
51
+ }
52
+
53
+ // Currently we only support `div`. This may change in future.
54
+ if (!supported.elements.includes(elementName)) {
55
+ return false;
56
+ }
57
+
58
+ // Ignore elements that contain dangerous attributes like `id`.
59
+ if (!containsOnlySupportedAttrs(node)) {
60
+ return false;
61
+ }
62
+
63
+ // Currently we don't transform anything to `Box` unless it defines styles
64
+ const cssAttr = ast.JSXElement.getAttributeByName(node, 'css');
65
+ if (!cssAttr) {
66
+ return false;
67
+ }
68
+
69
+ // Get `myStyles` in `css={myStyles}` as a string so we can search for the `const myStyles` VariableDefinition
70
+ const cssAttrValue = ast.JSXAttribute.getValue(cssAttr);
71
+ if ((cssAttrValue === null || cssAttrValue === void 0 ? void 0 : cssAttrValue.type) !== 'ExpressionStatement') {
72
+ return false;
73
+ }
74
+
75
+ // TODO: Everything below this line could be refactored to use `ast-nodes`.
76
+
77
+ // Bail if the styles are used on multiple JSXElements
78
+ if (getVariableUsagesCount(cssAttrValue.value, context) !== 1) {
79
+ return false;
80
+ }
81
+
82
+ // Find where `myStyles` is defined. We're looking for `const myStyles = css({...})`
83
+ const cssVariableDefinition = getIdentifierInParentScope(context.getScope(), cssAttrValue.value);
84
+ const cssVariableValue = getVariableDefinitionValue(cssVariableDefinition);
85
+ // Check if `cssVariableValue` is a function called `css()`
86
+ if (ast.FunctionCall.getName(cssVariableValue === null || cssVariableValue === void 0 ? void 0 : (_cssVariableValue$nod = cssVariableValue.node) === null || _cssVariableValue$nod === void 0 ? void 0 : _cssVariableValue$nod.init) !== 'css') {
87
+ return false;
88
+ }
89
+ if (!isValidCssPropertiesToTransform(cssVariableValue === null || cssVariableValue === void 0 ? void 0 : (_cssVariableValue$nod2 = cssVariableValue.node) === null || _cssVariableValue$nod2 === void 0 ? void 0 : _cssVariableValue$nod2.init, config)) {
90
+ return false;
91
+ }
92
+ const importDeclaration = ast.Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/primitives');
93
+
94
+ // If there is more than one `@atlaskit/primitives` import, then it becomes difficult to determine which import to transform
95
+ if (importDeclaration.length > 1) {
96
+ return false;
97
+ }
98
+ return true;
99
+ },
100
+ _fix(node, {
101
+ context
102
+ }) {
103
+ return fixer => {
104
+ const importFix = upsertImportDeclaration({
105
+ module: '@atlaskit/primitives',
106
+ specifiers: ['Box', 'xcss']
107
+ }, context, fixer);
108
+ const cssAttr = ast.JSXElement.getAttributeByName(node, 'css'); // Can strongly assert the type here, because we validated it exists in `check()`.
109
+ const attributeFix = ast.JSXAttribute.updateName(cssAttr, 'xcss', fixer);
110
+ const elementNameFixes = ast.JSXElement.updateName(node, 'Box', fixer);
111
+ const cssToXcssTransform = cssToXcssTransformer(node, context, fixer);
112
+ return [importFix, attributeFix, ...elementNameFixes, ...cssToXcssTransform].filter(fix => Boolean(fix)); // Some of the transformers can return arrays with undefined, so filter them out
113
+ };
114
+ }
115
+ };
@@ -0,0 +1,4 @@
1
+ export const elements = ['div'];
2
+ export const attributes = ['css'
3
+ // 'data-testid'
4
+ ];