@atlaskit/eslint-plugin-design-system 8.25.2 → 8.27.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 (80) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +4 -0
  3. package/constellation/index/usage.mdx +402 -6
  4. package/dist/cjs/presets/all.codegen.js +5 -1
  5. package/dist/cjs/presets/recommended.codegen.js +5 -1
  6. package/dist/cjs/rules/consistent-css-prop-usage/index.js +254 -32
  7. package/dist/cjs/rules/index.codegen.js +9 -1
  8. package/dist/cjs/rules/no-empty-styled-expression/index.js +75 -0
  9. package/dist/cjs/rules/no-exported-css/index.js +37 -0
  10. package/dist/cjs/rules/no-exported-keyframes/index.js +37 -0
  11. package/dist/cjs/rules/no-invalid-css-map/index.js +102 -0
  12. package/dist/cjs/rules/no-invalid-css-map/utils.js +193 -0
  13. package/dist/cjs/rules/utils/create-no-exported-rule/check-if-supported-export.js +158 -0
  14. package/dist/cjs/rules/utils/create-no-exported-rule/is-styled-component.js +80 -0
  15. package/dist/cjs/rules/utils/create-no-exported-rule/main.js +66 -0
  16. package/dist/cjs/rules/utils/get-first-supported-import.js +28 -0
  17. package/dist/cjs/rules/utils/is-supported-import.js +53 -16
  18. package/dist/es2019/presets/all.codegen.js +5 -1
  19. package/dist/es2019/presets/recommended.codegen.js +5 -1
  20. package/dist/es2019/rules/consistent-css-prop-usage/index.js +251 -33
  21. package/dist/es2019/rules/index.codegen.js +9 -1
  22. package/dist/es2019/rules/no-empty-styled-expression/index.js +65 -0
  23. package/dist/es2019/rules/no-exported-css/index.js +31 -0
  24. package/dist/es2019/rules/no-exported-keyframes/index.js +31 -0
  25. package/dist/es2019/rules/no-invalid-css-map/index.js +95 -0
  26. package/dist/es2019/rules/no-invalid-css-map/utils.js +134 -0
  27. package/dist/es2019/rules/utils/create-no-exported-rule/check-if-supported-export.js +142 -0
  28. package/dist/es2019/rules/utils/create-no-exported-rule/is-styled-component.js +70 -0
  29. package/dist/es2019/rules/utils/create-no-exported-rule/main.js +59 -0
  30. package/dist/es2019/rules/utils/get-first-supported-import.js +22 -0
  31. package/dist/es2019/rules/utils/is-supported-import.js +50 -15
  32. package/dist/esm/presets/all.codegen.js +5 -1
  33. package/dist/esm/presets/recommended.codegen.js +5 -1
  34. package/dist/esm/rules/consistent-css-prop-usage/index.js +255 -33
  35. package/dist/esm/rules/index.codegen.js +9 -1
  36. package/dist/esm/rules/no-empty-styled-expression/index.js +68 -0
  37. package/dist/esm/rules/no-exported-css/index.js +31 -0
  38. package/dist/esm/rules/no-exported-keyframes/index.js +31 -0
  39. package/dist/esm/rules/no-invalid-css-map/index.js +96 -0
  40. package/dist/esm/rules/no-invalid-css-map/utils.js +186 -0
  41. package/dist/esm/rules/utils/create-no-exported-rule/check-if-supported-export.js +151 -0
  42. package/dist/esm/rules/utils/create-no-exported-rule/is-styled-component.js +74 -0
  43. package/dist/esm/rules/utils/create-no-exported-rule/main.js +60 -0
  44. package/dist/esm/rules/utils/get-first-supported-import.js +22 -0
  45. package/dist/esm/rules/utils/is-supported-import.js +51 -15
  46. package/dist/types/index.codegen.d.ts +8 -0
  47. package/dist/types/presets/all.codegen.d.ts +5 -1
  48. package/dist/types/presets/recommended.codegen.d.ts +5 -1
  49. package/dist/types/rules/consistent-css-prop-usage/types.d.ts +7 -2
  50. package/dist/types/rules/index.codegen.d.ts +4 -0
  51. package/dist/types/rules/no-empty-styled-expression/index.d.ts +3 -0
  52. package/dist/types/rules/no-exported-css/index.d.ts +3 -0
  53. package/dist/types/rules/no-exported-keyframes/index.d.ts +3 -0
  54. package/dist/types/rules/no-invalid-css-map/index.d.ts +3 -0
  55. package/dist/types/rules/no-invalid-css-map/utils.d.ts +14 -0
  56. package/dist/types/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +1 -1
  57. package/dist/types/rules/utils/create-no-exported-rule/check-if-supported-export.d.ts +15 -0
  58. package/dist/types/rules/utils/create-no-exported-rule/is-styled-component.d.ts +14 -0
  59. package/dist/types/rules/utils/create-no-exported-rule/main.d.ts +19 -0
  60. package/dist/types/rules/utils/create-rule.d.ts +1 -1
  61. package/dist/types/rules/utils/get-first-supported-import.d.ts +17 -0
  62. package/dist/types/rules/utils/is-supported-import.d.ts +26 -8
  63. package/dist/types-ts4.5/index.codegen.d.ts +8 -0
  64. package/dist/types-ts4.5/presets/all.codegen.d.ts +5 -1
  65. package/dist/types-ts4.5/presets/recommended.codegen.d.ts +5 -1
  66. package/dist/types-ts4.5/rules/consistent-css-prop-usage/types.d.ts +7 -2
  67. package/dist/types-ts4.5/rules/index.codegen.d.ts +4 -0
  68. package/dist/types-ts4.5/rules/no-empty-styled-expression/index.d.ts +3 -0
  69. package/dist/types-ts4.5/rules/no-exported-css/index.d.ts +3 -0
  70. package/dist/types-ts4.5/rules/no-exported-keyframes/index.d.ts +3 -0
  71. package/dist/types-ts4.5/rules/no-invalid-css-map/index.d.ts +3 -0
  72. package/dist/types-ts4.5/rules/no-invalid-css-map/utils.d.ts +14 -0
  73. package/dist/types-ts4.5/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +1 -1
  74. package/dist/types-ts4.5/rules/utils/create-no-exported-rule/check-if-supported-export.d.ts +15 -0
  75. package/dist/types-ts4.5/rules/utils/create-no-exported-rule/is-styled-component.d.ts +14 -0
  76. package/dist/types-ts4.5/rules/utils/create-no-exported-rule/main.d.ts +19 -0
  77. package/dist/types-ts4.5/rules/utils/create-rule.d.ts +1 -1
  78. package/dist/types-ts4.5/rules/utils/get-first-supported-import.d.ts +17 -0
  79. package/dist/types-ts4.5/rules/utils/is-supported-import.d.ts +26 -8
  80. package/package.json +3 -1
