@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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
import type { SimpleCallExpression } from 'estree';
|
|
3
|
+
|
|
4
|
+
const NESTED_LIMIT: number = 4;
|
|
5
|
+
const TEST_RUNNER_IDENTIFIER = 'ffTest' as const;
|
|
6
|
+
|
|
7
|
+
const getDepthOfNestedRunner = (
|
|
8
|
+
node: SimpleCallExpression & Rule.NodeParentExtension,
|
|
9
|
+
): number => {
|
|
10
|
+
// Calculate the depth of a binary tree, using a queue to track path
|
|
11
|
+
let queue: typeof node[] = [];
|
|
12
|
+
queue.push(node);
|
|
13
|
+
let depth = 0;
|
|
14
|
+
while (queue.length > 0) {
|
|
15
|
+
let nodeCount = queue.length;
|
|
16
|
+
while (nodeCount > 0) {
|
|
17
|
+
let currentNode = queue.shift() as typeof node;
|
|
18
|
+
if (
|
|
19
|
+
currentNode.arguments[1].type === 'ArrowFunctionExpression' &&
|
|
20
|
+
currentNode.arguments[1].body.type === 'CallExpression' &&
|
|
21
|
+
currentNode.arguments[1].body.callee.type === 'Identifier' &&
|
|
22
|
+
currentNode.arguments[1].body.callee.name === TEST_RUNNER_IDENTIFIER
|
|
23
|
+
) {
|
|
24
|
+
queue.push({
|
|
25
|
+
...currentNode.arguments[1].body,
|
|
26
|
+
parent: currentNode.parent,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (
|
|
30
|
+
currentNode.arguments[2]?.type === 'ArrowFunctionExpression' &&
|
|
31
|
+
currentNode.arguments[2].body.type === 'CallExpression' &&
|
|
32
|
+
currentNode.arguments[2].body.callee.type === 'Identifier' &&
|
|
33
|
+
currentNode.arguments[2].body.callee.name === TEST_RUNNER_IDENTIFIER
|
|
34
|
+
) {
|
|
35
|
+
queue.push({
|
|
36
|
+
...currentNode.arguments[2].body,
|
|
37
|
+
parent: currentNode.parent,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
nodeCount--;
|
|
41
|
+
}
|
|
42
|
+
depth++;
|
|
43
|
+
}
|
|
44
|
+
return depth;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const rule: Rule.RuleModule = {
|
|
48
|
+
meta: {
|
|
49
|
+
docs: {
|
|
50
|
+
recommended: false,
|
|
51
|
+
},
|
|
52
|
+
type: 'problem',
|
|
53
|
+
messages: {
|
|
54
|
+
tooManyNestedTestRunner:
|
|
55
|
+
'{{nestedTestRunner}} test runners are nested. Feature flags may need a clean-up',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
create(context) {
|
|
59
|
+
return {
|
|
60
|
+
// Find the most outside test runner, could be inside a describe or not
|
|
61
|
+
[`Program > * > CallExpression[callee.name=/${TEST_RUNNER_IDENTIFIER}/], CallExpression[callee.name=/describe/] > * > * > * > CallExpression[callee.name=/${TEST_RUNNER_IDENTIFIER}/]`]:
|
|
62
|
+
(node: Rule.Node) => {
|
|
63
|
+
if (node.type === 'CallExpression') {
|
|
64
|
+
// Calculate the depth of nested test runners, counting from the most outside
|
|
65
|
+
const depth = getDepthOfNestedRunner(node);
|
|
66
|
+
|
|
67
|
+
if (depth > NESTED_LIMIT) {
|
|
68
|
+
return context.report({
|
|
69
|
+
node,
|
|
70
|
+
messageId: 'tooManyNestedTestRunner',
|
|
71
|
+
data: {
|
|
72
|
+
nestedTestRunner: depth.toString(),
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {};
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default rule;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { tester } from '../../../../__tests__/utils/_tester';
|
|
2
|
+
import rule from '../../index';
|
|
3
|
+
|
|
4
|
+
describe('enforce-feature-flag-usage-structure tests', () => {
|
|
5
|
+
tester.run('ensure-feature-flag-registration', rule, {
|
|
6
|
+
valid: [
|
|
7
|
+
{
|
|
8
|
+
// IfStatement
|
|
9
|
+
code: `if(getBooleanFF('test-flag')) { }`,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
// ConditionalExpression
|
|
13
|
+
code: `const val = getBooleanFF('test-flag') ? 'yay' : 'no';`,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
// LogicalExpression
|
|
17
|
+
code: `const val = 100 + (getBooleanFF('test-flag') && 50 || 10);`,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
invalid: [
|
|
21
|
+
{
|
|
22
|
+
code: `getBooleanFF('test-flag')`,
|
|
23
|
+
errors: [{ messageId: 'onlyInlineIf' }],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
code: `const val = getBooleanFF('test-flag')`,
|
|
27
|
+
errors: [{ messageId: 'onlyInlineIf' }],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
code: `const ff = "test-flag"; if(getBooleanFF(ff)) { }`,
|
|
31
|
+
errors: [{ messageId: 'onlyStringLiteral' }],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
code: `if(getBooleanFF('test-flag') && getBooleanFF('test-flag')) { }`,
|
|
35
|
+
errors: [
|
|
36
|
+
{ messageId: 'multipleFlagCheckInExpression' },
|
|
37
|
+
{ messageId: 'multipleFlagCheckInExpression' },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
code: `if((getBooleanFF('test-flag') || 1 == true) && getBooleanFF('test-flag')) { }`,
|
|
42
|
+
errors: [
|
|
43
|
+
{ messageId: 'multipleFlagCheckInExpression' },
|
|
44
|
+
{ messageId: 'multipleFlagCheckInExpression' },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
|
|
3
|
+
const FF_GETTER_BOOLEAN_IDENTIFIER = 'getBooleanFF' as const;
|
|
4
|
+
|
|
5
|
+
const __isOnlyOneFlagCheckInExpression = (
|
|
6
|
+
root: Rule.Node,
|
|
7
|
+
ignoredNode: Rule.Node,
|
|
8
|
+
): boolean => {
|
|
9
|
+
switch (root.type) {
|
|
10
|
+
case 'IfStatement':
|
|
11
|
+
return __isOnlyOneFlagCheckInExpression(
|
|
12
|
+
root.test as Rule.Node,
|
|
13
|
+
ignoredNode,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
case 'CallExpression':
|
|
17
|
+
if (root === ignoredNode) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return !(
|
|
21
|
+
root.callee.type === 'Identifier' &&
|
|
22
|
+
root.callee.name === FF_GETTER_BOOLEAN_IDENTIFIER
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// shouldn't ever get here but just in case
|
|
26
|
+
case 'Identifier':
|
|
27
|
+
return root.name !== FF_GETTER_BOOLEAN_IDENTIFIER;
|
|
28
|
+
|
|
29
|
+
case 'BinaryExpression':
|
|
30
|
+
case 'LogicalExpression':
|
|
31
|
+
return (
|
|
32
|
+
__isOnlyOneFlagCheckInExpression(root.left as Rule.Node, ignoredNode) &&
|
|
33
|
+
__isOnlyOneFlagCheckInExpression(root.right as Rule.Node, ignoredNode)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
default:
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const isOnlyOneFlagCheckInExpression = (node: Rule.Node): boolean => {
|
|
42
|
+
let root = node.parent;
|
|
43
|
+
// find the root node of the expression
|
|
44
|
+
while (root.type === 'LogicalExpression') {
|
|
45
|
+
root = root.parent;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return __isOnlyOneFlagCheckInExpression(root, node);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const rule: Rule.RuleModule = {
|
|
52
|
+
meta: {
|
|
53
|
+
hasSuggestions: false,
|
|
54
|
+
docs: {
|
|
55
|
+
recommended: false,
|
|
56
|
+
},
|
|
57
|
+
type: 'problem',
|
|
58
|
+
messages: {
|
|
59
|
+
onlyInlineIf:
|
|
60
|
+
"Only call feature flags as part of an expression, don't assign to a variable! See http://go/pff-eslint for more details",
|
|
61
|
+
onlyStringLiteral:
|
|
62
|
+
"Only get feature flags by string literal, don't use variables! See http://go/pff-eslint for more details",
|
|
63
|
+
multipleFlagCheckInExpression: `Only check one flag per expression! See http://go/pff-eslint for more details`,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
create(context) {
|
|
67
|
+
return {
|
|
68
|
+
[`CallExpression[callee.name=/${FF_GETTER_BOOLEAN_IDENTIFIER}/]`]: (
|
|
69
|
+
node: Rule.Node,
|
|
70
|
+
) => {
|
|
71
|
+
// to make typescript happy
|
|
72
|
+
if (node.type === 'CallExpression') {
|
|
73
|
+
const args = node.arguments;
|
|
74
|
+
|
|
75
|
+
if (args.length === 1 && args[0].type !== 'Literal') {
|
|
76
|
+
return context.report({
|
|
77
|
+
node,
|
|
78
|
+
messageId: 'onlyStringLiteral',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
switch (node.parent?.type) {
|
|
83
|
+
case 'IfStatement':
|
|
84
|
+
case 'ConditionalExpression':
|
|
85
|
+
break;
|
|
86
|
+
case 'LogicalExpression':
|
|
87
|
+
if (!isOnlyOneFlagCheckInExpression(node)) {
|
|
88
|
+
context.report({
|
|
89
|
+
node,
|
|
90
|
+
messageId: 'multipleFlagCheckInExpression',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
return context.report({
|
|
96
|
+
node,
|
|
97
|
+
messageId: 'onlyInlineIf',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {};
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default rule;
|
package/tmp/api-report-tmp.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ export const configs: {
|
|
|
12
12
|
plugins: string[];
|
|
13
13
|
rules: {
|
|
14
14
|
'@atlaskit/platform/ensure-feature-flag-registration': string;
|
|
15
|
+
'@atlaskit/platform/ensure-test-runner-arguments': string;
|
|
16
|
+
'@atlaskit/platform/ensure-test-runner-nested-count': string;
|
|
17
|
+
'@atlaskit/platform/no-invalid-feature-flag-usage': string;
|
|
15
18
|
};
|
|
16
19
|
};
|
|
17
20
|
};
|
|
@@ -19,6 +22,9 @@ export const configs: {
|
|
|
19
22
|
// @public (undocumented)
|
|
20
23
|
export const rules: {
|
|
21
24
|
'ensure-feature-flag-registration': Rule.RuleModule;
|
|
25
|
+
'ensure-test-runner-arguments': Rule.RuleModule;
|
|
26
|
+
'ensure-test-runner-nested-count': Rule.RuleModule;
|
|
27
|
+
'no-invalid-feature-flag-usage': Rule.RuleModule;
|
|
22
28
|
};
|
|
23
29
|
|
|
24
30
|
// (No @packageDocumentation comment for this package)
|