@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.
Files changed (126) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/afm-jira/tsconfig.json +20 -0
  3. package/dist/cjs/index.js +48 -2
  4. package/dist/cjs/rules/constants.js +11 -0
  5. package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +54 -6
  6. package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +16 -7
  7. package/dist/cjs/rules/ensure-valid-emotion-css-prop/index.js +91 -0
  8. package/dist/cjs/rules/inline-usage/index.js +94 -0
  9. package/dist/cjs/rules/no-alias/index.js +64 -0
  10. package/dist/cjs/rules/no-module-level-eval/index.js +45 -0
  11. package/dist/cjs/rules/no-preconditioning/index.js +108 -0
  12. package/dist/cjs/rules/prefer-fg/index.js +106 -0
  13. package/dist/cjs/rules/static-feature-flags/index.js +63 -0
  14. package/dist/cjs/rules/use-recommended-utils/index.js +47 -0
  15. package/dist/cjs/rules/util/registration-utils.js +2 -1
  16. package/dist/cjs/rules/utils.js +53 -0
  17. package/dist/es2019/index.js +52 -2
  18. package/dist/es2019/rules/constants.js +5 -0
  19. package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +52 -6
  20. package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +15 -7
  21. package/dist/es2019/rules/ensure-valid-emotion-css-prop/index.js +87 -0
  22. package/dist/es2019/rules/inline-usage/index.js +90 -0
  23. package/dist/es2019/rules/no-alias/index.js +58 -0
  24. package/dist/es2019/rules/no-module-level-eval/index.js +39 -0
  25. package/dist/es2019/rules/no-preconditioning/index.js +105 -0
  26. package/dist/es2019/rules/prefer-fg/index.js +81 -0
  27. package/dist/es2019/rules/static-feature-flags/index.js +54 -0
  28. package/dist/es2019/rules/use-recommended-utils/index.js +41 -0
  29. package/dist/es2019/rules/util/registration-utils.js +2 -1
  30. package/dist/es2019/rules/utils.js +29 -0
  31. package/dist/esm/index.js +48 -2
  32. package/dist/esm/rules/constants.js +5 -0
  33. package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +54 -6
  34. package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +16 -7
  35. package/dist/esm/rules/ensure-valid-emotion-css-prop/index.js +85 -0
  36. package/dist/esm/rules/inline-usage/index.js +87 -0
  37. package/dist/esm/rules/no-alias/index.js +57 -0
  38. package/dist/esm/rules/no-module-level-eval/index.js +39 -0
  39. package/dist/esm/rules/no-preconditioning/index.js +102 -0
  40. package/dist/esm/rules/prefer-fg/index.js +99 -0
  41. package/dist/esm/rules/static-feature-flags/index.js +56 -0
  42. package/dist/esm/rules/use-recommended-utils/index.js +41 -0
  43. package/dist/esm/rules/util/registration-utils.js +2 -1
  44. package/dist/esm/rules/utils.js +45 -0
  45. package/dist/types/index.d.ts +15 -0
  46. package/dist/types/rules/constants.d.ts +3 -0
  47. package/dist/types/rules/ensure-valid-emotion-css-prop/index.d.ts +3 -0
  48. package/dist/types/rules/inline-usage/index.d.ts +3 -0
  49. package/dist/types/rules/no-alias/index.d.ts +3 -0
  50. package/dist/types/rules/no-module-level-eval/index.d.ts +3 -0
  51. package/dist/types/rules/no-preconditioning/index.d.ts +3 -0
  52. package/dist/types/rules/prefer-fg/index.d.ts +3 -0
  53. package/dist/types/rules/static-feature-flags/index.d.ts +3 -0
  54. package/dist/types/rules/use-recommended-utils/index.d.ts +3 -0
  55. package/dist/types/rules/util/registration-utils.d.ts +1 -0
  56. package/dist/types/rules/utils.d.ts +7 -0
  57. package/dist/types-ts4.5/index.d.ts +15 -0
  58. package/dist/types-ts4.5/rules/constants.d.ts +3 -0
  59. package/dist/types-ts4.5/rules/ensure-valid-emotion-css-prop/index.d.ts +3 -0
  60. package/dist/types-ts4.5/rules/inline-usage/index.d.ts +3 -0
  61. package/dist/types-ts4.5/rules/no-alias/index.d.ts +3 -0
  62. package/dist/types-ts4.5/rules/no-module-level-eval/index.d.ts +3 -0
  63. package/dist/types-ts4.5/rules/no-preconditioning/index.d.ts +3 -0
  64. package/dist/types-ts4.5/rules/prefer-fg/index.d.ts +3 -0
  65. package/dist/types-ts4.5/rules/static-feature-flags/index.d.ts +3 -0
  66. package/dist/types-ts4.5/rules/use-recommended-utils/index.d.ts +3 -0
  67. package/dist/types-ts4.5/rules/util/registration-utils.d.ts +1 -0
  68. package/dist/types-ts4.5/rules/utils.d.ts +7 -0
  69. package/index.js +9 -9
  70. package/package.json +43 -44
  71. package/report.api.md +31 -30
  72. package/src/__tests__/utils/_tester.tsx +16 -16
  73. package/src/index.tsx +102 -51
  74. package/src/rules/constants.tsx +20 -0
  75. package/src/rules/ensure-atlassian-team/__tests__/unit/rule.test.ts +19 -19
  76. package/src/rules/ensure-atlassian-team/index.ts +39 -52
  77. package/src/rules/ensure-critical-dependency-resolutions/__test__/unit/rule.test.tsx +146 -81
  78. package/src/rules/ensure-critical-dependency-resolutions/index.tsx +152 -97
  79. package/src/rules/ensure-feature-flag-prefix/__tests__/unit/rule.test.tsx +51 -51
  80. package/src/rules/ensure-feature-flag-prefix/index.tsx +65 -80
  81. package/src/rules/ensure-feature-flag-registration/__tests__/unit/rule.test.tsx +97 -97
  82. package/src/rules/ensure-feature-flag-registration/index.tsx +88 -105
  83. package/src/rules/ensure-native-and-af-exports-synced/__tests__/unit/rule.test.tsx +180 -180
  84. package/src/rules/ensure-native-and-af-exports-synced/index.tsx +162 -168
  85. package/src/rules/ensure-publish-valid/__tests__/unit/rule.test.ts +34 -36
  86. package/src/rules/ensure-publish-valid/index.ts +66 -81
  87. package/src/rules/ensure-test-runner-arguments/__tests__/unit/rule.test.tsx +93 -93
  88. package/src/rules/ensure-test-runner-arguments/index.tsx +107 -121
  89. package/src/rules/ensure-test-runner-nested-count/__tests__/unit/rule.test.tsx +43 -43
  90. package/src/rules/ensure-test-runner-nested-count/index.tsx +68 -70
  91. package/src/rules/ensure-valid-emotion-css-prop/__tests__/unit/rule.test.ts +142 -0
  92. package/src/rules/ensure-valid-emotion-css-prop/index.ts +96 -0
  93. package/src/rules/inline-usage/README.md +53 -0
  94. package/src/rules/inline-usage/__tests__/rule.test.tsx +106 -0
  95. package/src/rules/inline-usage/index.tsx +130 -0
  96. package/src/rules/no-alias/README.md +29 -0
  97. package/src/rules/no-alias/__tests__/rule.test.tsx +76 -0
  98. package/src/rules/no-alias/index.tsx +75 -0
  99. package/src/rules/no-duplicate-dependencies/__tests__/unit/rule.test.ts +44 -44
  100. package/src/rules/no-duplicate-dependencies/index.ts +68 -73
  101. package/src/rules/no-invalid-feature-flag-usage/__tests__/unit/rule.test.tsx +64 -64
  102. package/src/rules/no-invalid-feature-flag-usage/index.tsx +105 -112
  103. package/src/rules/no-invalid-storybook-decorator-usage/__tests__/unit/rule.test.tsx +13 -13
  104. package/src/rules/no-invalid-storybook-decorator-usage/index.tsx +28 -30
  105. package/src/rules/no-module-level-eval/README.md +53 -0
  106. package/src/rules/no-module-level-eval/__tests__/test.tsx +133 -0
  107. package/src/rules/no-module-level-eval/index.tsx +52 -0
  108. package/src/rules/no-pre-post-installs/__tests__/unit/rule.test.ts +36 -36
  109. package/src/rules/no-pre-post-installs/index.ts +27 -27
  110. package/src/rules/no-preconditioning/README.md +69 -0
  111. package/src/rules/no-preconditioning/__tests__/rule.test.tsx +164 -0
  112. package/src/rules/no-preconditioning/index.tsx +138 -0
  113. package/src/rules/prefer-fg/README.md +3 -0
  114. package/src/rules/prefer-fg/__tests__/rule.test.tsx +83 -0
  115. package/src/rules/prefer-fg/index.tsx +108 -0
  116. package/src/rules/static-feature-flags/README.md +3 -0
  117. package/src/rules/static-feature-flags/__tests__/test.tsx +135 -0
  118. package/src/rules/static-feature-flags/index.tsx +103 -0
  119. package/src/rules/use-recommended-utils/README.md +67 -0
  120. package/src/rules/use-recommended-utils/__tests__/rule.test.tsx +78 -0
  121. package/src/rules/use-recommended-utils/index.tsx +57 -0
  122. package/src/rules/util/handle-ast-object.ts +21 -32
  123. package/src/rules/util/registration-utils.ts +31 -30
  124. package/src/rules/utils.tsx +46 -0
  125. package/tsconfig.app.json +35 -35
  126. package/tsconfig.dev.json +39 -39