@@ -0,0 +1,31 @@
1
+ import { createNoExportedRule } from '../utils/create-no-exported-rule/main';
2
+ import { createLintRule } from '../utils/create-rule';
3
+ import { isCss } from '../utils/is-supported-import';
4
+ const noExportedCssRule = createLintRule({
5
+ meta: {
6
+ name: 'no-exported-css',
7
+ type: 'problem',
8
+ docs: {
9
+ description: 'Forbid exporting `css` function calls. Exporting `css` function calls can result in unexpected behaviour at runtime, and is not statically analysable.',
10
+ recommended: true,
11
+ severity: 'warn'
12
+ },
13
+ messages: {
14
+ unexpected: "`css` can't be exported - this will cause unexpected behaviour at runtime. Instead, please move your `css(...)` code to the same file where these styles are being used."
15
+ },
16
+ schema: [{
17
+ type: 'object',
18
+ properties: {
19
+ importSources: {
20
+ type: 'array',
21
+ items: [{
22
+ type: 'string'
23
+ }]
24
+ }
25
+ },
26
+ additionalProperties: false
27
+ }]
28
+ },
29
+ create: createNoExportedRule(isCss, 'unexpected')
30
+ });
31
+ export default noExportedCssRule;
@@ -0,0 +1,31 @@
1
+ import { createNoExportedRule } from '../utils/create-no-exported-rule/main';
2
+ import { createLintRule } from '../utils/create-rule';
3
+ import { isKeyframes } from '../utils/is-supported-import';
4
+ const noExportedKeyframesRule = createLintRule({
5
+ meta: {
6
+ name: 'no-exported-keyframes',
7
+ type: 'problem',
8
+ docs: {
9
+ description: 'Forbid exporting `keyframes` function calls. Exporting `css` function calls can result in unexpected behaviour at runtime, and is not statically analysable.',
10
+ recommended: true,
11
+ severity: 'warn'
12
+ },
13
+ messages: {
14
+ unexpected: "`keyframes` can't be exported - this will cause unexpected behaviour at runtime. Instead, please move your `keyframes(...)` code to the same file where these styles are being used."
15
+ },
16
+ schema: [{
17
+ type: 'object',
18
+ properties: {
19
+ importSources: {
20
+ type: 'array',
21
+ items: [{
22
+ type: 'string'
23
+ }]
24
+ }
25
+ },
26
+ additionalProperties: false
27
+ }]
28
+ },
29
+ create: createNoExportedRule(isKeyframes, 'unexpected')
30
+ });
31
+ export default noExportedKeyframesRule;
@@ -0,0 +1,95 @@
1
+ import { checkIfSupportedExport } from '../utils/create-no-exported-rule/check-if-supported-export';
2
+ import { createLintRule } from '../utils/create-rule';
3
+ import { CSS_IN_JS_IMPORTS, isCssMap } from '../utils/is-supported-import';
4
+ import { CssMapObjectChecker, getCssMapObject } from './utils';
5
+ const IMPORT_SOURCES = [CSS_IN_JS_IMPORTS.compiled, CSS_IN_JS_IMPORTS.atlaskitCss];
6
+ const reportIfExported = (node, context) => {
7
+ const state = checkIfSupportedExport(context, node, IMPORT_SOURCES);
8
+ if (!state.isExport) {
9
+ return;
10
+ }
11
+ context.report({
12
+ messageId: 'noExportedCssMap',
13
+ node: state.node
14
+ });
15
+ };
16
+ const reportIfNotTopLevelScope = (node, context) => {
17
+ // Treat `export` keyword as valid because the reportIfExported function already handles those
18
+ const validTypes = ['ExportDefaultDeclaration', 'ExportNamedDeclaration', 'Program', 'VariableDeclaration', 'VariableDeclarator'];
19
+ let parentNode = node.parent;
20
+ while (parentNode) {
21
+ if (!validTypes.includes(parentNode.type)) {
22
+ context.report({
23
+ node: node,
24
+ messageId: 'mustBeTopLevelScope'
25
+ });
26
+ return;
27
+ }
28
+ parentNode = parentNode.parent;
29
+ }
30
+ };
31
+ const createCssMapRule = context => {
32
+ const {
33
+ text
34
+ } = context.getSourceCode();
35
+ if (IMPORT_SOURCES.every(importSource => !text.includes(importSource))) {
36
+ return {};
37
+ }
38
+ return {
39
+ CallExpression(node) {
40
+ const references = context.getScope().references;
41
+ if (!isCssMap(node.callee, references, IMPORT_SOURCES)) {
42
+ return;
43
+ }
44
+ reportIfExported(node, context);
45
+ reportIfNotTopLevelScope(node, context);
46
+ const cssMapObject = getCssMapObject(node);
47
+ if (!cssMapObject) {
48
+ return;
49
+ }
50
+ const cssMapObjectChecker = new CssMapObjectChecker(cssMapObject, context);
51
+ cssMapObjectChecker.run();
52
+ }
53
+ };
54
+ };
55
+ const noInvalidCssMapRule = createLintRule({
56
+ meta: {
57
+ name: 'no-invalid-css-map',
58
+ docs: {
59
+ description: "Checks the validity of a CSS map created through cssMap. This is intended to be used alongside TypeScript's type-checking.",
60
+ recommended: true,
61
+ severity: 'error'
62
+ },
63
+ messages: {
64
+ mustBeTopLevelScope: 'cssMap must only be used in the top-most scope of the module.',
65
+ noNonStaticallyEvaluable: 'Cannot statically evaluate the value of this variable. Values used in the cssMap function call should have a value evaluable at build time.',
66
+ noExportedCssMap: 'cssMap usages cannot be exported.',
67
+ noInlineFunctions: 'Cannot use functions as values in cssMap - values must only be statically evaluable values (e.g. strings, numbers).',
68
+ noFunctionCalls: 'Cannot call external functions in cssMap - values must only be statically evaluable values (e.g. strings, numbers).',
69
+ noSpreadElement: 'Cannot use the spread operator in cssMap.'
70
+ },
71
+ schema: [{
72
+ type: 'object',
73
+ properties: {
74
+ allowedFunctionCalls: {
75
+ type: 'array',
76
+ items: {
77
+ type: 'array',
78
+ minItems: 2,
79
+ maxItems: 2,
80
+ items: [{
81
+ type: 'string'
82
+ }, {
83
+ type: 'string'
84
+ }]
85
+ },
86
+ uniqueItems: true
87
+ }
88
+ },
89
+ additionalProperties: false
90
+ }],
91
+ type: 'problem'
92
+ },
93
+ create: createCssMapRule
94
+ });
95
+ export default noInvalidCssMapRule;
@@ -0,0 +1,134 @@
1
+ export const getCssMapObject = node => {
2
+ // We assume the argument `node` is already a cssMap() call.
3
+
4
+ // Things like the number of arguments to cssMap and the type of
5
+ // cssMap's argument are handled by the TypeScript compiler, so
6
+ // we don't bother with creating eslint errors for these here
7
+
8
+ if (node.arguments.length !== 1 || node.arguments[0].type !== 'ObjectExpression') {
9
+ return;
10
+ }
11
+ return node.arguments[0];
12
+ };
13
+ const findNodeReference = (references, node) => {
14
+ return references.find(reference => reference.identifier === node);
15
+ };
16
+ const getAllowedFunctionCalls = options => {
17
+ var _options$;
18
+ if (options.length === 0 || ((_options$ = options[0]) === null || _options$ === void 0 ? void 0 : _options$.allowedFunctionCalls) === undefined) {
19
+ return [];
20
+ }
21
+
22
+ // Beyond the basic check of "does allowedFunctionCalls exist?",
23
+ // we assume ESLint's rule checker type checks the contents of allowedFunctionCalls
24
+ // as it should
25
+ return options[0].allowedFunctionCalls;
26
+ };
27
+ export class CssMapObjectChecker {
28
+ constructor(cssMapObject, context) {
29
+ this.allowedFunctionCalls = getAllowedFunctionCalls(context.options);
30
+ this.cssMapObject = cssMapObject;
31
+ this.report = context.report;
32
+ this.references = context.getScope().references;
33
+ }
34
+ isNotWhitelistedFunction(callee) {
35
+ var _reference$resolved;
36
+ if (callee.type !== 'Identifier' || this.allowedFunctionCalls.length === 0) {
37
+ return true;
38
+ }
39
+ const reference = findNodeReference(this.references, callee);
40
+ const definitions = reference === null || reference === void 0 ? void 0 : (_reference$resolved = reference.resolved) === null || _reference$resolved === void 0 ? void 0 : _reference$resolved.defs;
41
+ if (!definitions) {
42
+ return true;
43
+ }
44
+ return definitions.some(definition => {
45
+ // We add some restrictions to keep this simple...
46
+ // Forbid non-imported functions
47
+ if (definition.type !== 'ImportBinding') {
48
+ return true;
49
+ }
50
+ // Forbid default imports (e.g. `import React from 'react'`)
51
+ if (definition.node.type !== 'ImportSpecifier') {
52
+ return true;
53
+ }
54
+ const packageName = definition.parent.source.value;
55
+ const importedFunctionName = definition.node.imported.name;
56
+ return !this.allowedFunctionCalls.some(([allowedPackageName, allowedFunctionName]) => allowedPackageName === packageName && allowedFunctionName === importedFunctionName);
57
+ });
58
+ }
59
+ checkCssMapObjectValue(value) {
60
+ if (value.type === 'CallExpression' && this.isNotWhitelistedFunction(value.callee)) {
61
+ // object value is a function call in the style
62
+ // {
63
+ // key: functionCall(), ...
64
+ // }
65
+ this.report({
66
+ node: value,
67
+ messageId: 'noFunctionCalls'
68
+ });
69
+ } else if (value.type === 'ArrowFunctionExpression' || value.type === 'FunctionExpression') {
70
+ // object value is a function call in the style
71
+ // {
72
+ // key: (prop) => prop.color, // ArrowFunctionExpression
73
+ // get danger() { return { ... } }, // FunctionExpression
74
+ // }
75
+ this.report({
76
+ node: value,
77
+ messageId: 'noInlineFunctions'
78
+ });
79
+ } else if (value.type === 'BinaryExpression' || value.type === 'LogicalExpression') {
80
+ this.checkCssMapObjectValue(value.left);
81
+ this.checkCssMapObjectValue(value.right);
82
+ } else if (value.type === 'Identifier') {
83
+ var _reference$resolved2;
84
+ const reference = findNodeReference(this.references, value);
85
+
86
+ // Get the variable's definition when initialised. Assume that the last definition
87
+ // is the most recent one.
88
+ //
89
+ // Ideally we would try to get the variable's value at the point at which
90
+ // cssMap() is run, but ESLint doesn't seem to give us an easy way to
91
+ // do that...
92
+ const definitions = reference === null || reference === void 0 ? void 0 : (_reference$resolved2 = reference.resolved) === null || _reference$resolved2 === void 0 ? void 0 : _reference$resolved2.defs;
93
+ if (!definitions || definitions.length === 0) {
94
+ // Variable is not defined :thinking:
95
+ return;
96
+ }
97
+ for (const definition of definitions) {
98
+ if (definition.type === 'Variable' && definition.node.init) {
99
+ return this.checkCssMapObjectValue(definition.node.init);
100
+ }
101
+ }
102
+ } else if (value.type === 'ObjectExpression') {
103
+ // Object inside another object
104
+ this.checkCssMapObject(value);
105
+ } else if (value.type === 'TemplateLiteral') {
106
+ // object value is a template literal, something like
107
+ // `hello world`
108
+ // `hello ${functionCall()} world`
109
+ // `hello ${someVariable} world`
110
+ // etc.
111
+ //
112
+ // where the expressions are the parts enclosed within the
113
+ // ${ ... }
114
+ for (const expression of value.expressions) {
115
+ this.checkCssMapObjectValue(expression);
116
+ }
117
+ }
118
+ }
119
+ checkCssMapObject(cssMapObject) {
120
+ for (const property of cssMapObject.properties) {
121
+ if (property.type === 'SpreadElement') {
122
+ this.report({
123
+ node: property,
124
+ messageId: 'noSpreadElement'
125
+ });
126
+ continue;
127
+ }
128
+ this.checkCssMapObjectValue(property.value);
129
+ }
130
+ }
131
+ run() {
132
+ this.checkCssMapObject(this.cssMapObject);
133
+ }
134
+ }
@@ -0,0 +1,142 @@
1
+ import { isStyledComponent } from './is-styled-component';
2
+ const getStack = (context, node) => {
3
+ var _scope;
4
+ const {
5
+ scopeManager
6
+ } = context.getSourceCode();
7
+ const stack = {
8
+ nodes: [],
9
+ root: node
10
+ };
11
+ let scope;
12
+ for (let current = node; current.type !== 'Program'; current = current.parent) {
13
+ if (!scope) {
14
+ const currentScope = scopeManager.acquire(current);
15
+ if (currentScope) {
16
+ scope = currentScope;
17
+ }
18
+ }
19
+ switch (current.type) {
20
+ case 'ExportDefaultDeclaration':
21
+ case 'ExportNamedDeclaration':
22
+ stack.root = current;
23
+ break;
24
+ case 'VariableDeclarator':
25
+ stack.root = current;
26
+ break;
27
+ case 'ExportSpecifier':
28
+ case 'ObjectExpression':
29
+ case 'VariableDeclaration':
30
+ break;
31
+ default:
32
+ stack.nodes.unshift(current);
33
+ }
34
+ }
35
+ return {
36
+ ...stack,
37
+ scope: (_scope = scope) !== null && _scope !== void 0 ? _scope : context.getScope()
38
+ };
39
+ };
40
+ const matches = (defs, refs) => {
41
+ // When there are no defs, the definition is inlined. This must be a match as we know the refs contain the initial
42
+ // definition.
43
+ if (!defs.length) {
44
+ return true;
45
+ }
46
+
47
+ // When there are no refs, the reference refers to the entire definition and therefore must be a match.
48
+ if (!refs.length) {
49
+ return true;
50
+ }
51
+
52
+ // When both the references and definitions exist, they should match in length
53
+ if (defs.length !== refs.length) {
54
+ return false;
55
+ }
56
+ return defs.every((def, i) => {
57
+ const ref = refs[i];
58
+ if (def.type === 'Property') {
59
+ // There is a match between the def and the ref when both names match:
60
+ //
61
+ // const fooDef = { bar: '' };
62
+ // const barRef = fooDef.bar
63
+ //
64
+ // There is no match when the ref property does not match the definition key name:
65
+ //
66
+ // const barRef = fooDef.notFound
67
+ return def.key.type === 'Identifier' && ref.type === 'MemberExpression' && ref.property.type === 'Identifier' && ref.property.name === def.key.name;
68
+ }
69
+
70
+ // Anything here is either unsupported or should not match...
71
+ return false;
72
+ });
73
+ };
74
+ export const checkIfSupportedExport = (context, node, importSources, scope = context.getScope()) => {
75
+ // Ignore any expression defined outside of the global or module scope as we have no way of statically analysing them
76
+ if (scope.type !== 'global' && scope.type !== 'module') {
77
+ return {
78
+ isExport: false
79
+ };
80
+ }
81
+ const {
82
+ root,
83
+ nodes
84
+ } = getStack(context, node.parent);
85
+ // Exporting a component with a css reference should be allowed
86
+ if (isStyledComponent(nodes, context, importSources)) {
87
+ return {
88
+ isExport: false
89
+ };
90
+ }
91
+ if (root.type === 'ExportDefaultDeclaration' || root.type === 'ExportNamedDeclaration') {
92
+ return {
93
+ isExport: true,
94
+ node: root
95
+ };
96
+ }
97
+ if (root.type !== 'VariableDeclarator') {
98
+ return {
99
+ isExport: false
100
+ };
101
+ }
102
+
103
+ // Find the reference to the variable declarator
104
+ const reference = scope.references.find(({
105
+ identifier
106
+ }) => identifier === root.id);
107
+ if (!reference) {
108
+ return {
109
+ isExport: false
110
+ };
111
+ }
112
+
113
+ // Iterate through all of the references to the resolved variable declarator node
114
+ const {
115
+ resolved
116
+ } = reference;
117
+ for (const {
118
+ identifier
119
+ } of (_resolved$references = resolved === null || resolved === void 0 ? void 0 : resolved.references) !== null && _resolved$references !== void 0 ? _resolved$references : []) {
120
+ var _resolved$references;
121
+ // Skip references to the root, since it has already been processed above
122
+ if (identifier === root.id) {
123
+ continue;
124
+ }
125
+ const {
126
+ nodes: refs,
127
+ scope: nextScope
128
+ } = getStack(context, identifier.parent);
129
+
130
+ // Only validate the resolved reference if it accesses the definition node
131
+ if (matches(nodes, refs.reverse())) {
132
+ // Now validate the identifier reference as a definition
133
+ const validity = checkIfSupportedExport(context, identifier, importSources, nextScope);
134
+ if (validity.isExport) {
135
+ return validity;
136
+ }
137
+ }
138
+ }
139
+ return {
140
+ isExport: false
141
+ };
142
+ };
@@ -0,0 +1,70 @@
1
+ import { getFirstSupportedImport } from '../get-first-supported-import';
2
+ /**
3
+ * Given a list of node, find and return the callee of the first Compiled or styled-components `styled` function call found in the list.
4
+ *
5
+ * For example, given `styled.div({ ... })`, we return the node corresponding to the
6
+ * `styled.div` part. Alternatively, given `styled(button)(style)`, we return the `styled`
7
+ * part.
8
+ *
9
+ * @param nodes
10
+ * @returns The callee of the first `styled` function call found.
11
+ */
12
+ const findNode = nodes => {
13
+ const node = nodes.find(n => n.type === 'TaggedTemplateExpression' || n.type === 'CallExpression');
14
+ if (!node) {
15
+ return;
16
+ }
17
+ if (node.type === 'CallExpression') {
18
+ // Eg. const Component = styled.button(style)
19
+ if (node.callee.type === 'MemberExpression') {
20
+ return node.callee;
21
+ }
22
+
23
+ // Eg. const Component = styled(button)(style)
24
+ if (node.callee.type === 'CallExpression' && node.callee.callee.type === 'Identifier') {
25
+ return node.callee.callee;
26
+ }
27
+ }
28
+
29
+ // Eg. const Component = styled.div`${styles}`;
30
+ if (node.type === 'TaggedTemplateExpression' && node.tag.type === 'MemberExpression') {
31
+ return node.tag;
32
+ }
33
+ return;
34
+ };
35
+
36
+ /**
37
+ * Given a rule, return the local name used to import the `styled` API. (for Compiled or styled-components).
38
+ *
39
+ * @param context Rule context.
40
+ * @returns The local name used to import the `styled` API.
41
+ */
42
+ const getStyledImportSpecifierName = (context, importSources) => {
43
+ var _supportedImport$spec;
44
+ const supportedImport = getFirstSupportedImport(context, importSources);
45
+ return supportedImport === null || supportedImport === void 0 ? void 0 : (_supportedImport$spec = supportedImport.specifiers.find(spec => spec.type === 'ImportSpecifier' && spec.imported.name === 'styled' || spec.type === 'ImportDefaultSpecifier' && spec.local.name === 'styled')) === null || _supportedImport$spec === void 0 ? void 0 : _supportedImport$spec.local.name;
46
+ };
47
+
48
+ /**
49
+ * Returns whether the node is a usage of the `styled` API in the libraries we support.
50
+ *
51
+ * @param nodes Nodes to check.
52
+ * @param context Rule context.
53
+ * @param importSources A list of libraries we support.
54
+ * @returns Whether the node is a usage of the `styled` API.
55
+ */
56
+ export const isStyledComponent = (nodes, context, importSources) => {
57
+ const node = findNode(nodes);
58
+ if (!node) {
59
+ return false;
60
+ }
61
+ const styledImportSpecifierName = getStyledImportSpecifierName(context, importSources);
62
+ if (styledImportSpecifierName) {
63
+ if (node.type === 'Identifier') {
64
+ return node.name === styledImportSpecifierName;
65
+ } else {
66
+ return node.object.type === 'Identifier' && node.object.name === styledImportSpecifierName;
67
+ }
68
+ }
69
+ return false;
70
+ };
@@ -0,0 +1,59 @@
1
+ import { getImportSources } from '../is-supported-import';
2
+ import { checkIfSupportedExport } from './check-if-supported-export';
3
+ /**
4
+ * Creates a new ESLint rule for banning exporting certain function calls, e.g.
5
+ * `css` and `keyframes`.
6
+ *
7
+ * Copied from the `utils/create-no-exported-rule/` folder in @compiled/eslint-plugin.
8
+ *
9
+ * Requires an importSources option defined on the rule, which is used to define additional
10
+ * packages which should be checked as part of this rule.
11
+ *
12
+ * @param isUsage A function that checks whether the current node matches the desired
13
+ * function call to check.
14
+ * @param messageId The ESLint error message to use for lint violations.
15
+ * @returns An eslint rule.
16
+ */
17
+ export const createNoExportedRule = (isUsage, messageId) => context => {
18
+ const importSources = getImportSources(context);
19
+ const {
20
+ text
21
+ } = context.getSourceCode();
22
+ if (importSources.every(importSource => !text.includes(importSource))) {
23
+ return {};
24
+ }
25
+ return {
26
+ CallExpression(node) {
27
+ const {
28
+ references
29
+ } = context.getScope();
30
+ if (!isUsage(node.callee, references, importSources)) {
31
+ return;
32
+ }
33
+ const state = checkIfSupportedExport(context, node, importSources);
34
+ if (!state.isExport) {
35
+ return;
36
+ }
37
+ context.report({
38
+ messageId,
39
+ node: state.node
40
+ });
41
+ },
42
+ TaggedTemplateExpression(node) {
43
+ const {
44
+ references
45
+ } = context.getScope();
46
+ if (!isUsage(node.tag, references, importSources)) {
47
+ return;
48
+ }
49
+ const state = checkIfSupportedExport(context, node, importSources);
50
+ if (!state.isExport) {
51
+ return;
52
+ }
53
+ context.report({
54
+ messageId,
55
+ node: state.node
56
+ });
57
+ }
58
+ };
59
+ };
@@ -0,0 +1,22 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ /**
3
+ * Get the first import declaration in the file that matches any of the packages
4
+ * in `importSources`.
5
+ *
6
+ * @param context Rule context.
7
+ * @param importSources The packages to check import statements for. If importSources
8
+ * contains more than one package, the first import statement
9
+ * detected in the file that matches any of the packages will be
10
+ * returned.
11
+ * @returns The first import declaration found in the file.
12
+ */
13
+ export const getFirstSupportedImport = (context, importSources) => {
14
+ const isSupportedImport = node => {
15
+ return isNodeOfType(node, 'ImportDeclaration') && typeof node.source.value === 'string' && importSources.includes(node.source.value);
16
+ };
17
+ const source = context.getSourceCode();
18
+ const supportedImports = source.ast.body.filter(isSupportedImport);
19
+ if (supportedImports.length) {
20
+ return supportedImports[0];
21
+ }
22
+ };
@@ -5,34 +5,68 @@ export const CSS_IN_JS_IMPORTS = {
5
5
  emotionReact: '@emotion/react',
6
6
  emotionCore: '@emotion/core',
7
7
  styledComponents: 'styled-components',
8
- atlaskitCss: '@atlaskit/css'
8
+ atlaskitCss: '@atlaskit/css',
9
+ atlaskitPrimitives: '@atlaskit/primitives'
9
10
  };
