@dineroregnskab/eslint-plugin-custom-rules 4.0.1 → 4.3.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/README.md +1 -1
- package/eslint-plugin-custom-rules.js +1 -0
- package/package.json +1 -1
- package/rules/no-feature-toggle-without-await.js +147 -0
- package/.DS_Store +0 -0
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ module.exports = {
|
|
|
63
63
|
### Publish & install new rule locally
|
|
64
64
|
|
|
65
65
|
1. Log in to npm using `npm login`
|
|
66
|
-
2. run:
|
|
66
|
+
2. run (in ./Packages/JavaScript/eslint-plugin-custom-rules/):
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
69
|
# {version_type}: The type of version increment (patch, minor, major)
|
|
@@ -11,6 +11,7 @@ const rules = {
|
|
|
11
11
|
'no-viewencapsulation-none': require('./rules/no-viewencapsulation-none'),
|
|
12
12
|
'enum-comparison-reminder': require('./rules/enum-comparison-reminder'),
|
|
13
13
|
'enum-lowercase': require('./rules/enum-lowercase'),
|
|
14
|
+
'no-feature-toggle-without-await': require('./rules/no-feature-toggle-without-await'),
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
console.log('Custom ESLint rules loaded:', Object.keys(rules)); // Debug log
|
package/package.json
CHANGED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'suggestion',
|
|
4
|
+
docs: {
|
|
5
|
+
description:
|
|
6
|
+
'Warn when createUserWithOrganization uses featureToggles in beforeEach but awaitFeatureReady is not used in the it() block',
|
|
7
|
+
category: 'Best Practices',
|
|
8
|
+
recommended: true,
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
missingAwaitFeatureReady:
|
|
12
|
+
'Feature toggles detected in beforeEach via createUserWithOrganization, but cy.awaitFeatureReady() not found in this it() block.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
create(context) {
|
|
17
|
+
// Map each describe/context CallExpression node -> whether its beforeEach contains feature toggles
|
|
18
|
+
const describeFeatureMap = new WeakMap();
|
|
19
|
+
|
|
20
|
+
function findNearestDescribe(node) {
|
|
21
|
+
let current = node && node.parent;
|
|
22
|
+
while (current) {
|
|
23
|
+
if (
|
|
24
|
+
current.type === 'CallExpression' &&
|
|
25
|
+
current.callee &&
|
|
26
|
+
(current.callee.name === 'describe' || current.callee.name === 'context')
|
|
27
|
+
) {
|
|
28
|
+
return current;
|
|
29
|
+
}
|
|
30
|
+
current = current.parent;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
'CallExpression[callee.name="beforeEach"]'(node) {
|
|
37
|
+
const describeNode = findNearestDescribe(node);
|
|
38
|
+
if (!describeNode) return;
|
|
39
|
+
|
|
40
|
+
const callback = node.arguments[0];
|
|
41
|
+
if (!callback) return;
|
|
42
|
+
|
|
43
|
+
let hasFeatureToggles = false;
|
|
44
|
+
walkNode(callback, (child) => {
|
|
45
|
+
if (
|
|
46
|
+
child.type === 'CallExpression' &&
|
|
47
|
+
child.callee &&
|
|
48
|
+
child.callee.name === 'createUserWithOrganization'
|
|
49
|
+
) {
|
|
50
|
+
const arg = child.arguments[0];
|
|
51
|
+
if (arg && arg.type === 'ObjectExpression') {
|
|
52
|
+
arg.properties.forEach((prop) => {
|
|
53
|
+
if (
|
|
54
|
+
prop.type === 'Property' &&
|
|
55
|
+
prop.key &&
|
|
56
|
+
(prop.key.name || prop.key.value)
|
|
57
|
+
) {
|
|
58
|
+
const keyName = prop.key.name || prop.key.value;
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
keyName === 'featureToggles' &&
|
|
62
|
+
prop.value.type === 'ArrayExpression' &&
|
|
63
|
+
prop.value.elements.length > 0
|
|
64
|
+
) {
|
|
65
|
+
hasFeatureToggles = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (hasFeatureToggles) {
|
|
74
|
+
describeFeatureMap.set(describeNode, true);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
'CallExpression[callee.name="it"]'(node) {
|
|
79
|
+
// Walk ancestors to see if any describe/context has feature toggles
|
|
80
|
+
let current = node.parent;
|
|
81
|
+
let hasFeatureTogglesInAnyAncestor = false;
|
|
82
|
+
while (current) {
|
|
83
|
+
if (
|
|
84
|
+
current.type === 'CallExpression' &&
|
|
85
|
+
current.callee &&
|
|
86
|
+
(current.callee.name === 'describe' || current.callee.name === 'context')
|
|
87
|
+
) {
|
|
88
|
+
if (describeFeatureMap.get(current)) {
|
|
89
|
+
hasFeatureTogglesInAnyAncestor = true;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
current = current.parent;
|
|
94
|
+
}
|
|
95
|
+
if (!hasFeatureTogglesInAnyAncestor) return;
|
|
96
|
+
|
|
97
|
+
const callback = node.arguments[1] || node.arguments[0];
|
|
98
|
+
if (!callback) return;
|
|
99
|
+
|
|
100
|
+
let hasAwaitFeatureReady = false;
|
|
101
|
+
walkNode(callback, (child) => {
|
|
102
|
+
if (
|
|
103
|
+
child.type === 'CallExpression' &&
|
|
104
|
+
child.callee &&
|
|
105
|
+
child.callee.type === 'MemberExpression' &&
|
|
106
|
+
child.callee.object &&
|
|
107
|
+
child.callee.object.name === 'cy' &&
|
|
108
|
+
child.callee.property &&
|
|
109
|
+
(child.callee.property.name === 'awaitFeatureReady' ||
|
|
110
|
+
child.callee.property.name === 'awaitFeaturesReady')
|
|
111
|
+
) {
|
|
112
|
+
hasAwaitFeatureReady = true;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (!hasAwaitFeatureReady) {
|
|
117
|
+
context.report({
|
|
118
|
+
node: node,
|
|
119
|
+
messageId: 'missingAwaitFeatureReady',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
function walkNode(node, callback) {
|
|
128
|
+
if (!node) return;
|
|
129
|
+
|
|
130
|
+
callback(node);
|
|
131
|
+
|
|
132
|
+
for (const key in node) {
|
|
133
|
+
if (key === 'parent' || key === 'loc' || key === 'range') continue;
|
|
134
|
+
|
|
135
|
+
const child = node[key];
|
|
136
|
+
|
|
137
|
+
if (Array.isArray(child)) {
|
|
138
|
+
child.forEach((item) => {
|
|
139
|
+
if (item && typeof item === 'object' && item.type) {
|
|
140
|
+
walkNode(item, callback);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
} else if (child && typeof child === 'object' && child.type) {
|
|
144
|
+
walkNode(child, callback);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
package/.DS_Store
DELETED
|
Binary file
|