@@ -0,0 +1,135 @@
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
+ });
@@ -0,0 +1,103 @@
1
+ import type { Rule } from 'eslint';
2
+ import { FEATURE_API_IMPORT_SOURCES } from '../constants';
3
+ import { getDef, isIdentifierImportedFrom, type Node } from '../utils';
4
+
5
+ const IMPORT_SOURCES = new Set([
6
+ ...FEATURE_API_IMPORT_SOURCES,
7
+ '@atlassian/jira-feature-flagging-utils',
8
+ '@atlassian/jira-feature-gate-component',
9
+ '@atlassian/jira-feature-gates-test-mocks',
10
+ '@atlassian/jira-feature-gates-storybook-mocks',
11
+ ]);
12
+
13
+ // Any functions not in this list should be skipped for performance.
14
+ const FUNCTION_NAMES = new Set([
15
+ 'ff',
16
+ 'fg',
17
+ 'getFeatureFlagValue',
18
+ 'getMultivariateFeatureFlag',
19
+ 'componentWithFF',
20
+ 'componentWithFG',
21
+ 'passGate',
22
+ 'withGate',
23
+ 'expVal',
24
+ 'expValEquals',
25
+ 'UNSAFE_noExposureExp',
26
+ 'mockExp',
27
+ 'withExp',
28
+ 'wasExperimentManuallyExposed',
29
+ ]);
30
+
31
+ const STATSIG_ONLY_FUNCTION_NAMES = new Set([
32
+ 'fg',
33
+ 'componentWithFG',
34
+ 'passGate',
35
+ 'withGate',
36
+ 'expVal',
37
+ 'expValEquals',
38
+ 'UNSAFE_noExposureExp',
39
+ 'mockExp',
40
+ 'withExp',
41
+ 'wasExperimentManuallyExposed',
42
+ ]);
43
+
44
+ const rule: Rule.RuleModule = {
45
+ meta: {
46
+ type: 'problem',
47
+ docs: {
48
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/static-feature-flags/README.md',
49
+ description: 'Ensure feature flags or gates are static string literals',
50
+ },
51
+ fixable: 'code',
52
+ messages: {
53
+ FFLiteral:
54
+ 'Use static string literal for `featureFlagName`. See https://team.atlassian.com/project/ATLAS-46997/about',
55
+ },
56
+ },
57
+ create(context) {
58
+ const targetedFunctionsSwitch =
59
+ context.options[0] === 'ssOnly' ? STATSIG_ONLY_FUNCTION_NAMES : FUNCTION_NAMES;
60
+
61
+ return {
62
+ // When they're not literals, show a message
63
+ 'CallExpression[callee.type="Identifier"][arguments.length>0][arguments.0.type!="Literal"]': (
64
+ node: Node<any>,
65
+ ) => {
66
+ if (node.type !== 'CallExpression') {
67
+ return;
68
+ }
69
+
70
+ if (
71
+ node.callee.type === 'Identifier' &&
72
+ (!targetedFunctionsSwitch.has(node.callee.name) ||
73
+ !isIdentifierImportedFrom(node.callee.name, IMPORT_SOURCES, context))
74
+ ) {
75
+ return;
76
+ }
77
+
78
+ const nameArgument = node.arguments[0];
79
+ if (nameArgument.type === 'Identifier') {
80
+ const def = getDef(nameArgument.name, context);
81
+ if (def != null && def.type === 'Variable') {
82
+ const { value } = def.node.init as any;
83
+
84
+ context.report({
85
+ node: nameArgument,
86
+ messageId: 'FFLiteral',
87
+ fix: (fixer) => fixer.replaceText(nameArgument, `'${value}'`),
88
+ });
89
+
90
+ return;
91
+ }
92
+ }
93
+
94
+ context.report({
95
+ node: nameArgument,
96
+ messageId: 'FFLiteral',
97
+ });
98
+ },
99
+ };
100
+ },
101
+ };
102
+
103
+ export default rule;
@@ -0,0 +1,67 @@
1
+ # Use `fg` instead of `FeatureGates.checkGate` (feature-flags/use-recommended-utils)
2
+
3
+ `fg` method is recommended over `FeatureGates.checkGate`. The former is a wrapper to
4
+ `FeatureGates.checkGate` with unit testing mocking capabilities.
5
+
6
+ ## Examples
7
+
8
+ ### Feature Gates
9
+
10
+ 👎 Examples of **incorrect** code for this rule: Gate is accessed with `FeatureGates.checkGate`
11
+
12
+ ```tsx
13
+ import FeatureGates from '@atlaskit/feature-gate-js-client';
14
+
15
+ export const getThing = () => {
16
+ if (FeatureGates.checkGate('my_gate')) {
17
+ return getNewThing();
18
+ }
19
+
20
+ return getOldThing();
21
+ };
22
+ ```
23
+
24
+ 👍 Examples of **correct** code for this rule: Gate is accessed with `fg`
25
+
26
+ ```tsx
27
+ import { fg } from '@atlassian/jira-feature-gating';
28
+
29
+ export const getThing = () => {
30
+ if (fg('my_gate')) {
31
+ return getNewThing();
32
+ }
33
+
34
+ return getOldThing();
35
+ };
36
+ ```
37
+
38
+ ### Experiments
39
+
40
+ 👎 Examples of **incorrect** code for this rule: experiment is accessed with
41
+ `FeatureGates.getExperimentValue`
42
+
43
+ ```tsx
44
+ import FeatureGates from '@atlaskit/feature-gate-js-client';
45
+
46
+ export const getThing = () => {
47
+ if (FeatureGates.getExperimentValue('my_experiment', 'is_enabled', false)) {
48
+ return newThing();
49
+ }
50
+
51
+ return oldThing();
52
+ };
53
+ ```
54
+
55
+ 👍 Examples of **correct** code for this rule: experiment is accessed with `expVal`
56
+
57
+ ```tsx
58
+ import { expVal } from '@atlassian/jira-feature-experiments';
59
+
60
+ export const getThing = () => {
61
+ if (expVal('my_experiment', 'is_enabled', false)) {
62
+ return newThing();
63
+ }
64
+
65
+ return oldThing();
66
+ };
67
+ ```
@@ -0,0 +1,78 @@
1
+ import outdent from 'outdent';
2
+ import { tester } from '../../../__tests__/utils/_tester';
3
+ import rule from '../index';
4
+
5
+ tester.run('feature-flags/use-recommended-utils', rule, {
6
+ valid: [
7
+ {
8
+ name: 'Other `FeatureGate` methods are allowed',
9
+ code: outdent`
10
+ import FeatureGates from '@atlaskit/feature-gate-js-client';
11
+
12
+ FeatureGates.initialize();
13
+ `,
14
+ },
15
+ {
16
+ name: 'Use `fg` to access gates',
17
+ code: outdent`
18
+ import { fg } from '@atlassian/jira-feature-gating';
19
+
20
+ export const Component = () => {
21
+ return fg('my_gate') ? <HelloWorld /> : null;
22
+ };
23
+ `,
24
+ },
25
+ {
26
+ name: 'Use `expVal` to access experiments',
27
+ code: outdent`
28
+ import { expVal } from '@atlassian/jira-feature-experiments';
29
+
30
+ export const getThing = () => {
31
+ if (expVal('my_experiment', 'is_enabled', false)) {
32
+ return newThing();
33
+ }
34
+
35
+ return oldThing();
36
+ };
37
+ `,
38
+ },
39
+ ],
40
+ invalid: [
41
+ {
42
+ name: '`checkGate` is not allowed',
43
+ code: outdent`
44
+ import FeatureGates from '@atlaskit/feature-gate-js-client';
45
+
46
+ export const Component = () => {
47
+ return FeatureGates.checkGate('my_gate') ? <HelloWorld /> : null;
48
+ };
49
+ `,
50
+ errors: [
51
+ {
52
+ message:
53
+ 'Please do not use FeatureGates.checkGate, use `fg` from `@atlaskit/platform-feature-flags` instead.',
54
+ },
55
+ ],
56
+ },
57
+ {
58
+ name: '`getExperimentValue` is not allowed',
59
+ code: outdent`
60
+ import FeatureGates from '@atlaskit/feature-gate-js-client';
61
+
62
+ export const getThing = () => {
63
+ if (FeatureGates.getExperimentValue('my_experiment', 'is_enabled', false)) {
64
+ return newThing();
65
+ }
66
+
67
+ return oldThing();
68
+ };
69
+ `,
70
+ errors: [
71
+ {
72
+ message:
73
+ 'Experimentation is not suported in platform feature flags, reach out to #help-statsig-switcheroo.',
74
+ },
75
+ ],
76
+ },
77
+ ],
78
+ });
@@ -0,0 +1,57 @@
1
+ import type { Rule } from 'eslint';
2
+ import { isIdentifierImportedFrom, type Node } from '../utils';
3
+
4
+ const BANNED_IMPORTS_SET = new Set(['@atlaskit/feature-gate-js-client']);
5
+
6
+ const rule: Rule.RuleModule = {
7
+ meta: {
8
+ docs: {
9
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/use-recommended-utils/README.md',
10
+ description: 'Prefer using the feature flag abstraction over direct statsig library.',
11
+ },
12
+ messages: {
13
+ notSupported:
14
+ 'Experimentation is not suported in platform feature flags, reach out to #help-statsig-switcheroo.',
15
+ useRecommended:
16
+ 'Please do not use FeatureGates.{{util}}, use {{recommended}} from {{lib}} instead.',
17
+ },
18
+ type: 'problem',
19
+ },
20
+ create(context) {
21
+ return {
22
+ 'CallExpression > MemberExpression:matches([property.name="checkGate"])': (
23
+ node: Node<'MemberExpression'>,
24
+ ) => {
25
+ if (
26
+ node.object.type === 'Identifier' &&
27
+ isIdentifierImportedFrom(node.object.name, BANNED_IMPORTS_SET, context)
28
+ ) {
29
+ context.report({
30
+ messageId: 'useRecommended',
31
+ node,
32
+ data: {
33
+ lib: '`@atlaskit/platform-feature-flags`',
34
+ util: (node.property as any).name,
35
+ recommended: '`fg`',
36
+ },
37
+ });
38
+ }
39
+ },
40
+ 'CallExpression > MemberExpression:matches([property.name="getExperimentValue"])': (
41
+ node: Node<'MemberExpression'>,
42
+ ) => {
43
+ if (
44
+ node.object.type === 'Identifier' &&
45
+ isIdentifierImportedFrom(node.object.name, BANNED_IMPORTS_SET, context)
46
+ ) {
47
+ context.report({
48
+ messageId: 'notSupported',
49
+ node,
50
+ });
51
+ }
52
+ },
53
+ };
54
+ },
55
+ };
56
+
57
+ export default rule;
@@ -1,44 +1,33 @@
1
- import type {
2
- ObjectExpression,
3
- SimpleLiteral,
4
- RegExpLiteral,
5
- BigIntLiteral,
6
- } from 'estree';
1
+ import type { ObjectExpression, SimpleLiteral, RegExpLiteral, BigIntLiteral } from 'estree';
7
2
 
