@atlaskit/eslint-plugin-design-system 8.18.0 → 8.19.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 (89) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -0
  3. package/constellation/index/usage.mdx +51 -0
  4. package/dist/cjs/presets/all.codegen.js +2 -1
  5. package/dist/cjs/presets/recommended.codegen.js +2 -1
  6. package/dist/cjs/rules/index.codegen.js +3 -1
  7. package/dist/cjs/rules/no-unsafe-style-overrides/index.js +74 -0
  8. package/dist/cjs/rules/use-primitives/index.js +40 -35
  9. package/dist/cjs/rules/use-primitives/transformers/css-to-xcss.js +10 -3
  10. package/dist/cjs/rules/use-primitives/transformers/index.js +8 -1
  11. package/dist/cjs/rules/use-primitives/transformers/styled-component-to-primitive.js +68 -0
  12. package/dist/cjs/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +16 -0
  13. package/dist/cjs/rules/use-primitives/utils/find-valid-jsx-usage-to-transform.js +39 -0
  14. package/dist/cjs/rules/use-primitives/utils/find-valid-styled-component-call.js +52 -0
  15. package/dist/cjs/rules/use-primitives/utils/index.js +21 -0
  16. package/dist/cjs/rules/use-primitives/utils/is-valid-css-properties-to-transform.js +38 -0
  17. package/dist/cjs/rules/use-primitives/utils/is-valid-tag-name.js +2 -2
  18. package/dist/cjs/rules/use-primitives/utils/update-jsx-element-name.js +1 -1
  19. package/dist/cjs/rules/utils/get-import-node-by-source.js +49 -1
  20. package/dist/cjs/rules/utils/jsx.js +17 -0
  21. package/dist/es2019/presets/all.codegen.js +2 -1
  22. package/dist/es2019/presets/recommended.codegen.js +2 -1
  23. package/dist/es2019/rules/index.codegen.js +3 -1
  24. package/dist/es2019/rules/no-unsafe-style-overrides/index.js +68 -0
  25. package/dist/es2019/rules/use-primitives/index.js +41 -37
  26. package/dist/es2019/rules/use-primitives/transformers/css-to-xcss.js +9 -6
  27. package/dist/es2019/rules/use-primitives/transformers/index.js +2 -1
  28. package/dist/es2019/rules/use-primitives/transformers/styled-component-to-primitive.js +59 -0
  29. package/dist/es2019/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +14 -0
  30. package/dist/es2019/rules/use-primitives/utils/find-valid-jsx-usage-to-transform.js +32 -0
  31. package/dist/es2019/rules/use-primitives/utils/find-valid-styled-component-call.js +47 -0
  32. package/dist/es2019/rules/use-primitives/utils/index.js +3 -0
  33. package/dist/es2019/rules/use-primitives/utils/is-valid-css-properties-to-transform.js +32 -0
  34. package/dist/es2019/rules/use-primitives/utils/is-valid-tag-name.js +1 -1
  35. package/dist/es2019/rules/use-primitives/utils/update-jsx-element-name.js +1 -1
  36. package/dist/es2019/rules/utils/get-import-node-by-source.js +27 -0
  37. package/dist/es2019/rules/utils/jsx.js +16 -0
  38. package/dist/esm/presets/all.codegen.js +2 -1
  39. package/dist/esm/presets/recommended.codegen.js +2 -1
  40. package/dist/esm/rules/index.codegen.js +3 -1
  41. package/dist/esm/rules/no-unsafe-style-overrides/index.js +68 -0
  42. package/dist/esm/rules/use-primitives/index.js +41 -37
  43. package/dist/esm/rules/use-primitives/transformers/css-to-xcss.js +9 -2
  44. package/dist/esm/rules/use-primitives/transformers/index.js +2 -1
  45. package/dist/esm/rules/use-primitives/transformers/styled-component-to-primitive.js +61 -0
  46. package/dist/esm/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +16 -0
  47. package/dist/esm/rules/use-primitives/utils/find-valid-jsx-usage-to-transform.js +34 -0
  48. package/dist/esm/rules/use-primitives/utils/find-valid-styled-component-call.js +47 -0
  49. package/dist/esm/rules/use-primitives/utils/index.js +3 -0
  50. package/dist/esm/rules/use-primitives/utils/is-valid-css-properties-to-transform.js +32 -0
  51. package/dist/esm/rules/use-primitives/utils/is-valid-tag-name.js +1 -1
  52. package/dist/esm/rules/use-primitives/utils/update-jsx-element-name.js +1 -1
  53. package/dist/esm/rules/utils/get-import-node-by-source.js +48 -0
  54. package/dist/esm/rules/utils/jsx.js +16 -0
  55. package/dist/types/index.codegen.d.ts +2 -0
  56. package/dist/types/presets/all.codegen.d.ts +2 -1
  57. package/dist/types/presets/recommended.codegen.d.ts +2 -1
  58. package/dist/types/rules/index.codegen.d.ts +1 -0
  59. package/dist/types/rules/no-unsafe-style-overrides/index.d.ts +2 -0
  60. package/dist/types/rules/use-primitives/transformers/css-to-xcss.d.ts +2 -1
  61. package/dist/types/rules/use-primitives/transformers/index.d.ts +1 -0
  62. package/dist/types/rules/use-primitives/transformers/styled-component-to-primitive.d.ts +13 -0
  63. package/dist/types/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.d.ts +1 -1
  64. package/dist/types/rules/use-primitives/utils/find-valid-jsx-usage-to-transform.d.ts +7 -0
  65. package/dist/types/rules/use-primitives/utils/find-valid-styled-component-call.d.ts +7 -0
  66. package/dist/types/rules/use-primitives/utils/index.d.ts +3 -0
  67. package/dist/types/rules/use-primitives/utils/is-valid-css-properties-to-transform.d.ts +3 -0
  68. package/dist/types/rules/use-primitives/utils/is-valid-tag-name.d.ts +1 -0
  69. package/dist/types/rules/use-primitives/utils/update-jsx-element-name.d.ts +1 -1
  70. package/dist/types/rules/utils/get-import-node-by-source.d.ts +9 -0
  71. package/dist/types/rules/utils/jsx.d.ts +2 -1
  72. package/dist/types-ts4.5/index.codegen.d.ts +2 -0
  73. package/dist/types-ts4.5/presets/all.codegen.d.ts +2 -1
  74. package/dist/types-ts4.5/presets/recommended.codegen.d.ts +2 -1
  75. package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -0
  76. package/dist/types-ts4.5/rules/no-unsafe-style-overrides/index.d.ts +2 -0
  77. package/dist/types-ts4.5/rules/use-primitives/transformers/css-to-xcss.d.ts +2 -1
  78. package/dist/types-ts4.5/rules/use-primitives/transformers/index.d.ts +1 -0
  79. package/dist/types-ts4.5/rules/use-primitives/transformers/styled-component-to-primitive.d.ts +13 -0
  80. package/dist/types-ts4.5/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.d.ts +1 -1
  81. package/dist/types-ts4.5/rules/use-primitives/utils/find-valid-jsx-usage-to-transform.d.ts +7 -0
  82. package/dist/types-ts4.5/rules/use-primitives/utils/find-valid-styled-component-call.d.ts +7 -0
  83. package/dist/types-ts4.5/rules/use-primitives/utils/index.d.ts +3 -0
  84. package/dist/types-ts4.5/rules/use-primitives/utils/is-valid-css-properties-to-transform.d.ts +3 -0
  85. package/dist/types-ts4.5/rules/use-primitives/utils/is-valid-tag-name.d.ts +1 -0
  86. package/dist/types-ts4.5/rules/use-primitives/utils/update-jsx-element-name.d.ts +1 -1
  87. package/dist/types-ts4.5/rules/utils/get-import-node-by-source.d.ts +9 -0
  88. package/dist/types-ts4.5/rules/utils/jsx.d.ts +2 -1
  89. package/package.json +1 -1
