@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,57 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import { FEATURE_API_IMPORT_SOURCES, FEATURE_MOCKS_IMPORT_SOURCES, FEATURE_UTILS_IMPORT_SOURCES } from '../constants';
3
+ import { isIdentifierImportedFrom } from '../utils';
4
+ var IMPORT_SOURCES = new Set([].concat(_toConsumableArray(FEATURE_API_IMPORT_SOURCES), _toConsumableArray(FEATURE_MOCKS_IMPORT_SOURCES), _toConsumableArray(FEATURE_UTILS_IMPORT_SOURCES)));
5
+ var rule = {
6
+ meta: {
7
+ docs: {
8
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/no-alias/README.md',
9
+ description: 'Disallow aliasing of feature flag utils to ensure feature flag usage is statically analyzable'
10
+ },
11
+ messages: {
12
+ noSpecifierAlias: 'Do not alias feature flag utils. Feature flag usage should be statically analyzable',
13
+ noNamespaceSpecifier: 'Destructure feature flag utils from import. Feature flag usage should be statically analyzable',
14
+ noReassignment: 'Do not reassign feature flag utils. Feature flag usage should be statically analyzable'
15
+ }
16
+ },
17
+ create: function create(context) {
18
+ return {
19
+ ImportDeclaration: function ImportDeclaration(node) {
20
+ var _node$specifiers;
21
+ if (typeof node.source.value === 'string' && !IMPORT_SOURCES.has(node.source.value)) {
22
+ return;
23
+ }
24
+ (_node$specifiers = node.specifiers) === null || _node$specifiers === void 0 || _node$specifiers.forEach(function (specifier) {
25
+ if (specifier.type === 'ImportSpecifier') {
26
+ var imported = specifier.imported,
27
+ local = specifier.local;
28
+ if (imported.name !== local.name) {
29
+ context.report({
30
+ messageId: 'noSpecifierAlias',
31
+ node: specifier
32
+ });
33
+ }
34
+ } else if (specifier.type === 'ImportNamespaceSpecifier') {
35
+ context.report({
36
+ messageId: 'noNamespaceSpecifier',
37
+ node: specifier
38
+ });
39
+ }
40
+ });
41
+ },
42
+ 'VariableDeclaration[kind="const"] > VariableDeclarator[id.type="Identifier"][init.type="Identifier"]': function VariableDeclarationKindConstVariableDeclaratorIdTypeIdentifierInitTypeIdentifier(node) {
43
+ if (!node.init || node.init.type !== 'Identifier') {
44
+ return;
45
+ }
46
+ var isReassignment = isIdentifierImportedFrom(node.init.name, IMPORT_SOURCES, context);
47
+ if (isReassignment) {
48
+ context.report({
49
+ messageId: 'noReassignment',
50
+ node: node
51
+ });
52
+ }
53
+ }
54
+ };
55
+ }
56
+ };
57
+ export default rule;
@@ -0,0 +1,39 @@
1
+ import { isAPIimport } from '../utils';
2
+ var isInFunctionLevel = function isInFunctionLevel(context) {
3
+ var scope = context.getScope();
4
+ while (((_scope = scope) === null || _scope === void 0 ? void 0 : _scope.type) !== 'module' && ((_scope2 = scope) === null || _scope2 === void 0 ? void 0 : _scope2.type) !== 'global') {
5
+ var _scope, _scope2;
6
+ if (scope.type === 'function') {
7
+ return true;
8
+ }
9
+ if (scope.type === 'class-field-initializer') {
10
+ return !scope.block.parent.static;
11
+ }
12
+ scope = scope.upper;
13
+ }
14
+ return false;
15
+ };
16
+ var rule = {
17
+ meta: {
18
+ docs: {
19
+ description: 'Disallow feature flag usage at module level',
20
+ 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'
21
+ },
22
+ messages: {
23
+ noModuleLevelEval: 'Do not evaluate feature flags at module level, it will always resolve to false when server side rendered.'
24
+ }
25
+ },
26
+ create: function create(context) {
27
+ return {
28
+ 'CallExpression[callee.type="Identifier"]': function CallExpressionCalleeTypeIdentifier(node) {
29
+ if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && isAPIimport(node.callee.name, context) && !isInFunctionLevel(context)) {
30
+ context.report({
31
+ messageId: 'noModuleLevelEval',
32
+ node: node
33
+ });
34
+ }
35
+ }
36
+ };
37
+ }
38
+ };
39
+ export default rule;
@@ -0,0 +1,102 @@
1
+ import { isAPIimport } from '../utils';
2
+ var isAndExpression = function isAndExpression(node) {
3
+ return node.type === 'LogicalExpression' && node.operator === '&&';
4
+ };
5
+ var isExpUsage = function isExpUsage(calleeName) {
6
+ return calleeName === 'expVal' || calleeName === 'expValEquals';
7
+ };
8
+ var getGateType = function getGateType(node, context) {
9
+ var type = node.type;
10
+ if (type === 'BinaryExpression') {
11
+ return getGateType(node.left, context) || getGateType(node.right, context);
12
+ }
13
+ if (node.type === 'CallExpression') {
14
+ var callee = node.callee;
15
+ var isFeatureGate = type === 'CallExpression' && callee.type === 'Identifier' && (
16
+ // Experiments cannot have other experiments as preconditions, only gates
17
+ callee.name === 'fg' || isExpUsage(callee.name)) && isAPIimport(callee.name, context);
18
+ return isFeatureGate ? callee.name : '';
19
+ }
20
+ return '';
21
+ };
22
+ var getPreconditionStatus = function getPreconditionStatus(logicalExpression, context) {
23
+ var _ref = logicalExpression,
24
+ left = _ref.left;
25
+ // If left side is a nested AND expression then the left side node is on the nested's right
26
+ var leftGateType = getGateType(isAndExpression(left) ? left.right : left, context);
27
+ if (leftGateType) {
28
+ var rightGateType = getGateType(logicalExpression.right, context);
29
+ // Check this scenario: fg('gate') && isAdmin
30
+ if (!rightGateType) {
31
+ return 'early-exposure';
32
+ }
33
+
34
+ // Using experiment values in logical expressions in valid
35
+ // i.e. expVal() && expVal()
36
+ if (isExpUsage(leftGateType) && isExpUsage(rightGateType)) {
37
+ return '';
38
+ }
39
+
40
+ // Then is scenario: fg('gate1') && fg('gate2')
41
+ return 'unnecessary-gate';
42
+ }
43
+ return '';
44
+ };
45
+ var rule = {
46
+ meta: {
47
+ docs: {
48
+ description: 'Inform on how to use gates and experiments in logical expressions',
49
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/no-preconditioning/README.md'
50
+ },
51
+ messages: {
52
+ useConfig: 'Do not precondition gates or experiments with another gate. Configure this in Statsig instead to reduce unnecessary code and simplify cleanup.',
53
+ incorrectExposure: 'Evaluate gates or experiments at the end of your logical expression to ensure exposure is tracked correctly.'
54
+ }
55
+ },
56
+ create: function create(context) {
57
+ return {
58
+ 'LogicalExpression[operator="&&"]': function LogicalExpressionOperator(node) {
59
+ var parent = node.parent;
60
+ // Don't analyze nested AND logical expressions
61
+ if (isAndExpression(parent)) {
62
+ return;
63
+ }
64
+ var isAssignmentStatement = parent.type !== 'IfStatement' && parent.type !== 'ConditionalExpression' &&
65
+ // @ts-expect-error — this isn't a valid statement but does fail tests when removed.
66
+ // When updating this rule please resolve this supression.
67
+ !(parent.type === 'LogicalExpression' && parent.operator === '||');
68
+ var nextLogicalExpression = node;
69
+ var exposureReported = false;
70
+ var configReported = false;
71
+ while (nextLogicalExpression) {
72
+ var preconditionStatus = getPreconditionStatus(nextLogicalExpression, context);
73
+ // Allow us to check for: fg('') && <Component />
74
+ var isReturningValue =
75
+ // Check if we are on the root logical expression
76
+ // as this is where the returning value is
77
+ // `node` is root logical expression
78
+ isAssignmentStatement && nextLogicalExpression === node;
79
+ if (!exposureReported && !isReturningValue && preconditionStatus === 'early-exposure') {
80
+ context.report({
81
+ messageId: 'incorrectExposure',
82
+ node: node
83
+ });
84
+ exposureReported = true;
85
+ }
86
+ if (!configReported && preconditionStatus === 'unnecessary-gate') {
87
+ context.report({
88
+ messageId: 'useConfig',
89
+ node: node
90
+ });
91
+ configReported = true;
92
+ }
93
+ if (exposureReported && configReported) {
94
+ return;
95
+ }
96
+ nextLogicalExpression = isAndExpression(nextLogicalExpression.left) ? nextLogicalExpression.left : undefined;
97
+ }
98
+ }
99
+ };
100
+ }
101
+ };
102
+ export default rule;
@@ -0,0 +1,99 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
4
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
6
+ import { FEATURE_API_IMPORT_SOURCES } from '../constants';
7
+ var validateUsage = function validateUsage(node, utilName, context, changeMap) {
8
+ var _context$getScope$ref;
9
+ var resolved = (_context$getScope$ref = context.getScope().references.find(function (ref) {
10
+ return ref.identifier.name === utilName;
11
+ })) === null || _context$getScope$ref === void 0 ? void 0 : _context$getScope$ref.resolved;
12
+ var importSpecifierDefinition = resolved === null || resolved === void 0 ? void 0 : resolved.defs.find(function (def) {
13
+ var _def$node, _def$parent;
14
+ return ((_def$node = def.node) === null || _def$node === void 0 ? void 0 : _def$node.type) === 'ImportSpecifier' && FEATURE_API_IMPORT_SOURCES.has((_def$parent = def.parent) === null || _def$parent === void 0 ? void 0 : _def$parent.source.value);
15
+ });
16
+ if (importSpecifierDefinition) {
17
+ var _node$arguments = _slicedToArray(node.arguments, 1),
18
+ flagNameArg = _node$arguments[0];
19
+ context.report({
20
+ messageId: 'preferFG',
21
+ node: node,
22
+ fix: /*#__PURE__*/_regeneratorRuntime.mark(function fix(fixer) {
23
+ var importDeclaration, changeCounts;
24
+ return _regeneratorRuntime.wrap(function fix$(_context) {
25
+ while (1) switch (_context.prev = _context.next) {
26
+ case 0:
27
+ _context.next = 2;
28
+ return fixer.replaceText(node, "fg(".concat(context.sourceCode.getText(flagNameArg), ")"));
29
+ case 2:
30
+ importDeclaration = importSpecifierDefinition.parent;
31
+ if (changeMap.has(importDeclaration)) {
32
+ changeCounts = changeMap.get(importDeclaration);
33
+ changeMap.set(importDeclaration, _objectSpread(_objectSpread({}, changeCounts), {}, _defineProperty({}, utilName, changeCounts[utilName] + 1 || 1)));
34
+ } else {
35
+ changeMap.set(importDeclaration, _defineProperty({}, utilName, 1));
36
+ }
37
+ case 4:
38
+ case "end":
39
+ return _context.stop();
40
+ }
41
+ }, fix);
42
+ })
43
+ });
44
+ }
45
+ };
46
+ var rule = {
47
+ meta: {
48
+ docs: {
49
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/prefer-fg/README.md',
50
+ description: 'Keep usages of boolean feature flags consistent'
51
+ },
52
+ fixable: 'code',
53
+ messages: {
54
+ preferFG: 'Use `fg` instead for boolean feature flags',
55
+ autoFixImports: 'Use `fg` instead for boolean feature flags'
56
+ }
57
+ },
58
+ create: function create(context) {
59
+ var changeMap;
60
+ return {
61
+ 'CallExpression[callee.name="getBooleanFF"]': function CallExpressionCalleeNameGetBooleanFF(node) {
62
+ if (node.type !== 'CallExpression') {
63
+ return;
64
+ }
65
+ changeMap = changeMap || new Map();
66
+ validateUsage(node, 'getBooleanFF', context, changeMap);
67
+ },
68
+ 'Program:exit': function ProgramExit() {
69
+ var _changeMap;
70
+ if ((_changeMap = changeMap) !== null && _changeMap !== void 0 && _changeMap.size) {
71
+ changeMap.forEach(function (changeCounts, importDeclaration) {
72
+ var _context$getScope$chi = _slicedToArray(context.getScope().childScopes, 1),
73
+ moduleScope = _context$getScope$chi[0];
74
+ var importSpecifiers = new Set(importDeclaration.specifiers.map(function (_ref) {
75
+ var imported = _ref.imported;
76
+ return imported.name;
77
+ }));
78
+ importSpecifiers.add('fg');
79
+ Object.keys(changeCounts).forEach(function (utilName) {
80
+ var _moduleScope$set$get;
81
+ if (changeCounts[utilName] === ((_moduleScope$set$get = moduleScope.set.get(utilName)) === null || _moduleScope$set$get === void 0 ? void 0 : _moduleScope$set$get.references.length)) {
82
+ importSpecifiers.delete(utilName);
83
+ }
84
+ });
85
+ context.report({
86
+ messageId: 'autoFixImports',
87
+ node: importDeclaration,
88
+ fix: function fix(fixer) {
89
+ return fixer.replaceText(importDeclaration, "import { ".concat(Array.from(importSpecifiers).join(', '), " } from '").concat(importDeclaration.source.value, "';"));
90
+ }
91
+ });
92
+ });
93
+ changeMap.clear();
94
+ }
95
+ }
96
+ };
97
+ }
98
+ };
99
+ export default rule;
@@ -0,0 +1,56 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import { FEATURE_API_IMPORT_SOURCES } from '../constants';
3
+ import { getDef, isIdentifierImportedFrom } from '../utils';
4
+ var IMPORT_SOURCES = new Set([].concat(_toConsumableArray(FEATURE_API_IMPORT_SOURCES), ['@atlassian/jira-feature-flagging-utils', '@atlassian/jira-feature-gate-component', '@atlassian/jira-feature-gates-test-mocks', '@atlassian/jira-feature-gates-storybook-mocks']));
5
+
6
+ // Any functions not in this list should be skipped for performance.
7
+ var FUNCTION_NAMES = new Set(['ff', 'fg', 'getFeatureFlagValue', 'getMultivariateFeatureFlag', 'componentWithFF', 'componentWithFG', 'passGate', 'withGate', 'expVal', 'expValEquals', 'UNSAFE_noExposureExp', 'mockExp', 'withExp', 'wasExperimentManuallyExposed']);
8
+ var STATSIG_ONLY_FUNCTION_NAMES = new Set(['fg', 'componentWithFG', 'passGate', 'withGate', 'expVal', 'expValEquals', 'UNSAFE_noExposureExp', 'mockExp', 'withExp', 'wasExperimentManuallyExposed']);
9
+ var rule = {
10
+ meta: {
11
+ type: 'problem',
12
+ docs: {
13
+ 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',
14
+ description: 'Ensure feature flags or gates are static string literals'
15
+ },
16
+ fixable: 'code',
17
+ messages: {
18
+ FFLiteral: 'Use static string literal for `featureFlagName`. See https://team.atlassian.com/project/ATLAS-46997/about'
19
+ }
20
+ },
21
+ create: function create(context) {
22
+ var targetedFunctionsSwitch = context.options[0] === 'ssOnly' ? STATSIG_ONLY_FUNCTION_NAMES : FUNCTION_NAMES;
23
+ return {
24
+ // When they're not literals, show a message
25
+ 'CallExpression[callee.type="Identifier"][arguments.length>0][arguments.0.type!="Literal"]': function CallExpressionCalleeTypeIdentifierArgumentsLength0Arguments0TypeLiteral(node) {
26
+ if (node.type !== 'CallExpression') {
27
+ return;
28
+ }
29
+ if (node.callee.type === 'Identifier' && (!targetedFunctionsSwitch.has(node.callee.name) || !isIdentifierImportedFrom(node.callee.name, IMPORT_SOURCES, context))) {
30
+ return;
31
+ }
32
+ var nameArgument = node.arguments[0];
33
+ if (nameArgument.type === 'Identifier') {
34
+ var def = getDef(nameArgument.name, context);
35
+ if (def != null && def.type === 'Variable') {
36
+ var _ref = def.node.init,
37
+ value = _ref.value;
38
+ context.report({
39
+ node: nameArgument,
40
+ messageId: 'FFLiteral',
41
+ fix: function fix(fixer) {
42
+ return fixer.replaceText(nameArgument, "'".concat(value, "'"));
43
+ }
44
+ });
45
+ return;
46
+ }
47
+ }
48
+ context.report({
49
+ node: nameArgument,
50
+ messageId: 'FFLiteral'
51
+ });
52
+ }
53
+ };
54
+ }
55
+ };
56
+ export default rule;
@@ -0,0 +1,41 @@
1
+ import { isIdentifierImportedFrom } from '../utils';
2
+ var BANNED_IMPORTS_SET = new Set(['@atlaskit/feature-gate-js-client']);
3
+ var rule = {
4
+ meta: {
5
+ docs: {
6
+ 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',
7
+ description: 'Prefer using the feature flag abstraction over direct statsig library.'
8
+ },
9
+ messages: {
10
+ notSupported: 'Experimentation is not suported in platform feature flags, reach out to #help-statsig-switcheroo.',
11
+ useRecommended: 'Please do not use FeatureGates.{{util}}, use {{recommended}} from {{lib}} instead.'
12
+ },
13
+ type: 'problem'
14
+ },
15
+ create: function create(context) {
16
+ return {
17
+ 'CallExpression > MemberExpression:matches([property.name="checkGate"])': function CallExpressionMemberExpressionMatchesPropertyNameCheckGate(node) {
18
+ if (node.object.type === 'Identifier' && isIdentifierImportedFrom(node.object.name, BANNED_IMPORTS_SET, context)) {
19
+ context.report({
20
+ messageId: 'useRecommended',
21
+ node: node,
22
+ data: {
23
+ lib: '`@atlaskit/platform-feature-flags`',
24
+ util: node.property.name,
25
+ recommended: '`fg`'
26
+ }
27
+ });
28
+ }
29
+ },
30
+ 'CallExpression > MemberExpression:matches([property.name="getExperimentValue"])': function CallExpressionMemberExpressionMatchesPropertyNameGetExperimentValue(node) {
31
+ if (node.object.type === 'Identifier' && isIdentifierImportedFrom(node.object.name, BANNED_IMPORTS_SET, context)) {
32
+ context.report({
33
+ messageId: 'notSupported',
34
+ node: node
35
+ });
36
+ }
37
+ }
38
+ };
39
+ }
40
+ };
41
+ export default rule;
@@ -7,7 +7,8 @@ import Fuse from 'fuse.js';
7
7
  // if you don't want to verify the type use `null` as the value