8
3
  export const getObjectPropertyAsLiteral = (
9
- node: ObjectExpression,
10
- property: string,
4
+ node: ObjectExpression,
5
+ property: string,
11
6
  ): SimpleLiteral['value'] | RegExpLiteral['value'] | BigIntLiteral['value'] => {
12
- const prop = node.properties.find(
13
- (p) =>
14
- p.type === 'Property' &&
15
- p.key.type === 'Literal' &&
16
- p.key.value === property,
17
- );
7
+ const prop = node.properties.find(
8
+ (p) => p.type === 'Property' && p.key.type === 'Literal' && p.key.value === property,
9
+ );
18
10
 
19
- // double check for property is to make typescript happy
20
- if (prop?.type === 'Property' && prop?.value.type === 'Literal') {
21
- return prop.value.value ?? null;
22
- }
11
+ // double check for property is to make typescript happy
12
+ if (prop?.type === 'Property' && prop?.value.type === 'Literal') {
13
+ return prop.value.value ?? null;
14
+ }
23
15
 
24
- return null;
16
+ return null;
25
17
  };
26
18
 
27
19
  export const getObjectPropertyAsObject = (
28
- node: ObjectExpression,
29
- property: string,
20
+ node: ObjectExpression,
21
+ property: string,
30
22
  ): ObjectExpression | null => {
31
- const prop = node.properties.find(
32
- (p) =>
33
- p.type === 'Property' &&
34
- p.key.type === 'Literal' &&
35
- p.key.value === property,
36
- );
23
+ const prop = node.properties.find(
24
+ (p) => p.type === 'Property' && p.key.type === 'Literal' && p.key.value === property,
25
+ );
37
26
 
38
- // double check for property is to make typescript happy
39
- if (prop?.type === 'Property' && prop?.value.type === 'ObjectExpression') {
40
- return prop.value ?? null;
41
- }
27
+ // double check for property is to make typescript happy
28
+ if (prop?.type === 'Property' && prop?.value.type === 'ObjectExpression') {
29
+ return prop.value ?? null;
30
+ }
42
31
 
43
- return null;
32
+ return null;
44
33
  };
