@atlaskit/eslint-plugin-platform 2.6.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.
Files changed (119) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/index.js +4 -3
  3. package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +2 -2
  4. package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +3 -0
  5. package/dist/cjs/rules/feature-gating/no-alias/index.js +1 -1
  6. package/dist/cjs/rules/no-direct-document-usage/index.js +1 -1
  7. package/dist/cjs/rules/no-set-immediate/index.js +39 -0
  8. package/dist/cjs/rules/util/context-compat.js +4 -2
  9. package/dist/es2019/index.js +4 -3
  10. package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +2 -2
  11. package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +3 -0
  12. package/dist/es2019/rules/feature-gating/no-alias/index.js +1 -1
  13. package/dist/es2019/rules/no-direct-document-usage/index.js +1 -1
  14. package/dist/es2019/rules/no-set-immediate/index.js +33 -0
  15. package/dist/es2019/rules/util/context-compat.js +4 -2
  16. package/dist/esm/index.js +4 -3
  17. package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +2 -2
  18. package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +3 -0
  19. package/dist/esm/rules/feature-gating/no-alias/index.js +1 -1
  20. package/dist/esm/rules/no-direct-document-usage/index.js +1 -1
  21. package/dist/esm/rules/no-set-immediate/index.js +33 -0
  22. package/dist/esm/rules/util/context-compat.js +4 -2
  23. package/dist/types/index.d.ts +14 -6
  24. package/dist/types/rules/util/handle-ast-object.d.ts +1 -1
  25. package/dist/types-ts4.5/index.d.ts +14 -6
  26. package/dist/types-ts4.5/rules/util/handle-ast-object.d.ts +1 -1
  27. package/package.json +2 -2
  28. package/afm-cc/tsconfig.json +0 -24
  29. package/afm-jira/tsconfig.json +0 -24
  30. package/build/tsconfig.json +0 -17
  31. package/dist/cjs/rules/ensure-valid-platform-yarn-protocol-usage/index.js +0 -79
  32. package/dist/es2019/rules/ensure-valid-platform-yarn-protocol-usage/index.js +0 -62
  33. package/dist/esm/rules/ensure-valid-platform-yarn-protocol-usage/index.js +0 -75
  34. package/src/__tests__/utils/_tester.tsx +0 -26
  35. package/src/index.tsx +0 -251
  36. package/src/rules/compiled/README.md +0 -3
  37. package/src/rules/compiled/expand-background-shorthand/README.md +0 -23
  38. package/src/rules/compiled/expand-background-shorthand/__tests__/rule.test.ts +0 -160
  39. package/src/rules/compiled/expand-background-shorthand/index.tsx +0 -43
  40. package/src/rules/compiled/expand-border-shorthand/README.md +0 -51
  41. package/src/rules/compiled/expand-border-shorthand/__tests__/rule.test.ts +0 -211
  42. package/src/rules/compiled/expand-border-shorthand/index.ts +0 -103
  43. package/src/rules/compiled/expand-spacing-shorthand/README.md +0 -38
  44. package/src/rules/compiled/expand-spacing-shorthand/__tests__/rule.test.ts +0 -448
  45. package/src/rules/compiled/expand-spacing-shorthand/index.ts +0 -240
  46. package/src/rules/constants.tsx +0 -20
  47. package/src/rules/ensure-atlassian-team/__tests__/unit/rule.test.ts +0 -24
  48. package/src/rules/ensure-atlassian-team/index.ts +0 -51
  49. package/src/rules/ensure-critical-dependency-resolutions/__test__/unit/rule.test.tsx +0 -200
  50. package/src/rules/ensure-critical-dependency-resolutions/index.tsx +0 -172
  51. package/src/rules/ensure-feature-flag-prefix/__tests__/unit/rule.test.tsx +0 -65
  52. package/src/rules/ensure-feature-flag-prefix/index.tsx +0 -81
  53. package/src/rules/ensure-feature-flag-registration/__tests__/unit/rule.test.tsx +0 -115
  54. package/src/rules/ensure-feature-flag-registration/index.tsx +0 -106
  55. package/src/rules/ensure-native-and-af-exports-synced/__tests__/unit/rule.test.tsx +0 -199
  56. package/src/rules/ensure-native-and-af-exports-synced/index.tsx +0 -188
  57. package/src/rules/ensure-no-private-dependencies/__tests__/unit/rule.test.ts +0 -212
  58. package/src/rules/ensure-no-private-dependencies/index.ts +0 -64
  59. package/src/rules/ensure-publish-valid/__tests__/unit/rule.test.ts +0 -39
  60. package/src/rules/ensure-publish-valid/index.ts +0 -81
  61. package/src/rules/ensure-test-runner-arguments/__tests__/unit/rule.test.tsx +0 -298
  62. package/src/rules/ensure-test-runner-arguments/index.tsx +0 -121
  63. package/src/rules/ensure-test-runner-nested-count/__tests__/unit/rule.test.tsx +0 -308
  64. package/src/rules/ensure-test-runner-nested-count/index.tsx +0 -82
  65. package/src/rules/ensure-valid-bin-values/__tests__/unit/rule.test.ts +0 -159
  66. package/src/rules/ensure-valid-bin-values/index.ts +0 -70
  67. package/src/rules/ensure-valid-platform-yarn-protocol-usage/__tests__/unit/rule.test.ts +0 -147
  68. package/src/rules/ensure-valid-platform-yarn-protocol-usage/index.ts +0 -67
  69. package/src/rules/feature-gating/README.md +0 -8
  70. package/src/rules/feature-gating/inline-usage/README.md +0 -53
  71. package/src/rules/feature-gating/inline-usage/__tests__/rule.test.tsx +0 -106
  72. package/src/rules/feature-gating/inline-usage/index.tsx +0 -135
  73. package/src/rules/feature-gating/no-alias/README.md +0 -29
  74. package/src/rules/feature-gating/no-alias/__tests__/rule.test.tsx +0 -76
  75. package/src/rules/feature-gating/no-alias/index.tsx +0 -80
  76. package/src/rules/feature-gating/no-module-level-eval/README.md +0 -53
  77. package/src/rules/feature-gating/no-module-level-eval/__tests__/test.tsx +0 -133
  78. package/src/rules/feature-gating/no-module-level-eval/index.tsx +0 -54
  79. package/src/rules/feature-gating/no-module-level-eval-nav4/README.md +0 -8
  80. package/src/rules/feature-gating/no-module-level-eval-nav4/__tests__/test.tsx +0 -130
  81. package/src/rules/feature-gating/no-module-level-eval-nav4/index.tsx +0 -73
  82. package/src/rules/feature-gating/no-preconditioning/README.md +0 -69
  83. package/src/rules/feature-gating/no-preconditioning/__tests__/rule.test.tsx +0 -164
  84. package/src/rules/feature-gating/no-preconditioning/index.tsx +0 -138
  85. package/src/rules/feature-gating/prefer-fg/README.md +0 -3
  86. package/src/rules/feature-gating/prefer-fg/__tests__/rule.test.tsx +0 -83
  87. package/src/rules/feature-gating/prefer-fg/index.tsx +0 -110
  88. package/src/rules/feature-gating/static-feature-flags/README.md +0 -3
  89. package/src/rules/feature-gating/static-feature-flags/__tests__/test.tsx +0 -135
  90. package/src/rules/feature-gating/static-feature-flags/index.tsx +0 -103
  91. package/src/rules/feature-gating/use-recommended-utils/README.md +0 -67
  92. package/src/rules/feature-gating/use-recommended-utils/__tests__/rule.test.tsx +0 -78
  93. package/src/rules/feature-gating/use-recommended-utils/index.tsx +0 -57
  94. package/src/rules/feature-gating/utils.tsx +0 -48
  95. package/src/rules/no-direct-document-usage/index.tsx +0 -109
  96. package/src/rules/no-duplicate-dependencies/__tests__/unit/rule.test.ts +0 -116
  97. package/src/rules/no-duplicate-dependencies/index.ts +0 -79
  98. package/src/rules/no-invalid-feature-flag-usage/__tests__/unit/rule.test.tsx +0 -69
  99. package/src/rules/no-invalid-feature-flag-usage/index.tsx +0 -128
  100. package/src/rules/no-invalid-storybook-decorator-usage/__tests__/unit/rule.test.tsx +0 -18
  101. package/src/rules/no-invalid-storybook-decorator-usage/index.tsx +0 -39
  102. package/src/rules/no-pre-post-installs/__tests__/unit/rule.test.ts +0 -41
  103. package/src/rules/no-pre-post-installs/index.ts +0 -35
  104. package/src/rules/no-sparse-checkout/__tests__/unit/rule.test.tsx +0 -48
  105. package/src/rules/no-sparse-checkout/index.tsx +0 -54
  106. package/src/rules/use-entrypoints-in-examples/README.md +0 -27
  107. package/src/rules/use-entrypoints-in-examples/__tests__/rule.test.tsx +0 -34
  108. package/src/rules/use-entrypoints-in-examples/index.tsx +0 -43
  109. package/src/rules/util/__tests__/context-compat.test.ts +0 -122
  110. package/src/rules/util/compiled-utils.ts +0 -27
  111. package/src/rules/util/context-compat.ts +0 -41
  112. package/src/rules/util/file-exclusions.ts +0 -39
  113. package/src/rules/util/handle-ast-object.ts +0 -33
  114. package/src/rules/util/registration-utils.ts +0 -59
  115. package/tsconfig.app.json +0 -43
  116. package/tsconfig.dev.json +0 -40
  117. package/tsconfig.json +0 -23
  118. /package/dist/types/rules/{ensure-valid-platform-yarn-protocol-usage → no-set-immediate}/index.d.ts +0 -0
  119. /package/dist/types-ts4.5/rules/{ensure-valid-platform-yarn-protocol-usage → no-set-immediate}/index.d.ts +0 -0