8
8
  export var getterIdentifierToFlagTypeMap = {
9
9
  getBooleanFF: 'boolean',
10
- ffTest: 'boolean'
10
+ ffTest: 'boolean',
11
+ fg: 'boolean'
11
12
  };
12
13
  // make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
13
14
  var pkgJsonCache = new Map();
@@ -0,0 +1,45 @@
1
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
2
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
3
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
4
+ import { FEATURE_API_IMPORT_SOURCES } from './constants';
5
+ export function isIdentifierImportedFrom(identifierName, sources, context) {
6
+ if (sources.size > 0) {
7
+ var _context$getScope$ref, _context$getScope$ref2;
8
+ return (_context$getScope$ref = (_context$getScope$ref2 = context.getScope().references.find(function (ref) {
9
+ return ref.identifier.name === identifierName;
10
+ })) === null || _context$getScope$ref2 === void 0 || (_context$getScope$ref2 = _context$getScope$ref2.resolved) === null || _context$getScope$ref2 === void 0 ? void 0 : _context$getScope$ref2.defs.some(function (def) {
11
+ var _def$parent;
12
+ return ((_def$parent = def.parent) === null || _def$parent === void 0 ? void 0 : _def$parent.type) === 'ImportDeclaration' && sources.has(def.parent.source.value + '');
13
+ })) !== null && _context$getScope$ref !== void 0 ? _context$getScope$ref : false;
14
+ }
15
+ return false;
16
+ }
17
+ export function isAPIimport(functionName, context) {
18
+ return isIdentifierImportedFrom(functionName, FEATURE_API_IMPORT_SOURCES, context);
19
+ }
20
+
21
+ // returns the definition node of a variable if it's declared within the scope of the file
22
+ export function getDef(name, context) {
23
+ var scope = context.getScope();
24
+ while (scope && scope.type !== 'global') {
25
+ var _iterator = _createForOfIteratorHelper(scope.variables),
26
+ _step;
27
+ try {
28
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
29
+ var variable = _step.value;
30
+ if (variable.name === name) {
31
+ var definition = variable.defs.find(function (def) {
32
+ return def.node && def.node.type === 'VariableDeclarator';
33
+ });
34
+ return definition;
35
+ }
36
+ }
37
+ } catch (err) {
38
+ _iterator.e(err);
39
+ } finally {
40
+ _iterator.f();
41
+ }
42
+ scope = scope.upper;
43
+ }
44
+ return null;
45
+ }
@@ -6,12 +6,20 @@ export declare const rules: {
6
6
  'ensure-test-runner-nested-count': import("eslint").Rule.RuleModule;
7
7
  'ensure-atlassian-team': import("eslint").Rule.RuleModule;
8
8
  'ensure-critical-dependency-resolutions': import("eslint").Rule.RuleModule;
9
+ 'ensure-valid-emotion-css-prop': import("eslint").Rule.RuleModule;
9
10
  'no-duplicate-dependencies': import("eslint").Rule.RuleModule;
10
11
  'no-invalid-feature-flag-usage': import("eslint").Rule.RuleModule;
11
12
  'no-pre-post-install-scripts': import("eslint").Rule.RuleModule;
12
13
  'no-invalid-storybook-decorator-usage': import("eslint").Rule.RuleModule;
13
14
  'ensure-publish-valid': import("eslint").Rule.RuleModule;
14
15
  'ensure-native-and-af-exports-synced': import("eslint").Rule.RuleModule;
16
+ 'no-module-level-eval': import("eslint").Rule.RuleModule;
17
+ 'static-feature-flags': import("eslint").Rule.RuleModule;
18
+ 'no-preconditioning': import("eslint").Rule.RuleModule;
19
+ 'inline-usage': import("eslint").Rule.RuleModule;
20
+ 'prefer-fg': import("eslint").Rule.RuleModule;
21
+ 'no-alias': import("eslint").Rule.RuleModule;
22
+ 'use-recommended-utils': import("eslint").Rule.RuleModule;
15
23
  };