@@ -5,54 +5,55 @@ import Fuse from 'fuse.js';
5
5
  // defines a "getter" to "type" map, if more types are required for feature flags (like string) add it here!
6
6
  // if you don't want to verify the type use `null` as the value
7
7
  export const getterIdentifierToFlagTypeMap = {
8
- getBooleanFF: 'boolean' as const,
9
- ffTest: 'boolean' as const,
8
+ getBooleanFF: 'boolean' as const,
9
+ ffTest: 'boolean' as const,
10
+ fg: 'boolean' as const,
10
11
  } as const;
11
12
 
12
13
  export type PlatformFeatureFlagRegistrationSection = {
13
- [key: string]: {
14
- // get the values of the object above
15
- type: (typeof getterIdentifierToFlagTypeMap)[keyof typeof getterIdentifierToFlagTypeMap];
16
- };
14
+ [key: string]: {
15
+ // get the values of the object above
16
+ type: (typeof getterIdentifierToFlagTypeMap)[keyof typeof getterIdentifierToFlagTypeMap];
17
+ };
17
18
  };
18
19
 
19
20
  export type EnhancedPackageJson = readPkgUp.PackageJson & {
20
- 'platform-feature-flags'?: PlatformFeatureFlagRegistrationSection;
21
+ 'platform-feature-flags'?: PlatformFeatureFlagRegistrationSection;
21
22
  };