@@ -1,164 +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-preconditioning', rule, {
6
- valid: [
7
- {
8
- code: outdent`
9
- import { fg } from '@atlassian/jira-feature-gating';
10
-
11
- if (preCheck && fg('gate')) {}
12
- `,
13
- },
14
- {
15
- code: outdent`
16
- import { fg } from '@atlassian/jira-feature-gating';
17
-
18
- if (preCheck1 && preCheck2 && fg('gate')) {}
19
- `,
20
- },
21
- {
22
- code: outdent`
23
- import { fg } from '@atlassian/jira-feature-gating';
24
-
25
- const value = fg('gate') && 'value';
26
- `,
27
- },
28
- {
29
- code: outdent`
30
- import { fg } from '@atlassian/jira-feature-gating';
31
-
32
- const value = preCheck && fg('gate') ? 'value' : '';
33
- `,
34
- },
35
- {
36
- code: outdent`
37
- import { expVal } from '@atlassian/jira-feature-experiments';
38
-
39
- if (expVal('one') && expVal('two') ) {}
40
- `,
41
- },
42
- {
43
- code: outdent`
44
- import { expVal, expValEquals } from '@atlassian/jira-feature-experiments';
45
-
46
- if (expVal('one') && expValEquals('two', 'value') ) {}
47
- `,
48
- },
49
- {
50
- code: outdent`
51
- import { expVal } from '@atlassian/jira-feature-experiments';
52
-
53
- if (expVal('one') || expVal('two') ) {}
54
- `,
55
- },
56
- {
57
- code: outdent`
58
- import { fg } from '@atlassian/jira-feature-gating';
59
-
60
- if (preGate && fg('one') || preGate && fg('two') ) {}
61
- `,
62
- },
63
- {
64
- code: outdent`
65
- import { fg } from '@atlassian/jira-feature-gating';
66
-
67
- if (count > 0 && fg('my_gate') ) {}
68
- `,
69
- },
70
- ],
71
- invalid: [
72
- {
73
- code: outdent`
74
- import { fg } from '@atlassian/jira-feature-gating';
75
-
76
- if (fg('one') && fg('two')) {}
77
- `,
78
- errors: [{ messageId: 'useConfig' }],
79
- },
80
- {
81
- code: outdent`
82
- import { fg } from '@atlassian/jira-feature-gating';
83
- import { expVal } from '@atlassian/jira-feature-experiments';
84
-
85
- if (expVal('one') && fg('two')) {}
86
- `,
87
- errors: [{ messageId: 'useConfig' }],
88
- },
89
- {
90
- code: outdent`
91
- import { fg } from '@atlassian/jira-feature-gating';
92
- import { expVal } from '@atlassian/jira-feature-experiments';
93
-
94
- if (fg('one') && fg('two') && expVal('three')) {}
95
- `,
96
- errors: [{ messageId: 'useConfig' }],
97
- },
98
- {
99
- code: outdent`
100
- import { fg } from '@atlassian/jira-feature-gating';
101
-
102
- if (preCheck && fg('one') && fg('two')) {}
103
- `,
104
- errors: [{ messageId: 'useConfig' }],
105
- },
106
- {
107
- code: outdent`
108
- import { fg } from '@atlassian/jira-feature-gating';
109
- import { expVal } from '@atlassian/jira-feature-experiments';
110
-
111
- if (fg('my_gate') && expVal('my_exp', 'cohort', 'not-enrolled') === 'variation') {}
112
- `,
113
- errors: [{ messageId: 'useConfig' }],
114
- },
115
- {
116
- code: outdent`
117
- import { fg } from '@atlassian/jira-feature-gating';
118
-
119
- const value = fg('one') && preCheck && fg('two') ? 'value' : '';
120
- `,
121
- errors: [{ messageId: 'incorrectExposure' }],
122
- },
123
- {
124
- code: outdent`
125
- import { fg } from '@atlassian/jira-feature-gating';
126
-
127
- if (fg('gate') && isAdmin) {}
128
- `,
129
- errors: [{ messageId: 'incorrectExposure' }],
130
- },
131
- {
132
- code: outdent`
133
- import { expVal } from '@atlassian/jira-feature-experiments';
134
-
135
- if (expVal('exp') && isAdmin) {}
136
- `,
137
- errors: [{ messageId: 'incorrectExposure' }],
138
- },
139
- {
140
- code: outdent`
141
- import { expValEquals } from '@atlassian/jira-feature-experiments';
142
-
143
- if (expValEquals('exp', 'value') && isAdmin) {}
144
- `,
145
- errors: [{ messageId: 'incorrectExposure' }],
146
- },
147
- {
148
- code: outdent`
149
- import { fg } from '@atlassian/jira-feature-gating';
150
-
151
- if (fg('one') && fg('two') && isAdmin) {}
152
- `,
153
- errors: [{ messageId: 'incorrectExposure' }, { messageId: 'useConfig' }],
154
- },
155
- {
156
- code: outdent`
157
- import { fg } from '@atlassian/jira-feature-gating';
158
-
159
- if (preGate && fg('one') || fg('two') && preGate ) {}
160
- `,
161
- errors: [{ messageId: 'incorrectExposure' }],
162
- },
163
- ],
164
- });
@@ -1,138 +0,0 @@
1
- import type { Rule } from 'eslint';
2
- import { isAPIimport, type Node } from '../utils';
3
-
4
- const isAndExpression = (node: Rule.Node): node is Node<'LogicalExpression'> =>
5
- node.type === 'LogicalExpression' && node.operator === '&&';
6
-
7
- const isExpUsage = (calleeName: string) => calleeName === 'expVal' || calleeName === 'expValEquals';
8
-
9
- const getGateType = (node: Rule.Node, context: Rule.RuleContext): string => {
10
- const { type } = node;
11
-
12
- if (type === 'BinaryExpression') {
13
- return (
14
- getGateType(node.left as Node<'BinaryExpression'>, context) ||
15
- getGateType(node.right as Node<'BinaryExpression'>, context)
16
- );
17
- }
18
-
19
- if (node.type === 'CallExpression') {
20
- const { callee } = node;
21
-
22
- const isFeatureGate =
23
- type === 'CallExpression' &&
24
- callee.type === 'Identifier' &&
25
- // Experiments cannot have other experiments as preconditions, only gates
26
- (callee.name === 'fg' || isExpUsage(callee.name)) &&
27
- isAPIimport(callee.name, context, node);
28
-
29
- return isFeatureGate ? callee.name : '';
30
- }
31
-
32
- return '';
33
- };
34
-
35
- const getPreconditionStatus = (
36
- logicalExpression: Node<'LogicalExpression'>,
37
- context: Rule.RuleContext,
38
- ) => {
39
- const { left } = logicalExpression as any;
40
- // If left side is a nested AND expression then the left side node is on the nested's right
41
- const leftGateType = getGateType(isAndExpression(left) ? left.right : left, context);
42
-
43
- if (leftGateType) {
44
- const rightGateType = getGateType(logicalExpression.right as any, context);
45
- // Check this scenario: fg('gate') && isAdmin
46
- if (!rightGateType) {
47
- return 'early-exposure';
48
- }
49
-
50
- // Using experiment values in logical expressions in valid
51
- // i.e. expVal() && expVal()
52
- if (isExpUsage(leftGateType) && isExpUsage(rightGateType)) {
53
- return '';
54
- }
55
-
56
- // Then is scenario: fg('gate1') && fg('gate2')
57
- return 'unnecessary-gate';
58
- }
59
-
60
- return '';
61
- };
62
-
63
- const rule: Rule.RuleModule = {
64
- meta: {
65
- docs: {
66
- description: 'Inform on how to use gates and experiments in logical expressions',
67
- url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/no-preconditioning/README.md',
68
- },
69
- messages: {
70
- useConfig:
71
- 'Do not precondition gates or experiments with another gate. Configure this in Statsig instead to reduce unnecessary code, simplify cleanup and to ensure accurate exposures in Statsig.',
72
- incorrectExposure:
73
- 'Evaluate gates or experiments at the end of your logical expression to ensure exposure is tracked correctly.',
74
- },
75
- },
76
- create(context) {
77
- return {
78
- 'LogicalExpression[operator="&&"]': (node: Node<'LogicalExpression'>) => {
79
- const { parent } = node;
80
- // Don't analyze nested AND logical expressions
81
- if (isAndExpression(parent)) {
82
- return;
83
- }
84
-
85
- const isAssignmentStatement =
86
- parent.type !== 'IfStatement' &&
87
- parent.type !== 'ConditionalExpression' &&
88
- // @ts-expect-error — this isn't a valid statement but does fail tests when removed.
89
- // When updating this rule please resolve this supression.
90
- !(parent.type === 'LogicalExpression' && parent.operator === '||');
91
-
92
- let nextLogicalExpression: Node<'LogicalExpression'> | undefined = node;
93
- let exposureReported = false;
94
- let configReported = false;
95
-
96
- while (nextLogicalExpression) {
97
- const preconditionStatus = getPreconditionStatus(nextLogicalExpression, context);
98
- // Allow us to check for: fg('') && <Component />
99
- const isReturningValue =
100
- // Check if we are on the root logical expression
101
- // as this is where the returning value is
102
- // `node` is root logical expression
103
- isAssignmentStatement && nextLogicalExpression === node;
104
-
105
- if (!exposureReported && !isReturningValue && preconditionStatus === 'early-exposure') {
106
- context.report({
107
- messageId: 'incorrectExposure',
108
- node,
109
- });
110
-
111
- exposureReported = true;
112
- }
113
-
114
- if (!configReported && preconditionStatus === 'unnecessary-gate') {
115
- context.report({
116
- messageId: 'useConfig',
117
- node,
118
- });
119
-
120
- configReported = true;
121
- }
122
-
123
- if (exposureReported && configReported) {
124
- return;
125
- }
126
-
127
- nextLogicalExpression = isAndExpression(
128
- nextLogicalExpression.left as Node<'LogicalExpression'>,
129
- )
130
- ? (nextLogicalExpression.left as Node<'LogicalExpression'>)
131
- : undefined;
132
- }
133
- },
134
- };
135
- },
136
- };
137
-
138
- export default rule;
@@ -1,3 +0,0 @@
1
- # Keep usages of boolean feature flags consistent (feature-flags/prefer-fg)
2
-
3
- Use `fg` with boolean feature flags
@@ -1,83 +0,0 @@
1
- import outdent from 'outdent';
2
- import { tester } from '../../../../__tests__/utils/_tester';
3
- import rule from '../index';
4
-
5
- const errors = [{ messageId: 'autoFixImports' }, { messageId: 'preferFG' }];
6
-
7
- tester.run('feature-flags/prefer-fg', rule, {
8
- valid: [
9
- {
10
- code: outdent`
11
- import { getFeatureFlagValue } from '@atlassian/jira-feature-flagging';
12
-
13
- const ffVal = getFeatureFlagValue('get.value', '');
14
- `,
15
- },
16
- {
17
- code: outdent`
18
- import { getFeatureFlagValue } from '@atlassian/jira-feature-flagging';
19
-
20
- const ffVal = getFeatureFlagValue('get.value', {});
21
- `,
22
- },
23
- {
24
- code: outdent`
25
- import { getFeatureFlagValue } from '@atlassian/jira-feature-flagging-using-meta';
26
-
27
- const ffVal = getFeatureFlagValue('get.value', 0);
28
- `,
29
- },
30
- {
31
- code: outdent`
32
- import { getFeatureFlagValue } from '@atlassian/jira-feature-flagging-using-meta';
33
-
34
- const isEnabled = getFeatureFlagValue('is.enabled', true);
35
- `,
36
- },
37
- {
38
- code: outdent`
39
- import { getFeatureFlagValue, getFeatureFlagClient } from '@atlassian/jira-feature-flagging-using-meta';
40
-
41
- const ffVal = getFeatureFlagValue('get.value', {});
42
- const newVal = getFeatureFlagClient().getFlagEvaluation('get.value', {});
43
- `,
44
- },
45
- {
46
- code: outdent`
47
- import { getFeatureFlagValue } from 'thirdparty';
48
-
49
- const isEnabled = getFeatureFlagValue('is.enabled', false);
50
- `,
51
- },
52
- ],
53
- invalid: [
54
- {
55
- code: outdent`
56
- import { getBooleanFF, fg } from '@atlaskit/platform-feature-flags';
57
-
58
- const isCorrect = fg('is.correct');
59
- const isAlsoEnabled = getBooleanFF('is.also.enabled', false);
60
- `,
61
- output: outdent`
62
- import { fg } from '@atlaskit/platform-feature-flags';
63
-
64
- const isCorrect = fg('is.correct');
65
- const isAlsoEnabled = fg('is.also.enabled');
66
- `,
67
- errors,
68
- },
69
- {
70
- code: outdent`
71
- import { getBooleanFF } from '@atlaskit/platform-feature-flags';
72
-
73
- const isEnabled = getBooleanFF('is.enabled', false);
74
- `,
75
- output: outdent`
76
- import { fg } from '@atlaskit/platform-feature-flags';
77
-
78
- const isEnabled = fg('is.enabled');
79
- `,
80
- errors: [{ messageId: 'autoFixImports' }, { messageId: 'preferFG' }],
81
- },
82
- ],
83
- });
@@ -1,110 +0,0 @@
1
- import type { Rule } from 'eslint';
2
-
3
- import { FEATURE_API_IMPORT_SOURCES } from '../../constants';
4
- import type { Node } from '../utils';
5
- import type { Program } from 'estree';
6
- import { getScope } from '../../util/context-compat';
7
-
8
- const validateUsage = (
9
- node: Node<'CallExpression'>,
10
- utilName: string,
11
- context: Rule.RuleContext,
12
- changeMap: Map<any, any>,
13
- ) => {
14
- const resolved = getScope(context, node).references.find(
15
- (ref) => ref.identifier.name === utilName,
16
- )?.resolved;
17
-
18
- const importSpecifierDefinition = resolved?.defs.find(
19
- (def: any) =>
20
- def.node?.type === 'ImportSpecifier' &&
21
- FEATURE_API_IMPORT_SOURCES.has(def.parent?.source.value),
22
- );
23
-
24
- if (importSpecifierDefinition) {
25
- const [flagNameArg] = node.arguments;
26
-
27
- context.report({
28
- messageId: 'preferFG',
29
- node,
30
- *fix(fixer) {
31
- yield fixer.replaceText(node, `fg(${context.sourceCode.getText(flagNameArg)})`);
32
-
33
- const importDeclaration = importSpecifierDefinition.parent;
34
-
35
- if (changeMap.has(importDeclaration)) {
36
- const changeCounts = changeMap.get(importDeclaration);
37
-
38
- changeMap.set(importDeclaration, {
39
- ...changeCounts,
40
- [utilName]: changeCounts[utilName] + 1 || 1,
41
- });
42
- } else {
43
- changeMap.set(importDeclaration, { [utilName]: 1 });
44
- }
45
- },
46
- });
47
- }
48
- };
49
-
50
- const rule: Rule.RuleModule = {
51
- meta: {
52
- docs: {
53
- url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/prefer-fg/README.md',
54
- description: 'Keep usages of boolean feature flags consistent',
55
- },
56
- fixable: 'code',
57
- messages: {
58
- preferFG: 'Use `fg` instead for boolean feature flags',
59
- autoFixImports: 'Use `fg` instead for boolean feature flags',
60
- },
61
- },
62
- create(context) {
63
- let changeMap: Map<any, any>;
64
-
65
- return {
66
- 'CallExpression[callee.name="getBooleanFF"]': (node: Node<any>) => {
67
- if (node.type !== 'CallExpression') {
68
- return;
69
- }
70
-
71
- changeMap = changeMap || new Map();
72
- validateUsage(node, 'getBooleanFF', context, changeMap);
73
- },
74
- 'Program:exit': (node: Program) => {
75
- if (changeMap?.size) {
76
- changeMap.forEach((changeCounts, importDeclaration) => {
77
- const [moduleScope] = getScope(context, node).childScopes;
78
- const importSpecifiers = new Set(
79
- importDeclaration.specifiers.map(({ imported }: any) => imported.name),
80
- );
81
-
82
- importSpecifiers.add('fg');
83
-
84
- Object.keys(changeCounts).forEach((utilName) => {
85
- if (changeCounts[utilName] === moduleScope.set.get(utilName)?.references.length) {
86
- importSpecifiers.delete(utilName);
87
- }
88
- });
89
-
90
- context.report({
91
- messageId: 'autoFixImports',
92
- node: importDeclaration,
93
- fix: (fixer) =>
94
- fixer.replaceText(
95
- importDeclaration,
96
- `import { ${Array.from(importSpecifiers).join(', ')} } from '${
97
- importDeclaration.source.value
98
- }';`,
99
- ),
100
- });
101
- });
102
-
103
- changeMap.clear();
104
- }
105
- },
106
- };
107
- },
108
- };
109
-
110
- export default rule;
@@ -1,3 +0,0 @@
1
- # Ensure `featureFlagName` is a string literal (feature-flags/static-feature-flags) 🍊
2
-
3
- Ensure feature flags are static
@@ -1,135 +0,0 @@
1
- import outdent from 'outdent';
2
- import { tester } from '../../../../__tests__/utils/_tester';
3
- import rule from '../index';
4
-
5
- const errors = [{ messageId: 'FFLiteral' }];
6
- const options = ['ssOnly'];
7
-
8
- tester.run('feature-flags/static-feature-flags', rule, {
9
- valid: [
10
- {
11
- code: outdent`
12
- import { ff } from '@atlassian/jira-feature-flagging';
13
-
14
- const isEnabled = ff('is.enabled');
15
- `,
16
- },
17
- {
18
- code: outdent`
19
- import { getFeatureFlagClient } from '@atlassian/jira-feature-flagging';
20
-
21
- const FLAG_NAME = 'flag';
22
- const isEnabled = getFeatureFlagClient().getBooleanValue(FLAG_NAME);
23
- `,
24
- },
25
- {
26
- code: outdent`
27
- import { fg } from '@atlassian/jira-feature-gating';
28
-
29
- const isEnabled = fg('fg_enabled');
30
- `,
31
- },
32
- {
33
- code: outdent`
34
- import { componentWithFF } from '@atlassian/jira-feature-flagging-utils';
35
-
36
- export const Component = componentWithFF('is_redesign', NewComponent, OldComponent);
37
- `,
38
- },
39
- {
40
- name: 'When using ssOnly option, Launch Darkly functions can avoid static usage',
41
- options,
42
- code: outdent`
43
- import { ff, getFeatureFlagValue, getMultivariateFeatureFlag } from '@atlassian/jira-feature-flagging';
44
- import { fg } from '@atlassian/jira-feature-gating';
45
- import { componentWithFF } from '@atlassian/jira-feature-flagging-utils';
46
-
47
- const FLAG_NAME = 'flag';
48
-
49
- const isEnabled = ff(FLAG_NAME);
50
- const FFValue = getFeatureFlagValue(FLAG_NAME, 10);
51
- const FFMultiValue = multiFFValuegetMultivariateFeatureFlag(FLAG_NAME, 10, [10, 20])
52
- const Component = componentWithFF(FLAG_NAME, NewComponent, OldComponent);
53
- `,
54
- },
55
- ],
56
- invalid: [
57
- {
58
- code: outdent`
59
- import { ff } from '@atlassian/jira-feature-flagging';
60
-
61
- const FLAG_NAME = 'flag';
62
- const isEnabled = ff(FLAG_NAME);
63
- `,
64
- output: outdent`
65
- import { ff } from '@atlassian/jira-feature-flagging';
66
-
67
- const FLAG_NAME = 'flag';
68
- const isEnabled = ff('flag');
69
- `,
70
- errors,
71
- },
72
- {
73
- code: outdent`
74
- import { FLAG_NAME } from 'names';
75
- import { ff } from '@atlassian/jira-feature-flagging';
76
-
77
- const isEnabled = ff(FLAG_NAME);
78
- `,
79
- errors,
80
- },
81
- {
82
- code: outdent`
83
- import { FLAG_NAME } from 'names';
84
- import { fg } from '@atlassian/jira-feature-gating';
85
-
86
- const isEnabled = fg(FLAG_NAME);
87
- `,
88
- errors,
89
- },
90
- {
91
- code: outdent`
92
- import { FLAG_NAME } from 'names';
93
- import { expVal } from '@atlassian/jira-feature-experiments';
94
-
95
- const isEnabled = expVal(FLAG_NAME, 'my_param', 'my_default_value');
96
- `,
97
- errors,
98
- },
99
- {
100
- code: outdent`
101
- import { FLAG_NAME } from 'names';
102
- import { UNSAFE_noExposureExp } from '@atlassian/jira-feature-experiments';
103
-
104
- const isEnabled = UNSAFE_noExposureExp(FLAG_NAME, 'my_param', 'my_default_value');
105
- `,
106
- errors,
107
- },
108
- {
109
- code: outdent`
110
- import { FLAG_NAME } from 'names';
111
- import { componentWithFF } from '@atlassian/jira-feature-flagging-utils';
112
-
113
- export const Component = componentWithFF(FLAG_NAME, NewComponent, OldComponent);
114
- `,
115
- errors,
116
- },
117
- {
118
- name: 'When using ssOnly option, Statsig functions should be static',
119
- options,
120
- code: outdent`
121
- import { fg } from '@atlassian/jira-feature-gating';
122
-
123
- const FLAG_NAME = 'flag';
124
- const isEnabled2 = fg(FLAG_NAME);
125
- `,
126
- output: outdent`
127
- import { fg } from '@atlassian/jira-feature-gating';
128
-
129
- const FLAG_NAME = 'flag';
130
- const isEnabled2 = fg('flag');
131
- `,
132
- errors,
133
- },
134
- ],
135
- });