16
24
  export declare const configs: {
17
25
  recommended: {
@@ -26,9 +34,16 @@ export declare const configs: {
26
34
  '@atlaskit/platform/no-invalid-feature-flag-usage': string;
27
35
  '@atlaskit/platform/no-invalid-storybook-decorator-usage': string;
28
36
  '@atlaskit/platform/ensure-atlassian-team': string;
37
+ '@atlaskit/platform/no-module-level-eval': string;
38
+ '@atlaskit/platform/static-feature-flags': string;
39
+ '@atlaskit/platform/no-preconditioning': string;
40
+ '@atlaskit/platform/inline-usage': string;
41
+ '@atlaskit/platform/prefer-fg': string;
42
+ '@atlaskit/platform/no-alias': string;
29
43
  };
30
44
  };
31
45
  };
32
46
  export declare const processors: {
33
47
  'package-json-processor': Linter.Processor<string | Linter.ProcessorFile>;
48
+ 'package-json-processor-for-flat-config': Linter.Processor<string | Linter.ProcessorFile>;
34
49
  };
@@ -0,0 +1,3 @@
1
+ export declare const FEATURE_API_IMPORT_SOURCES: Set<string>;
2
+ export declare const FEATURE_MOCKS_IMPORT_SOURCES: Set<string>;
3
+ export declare const FEATURE_UTILS_IMPORT_SOURCES: Set<string>;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -3,6 +3,7 @@ import Fuse from 'fuse.js';
3
3
  export declare const getterIdentifierToFlagTypeMap: {
4
4
  readonly getBooleanFF: "boolean";
5
5
  readonly ffTest: "boolean";
6
+ readonly fg: "boolean";
6
7
  };
