@atlaskit/eslint-plugin-platform 0.0.4 → 0.0.6
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 +12 -0
- package/dist/cjs/index.js +11 -2
- package/dist/cjs/rules/ensure-feature-flag-registration/index.js +1 -58
- package/dist/cjs/rules/ensure-test-runner-arguments/index.js +95 -0
- package/dist/cjs/rules/ensure-test-runner-nested-count/index.js +69 -0
- package/dist/cjs/rules/no-invalid-feature-flag-usage/index.js +87 -0
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/index.js +11 -2
- package/dist/es2019/rules/ensure-feature-flag-registration/index.js +1 -58
- package/dist/es2019/rules/ensure-test-runner-arguments/index.js +88 -0
- package/dist/es2019/rules/ensure-test-runner-nested-count/index.js +63 -0
- package/dist/es2019/rules/no-invalid-feature-flag-usage/index.js +80 -0
- package/dist/es2019/version.json +1 -1
- package/dist/esm/index.js +11 -2
- package/dist/esm/rules/ensure-feature-flag-registration/index.js +1 -58
- package/dist/esm/rules/ensure-test-runner-arguments/index.js +87 -0
- package/dist/esm/rules/ensure-test-runner-nested-count/index.js +61 -0
- package/dist/esm/rules/no-invalid-feature-flag-usage/index.js +79 -0
- package/dist/esm/version.json +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/rules/ensure-test-runner-arguments/index.d.ts +3 -0
- package/dist/types/rules/ensure-test-runner-nested-count/index.d.ts +3 -0
- package/dist/types/rules/no-invalid-feature-flag-usage/index.d.ts +3 -0
- package/package.json +1 -1
- package/report.api.md +6 -0
- package/src/index.tsx +9 -0
- package/src/rules/ensure-feature-flag-registration/__tests__/unit/rule.test.tsx +2 -37
- package/src/rules/ensure-feature-flag-registration/index.tsx +0 -77
- package/src/rules/ensure-test-runner-arguments/__tests__/unit/rule.test.tsx +221 -0
- package/src/rules/ensure-test-runner-arguments/index.tsx +110 -0
- package/src/rules/ensure-test-runner-nested-count/__tests__/unit/rule.test.tsx +308 -0
- package/src/rules/ensure-test-runner-nested-count/index.tsx +83 -0
- package/src/rules/no-invalid-feature-flag-usage/__tests__/unit/rule.test.tsx +49 -0
- package/src/rules/no-invalid-feature-flag-usage/index.tsx +108 -0
- package/tmp/api-report-tmp.d.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @atlaskit/eslint-plugin-platform
|
|
2
2
|
|
|
3
|
+
## 0.0.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`99449cce7f5`](https://bitbucket.org/atlassian/atlassian-frontend/commits/99449cce7f5) - Eslint rules around test runner arguments and limit on nested test runners
|
|
8
|
+
|
|
9
|
+
## 0.0.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`aeb52cac34c`](https://bitbucket.org/atlassian/atlassian-frontend/commits/aeb52cac34c) - Split feature flag registration rule into two to more easily use it in products
|
|
14
|
+
|
|
3
15
|
## 0.0.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/cjs/index.js
CHANGED
|
@@ -6,15 +6,24 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
});
|
|
7
7
|
exports.rules = exports.configs = void 0;
|
|
8
8
|
var _ensureFeatureFlagRegistration = _interopRequireDefault(require("./rules/ensure-feature-flag-registration"));
|
|
9
|
+
var _ensureTestRunnerArguments = _interopRequireDefault(require("./rules/ensure-test-runner-arguments"));
|
|
10
|
+
var _ensureTestRunnerNestedCount = _interopRequireDefault(require("./rules/ensure-test-runner-nested-count"));
|
|
11
|
+
var _noInvalidFeatureFlagUsage = _interopRequireDefault(require("./rules/no-invalid-feature-flag-usage"));
|
|
9
12
|
var rules = {
|
|
10
|
-
'ensure-feature-flag-registration': _ensureFeatureFlagRegistration.default
|
|
13
|
+
'ensure-feature-flag-registration': _ensureFeatureFlagRegistration.default,
|
|
14
|
+
'ensure-test-runner-arguments': _ensureTestRunnerArguments.default,
|
|
15
|
+
'ensure-test-runner-nested-count': _ensureTestRunnerNestedCount.default,
|
|
16
|
+
'no-invalid-feature-flag-usage': _noInvalidFeatureFlagUsage.default
|
|
11
17
|
};
|
|
12
18
|
exports.rules = rules;
|
|
13
19
|
var configs = {
|
|
14
20
|
recommended: {
|
|
15
21
|
plugins: ['@atlaskit/platform'],
|
|
16
22
|
rules: {
|
|
17
|
-
'@atlaskit/platform/ensure-feature-flag-registration': 'error'
|
|
23
|
+
'@atlaskit/platform/ensure-feature-flag-registration': 'error',
|
|
24
|
+
'@atlaskit/platform/ensure-test-runner-arguments': 'error',
|
|
25
|
+
'@atlaskit/platform/ensure-test-runner-nested-count': 'warn',
|
|
26
|
+
'@atlaskit/platform/no-invalid-feature-flag-usage': 'error'
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
};
|
|
@@ -31,34 +31,6 @@ var getPackageJsonForFileName = function getPackageJsonForFileName(filename) {
|
|
|
31
31
|
pkgJsonCache.set(pkgJsonPath, packageJson);
|
|
32
32
|
return packageJson;
|
|
33
33
|
};
|
|
34
|
-
var __isOnlyOneFlagCheckInExpression = function __isOnlyOneFlagCheckInExpression(root, ignoredNode) {
|
|
35
|
-
switch (root.type) {
|
|
36
|
-
case 'IfStatement':
|
|
37
|
-
return __isOnlyOneFlagCheckInExpression(root.test, ignoredNode);
|
|
38
|
-
case 'CallExpression':
|
|
39
|
-
if (root === ignoredNode) {
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
return !(root.callee.type === 'Identifier' && root.callee.name === 'getBooleanFF');
|
|
43
|
-
|
|
44
|
-
// shouldn't ever get here but just in case
|
|
45
|
-
case 'Identifier':
|
|
46
|
-
return root.name !== 'getBooleanFF';
|
|
47
|
-
case 'BinaryExpression':
|
|
48
|
-
case 'LogicalExpression':
|
|
49
|
-
return __isOnlyOneFlagCheckInExpression(root.left, ignoredNode) && __isOnlyOneFlagCheckInExpression(root.right, ignoredNode);
|
|
50
|
-
default:
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
var isOnlyOneFlagCheckInExpression = function isOnlyOneFlagCheckInExpression(node) {
|
|
55
|
-
var root = node.parent;
|
|
56
|
-
// find the root node of the expression
|
|
57
|
-
while (root.type === 'LogicalExpression') {
|
|
58
|
-
root = root.parent;
|
|
59
|
-
}
|
|
60
|
-
return __isOnlyOneFlagCheckInExpression(root, node);
|
|
61
|
-
};
|
|
62
34
|
var rule = {
|
|
63
35
|
meta: {
|
|
64
36
|
hasSuggestions: false,
|
|
@@ -67,11 +39,8 @@ var rule = {
|
|
|
67
39
|
},
|
|
68
40
|
type: 'problem',
|
|
69
41
|
messages: {
|
|
70
|
-
onlyInlineIf: "Only call feature flags as part of an expression, don't assign to a variable! See http://go/pff-eslint for more details",
|
|
71
|
-
onlyStringLiteral: "Only get feature flags by string literal, don't use variables! See http://go/pff-eslint for more details",
|
|
72
42
|
registrationSectionMissing: 'Please add a "platform-feature-flags" section to your package.json! See http://go/pff-eslint for more details',
|
|
73
|
-
featureFlagMissing: "Please add a \"{{ featureFlag }}\" section to the \"platform-feature-flags\" section in your package.json. See http://go/pff-eslint for more details"
|
|
74
|
-
multipleFlagCheckInExpression: "Only check one flag per expression! See http://go/pff-eslint for more details"
|
|
43
|
+
featureFlagMissing: "Please add a \"{{ featureFlag }}\" section to the \"platform-feature-flags\" section in your package.json. See http://go/pff-eslint for more details"
|
|
75
44
|
}
|
|
76
45
|
},
|
|
77
46
|
create: function create(context) {
|
|
@@ -79,33 +48,7 @@ var rule = {
|
|
|
79
48
|
'CallExpression[callee.name=/getBooleanFF/]': function CallExpressionCalleeNameGetBooleanFF(node) {
|
|
80
49
|
// to make typescript happy
|
|
81
50
|
if (node.type === 'CallExpression') {
|
|
82
|
-
var _node$parent;
|
|
83
51
|
var args = node.arguments;
|
|
84
|
-
if (args.length === 1 && args[0].type !== 'Literal') {
|
|
85
|
-
return context.report({
|
|
86
|
-
node: node,
|
|
87
|
-
messageId: 'onlyStringLiteral'
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
switch ((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) {
|
|
91
|
-
case 'IfStatement':
|
|
92
|
-
case 'ConditionalExpression':
|
|
93
|
-
break;
|
|
94
|
-
case 'LogicalExpression':
|
|
95
|
-
if (!isOnlyOneFlagCheckInExpression(node)) {
|
|
96
|
-
context.report({
|
|
97
|
-
node: node,
|
|
98
|
-
messageId: 'multipleFlagCheckInExpression'
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
break;
|
|
102
|
-
default:
|
|
103
|
-
return context.report({
|
|
104
|
-
node: node,
|
|
105
|
-
messageId: 'onlyInlineIf'
|
|
106
|
-
});
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
52
|
var filename = context.getFilename();
|
|
110
53
|
var packageJson = getPackageJsonForFileName(filename);
|
|
111
54
|
var platformFeatureFlags = packageJson['platform-feature-flags'];
|
|
@@ -0,0 +1,95 @@
|
|
|
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 _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
|
+
var TEST_RUNNER_IDENTIFIER = 'ffTest';
|
|
10
|
+
var rule = {
|
|
11
|
+
meta: {
|
|
12
|
+
docs: {
|
|
13
|
+
recommended: false
|
|
14
|
+
},
|
|
15
|
+
type: 'problem',
|
|
16
|
+
messages: {
|
|
17
|
+
onlyInlineFeatureFlag: 'Only pass in feature flag as string literal, please replace {{identifierName}} with its value.',
|
|
18
|
+
onlyInlineTestFunction: 'Only pass in test functions/cases in an inline manner. Test functions/cases should be passed in directly, instead of as variables. Please replace {{identifierName}} with its own definition.',
|
|
19
|
+
passDownExistingFeatureFlagParam: 'Existing feature flags need to be passed down as params when calling nested test runner. See examples in the package which declares this function.',
|
|
20
|
+
passDownExistingFeatureFlagArgument: 'Existing feature flags need to be passed in as argument when calling nested test runner. See examples in the package which declares this function.',
|
|
21
|
+
passDownExistingFeatureFlagNamesMatch: 'Argument names not matching when passing down existing feature flags. See examples in the package which declares this function.'
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
create: function create(context) {
|
|
25
|
+
return (0, _defineProperty2.default)({}, "CallExpression[callee.name=/".concat(TEST_RUNNER_IDENTIFIER, "/]"), function CallExpressionCalleeName(node) {
|
|
26
|
+
if (node.type === 'CallExpression') {
|
|
27
|
+
var _node$parent, _node$parent2;
|
|
28
|
+
var args = node.arguments;
|
|
29
|
+
|
|
30
|
+
// Verify FF is passed inline
|
|
31
|
+
if (args[0] && args[0].type !== 'Literal') {
|
|
32
|
+
return context.report({
|
|
33
|
+
node: node,
|
|
34
|
+
messageId: 'onlyInlineFeatureFlag',
|
|
35
|
+
data: {
|
|
36
|
+
identifierName: args[0].type === 'Identifier' ? args[0].name : ''
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Verify test functions/cases are passed inline
|
|
42
|
+
if (args[1] && args[1].type !== 'ArrowFunctionExpression') {
|
|
43
|
+
return context.report({
|
|
44
|
+
node: node,
|
|
45
|
+
messageId: 'onlyInlineTestFunction',
|
|
46
|
+
data: {
|
|
47
|
+
identifierName: args[1].type === 'Identifier' ? args[1].name : ''
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (args[2] && args[2].type !== 'ArrowFunctionExpression') {
|
|
52
|
+
return context.report({
|
|
53
|
+
node: node,
|
|
54
|
+
messageId: 'onlyInlineTestFunction',
|
|
55
|
+
data: {
|
|
56
|
+
identifierName: args[2].type === 'Identifier' ? args[2].name : ''
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Verify existing ff overrides are passed down if test runner is nested
|
|
62
|
+
var upperTestRunner = (_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.parent;
|
|
63
|
+
if ((upperTestRunner === null || upperTestRunner === void 0 ? void 0 : upperTestRunner.type) === 'CallExpression' && (upperTestRunner === null || upperTestRunner === void 0 ? void 0 : upperTestRunner.callee.type) === 'Identifier' && (upperTestRunner === null || upperTestRunner === void 0 ? void 0 : upperTestRunner.callee.name) === TEST_RUNNER_IDENTIFIER && ((_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.type) === 'ArrowFunctionExpression') {
|
|
64
|
+
// Not pass in ff to the function that calls test runner
|
|
65
|
+
if (!node.parent.params[0] || node.parent.params[0].type !== 'Identifier') {
|
|
66
|
+
return context.report({
|
|
67
|
+
node: node,
|
|
68
|
+
messageId: 'passDownExistingFeatureFlagParam'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Not pass in ff to test runner as 4th argument
|
|
73
|
+
if (!node.arguments[3] || node.arguments[3].type !== 'Identifier') {
|
|
74
|
+
return context.report({
|
|
75
|
+
node: node,
|
|
76
|
+
messageId: 'passDownExistingFeatureFlagArgument'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Pass in the above two, but names don't match
|
|
80
|
+
var paramName = node.parent.params[0].name;
|
|
81
|
+
var arguName = node.arguments[3].name;
|
|
82
|
+
if (paramName !== arguName) {
|
|
83
|
+
return context.report({
|
|
84
|
+
node: node,
|
|
85
|
+
messageId: 'passDownExistingFeatureFlagNamesMatch'
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var _default = rule;
|
|
95
|
+
exports.default = _default;
|
|
@@ -0,0 +1,69 @@
|
|
|
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 _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
10
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
11
|
+
var NESTED_LIMIT = 4;
|
|
12
|
+
var TEST_RUNNER_IDENTIFIER = 'ffTest';
|
|
13
|
+
var getDepthOfNestedRunner = function getDepthOfNestedRunner(node) {
|
|
14
|
+
// Calculate the depth of a binary tree, using a queue to track path
|
|
15
|
+
var queue = [];
|
|
16
|
+
queue.push(node);
|
|
17
|
+
var depth = 0;
|
|
18
|
+
while (queue.length > 0) {
|
|
19
|
+
var nodeCount = queue.length;
|
|
20
|
+
while (nodeCount > 0) {
|
|
21
|
+
var _currentNode$argument;
|
|
22
|
+
var currentNode = queue.shift();
|
|
23
|
+
if (currentNode.arguments[1].type === 'ArrowFunctionExpression' && currentNode.arguments[1].body.type === 'CallExpression' && currentNode.arguments[1].body.callee.type === 'Identifier' && currentNode.arguments[1].body.callee.name === TEST_RUNNER_IDENTIFIER) {
|
|
24
|
+
queue.push(_objectSpread(_objectSpread({}, currentNode.arguments[1].body), {}, {
|
|
25
|
+
parent: currentNode.parent
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
if (((_currentNode$argument = currentNode.arguments[2]) === null || _currentNode$argument === void 0 ? void 0 : _currentNode$argument.type) === 'ArrowFunctionExpression' && currentNode.arguments[2].body.type === 'CallExpression' && currentNode.arguments[2].body.callee.type === 'Identifier' && currentNode.arguments[2].body.callee.name === TEST_RUNNER_IDENTIFIER) {
|
|
29
|
+
queue.push(_objectSpread(_objectSpread({}, currentNode.arguments[2].body), {}, {
|
|
30
|
+
parent: currentNode.parent
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
nodeCount--;
|
|
34
|
+
}
|
|
35
|
+
depth++;
|
|
36
|
+
}
|
|
37
|
+
return depth;
|
|
38
|
+
};
|
|
39
|
+
var rule = {
|
|
40
|
+
meta: {
|
|
41
|
+
docs: {
|
|
42
|
+
recommended: false
|
|
43
|
+
},
|
|
44
|
+
type: 'problem',
|
|
45
|
+
messages: {
|
|
46
|
+
tooManyNestedTestRunner: '{{nestedTestRunner}} test runners are nested. Feature flags may need a clean-up'
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
create: function create(context) {
|
|
50
|
+
return (0, _defineProperty2.default)({}, "Program > * > CallExpression[callee.name=/".concat(TEST_RUNNER_IDENTIFIER, "/], CallExpression[callee.name=/describe/] > * > * > * > CallExpression[callee.name=/").concat(TEST_RUNNER_IDENTIFIER, "/]"), function ProgramCallExpressionCalleeNameCallExpressionCalleeNameDescribeCallExpressionCalleeName(node) {
|
|
51
|
+
if (node.type === 'CallExpression') {
|
|
52
|
+
// Calculate the depth of nested test runners, counting from the most outside
|
|
53
|
+
var depth = getDepthOfNestedRunner(node);
|
|
54
|
+
if (depth > NESTED_LIMIT) {
|
|
55
|
+
return context.report({
|
|
56
|
+
node: node,
|
|
57
|
+
messageId: 'tooManyNestedTestRunner',
|
|
58
|
+
data: {
|
|
59
|
+
nestedTestRunner: depth.toString()
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var _default = rule;
|
|
69
|
+
exports.default = _default;
|
|
@@ -0,0 +1,87 @@
|
|
|
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 _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
|
+
var FF_GETTER_BOOLEAN_IDENTIFIER = 'getBooleanFF';
|
|
10
|
+
var __isOnlyOneFlagCheckInExpression = function __isOnlyOneFlagCheckInExpression(root, ignoredNode) {
|
|
11
|
+
switch (root.type) {
|
|
12
|
+
case 'IfStatement':
|
|
13
|
+
return __isOnlyOneFlagCheckInExpression(root.test, ignoredNode);
|
|
14
|
+
case 'CallExpression':
|
|
15
|
+
if (root === ignoredNode) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return !(root.callee.type === 'Identifier' && root.callee.name === FF_GETTER_BOOLEAN_IDENTIFIER);
|
|
19
|
+
|
|
20
|
+
// shouldn't ever get here but just in case
|
|
21
|
+
case 'Identifier':
|
|
22
|
+
return root.name !== FF_GETTER_BOOLEAN_IDENTIFIER;
|
|
23
|
+
case 'BinaryExpression':
|
|
24
|
+
case 'LogicalExpression':
|
|
25
|
+
return __isOnlyOneFlagCheckInExpression(root.left, ignoredNode) && __isOnlyOneFlagCheckInExpression(root.right, ignoredNode);
|
|
26
|
+
default:
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var isOnlyOneFlagCheckInExpression = function isOnlyOneFlagCheckInExpression(node) {
|
|
31
|
+
var root = node.parent;
|
|
32
|
+
// find the root node of the expression
|
|
33
|
+
while (root.type === 'LogicalExpression') {
|
|
34
|
+
root = root.parent;
|
|
35
|
+
}
|
|
36
|
+
return __isOnlyOneFlagCheckInExpression(root, node);
|
|
37
|
+
};
|
|
38
|
+
var rule = {
|
|
39
|
+
meta: {
|
|
40
|
+
hasSuggestions: false,
|
|
41
|
+
docs: {
|
|
42
|
+
recommended: false
|
|
43
|
+
},
|
|
44
|
+
type: 'problem',
|
|
45
|
+
messages: {
|
|
46
|
+
onlyInlineIf: "Only call feature flags as part of an expression, don't assign to a variable! See http://go/pff-eslint for more details",
|
|
47
|
+
onlyStringLiteral: "Only get feature flags by string literal, don't use variables! See http://go/pff-eslint for more details",
|
|
48
|
+
multipleFlagCheckInExpression: "Only check one flag per expression! See http://go/pff-eslint for more details"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
create: function create(context) {
|
|
52
|
+
return (0, _defineProperty2.default)({}, "CallExpression[callee.name=/".concat(FF_GETTER_BOOLEAN_IDENTIFIER, "/]"), function CallExpressionCalleeName(node) {
|
|
53
|
+
// to make typescript happy
|
|
54
|
+
if (node.type === 'CallExpression') {
|
|
55
|
+
var _node$parent;
|
|
56
|
+
var args = node.arguments;
|
|
57
|
+
if (args.length === 1 && args[0].type !== 'Literal') {
|
|
58
|
+
return context.report({
|
|
59
|
+
node: node,
|
|
60
|
+
messageId: 'onlyStringLiteral'
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
switch ((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) {
|
|
64
|
+
case 'IfStatement':
|
|
65
|
+
case 'ConditionalExpression':
|
|
66
|
+
break;
|
|
67
|
+
case 'LogicalExpression':
|
|
68
|
+
if (!isOnlyOneFlagCheckInExpression(node)) {
|
|
69
|
+
context.report({
|
|
70
|
+
node: node,
|
|
71
|
+
messageId: 'multipleFlagCheckInExpression'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
default:
|
|
76
|
+
return context.report({
|
|
77
|
+
node: node,
|
|
78
|
+
messageId: 'onlyInlineIf'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var _default = rule;
|
|
87
|
+
exports.default = _default;
|
package/dist/cjs/version.json
CHANGED
package/dist/es2019/index.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import ensureFeatureFlagRegistration from './rules/ensure-feature-flag-registration';
|
|
2
|
+
import ensureTestRunnerArguments from './rules/ensure-test-runner-arguments';
|
|
3
|
+
import ensureTestRunnerNestedCount from './rules/ensure-test-runner-nested-count';
|
|
4
|
+
import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
|
|
2
5
|
export const rules = {
|
|
3
|
-
'ensure-feature-flag-registration': ensureFeatureFlagRegistration
|
|
6
|
+
'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
|
|
7
|
+
'ensure-test-runner-arguments': ensureTestRunnerArguments,
|
|
8
|
+
'ensure-test-runner-nested-count': ensureTestRunnerNestedCount,
|
|
9
|
+
'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage
|
|
4
10
|
};
|
|
5
11
|
export const configs = {
|
|
6
12
|
recommended: {
|
|
7
13
|
plugins: ['@atlaskit/platform'],
|
|
8
14
|
rules: {
|
|
9
|
-
'@atlaskit/platform/ensure-feature-flag-registration': 'error'
|
|
15
|
+
'@atlaskit/platform/ensure-feature-flag-registration': 'error',
|
|
16
|
+
'@atlaskit/platform/ensure-test-runner-arguments': 'error',
|
|
17
|
+
'@atlaskit/platform/ensure-test-runner-nested-count': 'warn',
|
|
18
|
+
'@atlaskit/platform/no-invalid-feature-flag-usage': 'error'
|
|
10
19
|
}
|
|
11
20
|
}
|
|
12
21
|
};
|
|
@@ -24,34 +24,6 @@ const getPackageJsonForFileName = filename => {
|
|
|
24
24
|
pkgJsonCache.set(pkgJsonPath, packageJson);
|
|
25
25
|
return packageJson;
|
|
26
26
|
};
|
|
27
|
-
const __isOnlyOneFlagCheckInExpression = (root, ignoredNode) => {
|
|
28
|
-
switch (root.type) {
|
|
29
|
-
case 'IfStatement':
|
|
30
|
-
return __isOnlyOneFlagCheckInExpression(root.test, ignoredNode);
|
|
31
|
-
case 'CallExpression':
|
|
32
|
-
if (root === ignoredNode) {
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
return !(root.callee.type === 'Identifier' && root.callee.name === 'getBooleanFF');
|
|
36
|
-
|
|
37
|
-
// shouldn't ever get here but just in case
|
|
38
|
-
case 'Identifier':
|
|
39
|
-
return root.name !== 'getBooleanFF';
|
|
40
|
-
case 'BinaryExpression':
|
|
41
|
-
case 'LogicalExpression':
|
|
42
|
-
return __isOnlyOneFlagCheckInExpression(root.left, ignoredNode) && __isOnlyOneFlagCheckInExpression(root.right, ignoredNode);
|
|
43
|
-
default:
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
const isOnlyOneFlagCheckInExpression = node => {
|
|
48
|
-
let root = node.parent;
|
|
49
|
-
// find the root node of the expression
|
|
50
|
-
while (root.type === 'LogicalExpression') {
|
|
51
|
-
root = root.parent;
|
|
52
|
-
}
|
|
53
|
-
return __isOnlyOneFlagCheckInExpression(root, node);
|
|
54
|
-
};
|
|
55
27
|
const rule = {
|
|
56
28
|
meta: {
|
|
57
29
|
hasSuggestions: false,
|
|
@@ -60,11 +32,8 @@ const rule = {
|
|
|
60
32
|
},
|
|
61
33
|
type: 'problem',
|
|
62
34
|
messages: {
|
|
63
|
-
onlyInlineIf: "Only call feature flags as part of an expression, don't assign to a variable! See http://go/pff-eslint for more details",
|
|
64
|
-
onlyStringLiteral: "Only get feature flags by string literal, don't use variables! See http://go/pff-eslint for more details",
|
|
65
35
|
registrationSectionMissing: 'Please add a "platform-feature-flags" section to your package.json! See http://go/pff-eslint for more details',
|
|
66
|
-
featureFlagMissing: `Please add a "{{ featureFlag }}" section to the "platform-feature-flags" section in your package.json. See http://go/pff-eslint for more details
|
|
67
|
-
multipleFlagCheckInExpression: `Only check one flag per expression! See http://go/pff-eslint for more details`
|
|
36
|
+
featureFlagMissing: `Please add a "{{ featureFlag }}" section to the "platform-feature-flags" section in your package.json. See http://go/pff-eslint for more details`
|
|
68
37
|
}
|
|
69
38
|
},
|
|
70
39
|
create(context) {
|
|
@@ -72,33 +41,7 @@ const rule = {
|
|
|
72
41
|
'CallExpression[callee.name=/getBooleanFF/]': node => {
|
|
73
42
|
// to make typescript happy
|
|
74
43
|
if (node.type === 'CallExpression') {
|
|
75
|
-
var _node$parent;
|
|
76
44
|
const args = node.arguments;
|
|
77
|
-
if (args.length === 1 && args[0].type !== 'Literal') {
|
|
78
|
-
return context.report({
|
|
79
|
-
node,
|
|
80
|
-
messageId: 'onlyStringLiteral'
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
switch ((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) {
|
|
84
|
-
case 'IfStatement':
|
|
85
|
-
case 'ConditionalExpression':
|
|
86
|
-
break;
|
|
87
|
-
case 'LogicalExpression':
|
|
88
|
-
if (!isOnlyOneFlagCheckInExpression(node)) {
|
|
89
|
-
context.report({
|
|
90
|
-
node,
|
|
91
|
-
messageId: 'multipleFlagCheckInExpression'
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
break;
|
|
95
|
-
default:
|
|
96
|
-
return context.report({
|
|
97
|
-
node,
|
|
98
|
-
messageId: 'onlyInlineIf'
|
|
99
|
-
});
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
45
|
const filename = context.getFilename();
|
|
103
46
|
const packageJson = getPackageJsonForFileName(filename);
|
|
104
47
|
const platformFeatureFlags = packageJson['platform-feature-flags'];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const TEST_RUNNER_IDENTIFIER = 'ffTest';
|
|
2
|
+
const rule = {
|
|
3
|
+
meta: {
|
|
4
|
+
docs: {
|
|
5
|
+
recommended: false
|
|
6
|
+
},
|
|
7
|
+
type: 'problem',
|
|
8
|
+
messages: {
|
|
9
|
+
onlyInlineFeatureFlag: 'Only pass in feature flag as string literal, please replace {{identifierName}} with its value.',
|
|
10
|
+
onlyInlineTestFunction: 'Only pass in test functions/cases in an inline manner. Test functions/cases should be passed in directly, instead of as variables. Please replace {{identifierName}} with its own definition.',
|
|
11
|
+
passDownExistingFeatureFlagParam: 'Existing feature flags need to be passed down as params when calling nested test runner. See examples in the package which declares this function.',
|
|
12
|
+
passDownExistingFeatureFlagArgument: 'Existing feature flags need to be passed in as argument when calling nested test runner. See examples in the package which declares this function.',
|
|
13
|
+
passDownExistingFeatureFlagNamesMatch: 'Argument names not matching when passing down existing feature flags. See examples in the package which declares this function.'
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
create(context) {
|
|
17
|
+
return {
|
|
18
|
+
[`CallExpression[callee.name=/${TEST_RUNNER_IDENTIFIER}/]`]: node => {
|
|
19
|
+
if (node.type === 'CallExpression') {
|
|
20
|
+
var _node$parent, _node$parent2;
|
|
21
|
+
const args = node.arguments;
|
|
22
|
+
|
|
23
|
+
// Verify FF is passed inline
|
|
24
|
+
if (args[0] && args[0].type !== 'Literal') {
|
|
25
|
+
return context.report({
|
|
26
|
+
node,
|
|
27
|
+
messageId: 'onlyInlineFeatureFlag',
|
|
28
|
+
data: {
|
|
29
|
+
identifierName: args[0].type === 'Identifier' ? args[0].name : ''
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Verify test functions/cases are passed inline
|
|
35
|
+
if (args[1] && args[1].type !== 'ArrowFunctionExpression') {
|
|
36
|
+
return context.report({
|
|
37
|
+
node,
|
|
38
|
+
messageId: 'onlyInlineTestFunction',
|
|
39
|
+
data: {
|
|
40
|
+
identifierName: args[1].type === 'Identifier' ? args[1].name : ''
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (args[2] && args[2].type !== 'ArrowFunctionExpression') {
|
|
45
|
+
return context.report({
|
|
46
|
+
node,
|
|
47
|
+
messageId: 'onlyInlineTestFunction',
|
|
48
|
+
data: {
|
|
49
|
+
identifierName: args[2].type === 'Identifier' ? args[2].name : ''
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Verify existing ff overrides are passed down if test runner is nested
|
|
55
|
+
let upperTestRunner = (_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.parent;
|
|
56
|
+
if ((upperTestRunner === null || upperTestRunner === void 0 ? void 0 : upperTestRunner.type) === 'CallExpression' && (upperTestRunner === null || upperTestRunner === void 0 ? void 0 : upperTestRunner.callee.type) === 'Identifier' && (upperTestRunner === null || upperTestRunner === void 0 ? void 0 : upperTestRunner.callee.name) === TEST_RUNNER_IDENTIFIER && ((_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.type) === 'ArrowFunctionExpression') {
|
|
57
|
+
// Not pass in ff to the function that calls test runner
|
|
58
|
+
if (!node.parent.params[0] || node.parent.params[0].type !== 'Identifier') {
|
|
59
|
+
return context.report({
|
|
60
|
+
node,
|
|
61
|
+
messageId: 'passDownExistingFeatureFlagParam'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Not pass in ff to test runner as 4th argument
|
|
66
|
+
if (!node.arguments[3] || node.arguments[3].type !== 'Identifier') {
|
|
67
|
+
return context.report({
|
|
68
|
+
node,
|
|
69
|
+
messageId: 'passDownExistingFeatureFlagArgument'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Pass in the above two, but names don't match
|
|
73
|
+
const paramName = node.parent.params[0].name;
|
|
74
|
+
const arguName = node.arguments[3].name;
|
|
75
|
+
if (paramName !== arguName) {
|
|
76
|
+
return context.report({
|
|
77
|
+
node,
|
|
78
|
+
messageId: 'passDownExistingFeatureFlagNamesMatch'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
export default rule;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const NESTED_LIMIT = 4;
|
|
2
|
+
const TEST_RUNNER_IDENTIFIER = 'ffTest';
|
|
3
|
+
const getDepthOfNestedRunner = node => {
|
|
4
|
+
// Calculate the depth of a binary tree, using a queue to track path
|
|
5
|
+
let queue = [];
|
|
6
|
+
queue.push(node);
|
|
7
|
+
let depth = 0;
|
|
8
|
+
while (queue.length > 0) {
|
|
9
|
+
let nodeCount = queue.length;
|
|
10
|
+
while (nodeCount > 0) {
|
|
11
|
+
var _currentNode$argument;
|
|
12
|
+
let currentNode = queue.shift();
|
|
13
|
+
if (currentNode.arguments[1].type === 'ArrowFunctionExpression' && currentNode.arguments[1].body.type === 'CallExpression' && currentNode.arguments[1].body.callee.type === 'Identifier' && currentNode.arguments[1].body.callee.name === TEST_RUNNER_IDENTIFIER) {
|
|
14
|
+
queue.push({
|
|
15
|
+
...currentNode.arguments[1].body,
|
|
16
|
+
parent: currentNode.parent
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (((_currentNode$argument = currentNode.arguments[2]) === null || _currentNode$argument === void 0 ? void 0 : _currentNode$argument.type) === 'ArrowFunctionExpression' && currentNode.arguments[2].body.type === 'CallExpression' && currentNode.arguments[2].body.callee.type === 'Identifier' && currentNode.arguments[2].body.callee.name === TEST_RUNNER_IDENTIFIER) {
|
|
20
|
+
queue.push({
|
|
21
|
+
...currentNode.arguments[2].body,
|
|
22
|
+
parent: currentNode.parent
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
nodeCount--;
|
|
26
|
+
}
|
|
27
|
+
depth++;
|
|
28
|
+
}
|
|
29
|
+
return depth;
|
|
30
|
+
};
|
|
31
|
+
const rule = {
|
|
32
|
+
meta: {
|
|
33
|
+
docs: {
|
|
34
|
+
recommended: false
|
|
35
|
+
},
|
|
36
|
+
type: 'problem',
|
|
37
|
+
messages: {
|
|
38
|
+
tooManyNestedTestRunner: '{{nestedTestRunner}} test runners are nested. Feature flags may need a clean-up'
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
create(context) {
|
|
42
|
+
return {
|
|
43
|
+
// Find the most outside test runner, could be inside a describe or not
|
|
44
|
+
[`Program > * > CallExpression[callee.name=/${TEST_RUNNER_IDENTIFIER}/], CallExpression[callee.name=/describe/] > * > * > * > CallExpression[callee.name=/${TEST_RUNNER_IDENTIFIER}/]`]: node => {
|
|
45
|
+
if (node.type === 'CallExpression') {
|
|
46
|
+
// Calculate the depth of nested test runners, counting from the most outside
|
|
47
|
+
const depth = getDepthOfNestedRunner(node);
|
|
48
|
+
if (depth > NESTED_LIMIT) {
|
|
49
|
+
return context.report({
|
|
50
|
+
node,
|
|
51
|
+
messageId: 'tooManyNestedTestRunner',
|
|
52
|
+
data: {
|
|
53
|
+
nestedTestRunner: depth.toString()
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
export default rule;
|