22
23
 
23
24
  export type PkgJsonMetaData = {
24
- pkgJson: EnhancedPackageJson;
25
- fuse: Fuse<string> | null;
25
+ pkgJson: EnhancedPackageJson;
26
+ fuse: Fuse<string> | null;
26
27
  };
27
28
  // make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
28
29
  const pkgJsonCache = new Map<string, PkgJsonMetaData>();
29
30
  // get the ancestor package.json for a given file
30
31
  export const getMetadataForFilename = (filename: string): PkgJsonMetaData => {
31
- const splitFilename = filename.split(path.sep);
32
- for (let i = 0; i < splitFilename.length; i++) {
33
- // attempt to search using the filename in the cache to see if we've read the package.json for a sibling file before
34
- const searchPath = path.join(...splitFilename.splice(0, i));
35
- const cachedMetaData = pkgJsonCache.get(searchPath);
32
+ const splitFilename = filename.split(path.sep);
33
+ for (let i = 0; i < splitFilename.length; i++) {
34
+ // attempt to search using the filename in the cache to see if we've read the package.json for a sibling file before
35
+ const searchPath = path.join(...splitFilename.splice(0, i));
36
+ const cachedMetaData = pkgJsonCache.get(searchPath);
36
37
 
37
- if (cachedMetaData) {
38
- return cachedMetaData;
39
- }
40
- }
38
+ if (cachedMetaData) {
39
+ return cachedMetaData;
40
+ }
41
+ }
41
42
 
42
- const { packageJson, path: pkgJsonPath } = readPkgUp.sync({
43
- cwd: filename,
44
- normalize: false,
45
- })!;
43
+ const { packageJson, path: pkgJsonPath } = readPkgUp.sync({
44
+ cwd: filename,
45
+ normalize: false,
46
+ })!;
46
47
 
47
- const pkgJson = packageJson as EnhancedPackageJson;
48
+ const pkgJson = packageJson as EnhancedPackageJson;
48
49
 
49
- const fuse =
50
- packageJson['platform-feature-flags'] == null
51
- ? null
52
- : new Fuse(Object.keys(pkgJson['platform-feature-flags']!));
50
+ const fuse =
51
+ packageJson['platform-feature-flags'] == null
52
+ ? null
53
+ : new Fuse(Object.keys(pkgJson['platform-feature-flags']!));
53
54
 
54
- const metaData = { pkgJson, fuse };
55
+ const metaData = { pkgJson, fuse };
55
56
 
56
- pkgJsonCache.set(pkgJsonPath, metaData);
57
- return metaData;
57
+ pkgJsonCache.set(pkgJsonPath, metaData);
58
+ return metaData;
58
59
  };
