@atlaskit/eslint-plugin-platform 0.6.1 → 0.7.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.
- package/CHANGELOG.md +103 -28
- package/afm-jira/tsconfig.json +20 -0
- package/dist/cjs/index.js +48 -2
- package/dist/cjs/rules/constants.js +11 -0
- package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +54 -6
- package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +16 -7
- package/dist/cjs/rules/ensure-valid-emotion-css-prop/index.js +91 -0
- package/dist/cjs/rules/inline-usage/index.js +94 -0
- package/dist/cjs/rules/no-alias/index.js +64 -0
- package/dist/cjs/rules/no-module-level-eval/index.js +45 -0
- package/dist/cjs/rules/no-preconditioning/index.js +108 -0
- package/dist/cjs/rules/prefer-fg/index.js +106 -0
- package/dist/cjs/rules/static-feature-flags/index.js +63 -0
- package/dist/cjs/rules/use-recommended-utils/index.js +47 -0
- package/dist/cjs/rules/util/registration-utils.js +2 -1
- package/dist/cjs/rules/utils.js +53 -0
- package/dist/es2019/index.js +52 -2
- package/dist/es2019/rules/constants.js +5 -0
- package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +52 -6
- package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +15 -7
- package/dist/es2019/rules/ensure-valid-emotion-css-prop/index.js +87 -0
- package/dist/es2019/rules/inline-usage/index.js +90 -0
- package/dist/es2019/rules/no-alias/index.js +58 -0
- package/dist/es2019/rules/no-module-level-eval/index.js +39 -0
- package/dist/es2019/rules/no-preconditioning/index.js +105 -0
- package/dist/es2019/rules/prefer-fg/index.js +81 -0
- package/dist/es2019/rules/static-feature-flags/index.js +54 -0
- package/dist/es2019/rules/use-recommended-utils/index.js +41 -0
- package/dist/es2019/rules/util/registration-utils.js +2 -1
- package/dist/es2019/rules/utils.js +29 -0
- package/dist/esm/index.js +48 -2
- package/dist/esm/rules/constants.js +5 -0
- package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +54 -6
- package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +16 -7
- package/dist/esm/rules/ensure-valid-emotion-css-prop/index.js +85 -0
- package/dist/esm/rules/inline-usage/index.js +87 -0
- package/dist/esm/rules/no-alias/index.js +57 -0
- package/dist/esm/rules/no-module-level-eval/index.js +39 -0
- package/dist/esm/rules/no-preconditioning/index.js +102 -0
- package/dist/esm/rules/prefer-fg/index.js +99 -0
- package/dist/esm/rules/static-feature-flags/index.js +56 -0
- package/dist/esm/rules/use-recommended-utils/index.js +41 -0
- package/dist/esm/rules/util/registration-utils.js +2 -1
- package/dist/esm/rules/utils.js +45 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/rules/constants.d.ts +3 -0
- package/dist/types/rules/ensure-valid-emotion-css-prop/index.d.ts +3 -0
- package/dist/types/rules/inline-usage/index.d.ts +3 -0
- package/dist/types/rules/no-alias/index.d.ts +3 -0
- package/dist/types/rules/no-module-level-eval/index.d.ts +3 -0
- package/dist/types/rules/no-preconditioning/index.d.ts +3 -0
- package/dist/types/rules/prefer-fg/index.d.ts +3 -0
- package/dist/types/rules/static-feature-flags/index.d.ts +3 -0
- package/dist/types/rules/use-recommended-utils/index.d.ts +3 -0
- package/dist/types/rules/util/registration-utils.d.ts +1 -0
- package/dist/types/rules/utils.d.ts +7 -0
- package/dist/types-ts4.5/index.d.ts +15 -0
- package/dist/types-ts4.5/rules/constants.d.ts +3 -0
- package/dist/types-ts4.5/rules/ensure-valid-emotion-css-prop/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/inline-usage/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-alias/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-module-level-eval/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-preconditioning/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/prefer-fg/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/static-feature-flags/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/use-recommended-utils/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/util/registration-utils.d.ts +1 -0
- package/dist/types-ts4.5/rules/utils.d.ts +7 -0
- package/index.js +9 -9
- package/package.json +43 -44
- package/report.api.md +31 -30
- package/src/__tests__/utils/_tester.tsx +16 -16
- package/src/index.tsx +102 -51
- package/src/rules/constants.tsx +20 -0
- package/src/rules/ensure-atlassian-team/__tests__/unit/rule.test.ts +19 -19
- package/src/rules/ensure-atlassian-team/index.ts +39 -52
- package/src/rules/ensure-critical-dependency-resolutions/__test__/unit/rule.test.tsx +146 -81
- package/src/rules/ensure-critical-dependency-resolutions/index.tsx +152 -97
- package/src/rules/ensure-feature-flag-prefix/__tests__/unit/rule.test.tsx +51 -51
- package/src/rules/ensure-feature-flag-prefix/index.tsx +65 -80
- package/src/rules/ensure-feature-flag-registration/__tests__/unit/rule.test.tsx +97 -97
- package/src/rules/ensure-feature-flag-registration/index.tsx +88 -105
- package/src/rules/ensure-native-and-af-exports-synced/__tests__/unit/rule.test.tsx +180 -180
- package/src/rules/ensure-native-and-af-exports-synced/index.tsx +162 -168
- package/src/rules/ensure-publish-valid/__tests__/unit/rule.test.ts +34 -36
- package/src/rules/ensure-publish-valid/index.ts +66 -81
- package/src/rules/ensure-test-runner-arguments/__tests__/unit/rule.test.tsx +93 -93
- package/src/rules/ensure-test-runner-arguments/index.tsx +107 -121
- package/src/rules/ensure-test-runner-nested-count/__tests__/unit/rule.test.tsx +43 -43
- package/src/rules/ensure-test-runner-nested-count/index.tsx +68 -70
- package/src/rules/ensure-valid-emotion-css-prop/__tests__/unit/rule.test.ts +142 -0
- package/src/rules/ensure-valid-emotion-css-prop/index.ts +96 -0
- package/src/rules/inline-usage/README.md +53 -0
- package/src/rules/inline-usage/__tests__/rule.test.tsx +106 -0
- package/src/rules/inline-usage/index.tsx +130 -0
- package/src/rules/no-alias/README.md +29 -0
- package/src/rules/no-alias/__tests__/rule.test.tsx +76 -0
- package/src/rules/no-alias/index.tsx +75 -0
- package/src/rules/no-duplicate-dependencies/__tests__/unit/rule.test.ts +44 -44
- package/src/rules/no-duplicate-dependencies/index.ts +68 -73
- package/src/rules/no-invalid-feature-flag-usage/__tests__/unit/rule.test.tsx +64 -64
- package/src/rules/no-invalid-feature-flag-usage/index.tsx +105 -112
- package/src/rules/no-invalid-storybook-decorator-usage/__tests__/unit/rule.test.tsx +13 -13
- package/src/rules/no-invalid-storybook-decorator-usage/index.tsx +28 -30
- package/src/rules/no-module-level-eval/README.md +53 -0
- package/src/rules/no-module-level-eval/__tests__/test.tsx +133 -0
- package/src/rules/no-module-level-eval/index.tsx +52 -0
- package/src/rules/no-pre-post-installs/__tests__/unit/rule.test.ts +36 -36
- package/src/rules/no-pre-post-installs/index.ts +27 -27
- package/src/rules/no-preconditioning/README.md +69 -0
- package/src/rules/no-preconditioning/__tests__/rule.test.tsx +164 -0
- package/src/rules/no-preconditioning/index.tsx +138 -0
- package/src/rules/prefer-fg/README.md +3 -0
- package/src/rules/prefer-fg/__tests__/rule.test.tsx +83 -0
- package/src/rules/prefer-fg/index.tsx +108 -0
- package/src/rules/static-feature-flags/README.md +3 -0
- package/src/rules/static-feature-flags/__tests__/test.tsx +135 -0
- package/src/rules/static-feature-flags/index.tsx +103 -0
- package/src/rules/use-recommended-utils/README.md +67 -0
- package/src/rules/use-recommended-utils/__tests__/rule.test.tsx +78 -0
- package/src/rules/use-recommended-utils/index.tsx +57 -0
- package/src/rules/util/handle-ast-object.ts +21 -32
- package/src/rules/util/registration-utils.ts +31 -30
- package/src/rules/utils.tsx +46 -0
- package/tsconfig.app.json +35 -35
- package/tsconfig.dev.json +39 -39
|
@@ -4,132 +4,125 @@ import type { Node, Expression } from 'estree';
|
|
|
4
4
|
|
|
5
5
|
const FF_GETTER_BOOLEAN_IDENTIFIER = 'getBooleanFF' as const;
|
|
6
6
|
|
|
7
|
-
const __isOnlyOneFlagCheckInExpression = (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return __isOnlyOneFlagCheckInExpression(root.test, ignoredNode);
|
|
14
|
-
case 'UnaryExpression':
|
|
15
|
-
return __isOnlyOneFlagCheckInExpression(root.argument, ignoredNode);
|
|
7
|
+
const __isOnlyOneFlagCheckInExpression = (root: Node | Expression, ignoredNode: Node): boolean => {
|
|
8
|
+
switch (root.type) {
|
|
9
|
+
case 'IfStatement':
|
|
10
|
+
return __isOnlyOneFlagCheckInExpression(root.test, ignoredNode);
|
|
11
|
+
case 'UnaryExpression':
|
|
12
|
+
return __isOnlyOneFlagCheckInExpression(root.argument, ignoredNode);
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
);
|
|
14
|
+
case 'CallExpression':
|
|
15
|
+
if (root === ignoredNode) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return !(
|
|
19
|
+
root.callee.type === 'Identifier' && root.callee.name === FF_GETTER_BOOLEAN_IDENTIFIER
|
|
20
|
+
);
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
// shouldn't ever get here but just in case
|
|
23
|
+
case 'Identifier':
|
|
24
|
+
return root.name !== FF_GETTER_BOOLEAN_IDENTIFIER;
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
case 'BinaryExpression':
|
|
27
|
+
case 'LogicalExpression':
|
|
28
|
+
return (
|
|
29
|
+
__isOnlyOneFlagCheckInExpression(root.left, ignoredNode) &&
|
|
30
|
+
__isOnlyOneFlagCheckInExpression(root.right, ignoredNode)
|
|
31
|
+
);
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
default:
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
40
36
|
};
|
|
41
37
|
|
|
42
38
|
const isOnlyOneFlagCheckInExpression = (node: Rule.Node): boolean => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
let root = node.parent;
|
|
40
|
+
// find the root node of the expression
|
|
41
|
+
// NOTE: This is not an exhaustive check for all ESTree.Expression types but is good enough
|
|
42
|
+
while (root.type.endsWith('Expression')) {
|
|
43
|
+
root = root.parent;
|
|
44
|
+
}
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
return __isOnlyOneFlagCheckInExpression(root, node);
|
|
51
47
|
};
|
|
52
48
|
|
|
53
49
|
const rule: Rule.RuleModule = {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (node.type === 'CallExpression') {
|
|
76
|
-
const args = node.arguments;
|
|
50
|
+
meta: {
|
|
51
|
+
hasSuggestions: false,
|
|
52
|
+
docs: {
|
|
53
|
+
recommended: false,
|
|
54
|
+
},
|
|
55
|
+
type: 'problem',
|
|
56
|
+
messages: {
|
|
57
|
+
onlyInlineIf:
|
|
58
|
+
"Only call feature flags as part of an expression, don't assign to a variable! See http://go/pff-eslint for more details",
|
|
59
|
+
onlyStringLiteral:
|
|
60
|
+
"Only get feature flags by string literal, don't use variables! See http://go/pff-eslint for more details",
|
|
61
|
+
multipleFlagCheckInExpression: `Only check one flag per expression! See http://go/pff-eslint for more details`,
|
|
62
|
+
noModuleScope: `Don't use platform feature flags in module scope! See http://go/pff-eslint for more details`,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
create(context) {
|
|
66
|
+
return {
|
|
67
|
+
[`CallExpression[callee.name=/${FF_GETTER_BOOLEAN_IDENTIFIER}/]`]: (node: Rule.Node) => {
|
|
68
|
+
// to make typescript happy
|
|
69
|
+
if (node.type === 'CallExpression') {
|
|
70
|
+
const args = node.arguments;
|
|
77
71
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
if (args.length === 1 && args[0].type !== 'Literal') {
|
|
73
|
+
return context.report({
|
|
74
|
+
node,
|
|
75
|
+
messageId: 'onlyStringLiteral',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
84
78
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
79
|
+
switch (node.parent?.type) {
|
|
80
|
+
case 'IfStatement':
|
|
81
|
+
break;
|
|
82
|
+
case 'ConditionalExpression':
|
|
83
|
+
switch (node.parent?.parent.type) {
|
|
84
|
+
case 'ExportDefaultDeclaration':
|
|
85
|
+
// handles "export default getBooleanFF('test-flag') ? "this is" : "not good";"
|
|
86
|
+
context.report({
|
|
87
|
+
node,
|
|
88
|
+
messageId: 'noModuleScope',
|
|
89
|
+
});
|
|
90
|
+
break;
|
|
91
|
+
case 'VariableDeclarator':
|
|
92
|
+
// handles "export const foo = getBooleanFF('test-flag') ? 'this is' : 'not good';"
|
|
93
|
+
if (
|
|
94
|
+
node.parent.parent.parent.type === 'VariableDeclaration' &&
|
|
95
|
+
node.parent.parent.parent.parent.type === 'ExportNamedDeclaration'
|
|
96
|
+
) {
|
|
97
|
+
context.report({
|
|
98
|
+
node,
|
|
99
|
+
messageId: 'noModuleScope',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case 'UnaryExpression':
|
|
106
|
+
case 'LogicalExpression':
|
|
107
|
+
if (!isOnlyOneFlagCheckInExpression(node)) {
|
|
108
|
+
context.report({
|
|
109
|
+
node,
|
|
110
|
+
messageId: 'multipleFlagCheckInExpression',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
return context.report({
|
|
116
|
+
node,
|
|
117
|
+
messageId: 'onlyInlineIf',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
128
121
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
122
|
+
return {};
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
},
|
|
133
126
|
};
|
|
134
127
|
|
|
135
128
|
export default rule;
|
|
@@ -2,17 +2,17 @@ import { tester } from '../../../../__tests__/utils/_tester';
|
|
|
2
2
|
import rule from '../../index';
|
|
3
3
|
|
|
4
4
|
describe('no-invalid-storybook-decorator-usage tests', () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
tester.run('no-invalid-storybook-decorator-usage', rule, {
|
|
6
|
+
valid: [
|
|
7
|
+
{
|
|
8
|
+
code: `withPlatformFeatureFlags({})(<SampleComponent/>)`,
|
|
9
|
+
},
|
|
10
|
+
],
|
|
11
|
+
invalid: [
|
|
12
|
+
{
|
|
13
|
+
code: `const flags = {'uip.sample.color': true}; withPlatformFeatureFlags(flags)(<SampleComponent/>)`,
|
|
14
|
+
errors: [{ messageId: 'onlyObjectExpression' }],
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
18
|
});
|
|
@@ -4,38 +4,36 @@ import type { Rule } from 'eslint';
|
|
|
4
4
|
const STORYBOOK_DECORATOR_IDENTIFIER = 'withPlatformFeatureFlags' as const;
|
|
5
5
|
|
|
6
6
|
const rule: Rule.RuleModule = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (node.type === 'CallExpression') {
|
|
25
|
-
const args = node.arguments;
|
|
7
|
+
meta: {
|
|
8
|
+
hasSuggestions: false,
|
|
9
|
+
docs: {
|
|
10
|
+
recommended: false,
|
|
11
|
+
},
|
|
12
|
+
type: 'problem',
|
|
13
|
+
messages: {
|
|
14
|
+
onlyObjectExpression:
|
|
15
|
+
'Only object literals allowed in the storybook decorator! See http://go/pff-eslint for more details',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
[`CallExpression[callee.name=/${STORYBOOK_DECORATOR_IDENTIFIER}/]`]: (node: Rule.Node) => {
|
|
21
|
+
// to make typescript happy
|
|
22
|
+
if (node.type === 'CallExpression') {
|
|
23
|
+
const args = node.arguments;
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
if (args.length === 1 && args[0].type !== 'ObjectExpression') {
|
|
26
|
+
return context.report({
|
|
27
|
+
node,
|
|
28
|
+
messageId: 'onlyObjectExpression',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
return {};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
},
|
|
39
37
|
};
|
|
40
38
|
|
|
41
39
|
export default rule;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Avoid using feature flags at module level (feature-flags/no-module-level-eval)
|
|
2
|
+
|
|
3
|
+
Disallow usage of feature flags in global/module scope. Due to JFE using SSR, feature flags should
|
|
4
|
+
not be called/initialised before components have mounted as it could cause errors.
|
|
5
|
+
|
|
6
|
+
## Examples
|
|
7
|
+
|
|
8
|
+
👎 Examples of **incorrect** code for this rule: ff is called in module scope
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
12
|
+
import { OldComponent } from './old';
|
|
13
|
+
import { NewComponent } from './new';
|
|
14
|
+
|
|
15
|
+
const isEnabled = ff('is_enabled');
|
|
16
|
+
|
|
17
|
+
export const Component = ff('is_redesign') ? NewComponent : OldComponent;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
👍 Examples of **correct** code to feature flag components: use ComponentWithFF
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
24
|
+
import { ComponentWithFF } from '@atlassian/jira-feature-flagging-utils';
|
|
25
|
+
|
|
26
|
+
export const Container = () => {
|
|
27
|
+
const isEnabled = ff('is_enabled');
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const Component = ComponentWithFF('is_redesign', NewComponent, OldComponent);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
👍 Examples of **correct** code to feature flag functions: use functionWithCondition
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
37
|
+
import { functionWithCondition } from '@atlassian/jira-feature-flagging-utils';
|
|
38
|
+
|
|
39
|
+
const selectorOld = createSelector(
|
|
40
|
+
permissionsSelector,
|
|
41
|
+
(permissions: Permissions): boolean => permissions.canCreateChildren,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Change the signature of the selector by adding new arguments
|
|
45
|
+
const selectorNew = createSelector(
|
|
46
|
+
permissionsSelector,
|
|
47
|
+
supportedChildIssueTypesSelector,
|
|
48
|
+
(permissions: Permissions, supportedChildIssueTypes: IssueTypeForHierarchyLevel[]): boolean =>
|
|
49
|
+
permissions.canCreateChildren && !!supportedChildIssueTypes.length,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export const selector = functionWithCondition(() => ff('is_enabled'), selectorNew, selectorOld);
|
|
53
|
+
```
|
|
@@ -0,0 +1,133 @@
|
|
|
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 { ff } from '@atlassian/jira-feature-flagging';
|
|
11
|
+
|
|
12
|
+
const Component = () => {
|
|
13
|
+
const ff1 = ff('flag');
|
|
14
|
+
}
|
|
15
|
+
`,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Boolean within arrow function',
|
|
19
|
+
code: outdent`
|
|
20
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
21
|
+
|
|
22
|
+
const someFunction = () => {
|
|
23
|
+
if(ff('flag')) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Immediate call within a function',
|
|
31
|
+
code: outdent`
|
|
32
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
33
|
+
|
|
34
|
+
function someFunction() {
|
|
35
|
+
return ff('flag');
|
|
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 { ff } from '@atlassian/jira-feature-flagging';
|
|
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 = ff('flag');
|
|
56
|
+
}
|
|
57
|
+
`,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'Class field',
|
|
61
|
+
code: outdent`
|
|
62
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
63
|
+
|
|
64
|
+
export class Component {
|
|
65
|
+
value = ff('flag') ? 'newValue' : 'oldValue';
|
|
66
|
+
}
|
|
67
|
+
`,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
invalid: [
|
|
71
|
+
{
|
|
72
|
+
name: 'Immediate call',
|
|
73
|
+
code: outdent`
|
|
74
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
75
|
+
|
|
76
|
+
ff('flag');
|
|
77
|
+
`,
|
|
78
|
+
errors: [{ messageId: 'noModuleLevelEval' }],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'Module level storage into a variable',
|
|
82
|
+
code: outdent`
|
|
83
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
84
|
+
|
|
85
|
+
const flag = ff('flag');
|
|
86
|
+
`,
|
|
87
|
+
errors: [{ messageId: 'noModuleLevelEval' }],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'Module level collection',
|
|
91
|
+
code: outdent`
|
|
92
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
93
|
+
|
|
94
|
+
const collection = [ff('flag') ? 0 : 1];
|
|
95
|
+
`,
|
|
96
|
+
errors: [{ messageId: 'noModuleLevelEval' }],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'Module level object',
|
|
100
|
+
code: outdent`
|
|
101
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
102
|
+
|
|
103
|
+
const object = {
|
|
104
|
+
isEnabled: ff('flag')
|
|
105
|
+
}
|
|
106
|
+
`,
|
|
107
|
+
errors: [{ messageId: 'noModuleLevelEval' }],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'Module level with boolean',
|
|
111
|
+
code: outdent`
|
|
112
|
+
import { ff } from '@atlassian/jira-feature-flagging';
|
|
113
|
+
import { NewComp } from 'new';
|
|
114
|
+
import { OldComp } from 'old';
|
|
115
|
+
|
|
116
|
+
export const Component = ff('flag') ? NewComp : OldComp;
|
|
117
|
+
`,
|
|
118
|
+
errors: [{ messageId: 'noModuleLevelEval' }],
|
|
119
|
+
},
|
|
120
|
+
// This test fails unsure what the environment difference is between Jira + Platform.
|
|
121
|
+
// {
|
|
122
|
+
// name: 'Static class field',
|
|
123
|
+
// code: outdent`
|
|
124
|
+
// import { ff } from '@atlassian/jira-feature-flagging';
|
|
125
|
+
|
|
126
|
+
// export class Component {
|
|
127
|
+
// static value = ff('flag') ? 'newValue' : 'oldValue';
|
|
128
|
+
// }
|
|
129
|
+
// `,
|
|
130
|
+
// errors: [{ messageId: 'noModuleLevelEval' }],
|
|
131
|
+
// },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
import { isAPIimport, type Node } from '../utils';
|
|
3
|
+
|
|
4
|
+
const isInFunctionLevel = (context: Rule.RuleContext) => {
|
|
5
|
+
let scope: any = context.getScope();
|
|
6
|
+
|
|
7
|
+
while (scope?.type !== 'module' && scope?.type !== 'global') {
|
|
8
|
+
if (scope.type === 'function') {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (scope.type === 'class-field-initializer') {
|
|
13
|
+
return !scope.block.parent.static;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
scope = scope.upper;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return false;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const rule: Rule.RuleModule = {
|
|
23
|
+
meta: {
|
|
24
|
+
docs: {
|
|
25
|
+
description: 'Disallow feature flag usage at module level',
|
|
26
|
+
url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/no-module-level-eval/README.md',
|
|
27
|
+
},
|
|
28
|
+
messages: {
|
|
29
|
+
noModuleLevelEval:
|
|
30
|
+
'Do not evaluate feature flags at module level, it will always resolve to false when server side rendered.',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
create(context) {
|
|
34
|
+
return {
|
|
35
|
+
'CallExpression[callee.type="Identifier"]': (node: Node<any>) => {
|
|
36
|
+
if (
|
|
37
|
+
node.type === 'CallExpression' &&
|
|
38
|
+
node.callee.type === 'Identifier' &&
|
|
39
|
+
isAPIimport(node.callee.name, context) &&
|
|
40
|
+
!isInFunctionLevel(context)
|
|
41
|
+
) {
|
|
42
|
+
context.report({
|
|
43
|
+
messageId: 'noModuleLevelEval',
|
|
44
|
+
node,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default rule;
|
|
@@ -2,40 +2,40 @@ import { tester } from '../../../../__tests__/utils/_tester';
|
|
|
2
2
|
import rule from '../../index';
|
|
3
3
|
|
|
4
4
|
describe('test no-pre-post-installs rule', () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
5
|
+
tester.run('no-pre-post-installs', rule, {
|
|
6
|
+
valid: [
|
|
7
|
+
{
|
|
8
|
+
code: `const foo = { "scripts": { "preinstall": 1, "postinstall": 2 }}`,
|
|
9
|
+
filename: 'hello/foo.ts',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
code: `const foo = { "scripts": { "preinstall": 1, "postinstall": 2 }}`,
|
|
13
|
+
filename: 'foo/dummy.json',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
code: `const foo = { "scripts": { "bar": 1, "dummy": 'echo 1' }}`,
|
|
17
|
+
filename: 'foo/package.json',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
code: `module.exports = { "scripts": { "fakePreinstall": 1 }};`,
|
|
21
|
+
filename: 'bar/package.json',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
code: `module.exports = { "scripts": { "fakePostinstall": 1 }};`,
|
|
25
|
+
filename: 'bar/package.json',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
invalid: [
|
|
29
|
+
{
|
|
30
|
+
code: `module.exports = { "scripts": { "preinstall": 1 }};`,
|
|
31
|
+
filename: 'bar/package.json',
|
|
32
|
+
errors: [{ messageId: 'prePostInstallScriptsNotAllowed' }],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: `const foo = { "scripts": { "postinstall": 1 }}`,
|
|
36
|
+
filename: 'baz/package.json',
|
|
37
|
+
errors: [{ messageId: 'prePostInstallScriptsNotAllowed' }],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
41
|
});
|