@@ -7,4 +7,31 @@
7
7
  */
8
8
  export const getImportedNodeBySource = (source, path) => {
9
9
  return source.ast.body.filter(node => node.type === 'ImportDeclaration').find(node => node.source.value === path);
10
+ };
11
+
12
+ /**
13
+ * Returns the module name of an identifier, if one exists.
14
+ *
15
+ * getModuleOfIdentifier(source, 'Button'); // "@atlaskit/button"
16
+ */
17
+ export const getModuleOfIdentifier = (source, identifierName) => {
18
+ for (const node of source.ast.body) {
19
+ if (node.type === 'ImportDeclaration') {
20
+ for (const spec of node.specifiers) {
21
+ if (spec.type === 'ImportDefaultSpecifier' && spec.local.name === identifierName) {
22
+ return {
23
+ moduleName: node.source.value + '',
24
+ importName: identifierName
25
+ };
26
+ }
27
+ if (spec.type === 'ImportSpecifier' && spec.local.name === identifierName) {
28
+ return {
29
+ moduleName: node.source.value + '',
30
+ importName: spec.imported.name
31
+ };
32
+ }
33
+ }
34
+ }
35
+ }
36
+ return undefined;
10
37
  };
@@ -1,4 +1,20 @@
1
1
  export function findProp(jsx, propName) {
2
2
  const labelProp = jsx.openingElement.attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.name === propName);
3
3
  return labelProp;
