eslint-plugin-jest 22.0.0 → 22.1.2
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 +2 -0
- package/docs/rules/no-jasmine-globals.md +1 -1
- package/docs/rules/no-truthy-falsy.md +32 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/rules/__tests__/no-alias-methods.test.js +36 -0
- package/rules/__tests__/no-jasmine-globals.test.js +3 -0
- package/rules/__tests__/no-truthy-falsy.test.js +102 -0
- package/rules/__tests__/prefer-to-be-null.test.js +1 -0
- package/rules/__tests__/prefer-to-be-undefined.test.js +1 -0
- package/rules/no-alias-methods.js +15 -5
- package/rules/no-disabled-tests.js +2 -37
- package/rules/no-jasmine-globals.js +60 -28
- package/rules/no-truthy-falsy.js +44 -0
- package/rules/util.js +43 -2
package/README.md
CHANGED
|
@@ -105,6 +105,7 @@ for more information about extending configuration files.
|
|
|
105
105
|
| [no-test-callback][] | Using a callback in asynchronous tests | | ![fixable-green][] |
|
|
106
106
|
| [no-test-prefixes][] | Disallow using `f` & `x` prefixes to define focused/skipped tests | ![recommended][] | ![fixable-green][] |
|
|
107
107
|
| [no-test-return-statement][] | Disallow explicitly returning from tests | | |
|
|
108
|
+
| [no-truthy-falsy][] | Disallow using `toBeTruthy()` & `toBeFalsy()` | | |
|
|
108
109
|
| [prefer-expect-assertions][] | Suggest using `expect.assertions()` OR `expect.hasAssertions()` | | |
|
|
109
110
|
| [prefer-spy-on][] | Suggest using `jest.spyOn()` | | ![fixable-green][] |
|
|
110
111
|
| [prefer-strict-equal][] | Suggest using `toStrictEqual()` | | ![fixable-green][] |
|
|
@@ -137,6 +138,7 @@ for more information about extending configuration files.
|
|
|
137
138
|
[no-test-callback]: docs/rules/no-test-callback.md
|
|
138
139
|
[no-test-prefixes]: docs/rules/no-test-prefixes.md
|
|
139
140
|
[no-test-return-statement]: docs/rules/no-test-return-statement.md
|
|
141
|
+
[no-truthy-falsy]: docs/rules/no-truthy-falsy.md
|
|
140
142
|
[prefer-expect-assertions]: docs/rules/prefer-expect-assertions.md
|
|
141
143
|
[prefer-spy-on]: docs/rules/prefer-spy-on.md
|
|
142
144
|
[prefer-strict-equal]: docs/rules/prefer-strict-equal.md
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`jest` uses `jasmine` as a test runner. A side effect of this is that both a
|
|
4
4
|
`jasmine` object, and some jasmine-specific globals, are exposed to the test
|
|
5
5
|
environment. Most functionality offered by Jasmine has been ported to Jest, and
|
|
6
|
-
the Jasmine globals will stop working in the future. Developers should
|
|
6
|
+
the Jasmine globals will stop working in the future. Developers should therefore
|
|
7
7
|
migrate to Jest's documented API instead of relying on the undocumented Jasmine
|
|
8
8
|
API.
|
|
9
9
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Disallow using `toBeTruthy()` & `toBeFalsy()` (no-truthy-falsy)
|
|
2
|
+
|
|
3
|
+
Tests against boolean values should assert true or false. Asserting `toBeTruthy`
|
|
4
|
+
or `toBeFalsy` matches non-boolean values as well and encourages weaker tests.
|
|
5
|
+
|
|
6
|
+
For example, `expect(someBoolean).toBeFalsy()` passes when
|
|
7
|
+
`someBoolean === null`, and when `someBoolean === false`.
|
|
8
|
+
|
|
9
|
+
Similarly, `expect(someBoolean).toBeTruthy()` passes when `someBoolean === []`,
|
|
10
|
+
and when `someBoolean === 'false'` (note that `'false'` is a string).
|
|
11
|
+
|
|
12
|
+
## Rule details
|
|
13
|
+
|
|
14
|
+
This rule triggers a warning if `toBeTruthy()` or `toBeFalsy()` are used.
|
|
15
|
+
|
|
16
|
+
This rule is disabled by default.
|
|
17
|
+
|
|
18
|
+
### Default configuration
|
|
19
|
+
|
|
20
|
+
The following patterns are considered warnings:
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
expect(someValue).toBeTruthy();
|
|
24
|
+
expect(someValue).toBeFalsy();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The following patterns are not considered warnings:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
expect(someValue).toBe(true);
|
|
31
|
+
expect(someValue).toBe(false);
|
|
32
|
+
```
|
package/index.js
CHANGED
|
@@ -26,6 +26,7 @@ const preferStrictEqual = require('./rules/prefer-strict-equal');
|
|
|
26
26
|
const requireTothrowMessage = require('./rules/require-tothrow-message');
|
|
27
27
|
const noAliasMethods = require('./rules/no-alias-methods');
|
|
28
28
|
const noTestCallback = require('./rules/no-test-callback');
|
|
29
|
+
const noTruthyFalsy = require('./rules/no-truthy-falsy');
|
|
29
30
|
|
|
30
31
|
const snapshotProcessor = require('./processors/snapshot-processor');
|
|
31
32
|
|
|
@@ -112,5 +113,6 @@ module.exports = {
|
|
|
112
113
|
'require-tothrow-message': requireTothrowMessage,
|
|
113
114
|
'no-alias-methods': noAliasMethods,
|
|
114
115
|
'no-test-callback': noTestCallback,
|
|
116
|
+
'no-truthy-falsy': noTruthyFalsy,
|
|
115
117
|
},
|
|
116
118
|
};
|
package/package.json
CHANGED
|
@@ -153,5 +153,41 @@ ruleTester.run('no-alias-methods', rule, {
|
|
|
153
153
|
],
|
|
154
154
|
output: 'expect(a).toThrow()',
|
|
155
155
|
},
|
|
156
|
+
{
|
|
157
|
+
code: 'expect(a).resolves.toThrowError()',
|
|
158
|
+
errors: [
|
|
159
|
+
{
|
|
160
|
+
message:
|
|
161
|
+
'Replace toThrowError() with its canonical name of toThrow()',
|
|
162
|
+
column: 20,
|
|
163
|
+
line: 1,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
output: 'expect(a).resolves.toThrow()',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
code: 'expect(a).rejects.toThrowError()',
|
|
170
|
+
errors: [
|
|
171
|
+
{
|
|
172
|
+
message:
|
|
173
|
+
'Replace toThrowError() with its canonical name of toThrow()',
|
|
174
|
+
column: 19,
|
|
175
|
+
line: 1,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
output: 'expect(a).rejects.toThrow()',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
code: 'expect(a).not.toThrowError()',
|
|
182
|
+
errors: [
|
|
183
|
+
{
|
|
184
|
+
message:
|
|
185
|
+
'Replace toThrowError() with its canonical name of toThrow()',
|
|
186
|
+
column: 15,
|
|
187
|
+
line: 1,
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
output: 'expect(a).not.toThrow()',
|
|
191
|
+
},
|
|
156
192
|
],
|
|
157
193
|
});
|
|
@@ -15,6 +15,9 @@ ruleTester.run('no-jasmine-globals', rule, {
|
|
|
15
15
|
'test("foo", function () {})',
|
|
16
16
|
'foo()',
|
|
17
17
|
`require('foo')('bar')`,
|
|
18
|
+
'function callback(fail) { fail() }',
|
|
19
|
+
'var spyOn = require("actions"); spyOn("foo")',
|
|
20
|
+
'function callback(pending) { pending() }',
|
|
18
21
|
],
|
|
19
22
|
invalid: [
|
|
20
23
|
{
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { RuleTester } = require('eslint');
|
|
4
|
+
const rule = require('../no-truthy-falsy');
|
|
5
|
+
|
|
6
|
+
const ruleTester = new RuleTester();
|
|
7
|
+
|
|
8
|
+
ruleTester.run('no-truthy-falsy', rule, {
|
|
9
|
+
valid: [
|
|
10
|
+
'expect(true).toBe(true);',
|
|
11
|
+
'expect(false).toBe(false);',
|
|
12
|
+
'expect("anything").toBe(true);',
|
|
13
|
+
'expect("anything").toEqual(false);',
|
|
14
|
+
'expect("anything").not.toBe(true);',
|
|
15
|
+
'expect("anything").not.toEqual(true);',
|
|
16
|
+
'expect(Promise.resolve({})).resolves.toBe(true);',
|
|
17
|
+
'expect(Promise.reject({})).rejects.toBe(true);',
|
|
18
|
+
],
|
|
19
|
+
|
|
20
|
+
invalid: [
|
|
21
|
+
{
|
|
22
|
+
code: 'expect(true).toBeTruthy();',
|
|
23
|
+
errors: [
|
|
24
|
+
{
|
|
25
|
+
message: 'Avoid toBeTruthy',
|
|
26
|
+
column: 14,
|
|
27
|
+
line: 1,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
code: 'expect(false).not.toBeTruthy();',
|
|
33
|
+
errors: [
|
|
34
|
+
{
|
|
35
|
+
message: 'Avoid toBeTruthy',
|
|
36
|
+
column: 19,
|
|
37
|
+
line: 1,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
code: 'expect(Promise.resolve({})).resolves.toBeTruthy()',
|
|
43
|
+
errors: [
|
|
44
|
+
{
|
|
45
|
+
message: 'Avoid toBeTruthy',
|
|
46
|
+
column: 38,
|
|
47
|
+
line: 1,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
code: 'expect(Promise.resolve({})).rejects.toBeTruthy()',
|
|
53
|
+
errors: [
|
|
54
|
+
{
|
|
55
|
+
message: 'Avoid toBeTruthy',
|
|
56
|
+
column: 37,
|
|
57
|
+
line: 1,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
code: 'expect(false).toBeFalsy();',
|
|
63
|
+
errors: [
|
|
64
|
+
{
|
|
65
|
+
message: 'Avoid toBeFalsy',
|
|
66
|
+
column: 15,
|
|
67
|
+
line: 1,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
code: 'expect(true).not.toBeFalsy();',
|
|
73
|
+
errors: [
|
|
74
|
+
{
|
|
75
|
+
message: 'Avoid toBeFalsy',
|
|
76
|
+
column: 18,
|
|
77
|
+
line: 1,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
code: 'expect(Promise.resolve({})).resolves.toBeFalsy()',
|
|
83
|
+
errors: [
|
|
84
|
+
{
|
|
85
|
+
message: 'Avoid toBeFalsy',
|
|
86
|
+
column: 38,
|
|
87
|
+
line: 1,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
code: 'expect(Promise.resolve({})).rejects.toBeFalsy()',
|
|
93
|
+
errors: [
|
|
94
|
+
{
|
|
95
|
+
message: 'Avoid toBeFalsy',
|
|
96
|
+
column: 37,
|
|
97
|
+
line: 1,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
});
|
|
@@ -32,9 +32,19 @@ module.exports = {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
let targetNode = method(node);
|
|
36
|
+
if (
|
|
37
|
+
targetNode.name === 'resolves' ||
|
|
38
|
+
targetNode.name === 'rejects' ||
|
|
39
|
+
targetNode.name === 'not'
|
|
40
|
+
) {
|
|
41
|
+
targetNode = method(node.parent);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check if the method used matches any of ours
|
|
45
|
+
const methodItem = methodNames.find(
|
|
46
|
+
item => item[1] === targetNode.name
|
|
47
|
+
);
|
|
38
48
|
|
|
39
49
|
if (methodItem) {
|
|
40
50
|
context.report({
|
|
@@ -43,9 +53,9 @@ module.exports = {
|
|
|
43
53
|
replace: methodItem[1],
|
|
44
54
|
canonical: methodItem[0],
|
|
45
55
|
},
|
|
46
|
-
node:
|
|
56
|
+
node: targetNode,
|
|
47
57
|
fix(fixer) {
|
|
48
|
-
return [fixer.replaceText(
|
|
58
|
+
return [fixer.replaceText(targetNode, methodItem[0])];
|
|
49
59
|
},
|
|
50
60
|
});
|
|
51
61
|
}
|
|
@@ -1,33 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { getDocsUrl, getNodeName } = require('./util');
|
|
4
|
-
|
|
5
|
-
function collectReferences(scope) {
|
|
6
|
-
const locals = new Set();
|
|
7
|
-
const unresolved = new Set();
|
|
8
|
-
|
|
9
|
-
let currentScope = scope;
|
|
10
|
-
|
|
11
|
-
while (currentScope !== null) {
|
|
12
|
-
for (const ref of currentScope.variables) {
|
|
13
|
-
const isReferenceDefined = ref.defs.some(def => {
|
|
14
|
-
return def.type !== 'ImplicitGlobalVariable';
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
if (isReferenceDefined) {
|
|
18
|
-
locals.add(ref.name);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
for (const ref of currentScope.through) {
|
|
23
|
-
unresolved.add(ref.identifier.name);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
currentScope = currentScope.upper;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return { locals, unresolved };
|
|
30
|
-
}
|
|
3
|
+
const { getDocsUrl, getNodeName, scopeHasLocalReference } = require('./util');
|
|
31
4
|
|
|
32
5
|
module.exports = {
|
|
33
6
|
meta: {
|
|
@@ -67,15 +40,7 @@ module.exports = {
|
|
|
67
40
|
}
|
|
68
41
|
},
|
|
69
42
|
'CallExpression[callee.name="pending"]'(node) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
// `pending` was found as a local variable or function declaration.
|
|
74
|
-
references.locals.has('pending') ||
|
|
75
|
-
// `pending` was not found as an unresolved reference,
|
|
76
|
-
// meaning it is likely not an implicit global reference.
|
|
77
|
-
!references.unresolved.has('pending')
|
|
78
|
-
) {
|
|
43
|
+
if (scopeHasLocalReference(context.getScope(), 'pending')) {
|
|
79
44
|
return;
|
|
80
45
|
}
|
|
81
46
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { getDocsUrl, getNodeName } = require('./util');
|
|
3
|
+
const { getDocsUrl, getNodeName, scopeHasLocalReference } = require('./util');
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
meta: {
|
|
@@ -8,6 +8,17 @@ module.exports = {
|
|
|
8
8
|
url: getDocsUrl(__filename),
|
|
9
9
|
},
|
|
10
10
|
fixable: 'code',
|
|
11
|
+
messages: {
|
|
12
|
+
illegalGlobal:
|
|
13
|
+
'Illegal usage of global `{{ global }}`, prefer `{{ replacement }}`',
|
|
14
|
+
illegalMethod:
|
|
15
|
+
'Illegal usage of `{{ method }}`, prefer `{{ replacement }}`',
|
|
16
|
+
illegalFail:
|
|
17
|
+
'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
|
|
18
|
+
illegalPending:
|
|
19
|
+
'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
|
|
20
|
+
illegalJasmine: 'Illegal usage of jasmine global',
|
|
21
|
+
},
|
|
11
22
|
},
|
|
12
23
|
create(context) {
|
|
13
24
|
return {
|
|
@@ -17,30 +28,39 @@ module.exports = {
|
|
|
17
28
|
if (!calleeName) {
|
|
18
29
|
return;
|
|
19
30
|
}
|
|
31
|
+
if (
|
|
32
|
+
calleeName === 'spyOn' ||
|
|
33
|
+
calleeName === 'spyOnProperty' ||
|
|
34
|
+
calleeName === 'fail' ||
|
|
35
|
+
calleeName === 'pending'
|
|
36
|
+
) {
|
|
37
|
+
if (scopeHasLocalReference(context.getScope(), calleeName)) {
|
|
38
|
+
// It's a local variable, not a jasmine global.
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
20
41
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
42
|
+
switch (calleeName) {
|
|
43
|
+
case 'spyOn':
|
|
44
|
+
case 'spyOnProperty':
|
|
45
|
+
context.report({
|
|
46
|
+
node,
|
|
47
|
+
messageId: 'illegalGlobal',
|
|
48
|
+
data: { global: calleeName, replacement: 'jest.spyOn' },
|
|
49
|
+
});
|
|
50
|
+
break;
|
|
51
|
+
case 'fail':
|
|
52
|
+
context.report({
|
|
53
|
+
node,
|
|
54
|
+
messageId: 'illegalFail',
|
|
55
|
+
});
|
|
56
|
+
break;
|
|
57
|
+
case 'pending':
|
|
58
|
+
context.report({
|
|
59
|
+
node,
|
|
60
|
+
messageId: 'illegalPending',
|
|
61
|
+
});
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
44
64
|
return;
|
|
45
65
|
}
|
|
46
66
|
|
|
@@ -59,7 +79,11 @@ module.exports = {
|
|
|
59
79
|
return [fixer.replaceText(node.callee.object, 'expect')];
|
|
60
80
|
},
|
|
61
81
|
node,
|
|
62
|
-
|
|
82
|
+
messageId: 'illegalMethod',
|
|
83
|
+
data: {
|
|
84
|
+
method: calleeName,
|
|
85
|
+
replacement: `expect.${functionName}`,
|
|
86
|
+
},
|
|
63
87
|
});
|
|
64
88
|
return;
|
|
65
89
|
}
|
|
@@ -67,7 +91,11 @@ module.exports = {
|
|
|
67
91
|
if (functionName === 'addMatchers') {
|
|
68
92
|
context.report({
|
|
69
93
|
node,
|
|
70
|
-
|
|
94
|
+
messageId: 'illegalMethod',
|
|
95
|
+
data: {
|
|
96
|
+
method: calleeName,
|
|
97
|
+
replacement: `expect.extend`,
|
|
98
|
+
},
|
|
71
99
|
});
|
|
72
100
|
return;
|
|
73
101
|
}
|
|
@@ -75,14 +103,18 @@ module.exports = {
|
|
|
75
103
|
if (functionName === 'createSpy') {
|
|
76
104
|
context.report({
|
|
77
105
|
node,
|
|
78
|
-
|
|
106
|
+
messageId: 'illegalMethod',
|
|
107
|
+
data: {
|
|
108
|
+
method: calleeName,
|
|
109
|
+
replacement: 'jest.fn',
|
|
110
|
+
},
|
|
79
111
|
});
|
|
80
112
|
return;
|
|
81
113
|
}
|
|
82
114
|
|
|
83
115
|
context.report({
|
|
84
116
|
node,
|
|
85
|
-
|
|
117
|
+
messageId: 'illegalJasmine',
|
|
86
118
|
});
|
|
87
119
|
}
|
|
88
120
|
},
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
getDocsUrl,
|
|
5
|
+
expectCase,
|
|
6
|
+
expectNotCase,
|
|
7
|
+
expectResolveCase,
|
|
8
|
+
expectRejectCase,
|
|
9
|
+
method,
|
|
10
|
+
} = require('./util');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
meta: {
|
|
14
|
+
docs: {
|
|
15
|
+
url: getDocsUrl(__filename),
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
CallExpression(node) {
|
|
21
|
+
if (
|
|
22
|
+
expectCase(node) ||
|
|
23
|
+
expectNotCase(node) ||
|
|
24
|
+
expectResolveCase(node) ||
|
|
25
|
+
expectRejectCase(node)
|
|
26
|
+
) {
|
|
27
|
+
const targetNode =
|
|
28
|
+
node.parent.parent.type === 'MemberExpression' ? node.parent : node;
|
|
29
|
+
|
|
30
|
+
const methodNode = method(targetNode);
|
|
31
|
+
const { name: methodName } = methodNode;
|
|
32
|
+
|
|
33
|
+
if (methodName === 'toBeTruthy' || methodName === 'toBeFalsy') {
|
|
34
|
+
context.report({
|
|
35
|
+
data: { methodName },
|
|
36
|
+
message: 'Avoid {{methodName}}',
|
|
37
|
+
node: methodNode,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
package/rules/util.js
CHANGED
|
@@ -73,9 +73,11 @@ const methodName = node => method(node).name;
|
|
|
73
73
|
|
|
74
74
|
const methodName2 = node => method2(node).name;
|
|
75
75
|
|
|
76
|
-
const argument = node =>
|
|
76
|
+
const argument = node =>
|
|
77
|
+
node.parent.parent.arguments && node.parent.parent.arguments[0];
|
|
77
78
|
|
|
78
|
-
const argument2 = node =>
|
|
79
|
+
const argument2 = node =>
|
|
80
|
+
node.parent.parent.parent.arguments && node.parent.parent.parent.arguments[0];
|
|
79
81
|
|
|
80
82
|
const describeAliases = Object.assign(Object.create(null), {
|
|
81
83
|
describe: true,
|
|
@@ -142,6 +144,44 @@ const getDocsUrl = filename => {
|
|
|
142
144
|
return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`;
|
|
143
145
|
};
|
|
144
146
|
|
|
147
|
+
const collectReferences = scope => {
|
|
148
|
+
const locals = new Set();
|
|
149
|
+
const unresolved = new Set();
|
|
150
|
+
|
|
151
|
+
let currentScope = scope;
|
|
152
|
+
|
|
153
|
+
while (currentScope !== null) {
|
|
154
|
+
for (const ref of currentScope.variables) {
|
|
155
|
+
const isReferenceDefined = ref.defs.some(def => {
|
|
156
|
+
return def.type !== 'ImplicitGlobalVariable';
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (isReferenceDefined) {
|
|
160
|
+
locals.add(ref.name);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const ref of currentScope.through) {
|
|
165
|
+
unresolved.add(ref.identifier.name);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
currentScope = currentScope.upper;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { locals, unresolved };
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const scopeHasLocalReference = (scope, referenceName) => {
|
|
175
|
+
const references = collectReferences(scope);
|
|
176
|
+
return (
|
|
177
|
+
// referenceName was found as a local variable or function declaration.
|
|
178
|
+
references.locals.has(referenceName) ||
|
|
179
|
+
// referenceName was not found as an unresolved reference,
|
|
180
|
+
// meaning it is likely not an implicit global reference.
|
|
181
|
+
!references.unresolved.has(referenceName)
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
145
185
|
module.exports = {
|
|
146
186
|
method,
|
|
147
187
|
method2,
|
|
@@ -160,4 +200,5 @@ module.exports = {
|
|
|
160
200
|
isFunction,
|
|
161
201
|
isTestCase,
|
|
162
202
|
getDocsUrl,
|
|
203
|
+
scopeHasLocalReference,
|
|
163
204
|
};
|