Skip to content

Commit 9e0e2fa

Browse files
authored
feat(valid-expect): support minArgs & maxArgs options (#584)
1 parent 18424de commit 9e0e2fa

File tree

3 files changed

+208
-25
lines changed

3 files changed

+208
-25
lines changed

docs/rules/valid-expect.md

+21-1
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,22 @@ This rule is enabled by default.
3030

3131
## Options
3232

33-
```js
33+
```json5
3434
{
3535
type: 'object',
3636
properties: {
3737
alwaysAwait: {
3838
type: 'boolean',
3939
default: false,
4040
},
41+
minArgs: {
42+
type: 'number',
43+
minimum: 1,
44+
},
45+
maxArgs: {
46+
type: 'number',
47+
minimum: 1,
48+
},
4149
},
4250
additionalProperties: false,
4351
}
@@ -70,6 +78,18 @@ test('test1', async () => {
7078
test('test2', () => expect(Promise.resolve(2)).resolves.toBe(2));
7179
```
7280

81+
### `minArgs` & `maxArgs`
82+
83+
Enforces the minimum and maximum number of arguments that `expect` can take, and
84+
is required to take.
85+
86+
Both of these properties have a default value of `1`, which is the number of
87+
arguments supported by vanilla `expect`.
88+
89+
This is useful when you're using libraries that increase the number of arguments
90+
supported by `expect`, such as
91+
[`jest-expect-message`](https://www.npmjs.com/package/jest-expect-message).
92+
7393
### Default configuration
7494

7595
The following patterns are considered warnings:

src/rules/__tests__/valid-expect.test.ts

+146-4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ ruleTester.run('valid-expect', rule, {
7373
return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => expect(Promise.resolve(2)).resolves.toBe(1));
7474
});`,
7575
},
76+
{
77+
code: 'expect(1).toBe(2);',
78+
options: [{ maxArgs: 2 }],
79+
},
80+
{
81+
code: 'expect(1, "1 !== 2").toBe(2);',
82+
options: [{ maxArgs: 2 }],
83+
},
84+
{
85+
code: 'expect(1, "1 !== 2").toBe(2);',
86+
options: [{ maxArgs: 2, minArgs: 2 }],
87+
},
7688
],
7789
invalid: [
7890
/*
@@ -97,22 +109,144 @@ ruleTester.run('valid-expect', rule, {
97109
'test("valid-expect", async () => { await expect(Promise.reject(2)).not.resolves.toBeDefined().then(() => console.log("valid-case")).catch(() => console.log("another valid case")); });',
98110
'test("valid-expect", async () => { await expect(Promise.reject(2)).not.resolves.toBeDefined().then(() => { expect(someMock).toHaveBeenCalledTimes(1); }); });',
99111
*/
112+
{
113+
code: 'expect().toBe(2);',
114+
options: [{ minArgs: undefined, maxArgs: undefined }],
115+
errors: [
116+
{
117+
messageId: 'notEnoughArgs',
118+
data: {
119+
s: '',
120+
amount: 1,
121+
},
122+
},
123+
],
124+
},
125+
100126
{
101127
code: 'expect().toBe(true);',
102128
errors: [
103-
{ endColumn: 8, column: 7, messageId: 'incorrectNumberOfArguments' },
129+
{
130+
endColumn: 8,
131+
column: 7,
132+
messageId: 'notEnoughArgs',
133+
data: {
134+
s: '',
135+
amount: 1,
136+
},
137+
},
104138
],
105139
},
106140
{
107141
code: 'expect().toEqual("something");',
108142
errors: [
109-
{ endColumn: 8, column: 7, messageId: 'incorrectNumberOfArguments' },
143+
{
144+
endColumn: 8,
145+
column: 7,
146+
messageId: 'notEnoughArgs',
147+
data: {
148+
s: '',
149+
amount: 1,
150+
},
151+
},
152+
],
153+
},
154+
{
155+
code: 'expect("something", "else").toEqual("something");',
156+
errors: [
157+
{
158+
endColumn: 26,
159+
column: 21,
160+
messageId: 'tooManyArgs',
161+
data: {
162+
s: '',
163+
amount: 1,
164+
},
165+
},
166+
],
167+
},
168+
{
169+
code: 'expect("something", "else", "entirely").toEqual("something");',
170+
options: [{ maxArgs: 2 }],
171+
errors: [
172+
{
173+
endColumn: 38,
174+
column: 29,
175+
messageId: 'tooManyArgs',
176+
data: {
177+
s: 's',
178+
amount: 2,
179+
},
180+
},
181+
],
182+
},
183+
{
184+
code: 'expect("something", "else", "entirely").toEqual("something");',
185+
options: [{ maxArgs: 2, minArgs: 2 }],
186+
errors: [
187+
{
188+
endColumn: 38,
189+
column: 29,
190+
messageId: 'tooManyArgs',
191+
data: {
192+
s: 's',
193+
amount: 2,
194+
},
195+
},
196+
],
197+
},
198+
{
199+
code: 'expect("something", "else", "entirely").toEqual("something");',
200+
options: [{ maxArgs: 2, minArgs: 1 }],
201+
errors: [
202+
{
203+
endColumn: 38,
204+
column: 29,
205+
messageId: 'tooManyArgs',
206+
data: {
207+
s: 's',
208+
amount: 2,
209+
},
210+
},
211+
],
212+
},
213+
{
214+
code: 'expect("something").toEqual("something");',
215+
options: [{ minArgs: 2 }],
216+
errors: [
217+
{
218+
endColumn: 8,
219+
column: 7,
220+
messageId: 'notEnoughArgs',
221+
data: {
222+
s: 's',
223+
amount: 2,
224+
},
225+
},
110226
],
111227
},
112228
{
113229
code: 'expect("something", "else").toEqual("something");',
230+
options: [{ maxArgs: 1, minArgs: 3 }],
114231
errors: [
115-
{ endColumn: 26, column: 21, messageId: 'incorrectNumberOfArguments' },
232+
{
233+
endColumn: 8,
234+
column: 7,
235+
messageId: 'notEnoughArgs',
236+
data: {
237+
s: 's',
238+
amount: 3,
239+
},
240+
},
241+
{
242+
endColumn: 26,
243+
column: 21,
244+
messageId: 'tooManyArgs',
245+
data: {
246+
s: '',
247+
amount: 1,
248+
},
249+
},
116250
],
117251
},
118252
{
@@ -123,7 +257,15 @@ ruleTester.run('valid-expect', rule, {
123257
code: 'expect();',
124258
errors: [
125259
{ endColumn: 9, column: 1, messageId: 'matcherNotFound' },
126-
{ endColumn: 8, column: 7, messageId: 'incorrectNumberOfArguments' },
260+
{
261+
endColumn: 8,
262+
column: 7,
263+
messageId: 'notEnoughArgs',
264+
data: {
265+
s: '',
266+
amount: 1,
267+
},
268+
},
127269
],
128270
},
129271
{

src/rules/valid-expect.ts

+41-20
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,18 @@ const promiseArrayExceptionKey = ({ start, end }: TSESTree.SourceLocation) =>
100100
`${start.line}:${start.column}-${end.line}:${end.column}`;
101101

102102
type MessageIds =
103-
| 'incorrectNumberOfArguments'
103+
| 'tooManyArgs'
104+
| 'notEnoughArgs'
104105
| 'modifierUnknown'
105106
| 'matcherNotFound'
106107
| 'matcherNotCalled'
107108
| 'asyncMustBeAwaited'
108109
| 'promisesWithAsyncAssertionsMustBeAwaited';
109110

110-
export default createRule<[{ alwaysAwait?: boolean }], MessageIds>({
111+
export default createRule<
112+
[{ alwaysAwait?: boolean; minArgs?: number; maxArgs?: number }],
113+
MessageIds
114+
>({
111115
name: __filename,
112116
meta: {
113117
docs: {
@@ -116,7 +120,8 @@ export default createRule<[{ alwaysAwait?: boolean }], MessageIds>({
116120
recommended: 'error',
117121
},
118122
messages: {
119-
incorrectNumberOfArguments: 'Expect takes one and only one argument.',
123+
tooManyArgs: 'Expect takes at most {{ amount }} argument{{ s }}.',
124+
notEnoughArgs: 'Expect requires at least {{ amount }} argument{{ s }}.',
120125
modifierUnknown: 'Expect has no modifier named "{{ modifierName }}".',
121126
matcherNotFound: 'Expect must have a corresponding matcher call.',
122127
matcherNotCalled: 'Matchers must be called to assert.',
@@ -133,13 +138,21 @@ export default createRule<[{ alwaysAwait?: boolean }], MessageIds>({
133138
type: 'boolean',
134139
default: false,
135140
},
141+
minArgs: {
142+
type: 'number',
143+
minimum: 1,
144+
},
145+
maxArgs: {
146+
type: 'number',
147+
minimum: 1,
148+
},
136149
},
137150
additionalProperties: false,
138151
},
139152
],
140153
},
141-
defaultOptions: [{ alwaysAwait: false }],
142-
create(context, [{ alwaysAwait }]) {
154+
defaultOptions: [{ alwaysAwait: false, minArgs: 1, maxArgs: 1 }],
155+
create(context, [{ alwaysAwait, minArgs = 1, maxArgs = 1 }]) {
143156
// Context state
144157
const arrayExceptions = new Set<string>();
145158

@@ -164,10 +177,10 @@ export default createRule<[{ alwaysAwait?: boolean }], MessageIds>({
164177

165178
const { expect, modifier, matcher } = parseExpectCall(node);
166179

167-
if (expect.arguments.length !== 1) {
180+
if (expect.arguments.length < minArgs) {
168181
const expectLength = getAccessorValue(expect.callee).length;
169182

170-
let loc: TSESTree.SourceLocation = {
183+
const loc: TSESTree.SourceLocation = {
171184
start: {
172185
column: node.loc.start.column + expectLength,
173186
line: node.loc.start.line,
@@ -178,21 +191,29 @@ export default createRule<[{ alwaysAwait?: boolean }], MessageIds>({
178191
},
179192
};
180193

181-
if (expect.arguments.length !== 0) {
182-
const { start } = expect.arguments[1].loc;
183-
const { end } = expect.arguments[node.arguments.length - 1].loc;
184-
185-
loc = {
186-
start,
187-
end: {
188-
column: end.column - 1,
189-
line: end.line,
190-
},
191-
};
192-
}
194+
context.report({
195+
messageId: 'notEnoughArgs',
196+
data: { amount: minArgs, s: minArgs === 1 ? '' : 's' },
197+
node,
198+
loc,
199+
});
200+
}
201+
202+
if (expect.arguments.length > maxArgs) {
203+
const { start } = expect.arguments[maxArgs].loc;
204+
const { end } = expect.arguments[node.arguments.length - 1].loc;
205+
206+
const loc = {
207+
start,
208+
end: {
209+
column: end.column - 1,
210+
line: end.line,
211+
},
212+
};
193213

194214
context.report({
195-
messageId: 'incorrectNumberOfArguments',
215+
messageId: 'tooManyArgs',
216+
data: { amount: maxArgs, s: maxArgs === 1 ? '' : 's' },
196217
node,
197218
loc,
198219
});

0 commit comments

Comments
 (0)