7
8
  export type PlatformFeatureFlagRegistrationSection = {
8
9
  [key: string]: {
@@ -0,0 +1,7 @@
1
+ import type { Rule, Scope } from 'eslint';
2
+ export declare function isIdentifierImportedFrom(identifierName: string, sources: Set<string>, context: Rule.RuleContext): boolean;
3
+ export declare function isAPIimport(functionName: string, context: Rule.RuleContext): boolean;
4
+ export declare function getDef(name: string, context: Rule.RuleContext): Scope.Definition | null | undefined;
5
+ export type Node<T extends Rule.Node['type']> = Extract<Rule.Node, {
6
+ type: T;
7
+ }>;
@@ -6,12 +6,20 @@ export declare const rules: {
6
6
  'ensure-test-runner-nested-count': import("eslint").Rule.RuleModule;
7
7
  'ensure-atlassian-team': import("eslint").Rule.RuleModule;
8
8
  'ensure-critical-dependency-resolutions': import("eslint").Rule.RuleModule;
9
+ 'ensure-valid-emotion-css-prop': import("eslint").Rule.RuleModule;
9
10
  'no-duplicate-dependencies': import("eslint").Rule.RuleModule;
10
11
  'no-invalid-feature-flag-usage': import("eslint").Rule.RuleModule;
11
12
  'no-pre-post-install-scripts': import("eslint").Rule.RuleModule;
12
13
  'no-invalid-storybook-decorator-usage': import("eslint").Rule.RuleModule;
13
14
  'ensure-publish-valid': import("eslint").Rule.RuleModule;
14
15
  'ensure-native-and-af-exports-synced': import("eslint").Rule.RuleModule;
16
+ 'no-module-level-eval': import("eslint").Rule.RuleModule;
17
+ 'static-feature-flags': import("eslint").Rule.RuleModule;
18
+ 'no-preconditioning': import("eslint").Rule.RuleModule;
19
+ 'inline-usage': import("eslint").Rule.RuleModule;
20
+ 'prefer-fg': import("eslint").Rule.RuleModule;
21
+ 'no-alias': import("eslint").Rule.RuleModule;
22
+ 'use-recommended-utils': import("eslint").Rule.RuleModule;
15
23
  };
16
24
  export declare const configs: {
17
25
  recommended: {
@@ -26,9 +34,16 @@ export declare const configs: {
26
34
  '@atlaskit/platform/no-invalid-feature-flag-usage': string;
27
35
  '@atlaskit/platform/no-invalid-storybook-decorator-usage': string;
28
36
  '@atlaskit/platform/ensure-atlassian-team': string;
37
+ '@atlaskit/platform/no-module-level-eval': string;
38
+ '@atlaskit/platform/static-feature-flags': string;
39
+ '@atlaskit/platform/no-preconditioning': string;
40
+ '@atlaskit/platform/inline-usage': string;
41
+ '@atlaskit/platform/prefer-fg': string;
42
+ '@atlaskit/platform/no-alias': string;
29
43
  };
30
44
  };
31
45
  };
32
46
  export declare const processors: {
33
47
  'package-json-processor': Linter.Processor<string | Linter.ProcessorFile>;
48
+ 'package-json-processor-for-flat-config': Linter.Processor<string | Linter.ProcessorFile>;
34
49
  };
@@ -0,0 +1,3 @@
1
+ export declare const FEATURE_API_IMPORT_SOURCES: Set<string>;
2
+ export declare const FEATURE_MOCKS_IMPORT_SOURCES: Set<string>;
3
+ export declare const FEATURE_UTILS_IMPORT_SOURCES: Set<string>;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;