10
11
 
11
- // List of CSS-in-JS libraries an import of a valid css, cx, cssMap, etc.
12
+ // A CSS-in-JS library an import of a valid css, cx, cssMap, etc.
12
13
  // function might originate from, e.g. @compiled/react, @emotion/core.
14
+ // All ESLint rules originating from `@compiled/eslint-plugin` should apply to these libraries.
15
+ export const DEFAULT_IMPORT_SOURCES = [CSS_IN_JS_IMPORTS.compiled, CSS_IN_JS_IMPORTS.atlaskitCss];
16
+
17
+ /**
18
+ * Given the ESLint rule context, extract and parse the value of the importSources rule option.
19
+ * The importSources option is used to define additional libraries for which an ESLint rule
20
+ * should apply to.
21
+ *
22
+ * Note that `@compiled/react` and `@atlaskit/css` are always included in importSources, regardless
23
+ * of what importSources is configured to by the user.
24
+ *
25
+ * @param context The rule context.
26
+ * @returns An array of strings representing what CSS-in-JS packages that should be checked, based
27
+ * on the rule options configuration.
28
+ */
29
+ export const getImportSources = context => {
30
+ const options = context.options;
31
+ if (!options.length) {
32
+ return DEFAULT_IMPORT_SOURCES;
33
+ }
34
+ if (options[0].importSources && Array.isArray(options[0].importSources)) {
35
+ return [...DEFAULT_IMPORT_SOURCES, ...options[0].importSources];
36
+ }
37
+ return DEFAULT_IMPORT_SOURCES;
38
+ };
13
39
  const isSupportedImportWrapper = functionName => {
14
- // This will need to be extended to support default imports once we start
15
- // checking cases like `import css from '@emotion/css'`
16
40
  const checkDefinitionHasImport = (def, importSources) => {
17
- var _def$parent, _def$parent2;
18
- return def.node.type === 'ImportSpecifier' && def.node.imported.type === 'Identifier' && def.node.imported.name === functionName && ((_def$parent = def.parent) === null || _def$parent === void 0 ? void 0 : _def$parent.type) === 'ImportDeclaration' && importSources.includes((_def$parent2 = def.parent) === null || _def$parent2 === void 0 ? void 0 : _def$parent2.source.value);
41
+ if (def.type !== 'ImportBinding') {
42
+ return false;
43
+ }
44
+ if (!def.parent || !importSources.includes(def.parent.source.value)) {
45
+ return false;
46
+ }
47
+ return (
48
+ // import { functionName } from 'import-source';
49
+ def.node.type === 'ImportSpecifier' && def.node.imported.name === functionName ||
50
+ // import functionName from 'import-source';
51
+ def.node.type === 'ImportDefaultSpecifier' && def.node.local.name === functionName
52
+ );
19
53
  };
20
54
 
21
55
  /**
22
56
  * Checks whether:
23
57
  *
24
- * 1. a function name `nodeToCheck` matches the name of the function we
58
+ * 1. A function name `nodeToCheck` matches the name of the function we
25
59
  * want to check for (e.g. `cx`, `css`, `cssMap`, or `keyframes`), and
26
- * 2. whether `nodeToCheck` originates from one of the libraries listed
60
+ * 2. Whether `nodeToCheck` originates from one of the libraries listed
27
61
  * in `importSources`.
28
62
  *
29
- * @param nodeToCheck the function callee we are checking (e.g. the `css` in `css()`)
30
- * @param referencesInScope list of references that are in scope. We'll use this
63
+ * @param nodeToCheck The function callee we are checking (e.g. The `css` in `css()`).
64
+ * @param referencesInScope List of references that are in scope. We'll use this
31
65
  * to check where the function callee is imported from.
32
- * @param importSources list of libraries that we want to ensure `nodeToCheck`
33
- * comes from
66
+ * @param importSources List of libraries that we want to ensure `nodeToCheck`
67
+ * comes from.
34
68
  *
35
- * @returns whether the above conditions are true
69
+ * @returns Whether the above conditions are true.
36
70
  */
37
71
  const isSupportedImport = (nodeToCheck, referencesInScope, importSources) => {
38
72
  return nodeToCheck.type === 'Identifier' && referencesInScope.some(reference => {
@@ -48,5 +82,6 @@ const isSupportedImportWrapper = functionName => {
48
82
  //
49
83
  export const isCss = isSupportedImportWrapper('css');
50
84
  export const isCxFunction = isSupportedImportWrapper('cx');
51
- // export const isCssMap = isLibraryImportWrapper('cssMap');
52
- // export const isKeyframes = isLibraryImportWrapper('keyframes');
85
+ export const isCssMap = isSupportedImportWrapper('cssMap');
86
+ export const isKeyframes = isSupportedImportWrapper('keyframes');
87
+ export const isStyled = isSupportedImportWrapper('styled');