@atlaskit/eslint-plugin-platform 0.7.3 → 0.7.4

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 (30) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/index.js +4 -3
  3. package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +0 -3
  4. package/dist/cjs/rules/no-duplicate-dependencies/index.js +3 -3
  5. package/dist/cjs/rules/no-module-level-eval-nav4/index.js +56 -0
  6. package/dist/cjs/rules/utils.js +3 -3
  7. package/dist/es2019/index.js +4 -3
  8. package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +0 -3
  9. package/dist/es2019/rules/no-module-level-eval-nav4/index.js +50 -0
  10. package/dist/esm/index.js +4 -3
  11. package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +0 -3
  12. package/dist/esm/rules/no-duplicate-dependencies/index.js +3 -3
  13. package/dist/esm/rules/no-module-level-eval-nav4/index.js +50 -0
  14. package/dist/esm/rules/utils.js +3 -3
  15. package/dist/types/index.d.ts +2 -1
  16. package/dist/types-ts4.5/index.d.ts +2 -1
  17. package/index.js +1 -1
  18. package/package.json +6 -5
  19. package/src/index.tsx +7 -3
  20. package/src/rules/ensure-native-and-af-exports-synced/index.tsx +0 -3
  21. package/src/rules/no-module-level-eval-nav4/README.md +8 -0
  22. package/src/rules/no-module-level-eval-nav4/__tests__/test.tsx +130 -0
  23. package/src/rules/no-module-level-eval-nav4/index.tsx +67 -0
  24. package/dist/cjs/rules/ensure-valid-emotion-css-prop/index.js +0 -91
  25. package/dist/es2019/rules/ensure-valid-emotion-css-prop/index.js +0 -87
  26. package/dist/esm/rules/ensure-valid-emotion-css-prop/index.js +0 -85
  27. package/src/rules/ensure-valid-emotion-css-prop/__tests__/unit/rule.test.ts +0 -142
  28. package/src/rules/ensure-valid-emotion-css-prop/index.ts +0 -96
  29. /package/dist/types/rules/{ensure-valid-emotion-css-prop → no-module-level-eval-nav4}/index.d.ts +0 -0
  30. /package/dist/types-ts4.5/rules/{ensure-valid-emotion-css-prop → no-module-level-eval-nav4}/index.d.ts +0 -0
