@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,94 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
+ var _utils = require("../utils");
10
+ var FUNCTION_NAMES = new Set(['ff', 'fg', 'expVal', 'expValEquals', 'UNSAFE_noExposureExp']);
11
+ var STATSIG_ONLY_FUNCTION_NAMES = new Set(['fg', 'expVal', 'expValEquals', 'UNSAFE_noExposureExp']);
12
+ var findDefinitionDeclaration = function findDefinitionDeclaration(node) {
13
+ return node.type === 'VariableDeclaration' || node.type === 'FunctionDeclaration' ? node : findDefinitionDeclaration(node.parent);
14
+ };
15
+ var validateCallExpression = function validateCallExpression(node, context) {
16
+ var targetedFunctionsSwitch = context.options[0] === 'ssOnly' ? STATSIG_ONLY_FUNCTION_NAMES : FUNCTION_NAMES;
17
+ var callee = node.callee;
18
+ var shouldWarn = callee.type === 'Identifier' && targetedFunctionsSwitch.has(callee.name) && (0, _utils.isAPIimport)(callee.name, context);
19
+ if (shouldWarn) {
20
+ var defDeclaration = findDefinitionDeclaration(node.parent);
21
+ context.report({
22
+ messageId: 'inlineUsage',
23
+ node: defDeclaration.parent.type === 'ExportNamedDeclaration' ? defDeclaration.parent : defDeclaration
24
+ });
25
+ return true;
26
+ }
27
+ return false;
28
+ };
29
+ var validateBinaryExpression = function validateBinaryExpression(node, context) {
30
+ // Match all comparator operators i.e ===, >=, <
31
+ if (node.operator.match(/^[=|<|>]/)) {
32
+ if (node.left.type === 'CallExpression' && validateCallExpression(node.left, context)) {
33
+ return;
34
+ }
35
+ if (node.right.type === 'CallExpression') {
36
+ validateCallExpression(node.right, context);
37
+ }
38
+ }
39
+ };
40
+ var validateReturnExpression = function validateReturnExpression(_ref, context) {
41
+ var body = _ref.body;
42
+ if (body.length !== 1) {
43
+ return;
44
+ }
45
+ var _body = (0, _slicedToArray2.default)(body, 1),
46
+ statement = _body[0];
47
+ if (statement.type === 'ReturnStatement') {
48
+ var argument = statement.argument;
49
+ if (argument && argument.type === 'CallExpression') {
50
+ validateCallExpression(argument, context);
51
+ } else if (argument && argument.type === 'BinaryExpression') {
52
+ validateBinaryExpression(argument, context);
53
+ }
54
+ }
55
+ };
56
+ var validateFunctionBody = function validateFunctionBody(body, context) {
57
+ switch (body.type) {
58
+ case 'CallExpression':
59
+ validateCallExpression(body, context);
60
+ break;
61
+ case 'BinaryExpression':
62
+ validateBinaryExpression(body, context);
63
+ break;
64
+ case 'BlockStatement':
65
+ validateReturnExpression(body, context);
66
+ break;
67
+ default:
68
+ }
69
+ };
70
+ var rule = {
71
+ meta: {
72
+ type: 'problem',
73
+ docs: {
74
+ description: 'Ensure feature flags/gates and experiments are inlined so that they can be statically analyzable.',
75
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/inline-usage/README.md'
76
+ },
77
+ messages: {
78
+ inlineUsage: 'Do not export or wrap feature flags/gates and experiments usages. Inline calls at the callsite to ensure it is statically analyzable.'
79
+ }
80
+ },
81
+ create: function create(context) {
82
+ return {
83
+ 'VariableDeclaration[declarations.length=1] > VariableDeclarator[id.type="Identifier"]:matches([init.type="ArrowFunctionExpression"], [init.type="FunctionExpression"]) > *.init': function VariableDeclarationDeclarationsLength1VariableDeclaratorIdTypeIdentifierMatchesInitTypeArrowFunctionExpressionInitTypeFunctionExpressionInit(_ref2) {
84
+ var body = _ref2.body;
85
+ validateFunctionBody(body, context);
86
+ },
87
+ FunctionDeclaration: function FunctionDeclaration(_ref3) {
88
+ var body = _ref3.body;
89
+ validateFunctionBody(body, context);
90
+ }
91
+ };
92
+ }
93
+ };
94
+ var _default = exports.default = rule;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ var _constants = require("../constants");
10
+ var _utils = require("../utils");
11
+ var IMPORT_SOURCES = new Set([].concat((0, _toConsumableArray2.default)(_constants.FEATURE_API_IMPORT_SOURCES), (0, _toConsumableArray2.default)(_constants.FEATURE_MOCKS_IMPORT_SOURCES), (0, _toConsumableArray2.default)(_constants.FEATURE_UTILS_IMPORT_SOURCES)));
12
+ var rule = {
13
+ meta: {
14
+ docs: {
15
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/no-alias/README.md',
16
+ description: 'Disallow aliasing of feature flag utils to ensure feature flag usage is statically analyzable'
17
+ },
18
+ messages: {
19
+ noSpecifierAlias: 'Do not alias feature flag utils. Feature flag usage should be statically analyzable',
20
+ noNamespaceSpecifier: 'Destructure feature flag utils from import. Feature flag usage should be statically analyzable',
21
+ noReassignment: 'Do not reassign feature flag utils. Feature flag usage should be statically analyzable'
22
+ }
23
+ },
24
+ create: function create(context) {
25
+ return {
26
+ ImportDeclaration: function ImportDeclaration(node) {
27
+ var _node$specifiers;
28
+ if (typeof node.source.value === 'string' && !IMPORT_SOURCES.has(node.source.value)) {
29
+ return;
30
+ }
31
+ (_node$specifiers = node.specifiers) === null || _node$specifiers === void 0 || _node$specifiers.forEach(function (specifier) {
32
+ if (specifier.type === 'ImportSpecifier') {
33
+ var imported = specifier.imported,
34
+ local = specifier.local;
35
+ if (imported.name !== local.name) {
36
+ context.report({
37
+ messageId: 'noSpecifierAlias',
38
+ node: specifier
39
+ });
40
+ }
41
+ } else if (specifier.type === 'ImportNamespaceSpecifier') {
42
+ context.report({
43
+ messageId: 'noNamespaceSpecifier',
44
+ node: specifier
45
+ });
46
+ }
47
+ });
48
+ },
49
+ 'VariableDeclaration[kind="const"] > VariableDeclarator[id.type="Identifier"][init.type="Identifier"]': function VariableDeclarationKindConstVariableDeclaratorIdTypeIdentifierInitTypeIdentifier(node) {
50
+ if (!node.init || node.init.type !== 'Identifier') {
51
+ return;
52
+ }
53
+ var isReassignment = (0, _utils.isIdentifierImportedFrom)(node.init.name, IMPORT_SOURCES, context);
54
+ if (isReassignment) {
55
+ context.report({
56
+ messageId: 'noReassignment',
57
+ node: node
58
+ });
59
+ }
60
+ }
61
+ };
62
+ }
63
+ };
64
+ var _default = exports.default = rule;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _utils = require("../utils");
8
+ var isInFunctionLevel = function isInFunctionLevel(context) {
9
+ var scope = context.getScope();
10
+ while (((_scope = scope) === null || _scope === void 0 ? void 0 : _scope.type) !== 'module' && ((_scope2 = scope) === null || _scope2 === void 0 ? void 0 : _scope2.type) !== 'global') {
11
+ var _scope, _scope2;
12
+ if (scope.type === 'function') {
13
+ return true;
14
+ }
15
+ if (scope.type === 'class-field-initializer') {
16
+ return !scope.block.parent.static;
17
+ }
18
+ scope = scope.upper;
19
+ }
20
+ return false;
21
+ };
22
+ var rule = {
23
+ meta: {
24
+ docs: {
25
+ description: 'Disallow feature flag usage at module level',
26
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/no-module-level-eval/README.md'
27
+ },
28
+ messages: {
29
+ noModuleLevelEval: 'Do not evaluate feature flags at module level, it will always resolve to false when server side rendered.'
30
+ }
31
+ },
32
+ create: function create(context) {
33
+ return {
34
+ 'CallExpression[callee.type="Identifier"]': function CallExpressionCalleeTypeIdentifier(node) {
35
+ if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && (0, _utils.isAPIimport)(node.callee.name, context) && !isInFunctionLevel(context)) {
36
+ context.report({
37
+ messageId: 'noModuleLevelEval',
38
+ node: node
39
+ });
40
+ }
41
+ }
42
+ };
43
+ }
44
+ };
45
+ var _default = exports.default = rule;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _utils = require("../utils");
8
+ var isAndExpression = function isAndExpression(node) {
9
+ return node.type === 'LogicalExpression' && node.operator === '&&';
10
+ };
11
+ var isExpUsage = function isExpUsage(calleeName) {
12
+ return calleeName === 'expVal' || calleeName === 'expValEquals';
13
+ };
14
+ var getGateType = function getGateType(node, context) {
15
+ var type = node.type;
16
+ if (type === 'BinaryExpression') {
17
+ return getGateType(node.left, context) || getGateType(node.right, context);
18
+ }
19
+ if (node.type === 'CallExpression') {
20
+ var callee = node.callee;
21
+ var isFeatureGate = type === 'CallExpression' && callee.type === 'Identifier' && (
22
+ // Experiments cannot have other experiments as preconditions, only gates
23
+ callee.name === 'fg' || isExpUsage(callee.name)) && (0, _utils.isAPIimport)(callee.name, context);
24
+ return isFeatureGate ? callee.name : '';
25
+ }
26
+ return '';
27
+ };
28
+ var getPreconditionStatus = function getPreconditionStatus(logicalExpression, context) {
29
+ var _ref = logicalExpression,
30
+ left = _ref.left;
31
+ // If left side is a nested AND expression then the left side node is on the nested's right
32
+ var leftGateType = getGateType(isAndExpression(left) ? left.right : left, context);
33
+ if (leftGateType) {
34
+ var rightGateType = getGateType(logicalExpression.right, context);
35
+ // Check this scenario: fg('gate') && isAdmin
36
+ if (!rightGateType) {
37
+ return 'early-exposure';
38
+ }
39
+
40
+ // Using experiment values in logical expressions in valid
41
+ // i.e. expVal() && expVal()
42
+ if (isExpUsage(leftGateType) && isExpUsage(rightGateType)) {
43
+ return '';
44
+ }
45
+
46
+ // Then is scenario: fg('gate1') && fg('gate2')
47
+ return 'unnecessary-gate';
48
+ }
49
+ return '';
50
+ };
51
+ var rule = {
52
+ meta: {
53
+ docs: {
54
+ description: 'Inform on how to use gates and experiments in logical expressions',
55
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/no-preconditioning/README.md'
56
+ },
57
+ messages: {
58
+ useConfig: 'Do not precondition gates or experiments with another gate. Configure this in Statsig instead to reduce unnecessary code and simplify cleanup.',
59
+ incorrectExposure: 'Evaluate gates or experiments at the end of your logical expression to ensure exposure is tracked correctly.'
60
+ }
61
+ },
62
+ create: function create(context) {
63
+ return {
64
+ 'LogicalExpression[operator="&&"]': function LogicalExpressionOperator(node) {
65
+ var parent = node.parent;
66
+ // Don't analyze nested AND logical expressions
67
+ if (isAndExpression(parent)) {
68
+ return;
69
+ }
70
+ var isAssignmentStatement = parent.type !== 'IfStatement' && parent.type !== 'ConditionalExpression' &&
71
+ // @ts-expect-error — this isn't a valid statement but does fail tests when removed.
72
+ // When updating this rule please resolve this supression.
73
+ !(parent.type === 'LogicalExpression' && parent.operator === '||');
74
+ var nextLogicalExpression = node;
75
+ var exposureReported = false;
76
+ var configReported = false;
77
+ while (nextLogicalExpression) {
78
+ var preconditionStatus = getPreconditionStatus(nextLogicalExpression, context);
79
+ // Allow us to check for: fg('') && <Component />
80
+ var isReturningValue =
81
+ // Check if we are on the root logical expression
82
+ // as this is where the returning value is
83
+ // `node` is root logical expression
84
+ isAssignmentStatement && nextLogicalExpression === node;
85
+ if (!exposureReported && !isReturningValue && preconditionStatus === 'early-exposure') {
86
+ context.report({
87
+ messageId: 'incorrectExposure',
88
+ node: node
89
+ });
90
+ exposureReported = true;
91
+ }
92
+ if (!configReported && preconditionStatus === 'unnecessary-gate') {
93
+ context.report({
94
+ messageId: 'useConfig',
95
+ node: node
96
+ });
97
+ configReported = true;
98
+ }
99
+ if (exposureReported && configReported) {
100
+ return;
101
+ }
102
+ nextLogicalExpression = isAndExpression(nextLogicalExpression.left) ? nextLogicalExpression.left : undefined;
103
+ }
104
+ }
105
+ };
106
+ }
107
+ };
108
+ var _default = exports.default = rule;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
10
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
+ var _constants = require("../constants");
12
+ 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; }
13
+ 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) { (0, _defineProperty2.default)(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; }
14
+ var validateUsage = function validateUsage(node, utilName, context, changeMap) {
15
+ var _context$getScope$ref;
16
+ var resolved = (_context$getScope$ref = context.getScope().references.find(function (ref) {
17
+ return ref.identifier.name === utilName;
18
+ })) === null || _context$getScope$ref === void 0 ? void 0 : _context$getScope$ref.resolved;
19
+ var importSpecifierDefinition = resolved === null || resolved === void 0 ? void 0 : resolved.defs.find(function (def) {
20
+ var _def$node, _def$parent;
21
+ return ((_def$node = def.node) === null || _def$node === void 0 ? void 0 : _def$node.type) === 'ImportSpecifier' && _constants.FEATURE_API_IMPORT_SOURCES.has((_def$parent = def.parent) === null || _def$parent === void 0 ? void 0 : _def$parent.source.value);
22
+ });
23
+ if (importSpecifierDefinition) {
24
+ var _node$arguments = (0, _slicedToArray2.default)(node.arguments, 1),
25
+ flagNameArg = _node$arguments[0];
26
+ context.report({
27
+ messageId: 'preferFG',
28
+ node: node,
29
+ fix: /*#__PURE__*/_regenerator.default.mark(function fix(fixer) {
30
+ var importDeclaration, changeCounts;
31
+ return _regenerator.default.wrap(function fix$(_context) {
32
+ while (1) switch (_context.prev = _context.next) {
33
+ case 0:
34
+ _context.next = 2;
35
+ return fixer.replaceText(node, "fg(".concat(context.sourceCode.getText(flagNameArg), ")"));
36
+ case 2:
37
+ importDeclaration = importSpecifierDefinition.parent;
38
+ if (changeMap.has(importDeclaration)) {
39
+ changeCounts = changeMap.get(importDeclaration);
40
+ changeMap.set(importDeclaration, _objectSpread(_objectSpread({}, changeCounts), {}, (0, _defineProperty2.default)({}, utilName, changeCounts[utilName] + 1 || 1)));
41
+ } else {
42
+ changeMap.set(importDeclaration, (0, _defineProperty2.default)({}, utilName, 1));
43
+ }
44
+ case 4:
45
+ case "end":
46
+ return _context.stop();
47
+ }
48
+ }, fix);
49
+ })
50
+ });
51
+ }
52
+ };
53
+ var rule = {
54
+ meta: {
55
+ docs: {
56
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/prefer-fg/README.md',
57
+ description: 'Keep usages of boolean feature flags consistent'
58
+ },
59
+ fixable: 'code',
60
+ messages: {
61
+ preferFG: 'Use `fg` instead for boolean feature flags',
62
+ autoFixImports: 'Use `fg` instead for boolean feature flags'
63
+ }
64
+ },
65
+ create: function create(context) {
66
+ var changeMap;
67
+ return {
68
+ 'CallExpression[callee.name="getBooleanFF"]': function CallExpressionCalleeNameGetBooleanFF(node) {
69
+ if (node.type !== 'CallExpression') {
70
+ return;
71
+ }
72
+ changeMap = changeMap || new Map();
73
+ validateUsage(node, 'getBooleanFF', context, changeMap);
74
+ },
75
+ 'Program:exit': function ProgramExit() {
76
+ var _changeMap;
77
+ if ((_changeMap = changeMap) !== null && _changeMap !== void 0 && _changeMap.size) {
78
+ changeMap.forEach(function (changeCounts, importDeclaration) {
79
+ var _context$getScope$chi = (0, _slicedToArray2.default)(context.getScope().childScopes, 1),
80
+ moduleScope = _context$getScope$chi[0];
81
+ var importSpecifiers = new Set(importDeclaration.specifiers.map(function (_ref) {
82
+ var imported = _ref.imported;
83
+ return imported.name;
84
+ }));
85
+ importSpecifiers.add('fg');
86
+ Object.keys(changeCounts).forEach(function (utilName) {
87
+ var _moduleScope$set$get;
88
+ if (changeCounts[utilName] === ((_moduleScope$set$get = moduleScope.set.get(utilName)) === null || _moduleScope$set$get === void 0 ? void 0 : _moduleScope$set$get.references.length)) {
89
+ importSpecifiers.delete(utilName);
90
+ }
91
+ });
92
+ context.report({
93
+ messageId: 'autoFixImports',
94
+ node: importDeclaration,
95
+ fix: function fix(fixer) {
96
+ return fixer.replaceText(importDeclaration, "import { ".concat(Array.from(importSpecifiers).join(', '), " } from '").concat(importDeclaration.source.value, "';"));
97
+ }
98
+ });
99
+ });
100
+ changeMap.clear();
101
+ }
102
+ }
103
+ };
104
+ }
105
+ };
106
+ var _default = exports.default = rule;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ var _constants = require("../constants");
10
+ var _utils = require("../utils");
11
+ var IMPORT_SOURCES = new Set([].concat((0, _toConsumableArray2.default)(_constants.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']));
12
+
13
+ // Any functions not in this list should be skipped for performance.
14
+ var FUNCTION_NAMES = new Set(['ff', 'fg', 'getFeatureFlagValue', 'getMultivariateFeatureFlag', 'componentWithFF', 'componentWithFG', 'passGate', 'withGate', 'expVal', 'expValEquals', 'UNSAFE_noExposureExp', 'mockExp', 'withExp', 'wasExperimentManuallyExposed']);
15
+ var STATSIG_ONLY_FUNCTION_NAMES = new Set(['fg', 'componentWithFG', 'passGate', 'withGate', 'expVal', 'expValEquals', 'UNSAFE_noExposureExp', 'mockExp', 'withExp', 'wasExperimentManuallyExposed']);
16
+ var rule = {
17
+ meta: {
18
+ type: 'problem',
19
+ docs: {
20
+ 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',
21
+ description: 'Ensure feature flags or gates are static string literals'
22
+ },
23
+ fixable: 'code',
24
+ messages: {
25
+ FFLiteral: 'Use static string literal for `featureFlagName`. See https://team.atlassian.com/project/ATLAS-46997/about'
26
+ }
27
+ },
28
+ create: function create(context) {
29
+ var targetedFunctionsSwitch = context.options[0] === 'ssOnly' ? STATSIG_ONLY_FUNCTION_NAMES : FUNCTION_NAMES;
30
+ return {
31
+ // When they're not literals, show a message
32
+ 'CallExpression[callee.type="Identifier"][arguments.length>0][arguments.0.type!="Literal"]': function CallExpressionCalleeTypeIdentifierArgumentsLength0Arguments0TypeLiteral(node) {
33
+ if (node.type !== 'CallExpression') {
34
+ return;
35
+ }
36
+ if (node.callee.type === 'Identifier' && (!targetedFunctionsSwitch.has(node.callee.name) || !(0, _utils.isIdentifierImportedFrom)(node.callee.name, IMPORT_SOURCES, context))) {
37
+ return;
38
+ }
39
+ var nameArgument = node.arguments[0];
40
+ if (nameArgument.type === 'Identifier') {
41
+ var def = (0, _utils.getDef)(nameArgument.name, context);
42
+ if (def != null && def.type === 'Variable') {
43
+ var _ref = def.node.init,
44
+ value = _ref.value;
45
+ context.report({
46
+ node: nameArgument,
47
+ messageId: 'FFLiteral',
48
+ fix: function fix(fixer) {
49
+ return fixer.replaceText(nameArgument, "'".concat(value, "'"));
50
+ }
51
+ });
52
+ return;
53
+ }
54
+ }
55
+ context.report({
56
+ node: nameArgument,
57
+ messageId: 'FFLiteral'
58
+ });
59
+ }
60
+ };
61
+ }
62
+ };
63
+ var _default = exports.default = rule;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _utils = require("../utils");
8
+ var BANNED_IMPORTS_SET = new Set(['@atlaskit/feature-gate-js-client']);
9
+ var rule = {
10
+ meta: {
11
+ docs: {
12
+ 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',
13
+ description: 'Prefer using the feature flag abstraction over direct statsig library.'
14
+ },
15
+ messages: {
16
+ notSupported: 'Experimentation is not suported in platform feature flags, reach out to #help-statsig-switcheroo.',
17
+ useRecommended: 'Please do not use FeatureGates.{{util}}, use {{recommended}} from {{lib}} instead.'
18
+ },
19
+ type: 'problem'
20
+ },
21
+ create: function create(context) {
22
+ return {
23
+ 'CallExpression > MemberExpression:matches([property.name="checkGate"])': function CallExpressionMemberExpressionMatchesPropertyNameCheckGate(node) {
24
+ if (node.object.type === 'Identifier' && (0, _utils.isIdentifierImportedFrom)(node.object.name, BANNED_IMPORTS_SET, context)) {
25
+ context.report({
26
+ messageId: 'useRecommended',
27
+ node: node,
28
+ data: {
29
+ lib: '`@atlaskit/platform-feature-flags`',
30
+ util: node.property.name,
31
+ recommended: '`fg`'
32
+ }
33
+ });
34
+ }
35
+ },
36
+ 'CallExpression > MemberExpression:matches([property.name="getExperimentValue"])': function CallExpressionMemberExpressionMatchesPropertyNameGetExperimentValue(node) {
37
+ if (node.object.type === 'Identifier' && (0, _utils.isIdentifierImportedFrom)(node.object.name, BANNED_IMPORTS_SET, context)) {
38
+ context.report({
39
+ messageId: 'notSupported',
40
+ node: node
41
+ });
42
+ }
43
+ }
44
+ };
45
+ }
46
+ };
47
+ var _default = exports.default = rule;
@@ -13,7 +13,8 @@ var _fuse = _interopRequireDefault(require("fuse.js"));
13
13
  // if you don't want to verify the type use `null` as the value
14
14
  var getterIdentifierToFlagTypeMap = exports.getterIdentifierToFlagTypeMap = {
15
15
  getBooleanFF: 'boolean',
16
- ffTest: 'boolean'
16
+ ffTest: 'boolean',
17
+ fg: 'boolean'
17
18
  };
18
19
  // make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
19
20
  var pkgJsonCache = new Map();
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getDef = getDef;
7
+ exports.isAPIimport = isAPIimport;
8
+ exports.isIdentifierImportedFrom = isIdentifierImportedFrom;
9
+ var _constants = require("./constants");
10
+ 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; } } }; }
11
+ 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); }
12
+ 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; }
13
+ function isIdentifierImportedFrom(identifierName, sources, context) {
14
+ if (sources.size > 0) {
15
+ var _context$getScope$ref, _context$getScope$ref2;
16
+ return (_context$getScope$ref = (_context$getScope$ref2 = context.getScope().references.find(function (ref) {
17
+ return ref.identifier.name === identifierName;
18
+ })) === 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) {
19
+ var _def$parent;
20
+ return ((_def$parent = def.parent) === null || _def$parent === void 0 ? void 0 : _def$parent.type) === 'ImportDeclaration' && sources.has(def.parent.source.value + '');
21
+ })) !== null && _context$getScope$ref !== void 0 ? _context$getScope$ref : false;
22
+ }
23
+ return false;
24
+ }
25
+ function isAPIimport(functionName, context) {
26
+ return isIdentifierImportedFrom(functionName, _constants.FEATURE_API_IMPORT_SOURCES, context);
27
+ }
28
+
29
+ // returns the definition node of a variable if it's declared within the scope of the file
30
+ function getDef(name, context) {
31
+ var scope = context.getScope();
32
+ while (scope && scope.type !== 'global') {
33
+ var _iterator = _createForOfIteratorHelper(scope.variables),
34
+ _step;
35
+ try {
36
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
37
+ var variable = _step.value;
38
+ if (variable.name === name) {
39
+ var definition = variable.defs.find(function (def) {
40
+ return def.node && def.node.type === 'VariableDeclarator';
41
+ });
42
+ return definition;
43
+ }
44
+ }
45
+ } catch (err) {
46
+ _iterator.e(err);
47
+ } finally {
48
+ _iterator.f();
49
+ }
50
+ scope = scope.upper;
51
+ }
52
+ return null;
53
+ }