@atlaskit/eslint-plugin-platform 2.7.0 → 2.7.1
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 +7 -0
- package/dist/cjs/index.js +0 -2
- package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +2 -2
- package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +3 -0
- package/dist/cjs/rules/feature-gating/no-alias/index.js +1 -1
- package/dist/cjs/rules/util/context-compat.js +4 -2
- package/dist/es2019/index.js +0 -2
- package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +2 -2
- package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +3 -0
- package/dist/es2019/rules/feature-gating/no-alias/index.js +1 -1
- package/dist/es2019/rules/util/context-compat.js +4 -2
- package/dist/esm/index.js +0 -2
- package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +2 -2
- package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +3 -0
- package/dist/esm/rules/feature-gating/no-alias/index.js +1 -1
- package/dist/esm/rules/util/context-compat.js +4 -2
- package/dist/types/index.d.ts +4 -6
- package/dist/types/rules/util/handle-ast-object.d.ts +1 -1
- package/dist/types-ts4.5/index.d.ts +4 -6
- package/dist/types-ts4.5/rules/util/handle-ast-object.d.ts +1 -1
- package/package.json +2 -2
- package/afm-cc/tsconfig.json +0 -24
- package/afm-jira/tsconfig.json +0 -24
- package/build/tsconfig.json +0 -17
- package/dist/cjs/rules/ensure-valid-platform-yarn-protocol-usage/index.js +0 -79
- package/dist/es2019/rules/ensure-valid-platform-yarn-protocol-usage/index.js +0 -62
- package/dist/esm/rules/ensure-valid-platform-yarn-protocol-usage/index.js +0 -75
- package/dist/types/rules/ensure-valid-platform-yarn-protocol-usage/index.d.ts +0 -3
- package/dist/types-ts4.5/rules/ensure-valid-platform-yarn-protocol-usage/index.d.ts +0 -3
- package/src/__tests__/utils/_tester.tsx +0 -26
- package/src/index.tsx +0 -254
- package/src/rules/compiled/README.md +0 -3
- package/src/rules/compiled/expand-background-shorthand/README.md +0 -23
- package/src/rules/compiled/expand-background-shorthand/__tests__/rule.test.ts +0 -160
- package/src/rules/compiled/expand-background-shorthand/index.tsx +0 -43
- package/src/rules/compiled/expand-border-shorthand/README.md +0 -51
- package/src/rules/compiled/expand-border-shorthand/__tests__/rule.test.ts +0 -211
- package/src/rules/compiled/expand-border-shorthand/index.ts +0 -103
- package/src/rules/compiled/expand-spacing-shorthand/README.md +0 -38
- package/src/rules/compiled/expand-spacing-shorthand/__tests__/rule.test.ts +0 -448
- package/src/rules/compiled/expand-spacing-shorthand/index.ts +0 -240
- package/src/rules/constants.tsx +0 -20
- package/src/rules/ensure-atlassian-team/__tests__/unit/rule.test.ts +0 -24
- package/src/rules/ensure-atlassian-team/index.ts +0 -51
- package/src/rules/ensure-critical-dependency-resolutions/__test__/unit/rule.test.tsx +0 -200
- package/src/rules/ensure-critical-dependency-resolutions/index.tsx +0 -172
- package/src/rules/ensure-feature-flag-prefix/__tests__/unit/rule.test.tsx +0 -65
- package/src/rules/ensure-feature-flag-prefix/index.tsx +0 -81
- package/src/rules/ensure-feature-flag-registration/__tests__/unit/rule.test.tsx +0 -115
- package/src/rules/ensure-feature-flag-registration/index.tsx +0 -106
- package/src/rules/ensure-native-and-af-exports-synced/__tests__/unit/rule.test.tsx +0 -199
- package/src/rules/ensure-native-and-af-exports-synced/index.tsx +0 -188
- package/src/rules/ensure-no-private-dependencies/__tests__/unit/rule.test.ts +0 -212
- package/src/rules/ensure-no-private-dependencies/index.ts +0 -64
- package/src/rules/ensure-publish-valid/__tests__/unit/rule.test.ts +0 -39
- package/src/rules/ensure-publish-valid/index.ts +0 -81
- package/src/rules/ensure-test-runner-arguments/__tests__/unit/rule.test.tsx +0 -298
- package/src/rules/ensure-test-runner-arguments/index.tsx +0 -121
- package/src/rules/ensure-test-runner-nested-count/__tests__/unit/rule.test.tsx +0 -308
- package/src/rules/ensure-test-runner-nested-count/index.tsx +0 -82
- package/src/rules/ensure-valid-bin-values/__tests__/unit/rule.test.ts +0 -159
- package/src/rules/ensure-valid-bin-values/index.ts +0 -70
- package/src/rules/ensure-valid-platform-yarn-protocol-usage/__tests__/unit/rule.test.ts +0 -147
- package/src/rules/ensure-valid-platform-yarn-protocol-usage/index.ts +0 -67
- package/src/rules/feature-gating/README.md +0 -8
- package/src/rules/feature-gating/inline-usage/README.md +0 -53
- package/src/rules/feature-gating/inline-usage/__tests__/rule.test.tsx +0 -106
- package/src/rules/feature-gating/inline-usage/index.tsx +0 -135
- package/src/rules/feature-gating/no-alias/README.md +0 -29
- package/src/rules/feature-gating/no-alias/__tests__/rule.test.tsx +0 -76
- package/src/rules/feature-gating/no-alias/index.tsx +0 -80
- package/src/rules/feature-gating/no-module-level-eval/README.md +0 -53
- package/src/rules/feature-gating/no-module-level-eval/__tests__/test.tsx +0 -133
- package/src/rules/feature-gating/no-module-level-eval/index.tsx +0 -54
- package/src/rules/feature-gating/no-module-level-eval-nav4/README.md +0 -8
- package/src/rules/feature-gating/no-module-level-eval-nav4/__tests__/test.tsx +0 -130
- package/src/rules/feature-gating/no-module-level-eval-nav4/index.tsx +0 -73
- package/src/rules/feature-gating/no-preconditioning/README.md +0 -69
- package/src/rules/feature-gating/no-preconditioning/__tests__/rule.test.tsx +0 -164
- package/src/rules/feature-gating/no-preconditioning/index.tsx +0 -138
- package/src/rules/feature-gating/prefer-fg/README.md +0 -3
- package/src/rules/feature-gating/prefer-fg/__tests__/rule.test.tsx +0 -83
- package/src/rules/feature-gating/prefer-fg/index.tsx +0 -110
- package/src/rules/feature-gating/static-feature-flags/README.md +0 -3
- package/src/rules/feature-gating/static-feature-flags/__tests__/test.tsx +0 -135
- package/src/rules/feature-gating/static-feature-flags/index.tsx +0 -103
- package/src/rules/feature-gating/use-recommended-utils/README.md +0 -67
- package/src/rules/feature-gating/use-recommended-utils/__tests__/rule.test.tsx +0 -78
- package/src/rules/feature-gating/use-recommended-utils/index.tsx +0 -57
- package/src/rules/feature-gating/utils.tsx +0 -48
- package/src/rules/no-direct-document-usage/index.tsx +0 -111
- package/src/rules/no-duplicate-dependencies/__tests__/unit/rule.test.ts +0 -116
- package/src/rules/no-duplicate-dependencies/index.ts +0 -79
- package/src/rules/no-invalid-feature-flag-usage/__tests__/unit/rule.test.tsx +0 -69
- package/src/rules/no-invalid-feature-flag-usage/index.tsx +0 -128
- package/src/rules/no-invalid-storybook-decorator-usage/__tests__/unit/rule.test.tsx +0 -18
- package/src/rules/no-invalid-storybook-decorator-usage/index.tsx +0 -39
- package/src/rules/no-pre-post-installs/__tests__/unit/rule.test.ts +0 -41
- package/src/rules/no-pre-post-installs/index.ts +0 -35
- package/src/rules/no-set-immediate/index.tsx +0 -43
- package/src/rules/no-sparse-checkout/__tests__/unit/rule.test.tsx +0 -48
- package/src/rules/no-sparse-checkout/index.tsx +0 -54
- package/src/rules/use-entrypoints-in-examples/README.md +0 -27
- package/src/rules/use-entrypoints-in-examples/__tests__/rule.test.tsx +0 -34
- package/src/rules/use-entrypoints-in-examples/index.tsx +0 -43
- package/src/rules/util/__tests__/context-compat.test.ts +0 -122
- package/src/rules/util/compiled-utils.ts +0 -27
- package/src/rules/util/context-compat.ts +0 -41
- package/src/rules/util/file-exclusions.ts +0 -39
- package/src/rules/util/handle-ast-object.ts +0 -33
- package/src/rules/util/registration-utils.ts +0 -59
- package/tsconfig.app.json +0 -43
- package/tsconfig.dev.json +0 -40
- package/tsconfig.json +0 -23
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { Rule } from 'eslint';
|
|
2
|
-
import {
|
|
3
|
-
FEATURE_API_IMPORT_SOURCES,
|
|
4
|
-
FEATURE_MOCKS_IMPORT_SOURCES,
|
|
5
|
-
FEATURE_UTILS_IMPORT_SOURCES,
|
|
6
|
-
} from '../../constants';
|
|
7
|
-
import { isIdentifierImportedFrom, type Node } from '../utils';
|
|
8
|
-
|
|
9
|
-
const IMPORT_SOURCES = new Set([
|
|
10
|
-
...FEATURE_API_IMPORT_SOURCES,
|
|
11
|
-
...FEATURE_MOCKS_IMPORT_SOURCES,
|
|
12
|
-
...FEATURE_UTILS_IMPORT_SOURCES,
|
|
13
|
-
]);
|
|
14
|
-
|
|
15
|
-
const rule: Rule.RuleModule = {
|
|
16
|
-
meta: {
|
|
17
|
-
docs: {
|
|
18
|
-
url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/no-alias/README.md',
|
|
19
|
-
description:
|
|
20
|
-
'Disallow aliasing of feature flag utils to ensure feature flag usage is statically analyzable',
|
|
21
|
-
},
|
|
22
|
-
messages: {
|
|
23
|
-
noSpecifierAlias:
|
|
24
|
-
'Do not alias feature flag utils. Feature flag usage should be statically analyzable',
|
|
25
|
-
noNamespaceSpecifier:
|
|
26
|
-
'Destructure feature flag utils from import. Feature flag usage should be statically analyzable',
|
|
27
|
-
noReassignment:
|
|
28
|
-
'Do not reassign feature flag utils. Feature flag usage should be statically analyzable',
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
create(context) {
|
|
32
|
-
return {
|
|
33
|
-
ImportDeclaration: (node) => {
|
|
34
|
-
if (typeof node.source.value === 'string' && !IMPORT_SOURCES.has(node.source.value)) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
node.specifiers?.forEach((specifier) => {
|
|
39
|
-
if (specifier.type === 'ImportSpecifier') {
|
|
40
|
-
const { imported, local } = specifier;
|
|
41
|
-
|
|
42
|
-
if (imported.name !== local.name) {
|
|
43
|
-
context.report({
|
|
44
|
-
messageId: 'noSpecifierAlias',
|
|
45
|
-
node: specifier,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
} else if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
49
|
-
context.report({
|
|
50
|
-
messageId: 'noNamespaceSpecifier',
|
|
51
|
-
node: specifier,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
},
|
|
56
|
-
'VariableDeclaration[kind="const"] > VariableDeclarator[id.type="Identifier"][init.type="Identifier"]':
|
|
57
|
-
(node: Node<'VariableDeclarator'>) => {
|
|
58
|
-
if (!node.init || node.init.type !== 'Identifier') {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const isReassignment = isIdentifierImportedFrom(
|
|
63
|
-
node.init.name,
|
|
64
|
-
IMPORT_SOURCES,
|
|
65
|
-
context,
|
|
66
|
-
node,
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
if (isReassignment) {
|
|
70
|
-
context.report({
|
|
71
|
-
messageId: 'noReassignment',
|
|
72
|
-
node,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export default rule;
|
|
@@ -1,53 +0,0 @@
|
|
|
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
|
-
```
|
|
@@ -1,133 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { Rule } from 'eslint';
|
|
2
|
-
import type { Node } from 'estree';
|
|
3
|
-
import { isAPIimport } from '../utils';
|
|
4
|
-
import { getScope } from '../../util/context-compat';
|
|
5
|
-
|
|
6
|
-
const isInFunctionLevel = (context: Rule.RuleContext, node: Node) => {
|
|
7
|
-
let scope: any = getScope(context, node);
|
|
8
|
-
|
|
9
|
-
while (scope?.type !== 'module' && scope?.type !== 'global') {
|
|
10
|
-
if (scope.type === 'function') {
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (scope.type === 'class-field-initializer') {
|
|
15
|
-
return !scope.block.parent.static;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
scope = scope.upper;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return false;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const rule: Rule.RuleModule = {
|
|
25
|
-
meta: {
|
|
26
|
-
docs: {
|
|
27
|
-
description: 'Disallow feature flag usage at module level',
|
|
28
|
-
url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/no-module-level-eval/README.md',
|
|
29
|
-
},
|
|
30
|
-
messages: {
|
|
31
|
-
noModuleLevelEval:
|
|
32
|
-
'Do not evaluate feature flags in the module level, it will always resolve to false when server side rendered or when flags are loaded async.',
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
create(context) {
|
|
36
|
-
return {
|
|
37
|
-
'CallExpression[callee.type="Identifier"]': (node: Node) => {
|
|
38
|
-
if (
|
|
39
|
-
node.type === 'CallExpression' &&
|
|
40
|
-
node.callee.type === 'Identifier' &&
|
|
41
|
-
isAPIimport(node.callee.name, context, node) &&
|
|
42
|
-
!isInFunctionLevel(context, node)
|
|
43
|
-
) {
|
|
44
|
-
context.report({
|
|
45
|
-
messageId: 'noModuleLevelEval',
|
|
46
|
-
node,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export default rule;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Avoid using navigation/visual refresh feature flag functions at module level (feature-flags/no-module-level-eval-nav4)
|
|
2
|
-
|
|
3
|
-
Disallow usage of navigation/visual refresh feature flag functions in global/module scope. Due to
|
|
4
|
-
JFE using SSR, feature flags should not be called/initialised before components have mounted as it
|
|
5
|
-
could cause errors.
|
|
6
|
-
|
|
7
|
-
This is a temporary lint rule cloned from no-module-level-nav4 while we explore pulling these
|
|
8
|
-
evaluations inline https://atlassian.slack.com/archives/CFGLH1ZS8/p1726449739284819
|
|
@@ -1,130 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import type { Rule } from 'eslint';
|
|
2
|
-
import type { Node } from 'estree';
|
|
3
|
-
import { getScope } from '../../util/context-compat';
|
|
4
|
-
|
|
5
|
-
const featureLibraryFunctions = new Set([
|
|
6
|
-
/*
|
|
7
|
-
* STOP!
|
|
8
|
-
*
|
|
9
|
-
* Your code should call the API functions directly!
|
|
10
|
-
* But, we are temporarily adding these methods to prevent SSR builds from breaking
|
|
11
|
-
* while we work out a solution for the features to be evaluated inline.
|
|
12
|
-
* Do not add anything here without the permission of #help-jfp-squads
|
|
13
|
-
*
|
|
14
|
-
* Slack thread: https://atlassian.slack.com/archives/CFGLH1ZS8/p1726449739284819
|
|
15
|
-
*/
|
|
16
|
-
'isVisualRefreshEnabled',
|
|
17
|
-
'getMetaBoolean',
|
|
18
|
-
'getNav4Rollout',
|
|
19
|
-
'getWillShowNav3',
|
|
20
|
-
'getWillShowNav4',
|
|
21
|
-
'getWillShowNav4UserOptIn',
|
|
22
|
-
'getWillShowNav4UserOptOut',
|
|
23
|
-
]);
|
|
24
|
-
|
|
25
|
-
const isInFunctionLevel = (context: Rule.RuleContext, node: Node) => {
|
|
26
|
-
let scope: any = getScope(context, node);
|
|
27
|
-
|
|
28
|
-
while (scope?.type !== 'module' && scope?.type !== 'global') {
|
|
29
|
-
if (scope.type === 'function') {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (scope.type === 'class-field-initializer') {
|
|
34
|
-
return !scope.block.parent.static;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
scope = scope.upper;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return false;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const rule: Rule.RuleModule = {
|
|
44
|
-
meta: {
|
|
45
|
-
docs: {
|
|
46
|
-
description: 'Disallow getWillShowNav4 or isVisualRefreshEnabled usage at module level',
|
|
47
|
-
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',
|
|
48
|
-
},
|
|
49
|
-
messages: {
|
|
50
|
-
noModuleLevelEval:
|
|
51
|
-
'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`.',
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
create(context) {
|
|
55
|
-
return {
|
|
56
|
-
'CallExpression[callee.type="Identifier"]': (node: Node) => {
|
|
57
|
-
if (
|
|
58
|
-
node.type === 'CallExpression' &&
|
|
59
|
-
node.callee.type === 'Identifier' &&
|
|
60
|
-
featureLibraryFunctions.has(node.callee.name) &&
|
|
61
|
-
!isInFunctionLevel(context, node)
|
|
62
|
-
) {
|
|
63
|
-
context.report({
|
|
64
|
-
messageId: 'noModuleLevelEval',
|
|
65
|
-
node,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export default rule;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# Enforce feature gate not used as a precondition in logical expressions (feature-flags/no-preconditioning)
|
|
2
|
-
|
|
3
|
-
Using a feature gate as a precondition in logical expressions can cause:
|
|
4
|
-
|
|
5
|
-
- Exposure to be tracked incorrectly
|
|
6
|
-
- Unnecessary code
|
|
7
|
-
|
|
8
|
-
## Examples
|
|
9
|
-
|
|
10
|
-
Instead of adding prerequisite gating in code, configure this in Statsig UI by adding rules to gates
|
|
11
|
-
and targeting for experiments. This will reduce unnecessary complexity in code and simplify cleanup.
|
|
12
|
-
|
|
13
|
-
```tsx
|
|
14
|
-
import { fg } from '@atlassian/jira-feature-gating';
|
|
15
|
-
import { expVal } from '@atlassian/jira-feature-experiments';
|
|
16
|
-
|
|
17
|
-
// Setup rule in `feature_milestone2` to fail if `feature_milestone1` fails
|
|
18
|
-
if (fg('feature_milestone1') && fg('feature_milestone2')) {
|
|
19
|
-
doSomething();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Setup targeting in `my_exp` to only enroll participants passing `my_gate`
|
|
23
|
-
if (fg('my_gate') && expVal('my_exp')) {
|
|
24
|
-
doSomething();
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Gating experiment values from same or different experiments is valid.
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
import { expVal } from '@atlassian/jira-feature-experiments';
|
|
32
|
-
|
|
33
|
-
if (expVal('my_exp', 'delayRetries', false) && expVal('my_exp', 'numRetries', 0) > 3) {
|
|
34
|
-
doSomething();
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
👎 Examples of **incorrect** exposure tracking
|
|
39
|
-
|
|
40
|
-
If `isAdmin` is false exposure will be fired even though feature was not intended for audience.
|
|
41
|
-
|
|
42
|
-
```tsx
|
|
43
|
-
import { fg } from '@atlassian/jira-feature-gating';
|
|
44
|
-
|
|
45
|
-
if (fg('my_gate') && isAdmin) {
|
|
46
|
-
doSomething();
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
If `hasSelectedProject` is false and experiment returns true. Expsoure will be tracked as true even
|
|
51
|
-
though feature was not exposed to the user.
|
|
52
|
-
|
|
53
|
-
```tsx
|
|
54
|
-
import { fg } from '@atlassian/jira-feature-gating';
|
|
55
|
-
|
|
56
|
-
if (expVal('my_experiment', 'is_enabled', false) && hasSelectedProject) {
|
|
57
|
-
doSomething();
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
👍 Examples of **correct** exposure tracking
|
|
62
|
-
|
|
63
|
-
```tsx
|
|
64
|
-
import { fg } from '@atlassian/jira-feature-gating';
|
|
65
|
-
|
|
66
|
-
if (isAdmin && fg('my_gate')) {
|
|
67
|
-
doSomething();
|
|
68
|
-
}
|
|
69
|
-
```
|