@@ -0,0 +1,130 @@
1
+ import outdent from 'outdent';
2
+ import { tester } from '../../../__tests__/utils/_tester';
3
+ import rule from '../index';
4
+
5
+ tester.run('feature-flags/no-module-level-eval', rule, {
6
+ valid: [
7
+ {
8
+ name: 'Variable declaration within arrow function',
9
+ code: outdent`
10
+ import { getWillShowNav4 } from '@atlassian/jira-navigation-apps-sidebar-nav4-rollout';
11
+
12
+ const Component = () => {
13
+ const ff1 = getWillShowNav4();
14
+ }
15
+ `,
16
+ },
17
+ {
18
+ name: 'Boolean within arrow function',
19
+ code: outdent`
20
+ import { getWillShowNav4 } from '@atlassian/jira-navigation-apps-sidebar-nav4-rollout';
21
+
22
+ const someFunction = () => {
23
+ if(getWillShowNav4()) {
24
+ return true;
25
+ }
26
+ }
27
+ `,
28
+ },
29
+ {
30
+ name: 'Immediate call within a function',
31
+ code: outdent`
32
+ import { getWillShowNav4 } from '@atlassian/jira-navigation-apps-sidebar-nav4-rollout';
33
+
34
+ function someFunction() {
35
+ return getWillShowNav4();
36
+ }
37
+ `,
38
+ },
39
+
40
+ {
41
+ name: 'Does not affect other function calls in global scope',
42
+ code: outdent`
43
+ import { foo } from 'foo-lib'
44
+ import { getWillShowNav4 } from '@atlassian/jira-navigation-apps-sidebar-nav4-rollout';
45
+
46
+ foo();
47
+ `,
48
+ },
49
+ {
50
+ name: 'Variable declaration within an arrow function with different import source',
51
+ code: outdent`
52
+ import { ff } from '@atlassian/jira-feature-flagging-using-meta';
53
+
54
+ const Component = () => {
55
+ const ff1 = getWillShowNav4();
56
+ }
57
+ `,
58
+ },
59
+ {
60
+ name: 'Class field',
61
+ code: outdent`
62
+ import { getWillShowNav4 } from '@atlassian/jira-navigation-apps-sidebar-nav4-rollout';
63
+
64
+ export class Component {
65
+ value = getWillShowNav4() ? 'newValue' : 'oldValue';
66
+ }
67
+ `,
68
+ },
69
+ ],
70
+ invalid: [
71
+ {
72
+ name: 'Immediate call',
73
+ code: outdent`
74
+ import { getWillShowNav4 } from '@atlassian/jira-navigation-apps-sidebar-nav4-rollout';
75
+
76
+ getWillShowNav4();
77
+ `,
78
+ errors: [{ messageId: 'noModuleLevelEval' }],
79
+ },
80
+ {
81
+ name: 'Immediate call isVisualRefreshEnabled',
82
+ code: outdent`
83
+ import { isVisualRefreshEnabled } from '@atlassian/jira-visual-refresh-rollout/src/feature-switch';
84
+
85
+ isVisualRefreshEnabled();
86
+ `,
87
+ errors: [{ messageId: 'noModuleLevelEval' }],
88
+ },
89
+ {
90
+ name: 'Module level storage into a variable',
91
+ code: outdent`
92
+ import { getWillShowNav4 } from '@atlassian/jira-navigation-apps-sidebar-nav4-rollout';
93
+
94
+ const flag = getWillShowNav4();
95
+ `,
96
+ errors: [{ messageId: 'noModuleLevelEval' }],
97
+ },
98
+ {
99
+ name: 'Module level collection',
100
+ code: outdent`
101
+ import { getWillShowNav4 } from '@atlassian/jira-navigation-apps-sidebar-nav4-rollout';
102
+
103
+ const collection = [getWillShowNav4() ? 0 : 1];
104
+ `,
105
+ errors: [{ messageId: 'noModuleLevelEval' }],
106
+ },
107
+ {
108
+ name: 'Module level object',
109
+ code: outdent`
110
+ import { getWillShowNav4 } from '@atlassian/jira-navigation-apps-sidebar-nav4-rollout';
111
+
112
+ const object = {
113
+ isEnabled: getWillShowNav4()
114
+ }
115
+ `,
116
+ errors: [{ messageId: 'noModuleLevelEval' }],
117
+ },
118
+ {
119
+ name: 'Module level with boolean',
120
+ code: outdent`
121
+ import { getWillShowNav4 } from '@atlassian/jira-navigation-apps-sidebar-nav4-rollout';
122
+ import { NewComp } from 'new';
123
+ import { OldComp } from 'old';
124
+
125
+ export const Component = getWillShowNav4() ? NewComp : OldComp;
126
+ `,
127
+ errors: [{ messageId: 'noModuleLevelEval' }],
128
+ },
129
+ ],
130
+ });
@@ -0,0 +1,67 @@
1
+ import type { Rule } from 'eslint';
2
+ import { type Node } from '../utils';
3
+
4
+ const featureLibraryFunctions = new Set([
5
+ /*
6
+ * STOP!
7
+ *
8
+ * Your code should call the API functions directly!
9
+ * But, we are temporarily adding these methods to prevent SSR builds from breaking
10
+ * while we work out a solution for the features to be evaluated inline.
11
+ * Do not add anything here without the permission of #help-jfp-squads
12
+ *
13
+ * Slack thread: https://atlassian.slack.com/archives/CFGLH1ZS8/p1726449739284819
14
+ */
15
+ 'isVisualRefreshEnabled',
16
+ 'getWillShowNav4',
17
+ ]);
18
+
19
+ const isInFunctionLevel = (context: Rule.RuleContext) => {
20
+ let scope: any = context.getScope();
21
+
22
+ while (scope?.type !== 'module' && scope?.type !== 'global') {
23
+ if (scope.type === 'function') {
24
+ return true;
25
+ }
26
+
27
+ if (scope.type === 'class-field-initializer') {
28
+ return !scope.block.parent.static;
29
+ }
30
+
31
+ scope = scope.upper;
32
+ }
33
+
34
+ return false;
35
+ };
36
+
37
+ const rule: Rule.RuleModule = {
38
+ meta: {
39
+ docs: {
40
+ description: 'Disallow getWillShowNav4 or isVisualRefreshEnabled usage at module level',
41
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/no-module-level-eval-nav4/README.md',
42
+ },
43
+ messages: {
44
+ noModuleLevelEval:
45
+ 'Do not evaluate getWillShowNav4 or isVisualRefreshEnabled at module level. This causes complications with SSR. If feature flagging components in `jira` use `componentWithCondition` from `@atlassian/jira-feature-flagging-utils`.',
46
+ },
47
+ },
48
+ create(context) {
49
+ return {
50
+ 'CallExpression[callee.type="Identifier"]': (node: Node<any>) => {
51
+ if (
52
+ node.type === 'CallExpression' &&
53
+ node.callee.type === 'Identifier' &&
54
+ featureLibraryFunctions.has(node.callee.name) &&
55
+ !isInFunctionLevel(context)
56
+ ) {
57
+ context.report({
58
+ messageId: 'noModuleLevelEval',
59
+ node,
60
+ });
61
+ }
62
+ },
63
+ };
64
+ },
65
+ };
66
+
67
+ export default rule;
@@ -1,91 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- // eslint-disable-next-line import/no-extraneous-dependencies
8
-
9
- var rule = {
10
- meta: {
11
- type: 'problem',
12
- docs: {
13
- description: 'Ensure valid use of the `css` prop from `@emotion/react`',
14
- recommended: true
15
- },
16
- messages: {
17
- noEmotionCssImport: 'Must import `css` from `@emotion/react` when using the `css` prop.',
18
- noEmotionCssPropFunctionCall: 'No function calls allowed when passing an object directly to the `css` prop with `@emotion/react`.'
19
- }
20
- },
21
- create: function create(context) {
22
- var emotionJsxImported = false;
23
- var emotionJsxImportPosition;
24
- var emotionCssImported = false;
25
- var cssPropExpressonUsed = false;
26
-
27
- // Ignore files in these directories
28
- if (/example|__tests__|__fixtures__/.test(context.filename)) {
29
- return {};
30
- }
31
- return {
32
- ImportDeclaration: function ImportDeclaration(node) {
33
- if (node.source.value === '@emotion/react') {
34
- node.specifiers.forEach(function (specifier) {
35
- if (specifier.type === 'ImportSpecifier') {
36
- if (specifier.imported.name === 'jsx') {
37
- var _specifier$loc;
38
- emotionJsxImported = true;
39
- emotionJsxImportPosition = (_specifier$loc = specifier.loc) === null || _specifier$loc === void 0 ? void 0 : _specifier$loc.start;
40
- }
41
- if (specifier.imported.name === 'css') {
42
- emotionCssImported = true;
43
- }
44
- }
45
- });
46
- }
47
- },
48
- JSXAttribute: function JSXAttribute(node) {
49
- var name = node.name,
50
- value = node.value;
51
-
52
- // Only run on emotion css props
53
- if (!emotionJsxImported) return;
54
- if (name.name !== 'css') return;
55
- if (value.type === 'JSXExpressionContainer' && value.expression.type === 'ObjectExpression') {
56
- cssPropExpressonUsed = true;
57
- var containsFunctionExpression = false;
58
-
59
- // Iterate over the properties of the object
60
- value.expression.properties.forEach(function (prop) {
61
- var _prop$value, _prop$value2, _prop$value3;
62
- // Check for function expressions directly within the object literal
63
- if (((_prop$value = prop.value) === null || _prop$value === void 0 ? void 0 : _prop$value.type) === 'ArrowFunctionExpression' || ((_prop$value2 = prop.value) === null || _prop$value2 === void 0 ? void 0 : _prop$value2.type) === 'FunctionExpression' || ((_prop$value3 = prop.value) === null || _prop$value3 === void 0 ? void 0 : _prop$value3.type) === 'CallExpression') {
64
- containsFunctionExpression = true;
65
- }
66
- });
67
-
68
- // If a function expression is found within the direct object literal, report an error
69
- if (containsFunctionExpression) {
70
- context.report({
71
- node: node,
72
- messageId: 'noEmotionCssPropFunctionCall'
73
- });
74
- }
75
- }
76
- },
77
- 'Program:exit': function ProgramExit() {
78
- if (emotionJsxImported && cssPropExpressonUsed && !emotionCssImported) {
79
- context.report({
80
- messageId: 'noEmotionCssImport',
81
- loc: emotionJsxImportPosition || {
82
- line: 1,
83
- column: 0
84
- }
85
- });
86
- }
87
- }
88
- };
89
- }
90
- };
91
- var _default = exports.default = rule;
@@ -1,87 +0,0 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
2
-
3
- const rule = {
4
- meta: {
5
- type: 'problem',
6
- docs: {
7
- description: 'Ensure valid use of the `css` prop from `@emotion/react`',
8
- recommended: true
9
- },
10
- messages: {
11
- noEmotionCssImport: 'Must import `css` from `@emotion/react` when using the `css` prop.',
12
- noEmotionCssPropFunctionCall: 'No function calls allowed when passing an object directly to the `css` prop with `@emotion/react`.'
13
- }
14
- },
15
- create(context) {
16
- let emotionJsxImported = false;
17
- let emotionJsxImportPosition;
18
- let emotionCssImported = false;
19
- let cssPropExpressonUsed = false;
20
-
21
- // Ignore files in these directories
22
- if (/example|__tests__|__fixtures__/.test(context.filename)) {
23
- return {};
24
- }
25
- return {
26
- ImportDeclaration(node) {
27
- if (node.source.value === '@emotion/react') {
28
- node.specifiers.forEach(specifier => {
29
- if (specifier.type === 'ImportSpecifier') {
30
- if (specifier.imported.name === 'jsx') {
31
- var _specifier$loc;
32
- emotionJsxImported = true;
33
- emotionJsxImportPosition = (_specifier$loc = specifier.loc) === null || _specifier$loc === void 0 ? void 0 : _specifier$loc.start;
34
- }
35
- if (specifier.imported.name === 'css') {
36
- emotionCssImported = true;
37
- }
38
- }
39
- });
40
- }
41
- },
42
- JSXAttribute(node) {
43
- const {
44
- name,
45
- value
46
- } = node;
47
-
48
- // Only run on emotion css props
49
- if (!emotionJsxImported) return;
50
- if (name.name !== 'css') return;
51
- if (value.type === 'JSXExpressionContainer' && value.expression.type === 'ObjectExpression') {
52
- cssPropExpressonUsed = true;
53
- let containsFunctionExpression = false;
54
-
55
- // Iterate over the properties of the object
56
- value.expression.properties.forEach(prop => {
57
- var _prop$value, _prop$value2, _prop$value3;
58
- // Check for function expressions directly within the object literal
59
- if (((_prop$value = prop.value) === null || _prop$value === void 0 ? void 0 : _prop$value.type) === 'ArrowFunctionExpression' || ((_prop$value2 = prop.value) === null || _prop$value2 === void 0 ? void 0 : _prop$value2.type) === 'FunctionExpression' || ((_prop$value3 = prop.value) === null || _prop$value3 === void 0 ? void 0 : _prop$value3.type) === 'CallExpression') {
60
- containsFunctionExpression = true;
61
- }
62
- });
63
-
64
- // If a function expression is found within the direct object literal, report an error
65
- if (containsFunctionExpression) {
66
- context.report({
67
- node,
68
- messageId: 'noEmotionCssPropFunctionCall'
69
- });
70
- }
71
- }
72
- },
73
- 'Program:exit'() {
74
- if (emotionJsxImported && cssPropExpressonUsed && !emotionCssImported) {
75
- context.report({
76
- messageId: 'noEmotionCssImport',
77
- loc: emotionJsxImportPosition || {
78
- line: 1,
79
- column: 0
80
- }
81
- });
82
- }
83
- }
84
- };
85
- }
86
- };
87
- export default rule;
@@ -1,85 +0,0 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
2
-
3
- var rule = {
4
- meta: {
5
- type: 'problem',
6
- docs: {
7
- description: 'Ensure valid use of the `css` prop from `@emotion/react`',
8
- recommended: true
9
- },
10
- messages: {
11
- noEmotionCssImport: 'Must import `css` from `@emotion/react` when using the `css` prop.',
12
- noEmotionCssPropFunctionCall: 'No function calls allowed when passing an object directly to the `css` prop with `@emotion/react`.'
13
- }
14
- },
15
- create: function create(context) {
16
- var emotionJsxImported = false;
17
- var emotionJsxImportPosition;
18
- var emotionCssImported = false;
19
- var cssPropExpressonUsed = false;
20
-
21
- // Ignore files in these directories
22
- if (/example|__tests__|__fixtures__/.test(context.filename)) {
23
- return {};
24
- }
25
- return {
26
- ImportDeclaration: function ImportDeclaration(node) {
27
- if (node.source.value === '@emotion/react') {
28
- node.specifiers.forEach(function (specifier) {
29
- if (specifier.type === 'ImportSpecifier') {
30
- if (specifier.imported.name === 'jsx') {
31
- var _specifier$loc;
32
- emotionJsxImported = true;
33
- emotionJsxImportPosition = (_specifier$loc = specifier.loc) === null || _specifier$loc === void 0 ? void 0 : _specifier$loc.start;
34
- }
35
- if (specifier.imported.name === 'css') {
36
- emotionCssImported = true;
37
- }
38
- }
39
- });
40
- }
41
- },
42
- JSXAttribute: function JSXAttribute(node) {
43
- var name = node.name,
44
- value = node.value;
45
-
46
- // Only run on emotion css props
47
- if (!emotionJsxImported) return;
48
- if (name.name !== 'css') return;
49
- if (value.type === 'JSXExpressionContainer' && value.expression.type === 'ObjectExpression') {
50
- cssPropExpressonUsed = true;
51
- var containsFunctionExpression = false;
52
-
53
- // Iterate over the properties of the object
54
- value.expression.properties.forEach(function (prop) {
55
- var _prop$value, _prop$value2, _prop$value3;
56
- // Check for function expressions directly within the object literal
57
- if (((_prop$value = prop.value) === null || _prop$value === void 0 ? void 0 : _prop$value.type) === 'ArrowFunctionExpression' || ((_prop$value2 = prop.value) === null || _prop$value2 === void 0 ? void 0 : _prop$value2.type) === 'FunctionExpression' || ((_prop$value3 = prop.value) === null || _prop$value3 === void 0 ? void 0 : _prop$value3.type) === 'CallExpression') {
58
- containsFunctionExpression = true;
59
- }
60
- });
61
-
62
- // If a function expression is found within the direct object literal, report an error
63
- if (containsFunctionExpression) {
64
- context.report({
65
- node: node,
66
- messageId: 'noEmotionCssPropFunctionCall'
67
- });
68
- }
69
- }
70
- },
71
- 'Program:exit': function ProgramExit() {
72
- if (emotionJsxImported && cssPropExpressonUsed && !emotionCssImported) {
73
- context.report({
74
- messageId: 'noEmotionCssImport',
75
- loc: emotionJsxImportPosition || {
76
- line: 1,
77
- column: 0
78
- }
79
- });
80
- }
81
- }
82
- };
83
- }
84
- };
85
- export default rule;
@@ -1,142 +0,0 @@
1
- import { tester } from '../../../../__tests__/utils/_tester';
2
- import rule from '../../index';
3
-
4
- describe('test ensure-valid-emotion-css-prop', () => {
5
- tester.run('ensure-valid-emotion-css-prop', rule, {
6
- valid: [
7
- {
8
- code: `
9
- /** @jsx jsx */
10
- import { jsx, css } from '@emotion/react';
11
- const styles = css({ opacity: 0.5 });
12
- const Component = () => <div css={styles} />;`,
13
- filename: 'src/index.tsx',
14
- },
15
- {
16
- code: `
17
- /** @jsx jsx */
18
- import { jsx, css } from '@emotion/react';
19
- const styles = css({ opacity: 0.5 });
20
- const Component = () => <div css={[styles, { color: 'red' }]} />;`,
21
- filename: 'src/index.tsx',
22
- },
23
- {
24
- code: `
25
- /** @jsx jsx */
26
- import { jsx } from '@emotion/react';
27
- const Component = () => <div />;`,
28
- filename: 'src/index.tsx',
29
- },
30
- {
31
- code: `
32
- /** @jsx jsx */
33
- import { jsx, css } from '@emotion/react';
34
- const Component = () => <div css={css({ opacity: 0.5 })} />;`,
35
- filename: 'src/index.tsx',
36
- },
37
- {
38
- code: `
39
- /** @jsx jsx */
40
- import { jsx } from '@emotion/react';
41
- const Component = () => <div css={{ opacity: 0.5 }} />;`,
42
- filename: 'src/__tests__/test.tsx',
43
- },
44
- {
45
- code: `
46
- /** @jsx jsx */
47
- import { jsx } from '@emotion/react';
48
- const Component = () => <div css={{ opacity: 0.5 }} />;`,
49
- filename: 'src/examples/example.tsx',
50
- },
51
- {
52
- code: `
53
- /** @jsx jsx */
54
- import { jsx } from '@emotion/react';
55
- const Component = () => <div css={{ opacity: 0.5 }} />;`,
56
- filename: 'src/example-helpers/index.tsx',
57
- },
58
- {
59
- code: `
60
- /** @jsx jsx */
61
- import { jsx } from '@emotion/react';
62
- const Component = () => <div css={{ opacity: 0.5 }} />;`,
63
- filename: 'src/__fixtures__/index.tsx',
64
- },
65
- {
66
- code: `
67
- /** @jsx jsx */
68
- import { jsx } from '@compiled/react';
69
- const Component = () => <div />;`,
70
- filename: 'src/index.tsx',
71
- },
72
- {
73
- code: `
74
- /** @jsx jsx */
75
- import { jsx } from '@emotion/react';
76
- const styles = { opacity: 0.5 };
77
- const Component = () => <div css={styles} />;`,
78
- filename: 'src/index.tsx',
79
- },
80
- {
81
- code: `
82
- /** @jsx jsx */
83
- import { jsx, css } from '@emotion/react';
84
- const getOpacity = (foo) => 0.5;
85
- const Component = (props) => {
86
- const styles = { opacity: getOpacity(props.foo) };
87
- return <div css={styles} />
88
- };`,
89
- filename: 'src/index.tsx',
90
- },
91
- {
92
- code: `
93
- /** @jsx jsx */
94
- import { jsx, css } from '@emotion/react';
95
- const getStyles = (opacity) => ({ color: 'red', opacity });
96
- const Component = (props) => <div css={getStyles(props.opacity)} />`,
97
- filename: 'src/index.tsx',
98
- },
99
- {
100
- code: `
101
- /** @jsx jsx */
102
- import { jsx, css } from '@emotion/react';
103
- const getOpacity = (foo) => 0.5;
104
- function getStyles(props) {
105
- return { color: 'red', opacity: props.opacity };
106
- }
107
- const Component = (props) => <div css={getStyles(props)} />;`,
108
- filename: 'src/index.tsx',
109
- },
110
- ],
111
- invalid: [
112
- {
113
- code: `
114
- /** @jsx jsx */
115
- import { jsx } from '@emotion/react';
116
- const Component = () => <div css={{ opacity: 0.5 }} />;`,
117
- filename: 'src/index.tsx',
118
- errors: [{ messageId: 'noEmotionCssImport' }],
119
- },
120
- {
121
- code: `
122
- /** @jsx jsx */
123
- import { jsx, css } from '@emotion/react';
124
- const getOpacity = (foo) => 0.5;
125
- const Component = (props) => <div css={{ opacity: getOpacity(props.foo) }} />;`,
126
- filename: 'src/index.tsx',
127
- errors: [{ messageId: 'noEmotionCssPropFunctionCall' }],
128
- },
129
- {
130
- code: `
131
- /** @jsx jsx */
132
- import { jsx, css } from '@emotion/react';
133
- function getOpacity(foo) {
134
- return 0.5;
135
- }
136
- const Component = (props) => <div css={{ opacity: getOpacity(props.foo) }} />;`,
137
- filename: 'src/index.tsx',
138
- errors: [{ messageId: 'noEmotionCssPropFunctionCall' }],
139
- },
140
- ],
141
- });
142
- });
@@ -1,96 +0,0 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
2
- import type { Rule } from 'eslint';
3
-
4
- type Position = {
5
- line: number;
6
- column: number;
7
- };
8
-
9
- const rule: Rule.RuleModule = {
10
- meta: {
11
- type: 'problem',
12
- docs: {
13
- description: 'Ensure valid use of the `css` prop from `@emotion/react`',
14
- recommended: true,
15
- },
16
- messages: {
17
- noEmotionCssImport: 'Must import `css` from `@emotion/react` when using the `css` prop.',
18
- noEmotionCssPropFunctionCall:
19
- 'No function calls allowed when passing an object directly to the `css` prop with `@emotion/react`.',
20
- },
21
- },
22
- create(context) {
23
- let emotionJsxImported = false;
24
- let emotionJsxImportPosition: Position | null | undefined;
25
- let emotionCssImported = false;
26
- let cssPropExpressonUsed = false;
27
-
28
- // Ignore files in these directories
29
- if (/example|__tests__|__fixtures__/.test(context.filename)) {
30
- return {};
31
- }
32
-
33
- return {
34
- ImportDeclaration(node) {
35
- if (node.source.value === '@emotion/react') {
36
- node.specifiers.forEach((specifier) => {
37
- if (specifier.type === 'ImportSpecifier') {
38
- if (specifier.imported.name === 'jsx') {
39
- emotionJsxImported = true;
40
- emotionJsxImportPosition = specifier.loc?.start;
41
- }
42
- if (specifier.imported.name === 'css') {
43
- emotionCssImported = true;
44
- }
45
- }
46
- });
47
- }
48
- },
49
- JSXAttribute(node: any) {
50
- const { name, value } = node;
51
-
52
- // Only run on emotion css props
53
- if (!emotionJsxImported) return;
54
- if (name.name !== 'css') return;
55
-
56
- if (
57
- value.type === 'JSXExpressionContainer' &&
58
- value.expression.type === 'ObjectExpression'
59
- ) {
60
- cssPropExpressonUsed = true;
61
- let containsFunctionExpression = false;
62
-
63
- // Iterate over the properties of the object
64
- value.expression.properties.forEach((prop: any) => {
65
- // Check for function expressions directly within the object literal
66
- if (
67
- prop.value?.type === 'ArrowFunctionExpression' ||
68
- prop.value?.type === 'FunctionExpression' ||
69
- prop.value?.type === 'CallExpression'
70
- ) {
71
- containsFunctionExpression = true;
72
- }
73
- });
74
-
75
- // If a function expression is found within the direct object literal, report an error
76
- if (containsFunctionExpression) {
77
- context.report({
78
- node,
79
- messageId: 'noEmotionCssPropFunctionCall',
80
- });
81
- }
82
- }
83
- },
84
- 'Program:exit'() {
85
- if (emotionJsxImported && cssPropExpressonUsed && !emotionCssImported) {
86
- context.report({
87
- messageId: 'noEmotionCssImport',
88
- loc: emotionJsxImportPosition || { line: 1, column: 0 },
89
- });
90
- }
91
- },
92
- };
93
- },
94
- };
95
-
96
- export default rule;