@@ -0,0 +1,46 @@
1
+ import { FEATURE_API_IMPORT_SOURCES } from './constants';
2
+ import type { Rule, Scope } from 'eslint';
3
+
4
+ export function isIdentifierImportedFrom(
5
+ identifierName: string,
6
+ sources: Set<string>,
7
+ context: Rule.RuleContext,
8
+ ) {
9
+ if (sources.size > 0) {
10
+ return (
11
+ context
12
+ .getScope()
13
+ .references.find((ref) => ref.identifier.name === identifierName)
14
+ ?.resolved?.defs.some(
15
+ (def) =>
16
+ def.parent?.type === 'ImportDeclaration' && sources.has(def.parent.source.value + ''),
17
+ ) ?? false
18
+ );
19
+ }
20
+
21
+ return false;
22
+ }
23
+
24
+ export function isAPIimport(functionName: string, context: Rule.RuleContext) {
25
+ return isIdentifierImportedFrom(functionName, FEATURE_API_IMPORT_SOURCES, context);
26
+ }
27
+
28
+ // returns the definition node of a variable if it's declared within the scope of the file
29
+ export function getDef(name: string, context: Rule.RuleContext) {
30
+ let scope: Scope.Scope | null = context.getScope();
31
+
32
+ while (scope && scope.type !== 'global') {
33
+ for (const variable of scope.variables) {
34
+ if (variable.name === name) {
35
+ const definition = variable.defs.find(
36
+ (def) => def.node && def.node.type === 'VariableDeclarator',
37
+ );
38
+ return definition;
39
+ }
40
+ }
41
+ scope = scope.upper;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ export type Node<T extends Rule.Node['type']> = Extract<Rule.Node, { type: T }>;