4
+ }
5
+ function unrollMemberExpression(exp) {
6
+ if (exp.type === 'JSXIdentifier') {
7
+ return exp.name;
8
+ }
9
+ return unrollMemberExpression(exp.object);
10
+ }
11
+ export function getJSXElementName(jsx) {
12
+ switch (jsx.name.type) {
13
+ case 'JSXIdentifier':
14
+ return jsx.name.name;
15
+ case 'JSXMemberExpression':
16
+ return unrollMemberExpression(jsx.name.object);
17
+ case 'JSXNamespacedName':
18
+ return jsx.name.namespace.name;
19
+ }
4
20
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
3
- * @codegen <<SignedSource::37a857efd0971ab26170d5f1f526279b>>
3
+ * @codegen <<SignedSource::6efa1e48692b3e287d6dfcd500a5f0ab>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  export default {
@@ -19,6 +19,7 @@ export default {
19
19
  '@atlaskit/design-system/no-nested-styles': 'error',
20
20
  '@atlaskit/design-system/no-physical-properties': 'error',
21
21
  '@atlaskit/design-system/no-unsafe-design-token-usage': 'error',
22
+ '@atlaskit/design-system/no-unsafe-style-overrides': 'warn',
22
23
  '@atlaskit/design-system/no-unsupported-drag-and-drop-libraries': 'error',
23
24
  '@atlaskit/design-system/prefer-primitives': 'warn',
24
25
  '@atlaskit/design-system/use-drawer-label': 'warn',
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
3
- * @codegen <<SignedSource::b3bce8efb2db1fed84370af608ca0d4c>>
3
+ * @codegen <<SignedSource::be810d87ec2d253e3b053dc06ff1b99a>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  export default {
@@ -15,6 +15,7 @@ export default {
15
15
  '@atlaskit/design-system/no-deprecated-imports': 'error',
16
16
  '@atlaskit/design-system/no-nested-styles': 'error',
17
17
  '@atlaskit/design-system/no-unsafe-design-token-usage': 'error',
18
+ '@atlaskit/design-system/no-unsafe-style-overrides': 'warn',
18
19
  '@atlaskit/design-system/no-unsupported-drag-and-drop-libraries': 'error',
19
20
  '@atlaskit/design-system/use-drawer-label': 'warn',
20
21
  '@atlaskit/design-system/use-heading-level-in-spotlight-card': 'warn',
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
3
- * @codegen <<SignedSource::e2cc357c90120dbbd89b2928dba968c0>>
3
+ * @codegen <<SignedSource::ab1f5b129d07027c228dbd79da5f3572>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import consistentCssPropUsage from './consistent-css-prop-usage';
@@ -16,6 +16,7 @@ import noMargin from './no-margin';
16
16
  import noNestedStyles from './no-nested-styles';
17
17
  import noPhysicalProperties from './no-physical-properties';
18
18
  import noUnsafeDesignTokenUsage from './no-unsafe-design-token-usage';
19
+ import noUnsafeStyleOverrides from './no-unsafe-style-overrides';
19
20
  import noUnsupportedDragAndDropLibraries from './no-unsupported-drag-and-drop-libraries';
20
21
  import preferPrimitives from './prefer-primitives';
21
22
  import useDrawerLabel from './use-drawer-label';
@@ -37,6 +38,7 @@ export default {
37
38
  'no-nested-styles': noNestedStyles,
38
39
  'no-physical-properties': noPhysicalProperties,
39
40
  'no-unsafe-design-token-usage': noUnsafeDesignTokenUsage,
41
+ 'no-unsafe-style-overrides': noUnsafeStyleOverrides,
40
42
  'no-unsupported-drag-and-drop-libraries': noUnsupportedDragAndDropLibraries,
41
43
  'prefer-primitives': preferPrimitives,
42
44
  'use-drawer-label': useDrawerLabel,
@@ -0,0 +1,68 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import { createLintRule } from '../utils/create-rule';
3
+ import { getModuleOfIdentifier } from '../utils/get-import-node-by-source';
4
+ import { getJSXElementName } from '../utils/jsx';
5
+ var unsafeOverrides = ['css', 'className', 'theme', 'cssFn', 'styles'];
6
+ var rule = createLintRule({
7
+ meta: {
8
+ docs: {
9
+ recommended: true,
10
+ // This should be an error but for now we're rolling it out as warn so we can actually get it into codebases.
11
+ severity: 'warn',
12
+ description: 'Discourage usage of unsafe style overrides used against the Atlassian Design System.'
13
+ },
14
+ name: 'no-unsafe-style-overrides',
15
+ messages: {
16
+ noUnsafeStyledOverride: 'Wrapping {{componentName}} in a styled component encourages unsafe style overrides which cause friction and incidents when its internals change.',
17
+ noUnsafeOverrides: 'The {{propName}} prop encourages unsafe style overrides which cause friction and incidents when {{componentName}} internals change.'
18
+ }
19
+ },
20
+ create: function create(context) {
21
+ return {
22
+ CallExpression: function CallExpression(node) {
23
+ if (node.callee.type !== 'Identifier' || !node.callee.name.toLowerCase().includes('styled')) {
24
+ // Ignore functions that don't look like styled().
25
+ return;
26
+ }
27
+ var firstArgument = node.arguments[0];
28
+ if (!firstArgument || firstArgument.type !== 'Identifier') {
29
+ return;
30
+ }
31
+ var moduleName = getModuleOfIdentifier(context.getSourceCode(), firstArgument.name);
32
+ if (!moduleName || !moduleName.moduleName.startsWith('@atlaskit')) {
33
+ // Ignore styled calls with non-atlaskit components.
34
+ return;
35
+ }
36
+ context.report({
37
+ node: firstArgument,
38
+ messageId: 'noUnsafeStyledOverride',
39
+ data: {
40
+ componentName: moduleName.importName
41
+ }
42
+ });
43
+ },
44
+ JSXAttribute: function JSXAttribute(node) {
45
+ if (!isNodeOfType(node, 'JSXAttribute') || !(node.parent && isNodeOfType(node.parent, 'JSXOpeningElement'))) {
46
+ return;
47
+ }
48
+ var elementName = getJSXElementName(node.parent);
49
+ var moduleName = getModuleOfIdentifier(context.getSourceCode(), elementName);
50
+ if (!moduleName || !moduleName.moduleName.startsWith('@atlaskit')) {
51
+ return;
52
+ }
53
+ var propName = typeof node.name.name === 'string' ? node.name.name : node.name.name.name;
54
+ if (unsafeOverrides.includes(propName)) {
55
+ context.report({
56
+ node: node,
57
+ messageId: 'noUnsafeOverrides',
58
+ data: {
59
+ propName: propName,
60
+ componentName: moduleName.importName
61
+ }
62
+ });
63
+ }
64
+ }
65
+ };
66
+ }
67
+ });
68
+ export default rule;
@@ -1,8 +1,12 @@
1
1
  import { getIdentifierInParentScope, isNodeOfType } from 'eslint-codemod-utils';
2
+ import assign from 'lodash/assign';
2
3
  import { createLintRule } from '../utils/create-rule';
3
- import { jsxElementToBoxTransformer, supportedStylesMap } from './transformers';
4
- import { containsOnlySupportedAttrs, convertASTObjectExpressionToJSObject, getAttributeValueIdentifier, getFunctionArgumentAtPos, getJSXAttributeByName, getVariableDefinitionValue, getVariableUsagesCount, isFunctionNamed, isValidTagName } from './utils';
4
+ import { jsxElementToBoxTransformer, styledComponentToPrimitive } from './transformers';
5
+ import { containsOnlySupportedAttrs, findValidJsxUsageToTransform, findValidStyledComponentCall, getAttributeValueIdentifier, getJSXAttributeByName, getVariableDefinitionValue, getVariableUsagesCount, isFunctionNamed, isValidCssPropertiesToTransform, isValidTagName } from './utils';
5
6
  var boxDocsUrl = 'https://atlassian.design/components/primitives/box';
7
+ var defaultConfig = {
8
+ preview: false
9
+ };
6
10
  var rule = createLintRule({
7
11
  meta: {
8
12
  name: 'use-primitives',
@@ -15,11 +19,43 @@ var rule = createLintRule({
15
19
  severity: 'warn'
16
20
  },
17
21
  messages: {
18
- preferPrimitivesBox: "This \"{{element}}\" may be able to be replaced with a \"Box\". See ".concat(boxDocsUrl, " for guidance.")
22
+ preferPrimitivesBox: "This element can be replaced with a \"Box\" primitive. See ".concat(boxDocsUrl, " for additional guidance.")
19
23
  }
20
24
  },
21
25
  create: function create(context) {
26
+ var mergedConfig = assign({}, defaultConfig, context.options[0]);
22
27
  return {
28
+ // transforms styled.<html>(...) usages
29
+ CallExpression: function CallExpression(node) {
30
+ if (!mergedConfig.preview) {
31
+ return;
32
+ }
33
+ if (!isNodeOfType(node, 'CallExpression')) {
34
+ return;
35
+ }
36
+ var styledComponentVariableRef = findValidStyledComponentCall(node);
37
+ if (!styledComponentVariableRef || !isNodeOfType(styledComponentVariableRef.id, 'Identifier') || !isValidCssPropertiesToTransform(node)) {
38
+ return;
39
+ }
40
+ var styledComponentJsxRef = findValidJsxUsageToTransform(styledComponentVariableRef.id.name, context.getScope());
41
+ if (!styledComponentJsxRef) {
42
+ return;
43
+ }
44
+
45
+ // if we have both references at this point then we can offer a fix \o/
46
+ context.report({
47
+ node: styledComponentVariableRef,
48
+ messageId: 'preferPrimitivesBox',
49
+ suggest: [{
50
+ desc: "Convert ".concat(styledComponentVariableRef.id.name, " to Box"),
51
+ fix: styledComponentToPrimitive({
52
+ stylesRef: styledComponentVariableRef,
53
+ jsxRef: styledComponentJsxRef
54
+ }, context)
55
+ }]
56
+ });
57
+ },
58
+ // transforms <div css={...}> usages
23
59
  JSXOpeningElement: function JSXOpeningElement(node) {
24
60
  if (!isNodeOfType(node, 'JSXOpeningElement')) {
25
61
  return;
@@ -91,42 +127,10 @@ var shouldSuggestBox = function shouldSuggestBox(node, context
91
127
  // Find where `cssVariableName` is defined. We're looking for `const myStyles = css({...})`
92
128
  var cssVariableDefinition = getIdentifierInParentScope(context.getScope(), cssVariableName);
93
129
  var cssVariableValue = getVariableDefinitionValue(cssVariableDefinition);
94
-
95
130
  // 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
- var cssObjectExpression = getFunctionArgumentAtPos(cssVariableValue, 0);
103
-
104
- // Bail on empty `css()` calls
105
- if (!cssObjectExpression) {
106
- return false;
107
- }
108
- var 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
- var containsOnlyValidStyles = Object.keys(cssObject).every(function (styleProperty) {
120
- var 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) {
131
+ if (!cssVariableValue || !isFunctionNamed(cssVariableValue, 'css')) {
128
132
  return false;
129
133
  }
130
- return true;
134
+ return isValidCssPropertiesToTransform(cssVariableValue.node.init);
131
135
  };
132
136
  export default rule;
@@ -29,7 +29,14 @@ export var cssToXcssTransformer = function cssToXcssTransformer(node, context, f
29
29
  var cssObjectExpression = getFunctionArgumentAtPos(cssVariableValue, 0);
30
30
  return [
31
31
  // Update `css` function name to `xcss`.
32
- fixer.replaceText(cssVariableValue.node.init.callee, identifier('xcss').toString())].concat(_toConsumableArray(cssObjectExpression.properties.map(function (entry) {
32
+ fixer.replaceText(cssVariableValue.node.init.callee, identifier('xcss').toString())].concat(_toConsumableArray(styledObjectToXcssTokens(cssObjectExpression, fixer)));
33
+ };
34
+
35
+ // Update css object values to xcss values. e.g. `'8px'` -> `'space.100'`
36
+ // Note: `properties` in this context is a group of AST nodes that make up a key/value pair in an object.
37
+ // e.g. `padding: '8px'`. For clarity, it's renamed to `entry` inside the `.map()`.
38
+ export var styledObjectToXcssTokens = function styledObjectToXcssTokens(styles, fixer) {
39
+ return styles.properties.map(function (entry) {
33
40
  if (!isNodeOfType(entry, 'Property')) {
34
41
  return;
35
42
  }
@@ -44,7 +51,7 @@ export var cssToXcssTransformer = function cssToXcssTransformer(node, context, f
44
51
  return;
45
52
  }
46
53
  return fixer.replaceText(entry.value, literal("'".concat(supportedStylesMap[entry.key.name][value], "'")).toString());
47
- })));
54
+ });
48
55
  };
49
56
  export var spaceTokenMap = {
50
57
  '0px': 'space.0',
@@ -1,2 +1,3 @@
1
1
  export { cssToXcssTransformer, supportedStylesMap, spaceTokenMap } from './css-to-xcss';
2
- export { jsxElementToBoxTransformer } from './jsx-element-to-box';
2
+ export { jsxElementToBoxTransformer } from './jsx-element-to-box';
3
+ export { styledComponentToPrimitive } from './styled-component-to-primitive';
@@ -0,0 +1,61 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import { isNodeOfType } from 'eslint-codemod-utils';
3
+ import { updateJSXElementName, upsertImportDeclaration } from '../utils';
4
+ import { styledObjectToXcssTokens } from './css-to-xcss';
5
+ /**
6
+ * All required validation steps have been taken care of before this
7
+ * transformer is called, so it just goes ahead providing all necessary fixes
8
+ */
9
+ export var styledComponentToPrimitive = function styledComponentToPrimitive(_ref, context) {
10
+ var stylesRef = _ref.stylesRef,
11
+ jsxRef = _ref.jsxRef;
12
+ return function (fixer) {
13
+ // generates the new variable name: MyComponent -> myComponentStyles
14
+ var calculatedStylesVariableName = isNodeOfType(stylesRef.id, 'Identifier') && "".concat(stylesRef.id.name.replace(stylesRef.id.name[0], stylesRef.id.name[0].toLowerCase()), "Styles");
15
+ if (!calculatedStylesVariableName) {
16
+ return [];
17
+ }
18
+ var importFixes = upsertImportDeclaration({
19
+ packageName: '@atlaskit/primitives',
20
+ specifiers: ['Box', 'xcss']
21
+ }, context, fixer);
22
+ var stylesFixes = convertStyledComponentToXcss(stylesRef, calculatedStylesVariableName, fixer);
23
+ var jsxFixes = convertJsxCallSite(jsxRef, calculatedStylesVariableName, fixer);
24
+ return [importFixes].concat(stylesFixes, jsxFixes).filter(function (fix) {
25
+ return Boolean(fix);
26
+ }); // Some of the transformers can return arrays with undefined, so filter them out
27
+ };
28
+ };
29
+
30
+ var convertStyledComponentToXcss = function convertStyledComponentToXcss(stylesRef, newStylesVariableName, fixer) {
31
+ var fixes = [];
32
+
33
+ // renames the variable from MyComponent to myComponentStyles
34
+ fixes.push(fixer.replaceText(stylesRef.id, newStylesVariableName));
35
+
36
+ // renames the function call from styled.<tag> to xcss
37
+ if (stylesRef.init && isNodeOfType(stylesRef.init, 'CallExpression')) {
38
+ fixes.push(fixer.replaceText(stylesRef.init.callee, 'xcss'));
39
+ }
40
+
41
+ // converts CSS values to XCSS-compatible tokens
42
+ if (stylesRef.init && isNodeOfType(stylesRef.init, 'CallExpression')) {
43
+ var objectExpression = stylesRef.init.arguments[0];
44
+ if (isNodeOfType(objectExpression, 'ObjectExpression')) {
45
+ fixes.push.apply(fixes, _toConsumableArray(styledObjectToXcssTokens(objectExpression, fixer)));
46
+ }
47
+ }
48
+ return fixes;
49
+ };
50
+ var convertJsxCallSite = function convertJsxCallSite(jsxRef, newStylesVariableName, fixer) {
51
+ var fixes = [];
52
+
53
+ // renames the JSX call site
54
+ if (isNodeOfType(jsxRef.parent, 'JSXElement')) {
55
+ fixes.push.apply(fixes, _toConsumableArray(updateJSXElementName(jsxRef.parent, 'Box', fixer)));
56
+ }
57
+
58
+ // adds xcss prop
59
+ fixes.push(fixer.insertTextAfter(jsxRef.name, " xcss={".concat(newStylesVariableName, "}")));
60
+ return fixes;
61
+ };
@@ -7,6 +7,22 @@ import { isNodeOfType } from 'eslint-codemod-utils';
7
7
  */
8
8
  export var convertASTObjectExpressionToJSObject = function convertASTObjectExpressionToJSObject(styles) {
9
9
  var styleObj = {};
10
+
11
+ // if we see any spread props we stop and return false to indicate this is unsupported
12
+ if (!styles.properties.every(function (prop) {
13
+ return isNodeOfType(prop, 'Property');
14
+ })) {
15
+ return false;
16
+ }
17
+
18
+ // TODO: We need to harden this logic.
19
+ // It currently generates a false positive for:
20
+ // styled.div({
21
+ // marginTop: "0px",
22
+ // marginBottom: token("space.100", "8px"),
23
+ // })
24
+ // as the value for `marginBottom` is not a string, so it is just skipped
25
+ // from the resulting map and this causes the rule to trigger when it shouldn't
10
26
  styles.properties.forEach(function (prop) {
11
27
  if (!isNodeOfType(prop, 'Property')) {
12
28
  return;
@@ -0,0 +1,34 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+
3
+ /**
4
+ * Given a component name finds its JSX usages and performs some
5
+ * additional validations to ensure transformation can be done correctly
6
+ */
7
+ export var findValidJsxUsageToTransform = function findValidJsxUsageToTransform(componentName, scope) {
8
+ var variableDeclaration = scope.variables.find(function (v) {
9
+ return v.name === componentName;
10
+ });
11
+ if (!variableDeclaration) {
12
+ return;
13
+ }
14
+
15
+ // length here should be exactly 2 to indicate only two references:
16
+ // one being the variable declaration itself
17
+ // second being the JSX call site
18
+ // we might consider handling multiple local JSX call sites in the future
19
+ // but "this is good enough for now"™️
20
+ if (variableDeclaration.references.length !== 2) {
21
+ return;
22
+ }
23
+ var jsxUsage = variableDeclaration.references[1].identifier;
24
+ if (!isNodeOfType(jsxUsage, 'JSXIdentifier')) {
25
+ return;
26
+ }
27
+ var jsxOpeningElement = jsxUsage.parent;
28
+ // we could relatively easily support some safe attributes like
29
+ // "id" or "testId" but support will be expanded as we go
30
+ if (!isNodeOfType(jsxOpeningElement, 'JSXOpeningElement') || jsxOpeningElement.attributes.length > 0) {
31
+ return;
32
+ }
33
+ return jsxOpeningElement;
34
+ };
@@ -0,0 +1,47 @@
1
+ import { closestOfType, isNodeOfType } from 'eslint-codemod-utils';
2
+ import { validPrimitiveElements } from './is-valid-tag-name';
3
+
4
+ /**
5
+ * returns a variable reference if preconditions are favourable for
6
+ * the transformation to proceed, undefined otherwise.
7
+ */
8
+
9
+ export var findValidStyledComponentCall = function findValidStyledComponentCall(node) {
10
+ // halts unless we are dealing with a styled component
11
+ if (!isStyledCallExpression(node)) {
12
+ return;
13
+ }
14
+ // halts if the component is being exported directly
15
+ if (closestOfType(node, 'ExportNamedDeclaration')) {
16
+ return;
17
+ }
18
+ var styledComponentVariableRef = node.parent;
19
+ // halts if the styled component is not assigned to a variable immediately
20
+ if (!isNodeOfType(styledComponentVariableRef, 'VariableDeclarator')) {
21
+ return;
22
+ }
23
+ return styledComponentVariableRef;
24
+ };
25
+
26
+ /**
27
+ * Some verbose precondition checks but all it does is check
28
+ * a call expression is of form `styled.div` or `styled2.div`
29
+ *
30
+ * In the future it could be enhanced to double check `styled` and `styled2`
31
+ * are Compiled imports but as is should work for the majority of use cases
32
+ */
33
+ var isStyledCallExpression = function isStyledCallExpression(call) {
34
+ if (!isNodeOfType(call, 'CallExpression')) {
35
+ return false;
36
+ }
37
+ if (!isNodeOfType(call.callee, 'MemberExpression')) {
38
+ return false;
39
+ }
40
+ if (!isNodeOfType(call.callee.object, 'Identifier') || !isNodeOfType(call.callee.property, 'Identifier')) {
41
+ return false;
42
+ }
43
+ if (/^styled2?$/.test(call.callee.object.name) && validPrimitiveElements.has(call.callee.property.name)) {
44
+ return true;
45
+ }
46
+ return false;
47
+ };
@@ -1,11 +1,14 @@
1
1
  export { containsOnlySupportedAttrs } from './contains-only-supported-attrs';
2
2
  export { convertASTObjectExpressionToJSObject } from './convert-ast-object-expression-to-js-object';
3
+ export { findValidJsxUsageToTransform } from './find-valid-jsx-usage-to-transform';
4
+ export { findValidStyledComponentCall } from './find-valid-styled-component-call';
3
5
  export { getAttributeValueIdentifier } from './get-attribute-value-identifier';
4
6
  export { getFunctionArgumentAtPos } from './get-function-argument-at-pos';
5
7
  export { getJSXAttributeByName } from './get-jsx-attribute-by-name';
6
8
  export { getVariableDefinitionValue } from './get-variable-definition-value';
7
9
  export { getVariableUsagesCount } from './get-variable-usage-count';
8
10
  export { isFunctionNamed } from './is-function-named';
11
+ export { isValidCssPropertiesToTransform } from './is-valid-css-properties-to-transform';
9
12
  export { isValidTagName } from './is-valid-tag-name';
10
13
  export { updateJSXAttributeByName } from './update-jsx-attribute-by-name';
11
14
  export { updateJSXElementName } from './update-jsx-element-name';
@@ -0,0 +1,32 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import { supportedStylesMap } from '../transformers/css-to-xcss';
3
+ import { convertASTObjectExpressionToJSObject } from './convert-ast-object-expression-to-js-object';
4
+ export var isValidCssPropertiesToTransform = function isValidCssPropertiesToTransform(node) {
5
+ var cssObjectExpression = node.arguments[0];
6
+ // Bail on empty object calls
7
+ if (!cssObjectExpression || !isNodeOfType(cssObjectExpression, 'ObjectExpression')) {
8
+ return false;
9
+ }
10
+ var cssObject = convertASTObjectExpressionToJSObject(cssObjectExpression);
11
+ // Bail if there are less or more than 1 styles defined
12
+ if (!cssObject || Object.keys(cssObject).length !== 1) {
13
+ return false;
14
+ }
15
+
16
+ // NOTE: Our approach with this lint rule is to strictly whitelist css properties we can map.
17
+ // It means we have to provide mappings for everything (e.g. `display: block`).
18
+ // However, from a maker's experience, it's much better that the rule doesn't report (if we miss a mapping)
19
+ // than the rule reporting on things that can't be mapped.
20
+ var containsOnlyValidStyles = Object.keys(cssObject).every(function (styleProperty) {
21
+ var styleValue = cssObject[styleProperty];
22
+ return supportedStylesMap[styleProperty] &&
23
+ // Is the key something we can map
24
+ supportedStylesMap[styleProperty][styleValue] // Is the value something we can map
25
+ ;
26
+ });
27
+
28
+ if (!containsOnlyValidStyles) {
29
+ return false;
30
+ }
31
+ return true;
32
+ };
@@ -1,4 +1,4 @@
1
- var validPrimitiveElements = new Set(['div']);
1
+ export var validPrimitiveElements = new Set(['div']);
2
2
  export var isValidTagName = function isValidTagName(node) {
3
3
  if (node.openingElement.name.type !== 'JSXIdentifier') {
4
4
  return false;
@@ -6,5 +6,5 @@ export var updateJSXElementName = function updateJSXElementName(node, newName, f
6
6
  var newClosingElement = closingElement &&
7
7
  // Self closing tags, like `<div />` don't need to have the closing tag updated
8
8
  fixer.replaceText(closingElement.name, jsxIdentifier(newName).toString());
9
- return [newOpeningElement, newClosingElement];
9
+ return [newOpeningElement, newClosingElement || undefined];
10
10
  };
@@ -1,3 +1,6 @@
1
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
2
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
3
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
1
4
  // eslint-disable-next-line import/no-extraneous-dependencies
2
5
 
3
6
  /**
@@ -11,4 +14,49 @@ export var getImportedNodeBySource = function getImportedNodeBySource(source, pa
11
14
  }).find(function (node) {
12
15
  return node.source.value === path;
13
16
  });
17
+ };
18
+
19
+ /**
20
+ * Returns the module name of an identifier, if one exists.
21
+ *
22
+ * getModuleOfIdentifier(source, 'Button'); // "@atlaskit/button"
23
+ */
24
+ export var getModuleOfIdentifier = function getModuleOfIdentifier(source, identifierName) {
25
+ var _iterator = _createForOfIteratorHelper(source.ast.body),
26
+ _step;
27
+ try {
28
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
29
+ var node = _step.value;
30
+ if (node.type === 'ImportDeclaration') {
31
+ var _iterator2 = _createForOfIteratorHelper(node.specifiers),
32
+ _step2;
33
+ try {
34
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
35
+ var spec = _step2.value;
36
+ if (spec.type === 'ImportDefaultSpecifier' && spec.local.name === identifierName) {
37
+ return {
38
+ moduleName: node.source.value + '',
39
+ importName: identifierName
40
+ };
41
+ }
42
+ if (spec.type === 'ImportSpecifier' && spec.local.name === identifierName) {
43
+ return {
44
+ moduleName: node.source.value + '',
45
+ importName: spec.imported.name
46
+ };
47
+ }
48
+ }
49
+ } catch (err) {
50
+ _iterator2.e(err);
51
+ } finally {
52
+ _iterator2.f();
53
+ }
54
+ }
55
+ }
56
+ } catch (err) {
57
+ _iterator.e(err);
58
+ } finally {
59
+ _iterator.f();
60
+ }
61
+ return undefined;
14
62
  };
@@ -3,4 +3,20 @@ export function findProp(jsx, propName) {
3
3
  return attr.type === 'JSXAttribute' && attr.name.name === propName;
4
4
  });
5
5
  return labelProp;
6
+ }
7
+ function unrollMemberExpression(exp) {
8
+ if (exp.type === 'JSXIdentifier') {
9
+ return exp.name;
10
+ }
11
+ return unrollMemberExpression(exp.object);
12
+ }
13
+ export function getJSXElementName(jsx) {
14
+ switch (jsx.name.type) {
15
+ case 'JSXIdentifier':
16
+ return jsx.name.name;
17
+ case 'JSXMemberExpression':
18
+ return unrollMemberExpression(jsx.name.object);
19
+ case 'JSXNamespacedName':
20
+ return jsx.name.namespace.name;
21
+ }
6
22
  }
@@ -16,6 +16,7 @@ export declare const configs: {
16
16
  '@atlaskit/design-system/no-nested-styles': string;
17
17
  '@atlaskit/design-system/no-physical-properties': string;
18
18
  '@atlaskit/design-system/no-unsafe-design-token-usage': string;
19
+ '@atlaskit/design-system/no-unsafe-style-overrides': string;
19
20
  '@atlaskit/design-system/no-unsupported-drag-and-drop-libraries': string;
20
21
  '@atlaskit/design-system/prefer-primitives': string;
21
22
  '@atlaskit/design-system/use-drawer-label': string;
@@ -37,6 +38,7 @@ export declare const configs: {
37
38
  '@atlaskit/design-system/no-deprecated-imports': string;
38
39
  '@atlaskit/design-system/no-nested-styles': string;
39
40
  '@atlaskit/design-system/no-unsafe-design-token-usage': string;
41
+ '@atlaskit/design-system/no-unsafe-style-overrides': string;
40
42
  '@atlaskit/design-system/no-unsupported-drag-and-drop-libraries': string;
41
43
  '@atlaskit/design-system/use-drawer-label': string;
42
44
  '@atlaskit/design-system/use-heading-level-in-spotlight-card': string;