forked from jest-community/eslint-plugin-jest
/
valid-expect.js
182 lines (167 loc) · 5.74 KB
/
valid-expect.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
'use strict';
/*
* This implementation is ported from from eslint-plugin-jasmine.
* MIT license, Tom Vincent.
*/
const util = require('./util');
const expectProperties = ['not', 'resolves', 'rejects'];
function isResolvesOrRejectsExpression(node) {
if (node.expression.type !== 'CallExpression') {
return false;
}
const callee = node.expression.callee;
return (
callee.object &&
callee.object.object &&
callee.object.object.callee &&
callee.object.object.callee.name === 'expect' &&
callee.object.property &&
(callee.object.property.name === 'resolves' ||
callee.object.property.name === 'rejects')
);
}
module.exports = {
meta: {
docs: {
url: util.getDocsUrl('valid-expect.md'),
},
},
create(context) {
function validateAsyncExpects(node) {
const callback = node.arguments[1];
if (callback && util.isFunction(callback)) {
if (callback.body.type === 'BlockStatement') {
callback.body.body
.filter(node => node.type === 'ExpressionStatement')
.filter(isResolvesOrRejectsExpression)
.forEach(node => {
if (
node.expression.callee.object.object.arguments[0].type ===
'AwaitExpression'
) {
return;
}
const propertyName = node.expression.callee.object.property.name;
context.report({
node,
message: callback.async
? "Must await 'expect.{{ propertyName }}' statement"
: "Must return or await 'expect.{{ propertyName }}' statement",
data: { propertyName },
});
});
} else if (callback.async && callback.body.type !== 'AwaitExpression') {
context.report({
node: callback.body,
message: 'Unexpected use of async without await',
});
}
}
}
return {
CallExpression(node) {
if (util.isTestCase(node)) {
validateAsyncExpects(node);
}
const calleeName = node.callee.name;
if (calleeName === 'expect') {
// checking "expect()" arguments
if (node.arguments.length > 1) {
const secondArgumentLocStart = node.arguments[1].loc.start;
const lastArgumentLocEnd =
node.arguments[node.arguments.length - 1].loc.end;
context.report({
loc: {
end: {
column: lastArgumentLocEnd.column - 1,
line: lastArgumentLocEnd.line,
},
start: secondArgumentLocStart,
},
message: 'More than one argument was passed to expect().',
node,
});
} else if (node.arguments.length === 0) {
const expectLength = calleeName.length;
context.report({
loc: {
end: {
column: node.loc.start.column + expectLength + 1,
line: node.loc.start.line,
},
start: {
column: node.loc.start.column + expectLength,
line: node.loc.start.line,
},
},
message: 'No arguments were passed to expect().',
node,
});
}
// something was called on `expect()`
if (
node.parent &&
node.parent.type === 'MemberExpression' &&
node.parent.parent
) {
let parentNode = node.parent;
let parentProperty = parentNode.property;
let propertyName = parentProperty.name;
let grandParent = parentNode.parent;
// a property is accessed, get the next node
if (grandParent.type === 'MemberExpression') {
// a modifier is used, just get the next one
if (expectProperties.indexOf(propertyName) > -1) {
grandParent = grandParent.parent;
} else {
// only a few properties are allowed
context.report({
// For some reason `endColumn` isn't set in tests if `loc` is
// not added
loc: parentProperty.loc,
message: `"${propertyName}" is not a valid property of expect.`,
node: parentProperty,
});
}
// this next one should be the matcher
parentNode = parentNode.parent;
parentProperty = parentNode.property;
propertyName = parentProperty.name;
}
// matcher was not called
if (grandParent.type === 'ExpressionStatement') {
let message;
if (expectProperties.indexOf(propertyName) > -1) {
message = `"${propertyName}" needs to call a matcher.`;
} else {
message = `"${propertyName}" was not called.`;
}
context.report({
// For some reason `endColumn` isn't set in tests if `loc` is not
// added
loc: parentProperty.loc,
message,
node: parentProperty,
});
}
}
}
},
// nothing called on "expect()"
'CallExpression:exit'(node) {
if (
node.callee.name === 'expect' &&
node.parent.type === 'ExpressionStatement'
) {
context.report({
// For some reason `endColumn` isn't set in tests if `loc` is not
// added
loc: node.loc,
message: 'No assertion was called on expect().',
node,
});
}
},
};
},
};