@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.
Files changed (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/index.js +11 -2
  3. package/dist/cjs/rules/ensure-feature-flag-registration/index.js +1 -58
  4. package/dist/cjs/rules/ensure-test-runner-arguments/index.js +95 -0
  5. package/dist/cjs/rules/ensure-test-runner-nested-count/index.js +69 -0
  6. package/dist/cjs/rules/no-invalid-feature-flag-usage/index.js +87 -0
  7. package/dist/cjs/version.json +1 -1
  8. package/dist/es2019/index.js +11 -2
  9. package/dist/es2019/rules/ensure-feature-flag-registration/index.js +1 -58
  10. package/dist/es2019/rules/ensure-test-runner-arguments/index.js +88 -0
  11. package/dist/es2019/rules/ensure-test-runner-nested-count/index.js +63 -0
  12. package/dist/es2019/rules/no-invalid-feature-flag-usage/index.js +80 -0
  13. package/dist/es2019/version.json +1 -1
  14. package/dist/esm/index.js +11 -2
  15. package/dist/esm/rules/ensure-feature-flag-registration/index.js +1 -58
  16. package/dist/esm/rules/ensure-test-runner-arguments/index.js +87 -0
  17. package/dist/esm/rules/ensure-test-runner-nested-count/index.js +61 -0
  18. package/dist/esm/rules/no-invalid-feature-flag-usage/index.js +79 -0
  19. package/dist/esm/version.json +1 -1
  20. package/dist/types/index.d.ts +6 -0
  21. package/dist/types/rules/ensure-test-runner-arguments/index.d.ts +3 -0
  22. package/dist/types/rules/ensure-test-runner-nested-count/index.d.ts +3 -0
  23. package/dist/types/rules/no-invalid-feature-flag-usage/index.d.ts +3 -0
  24. package/package.json +1 -1
  25. package/report.api.md +6 -0
  26. package/src/index.tsx +9 -0
  27. package/src/rules/ensure-feature-flag-registration/__tests__/unit/rule.test.tsx +2 -37
  28. package/src/rules/ensure-feature-flag-registration/index.tsx +0 -77
  29. package/src/rules/ensure-test-runner-arguments/__tests__/unit/rule.test.tsx +221 -0
  30. package/src/rules/ensure-test-runner-arguments/index.tsx +110 -0
  31. package/src/rules/ensure-test-runner-nested-count/__tests__/unit/rule.test.tsx +308 -0
  32. package/src/rules/ensure-test-runner-nested-count/index.tsx +83 -0
  33. package/src/rules/no-invalid-feature-flag-usage/__tests__/unit/rule.test.tsx +49 -0
  34. package/src/rules/no-invalid-feature-flag-usage/index.tsx +108 -0
  35. 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;
@@ -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)