@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.
- package/CHANGELOG.md +103 -28
- package/afm-jira/tsconfig.json +20 -0
- package/dist/cjs/index.js +48 -2
- package/dist/cjs/rules/constants.js +11 -0
- package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +54 -6
- package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +16 -7
- package/dist/cjs/rules/ensure-valid-emotion-css-prop/index.js +91 -0
- package/dist/cjs/rules/inline-usage/index.js +94 -0
- package/dist/cjs/rules/no-alias/index.js +64 -0
- package/dist/cjs/rules/no-module-level-eval/index.js +45 -0
- package/dist/cjs/rules/no-preconditioning/index.js +108 -0
- package/dist/cjs/rules/prefer-fg/index.js +106 -0
- package/dist/cjs/rules/static-feature-flags/index.js +63 -0
- package/dist/cjs/rules/use-recommended-utils/index.js +47 -0
- package/dist/cjs/rules/util/registration-utils.js +2 -1
- package/dist/cjs/rules/utils.js +53 -0
- package/dist/es2019/index.js +52 -2
- package/dist/es2019/rules/constants.js +5 -0
- package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +52 -6
- package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +15 -7
- package/dist/es2019/rules/ensure-valid-emotion-css-prop/index.js +87 -0
- package/dist/es2019/rules/inline-usage/index.js +90 -0
- package/dist/es2019/rules/no-alias/index.js +58 -0
- package/dist/es2019/rules/no-module-level-eval/index.js +39 -0
- package/dist/es2019/rules/no-preconditioning/index.js +105 -0
- package/dist/es2019/rules/prefer-fg/index.js +81 -0
- package/dist/es2019/rules/static-feature-flags/index.js +54 -0
- package/dist/es2019/rules/use-recommended-utils/index.js +41 -0
- package/dist/es2019/rules/util/registration-utils.js +2 -1
- package/dist/es2019/rules/utils.js +29 -0
- package/dist/esm/index.js +48 -2
- package/dist/esm/rules/constants.js +5 -0
- package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +54 -6
- package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +16 -7
- package/dist/esm/rules/ensure-valid-emotion-css-prop/index.js +85 -0
- package/dist/esm/rules/inline-usage/index.js +87 -0
- package/dist/esm/rules/no-alias/index.js +57 -0
- package/dist/esm/rules/no-module-level-eval/index.js +39 -0
- package/dist/esm/rules/no-preconditioning/index.js +102 -0
- package/dist/esm/rules/prefer-fg/index.js +99 -0
- package/dist/esm/rules/static-feature-flags/index.js +56 -0
- package/dist/esm/rules/use-recommended-utils/index.js +41 -0
- package/dist/esm/rules/util/registration-utils.js +2 -1
- package/dist/esm/rules/utils.js +45 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/rules/constants.d.ts +3 -0
- package/dist/types/rules/ensure-valid-emotion-css-prop/index.d.ts +3 -0
- package/dist/types/rules/inline-usage/index.d.ts +3 -0
- package/dist/types/rules/no-alias/index.d.ts +3 -0
- package/dist/types/rules/no-module-level-eval/index.d.ts +3 -0
- package/dist/types/rules/no-preconditioning/index.d.ts +3 -0
- package/dist/types/rules/prefer-fg/index.d.ts +3 -0
- package/dist/types/rules/static-feature-flags/index.d.ts +3 -0
- package/dist/types/rules/use-recommended-utils/index.d.ts +3 -0
- package/dist/types/rules/util/registration-utils.d.ts +1 -0
- package/dist/types/rules/utils.d.ts +7 -0
- package/dist/types-ts4.5/index.d.ts +15 -0
- package/dist/types-ts4.5/rules/constants.d.ts +3 -0
- package/dist/types-ts4.5/rules/ensure-valid-emotion-css-prop/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/inline-usage/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-alias/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-module-level-eval/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-preconditioning/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/prefer-fg/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/static-feature-flags/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/use-recommended-utils/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/util/registration-utils.d.ts +1 -0
- package/dist/types-ts4.5/rules/utils.d.ts +7 -0
- package/index.js +9 -9
- package/package.json +43 -44
- package/report.api.md +31 -30
- package/src/__tests__/utils/_tester.tsx +16 -16
- package/src/index.tsx +102 -51
- package/src/rules/constants.tsx +20 -0
- package/src/rules/ensure-atlassian-team/__tests__/unit/rule.test.ts +19 -19
- package/src/rules/ensure-atlassian-team/index.ts +39 -52
- package/src/rules/ensure-critical-dependency-resolutions/__test__/unit/rule.test.tsx +146 -81
- package/src/rules/ensure-critical-dependency-resolutions/index.tsx +152 -97
- package/src/rules/ensure-feature-flag-prefix/__tests__/unit/rule.test.tsx +51 -51
- package/src/rules/ensure-feature-flag-prefix/index.tsx +65 -80
- package/src/rules/ensure-feature-flag-registration/__tests__/unit/rule.test.tsx +97 -97
- package/src/rules/ensure-feature-flag-registration/index.tsx +88 -105
- package/src/rules/ensure-native-and-af-exports-synced/__tests__/unit/rule.test.tsx +180 -180
- package/src/rules/ensure-native-and-af-exports-synced/index.tsx +162 -168
- package/src/rules/ensure-publish-valid/__tests__/unit/rule.test.ts +34 -36
- package/src/rules/ensure-publish-valid/index.ts +66 -81
- package/src/rules/ensure-test-runner-arguments/__tests__/unit/rule.test.tsx +93 -93
- package/src/rules/ensure-test-runner-arguments/index.tsx +107 -121
- package/src/rules/ensure-test-runner-nested-count/__tests__/unit/rule.test.tsx +43 -43
- package/src/rules/ensure-test-runner-nested-count/index.tsx +68 -70
- package/src/rules/ensure-valid-emotion-css-prop/__tests__/unit/rule.test.ts +142 -0
- package/src/rules/ensure-valid-emotion-css-prop/index.ts +96 -0
- package/src/rules/inline-usage/README.md +53 -0
- package/src/rules/inline-usage/__tests__/rule.test.tsx +106 -0
- package/src/rules/inline-usage/index.tsx +130 -0
- package/src/rules/no-alias/README.md +29 -0
- package/src/rules/no-alias/__tests__/rule.test.tsx +76 -0
- package/src/rules/no-alias/index.tsx +75 -0
- package/src/rules/no-duplicate-dependencies/__tests__/unit/rule.test.ts +44 -44
- package/src/rules/no-duplicate-dependencies/index.ts +68 -73
- package/src/rules/no-invalid-feature-flag-usage/__tests__/unit/rule.test.tsx +64 -64
- package/src/rules/no-invalid-feature-flag-usage/index.tsx +105 -112
- package/src/rules/no-invalid-storybook-decorator-usage/__tests__/unit/rule.test.tsx +13 -13
- package/src/rules/no-invalid-storybook-decorator-usage/index.tsx +28 -30
- package/src/rules/no-module-level-eval/README.md +53 -0
- package/src/rules/no-module-level-eval/__tests__/test.tsx +133 -0
- package/src/rules/no-module-level-eval/index.tsx +52 -0
- package/src/rules/no-pre-post-installs/__tests__/unit/rule.test.ts +36 -36
- package/src/rules/no-pre-post-installs/index.ts +27 -27
- package/src/rules/no-preconditioning/README.md +69 -0
- package/src/rules/no-preconditioning/__tests__/rule.test.tsx +164 -0
- package/src/rules/no-preconditioning/index.tsx +138 -0
- package/src/rules/prefer-fg/README.md +3 -0
- package/src/rules/prefer-fg/__tests__/rule.test.tsx +83 -0
- package/src/rules/prefer-fg/index.tsx +108 -0
- package/src/rules/static-feature-flags/README.md +3 -0
- package/src/rules/static-feature-flags/__tests__/test.tsx +135 -0
- package/src/rules/static-feature-flags/index.tsx +103 -0
- package/src/rules/use-recommended-utils/README.md +67 -0
- package/src/rules/use-recommended-utils/__tests__/rule.test.tsx +78 -0
- package/src/rules/use-recommended-utils/index.tsx +57 -0
- package/src/rules/util/handle-ast-object.ts +21 -32
- package/src/rules/util/registration-utils.ts +31 -30
- package/src/rules/utils.tsx +46 -0
- package/tsconfig.app.json +35 -35
- 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
|
+
}
|