Skip to content

Commit edbd1a2

Browse files
jdelStrotherSimenB
authored andcommitted
fix(no-jasmine-globals): fix false positives for pending/fail/spyOn (#205)
Fixes #156
1 parent 4db773d commit edbd1a2

File tree

4 files changed

+104
-65
lines changed

4 files changed

+104
-65
lines changed

rules/__tests__/no-jasmine-globals.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ ruleTester.run('no-jasmine-globals', rule, {
1515
'test("foo", function () {})',
1616
'foo()',
1717
`require('foo')('bar')`,
18+
'function callback(fail) { fail() }',
19+
'var spyOn = require("actions"); spyOn("foo")',
20+
'function callback(pending) { pending() }',
1821
],
1922
invalid: [
2023
{

rules/no-disabled-tests.js

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,6 @@
11
'use strict';
22

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');
314

325
module.exports = {
336
meta: {
@@ -67,15 +40,7 @@ module.exports = {
6740
}
6841
},
6942
'CallExpression[callee.name="pending"]'(node) {
70-
const references = collectReferences(context.getScope());
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')) {
7944
return;
8045
}
8146

rules/no-jasmine-globals.js

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
'use strict';
22

3-
const { getDocsUrl, getNodeName } = require('./util');
3+
const { getDocsUrl, getNodeName, scopeHasLocalReference } = require('./util');
44

55
module.exports = {
66
meta: {
77
docs: {
88
url: getDocsUrl(__filename),
99
},
1010
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+
},
1122
},
1223
create(context) {
1324
return {
@@ -17,30 +28,39 @@ module.exports = {
1728
if (!calleeName) {
1829
return;
1930
}
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+
}
2041

21-
if (calleeName === 'spyOn' || calleeName === 'spyOnProperty') {
22-
context.report({
23-
node,
24-
message: `Illegal usage of global \`${calleeName}\`, prefer \`jest.spyOn\``,
25-
});
26-
return;
27-
}
28-
29-
if (calleeName === 'fail') {
30-
context.report({
31-
node,
32-
message:
33-
'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
34-
});
35-
return;
36-
}
37-
38-
if (calleeName === 'pending') {
39-
context.report({
40-
node,
41-
message:
42-
'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
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+
}
4464
return;
4565
}
4666

@@ -59,30 +79,42 @@ module.exports = {
5979
return [fixer.replaceText(node.callee.object, 'expect')];
6080
},
6181
node,
62-
message: `Illegal usage of \`${calleeName}\`, prefer \`expect.${functionName}\``,
82+
messageId: 'illegalMethod',
83+
data: {
84+
method: calleeName,
85+
replacement: `expect.${functionName}`,
86+
},
6387
});
6488
return;
6589
}
6690

6791
if (functionName === 'addMatchers') {
6892
context.report({
6993
node,
70-
message: `Illegal usage of \`${calleeName}\`, prefer \`expect.extend\``,
94+
messageId: 'illegalMethod',
95+
data: {
96+
method: calleeName,
97+
replacement: `expect.extend`,
98+
},
7199
});
72100
return;
73101
}
74102

75103
if (functionName === 'createSpy') {
76104
context.report({
77105
node,
78-
message: `Illegal usage of \`${calleeName}\`, prefer \`jest.fn\``,
106+
messageId: 'illegalMethod',
107+
data: {
108+
method: calleeName,
109+
replacement: 'jest.fn',
110+
},
79111
});
80112
return;
81113
}
82114

83115
context.report({
84116
node,
85-
message: 'Illegal usage of jasmine global',
117+
messageId: 'illegalJasmine',
86118
});
87119
}
88120
},

rules/util.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,44 @@ const getDocsUrl = filename => {
142142
return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`;
143143
};
144144

145+
const collectReferences = scope => {
146+
const locals = new Set();
147+
const unresolved = new Set();
148+
149+
let currentScope = scope;
150+
151+
while (currentScope !== null) {
152+
for (const ref of currentScope.variables) {
153+
const isReferenceDefined = ref.defs.some(def => {
154+
return def.type !== 'ImplicitGlobalVariable';
155+
});
156+
157+
if (isReferenceDefined) {
158+
locals.add(ref.name);
159+
}
160+
}
161+
162+
for (const ref of currentScope.through) {
163+
unresolved.add(ref.identifier.name);
164+
}
165+
166+
currentScope = currentScope.upper;
167+
}
168+
169+
return { locals, unresolved };
170+
};
171+
172+
const scopeHasLocalReference = (scope, referenceName) => {
173+
const references = collectReferences(scope);
174+
return (
175+
// referenceName was found as a local variable or function declaration.
176+
references.locals.has(referenceName) ||
177+
// referenceName was not found as an unresolved reference,
178+
// meaning it is likely not an implicit global reference.
179+
!references.unresolved.has(referenceName)
180+
);
181+
};
182+
145183
module.exports = {
146184
method,
147185
method2,
@@ -160,4 +198,5 @@ module.exports = {
160198
isFunction,
161199
isTestCase,
162200
getDocsUrl,
201+
scopeHasLocalReference,
163202
};

0 commit comments

Comments
 (0)