@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.
- package/CHANGELOG.md +8 -0
- package/dist/cjs/index.js +4 -3
- package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +0 -3
- package/dist/cjs/rules/no-duplicate-dependencies/index.js +3 -3
- package/dist/cjs/rules/no-module-level-eval-nav4/index.js +56 -0
- package/dist/cjs/rules/utils.js +3 -3
- package/dist/es2019/index.js +4 -3
- package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +0 -3
- package/dist/es2019/rules/no-module-level-eval-nav4/index.js +50 -0
- package/dist/esm/index.js +4 -3
- package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +0 -3
- package/dist/esm/rules/no-duplicate-dependencies/index.js +3 -3
- package/dist/esm/rules/no-module-level-eval-nav4/index.js +50 -0
- package/dist/esm/rules/utils.js +3 -3
- package/dist/types/index.d.ts +2 -1
- package/dist/types-ts4.5/index.d.ts +2 -1
- package/index.js +1 -1
- package/package.json +6 -5
- package/src/index.tsx +7 -3
- package/src/rules/ensure-native-and-af-exports-synced/index.tsx +0 -3
- package/src/rules/no-module-level-eval-nav4/README.md +8 -0
- package/src/rules/no-module-level-eval-nav4/__tests__/test.tsx +130 -0
- package/src/rules/no-module-level-eval-nav4/index.tsx +67 -0
- package/dist/cjs/rules/ensure-valid-emotion-css-prop/index.js +0 -91
- package/dist/es2019/rules/ensure-valid-emotion-css-prop/index.js +0 -87
- package/dist/esm/rules/ensure-valid-emotion-css-prop/index.js +0 -85
- package/src/rules/ensure-valid-emotion-css-prop/__tests__/unit/rule.test.ts +0 -142
- package/src/rules/ensure-valid-emotion-css-prop/index.ts +0 -96
- /package/dist/types/rules/{ensure-valid-emotion-css-prop → no-module-level-eval-nav4}/index.d.ts +0 -0
- /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;
|