Skip to content

Commit 7b10fc8

Browse files
committed
change the behaviour
1 parent 30171fd commit 7b10fc8

File tree

4 files changed

+186
-21
lines changed

4 files changed

+186
-21
lines changed

docs/rules/prefer-t-throws.md

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Prefer using `t.throws()` over try/catch
1+
# Prefer using `t.throws()` or `t.throwsAsync()` over try/catch
22

3-
This rule will enforce the use of `t.throws()` when possible.
3+
This rule will enforce the use of `t.throws()` or `t.throwsAsync()` when possible.
44

55
## Fail
66

@@ -17,13 +17,41 @@ test('some test', async t => {
1717
});
1818
```
1919

20+
```js
21+
const test = require('ava');
22+
23+
test('some test', async t => {
24+
try {
25+
await potentiallyThrowingFunction();
26+
await anotherPromise;
27+
await timeout(100, 'Unicorn timeout');
28+
t.fail();
29+
} catch (error) {
30+
t.ok(error.message.startsWith('Unicorn'));
31+
}
32+
});
33+
```
34+
35+
```js
36+
const test = require('ava');
37+
38+
test('some test', async t => {
39+
try {
40+
synchronousThrowingFunction();
41+
t.fail();
42+
} catch (error) {
43+
t.is(error.message, 'Missing Unicorn argument');
44+
}
45+
});
46+
```
47+
2048
## Pass
2149

2250
```js
2351
const test = require('ava');
2452

2553
test('some test', async t => {
26-
const error = await t.throws(throwingFunction());
54+
const error = await t.throwsAsync(asyncThrowingFunction());
2755
t.is(error.message, 'Unicorn overload');
2856
});
2957
```
@@ -47,13 +75,10 @@ const test = require('ava');
4775

4876
test('some test', async t => {
4977
try {
50-
// This is also because handling multiple promises with try/catch may be easier or may be a deliberate choice.
51-
await potentiallyThrowingFunction();
52-
await anotherPromise;
53-
await timeout(100, 'Unicorn timeout');
5478
t.fail();
79+
myFunction();
5580
} catch (error) {
56-
t.ok(error.message.startsWith('Unicorn'));
81+
t.is(error.message, 'Unicorn overload');
5782
}
5883
});
5984
```

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ The rules will only activate in test files.
9393
- [prefer-async-await](docs/rules/prefer-async-await.md) - Prefer using async/await instead of returning a Promise.
9494
- [prefer-power-assert](docs/rules/prefer-power-assert.md) - Allow only use of the asserts that have no [power-assert](https://github.com/power-assert-js/power-assert) alternative.
9595
- [prefer-t-regex](docs/rules/prefer-t-regex.md) - Prefer using `t.regex()` to test regular expressions. *(fixable)*
96-
- [prefer-t-throws](docs/rules/prefer-t-throws.md) - Prefer using `t.throws()` over try/catch.
96+
- [prefer-t-throws](docs/rules/prefer-t-throws.md) - Prefer using `t.throws()` or `t.throwsAsync()` over try/catch.
9797
- [test-title](docs/rules/test-title.md) - Ensure tests have a title.
9898
- [test-title-format](docs/rules/test-title-format.md) - Ensure test titles have a certain format.
9999
- [use-t](docs/rules/use-t.md) - Ensure test functions use `t` as their parameter.

rules/prefer-t-throws.js

Lines changed: 109 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,98 @@ const {visitIf} = require('enhance-visitors');
44
const createAvaRule = require('../create-ava-rule');
55
const util = require('../util');
66

7+
function hasAwaitExpression(nodes) {
8+
if (!nodes) {
9+
return false
10+
}
11+
12+
for (const node of nodes) {
13+
if (!node) {
14+
continue;
15+
}
16+
17+
if (node.type === 'ExpressionStatement' && hasAwaitExpression([node.expression])) {
18+
return true;
19+
}
20+
21+
if (node.type === 'AwaitExpression') {
22+
return true;
23+
}
24+
25+
// Make the rule passes if a return statement is found.
26+
if (node.type === 'ReturnStatement') {
27+
throw undefined;
28+
}
29+
30+
31+
if (node.expressions && hasAwaitExpression(node.expressions)) {
32+
return true;
33+
}
34+
35+
if (node.type === 'BlockStatement' && hasAwaitExpression(node.body)) {
36+
return true;
37+
}
38+
39+
if (node.type === 'MemberExpression' && hasAwaitExpression([node.object, node.property])) {
40+
return true
41+
}
42+
43+
if ((node.type === 'CallExpression' || node.type === 'NewExpression')
44+
&& hasAwaitExpression([...node.arguments, node.callee])) {
45+
return true;
46+
}
47+
48+
if (node.left && node.right && hasAwaitExpression([node.left, node.right])) {
49+
return true;
50+
}
51+
52+
if (node.type === 'SequenceExpression' && hasAwaitExpression(node.expressions)) {
53+
return true;
54+
}
55+
56+
if (node.type === 'VariableDeclaration'
57+
&& hasAwaitExpression(node.declarations.map(declaration => declaration.init))) {
58+
return true;
59+
}
60+
61+
if (node.type === 'ThrowStatement' && hasAwaitExpression([node.argument])) {
62+
return true;
63+
}
64+
65+
if (node.type === 'IfStatement' && hasAwaitExpression([node.test, node.consequent, node.alternate])) {
66+
return true;
67+
}
68+
69+
if (node.type === 'SwitchStatement' && hasAwaitExpression([node.discriminant, ...node.cases.flatMap(caseNode => {
70+
return [caseNode.test].concat(caseNode.consequent);
71+
})])) {
72+
return true;
73+
}
74+
75+
if (node.type.endsWith('WhileStatement') && hasAwaitExpression([node.test, node.body])) {
76+
return true;
77+
}
78+
79+
if (node.type === 'ForStatement' && hasAwaitExpression([node.init, node.test, node.update, node.body])) {
80+
return true;
81+
}
82+
83+
if (node.type === 'ForInStatement' && hasAwaitExpression([node.right, node.body])) {
84+
return true;
85+
}
86+
87+
if (node.type === 'ForOfStatement' && (node.await || hasAwaitExpression([node.right, node.body]))) {
88+
return true;
89+
}
90+
91+
if (node.type === 'WithStatement' && hasAwaitExpression([node.object, node.body])) {
92+
return true;
93+
}
94+
}
95+
96+
return false;
97+
}
98+
799
const create = context => {
8100
const ava = createAvaRule();
9101

@@ -12,25 +104,32 @@ const create = context => {
12104
ava.isInTestFile,
13105
ava.isInTestNode,
14106
])(node => {
15-
const expressions = node.block.body.filter(node => node.type === 'ExpressionStatement');
16-
if (expressions.length < 2) {
107+
const nodes = node.block.body;
108+
if (nodes.length < 2) {
17109
return;
18110
}
19111

20-
const lastExpression = expressions[expressions.length - 1].expression;
21-
if (lastExpression.type !== 'CallExpression'
22-
|| lastExpression.callee.object.name !== 't'
23-
|| lastExpression.callee.property.name !== 'fail'
24-
) {
112+
const tFailIndex = [...nodes].reverse().findIndex(node => {
113+
return node.type === 'ExpressionStatement'
114+
&& node.expression.type === 'CallExpression'
115+
&& node.expression.callee.object
116+
&& node.expression.callee.object.name === 't'
117+
&& node.expression.callee.property
118+
&& node.expression.callee.property.name === 'fail';
119+
});
120+
// Return if there is no t.fail() or if it's the first node
121+
if (tFailIndex === -1 || tFailIndex === nodes.length - 1) {
25122
return;
26123
}
27124

28-
if (expressions.filter(node => node.expression.type === 'AwaitExpression').length === 1) {
125+
const beforeNodes = nodes.slice(0, nodes.length - 1 - tFailIndex);
126+
127+
try {
29128
context.report({
30129
node,
31-
message: 'Prefer using the `t.throws()` assertion.',
130+
message: `Prefer using the \`t.throws${hasAwaitExpression(beforeNodes) ? 'Async' : ''}()\` assertion.`,
32131
});
33-
}
132+
} catch {}
34133
}),
35134
});
36135
};

test/prefer-t-throws.js

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,54 @@ const header = 'const test = require(\'ava\');\n';
1414

1515
ruleTester.run('prefer-t-throws', rule, {
1616
valid: [
17-
`${header}test(async t => { const error = await t.throws(promise); t.is(error, 'error'); });`,
17+
`${header}test(async t => { const error = await t.throwsAsync(promise); t.is(error, 'error'); });`,
18+
`${header}test(t => { const error = t.throws(fn()); t.is(error, 'error'); });`,
19+
`${header}test(async t => { try { t.fail(); unicorn(); } catch (error) { t.is(error, 'error'); } });`,
1820
`${header}test(async t => { try { await promise; } catch (error) { t.is(error, 'error'); } });`,
19-
`${header}test(async t => { try { await promise; await anotherPromise; t.fail(); } catch (error) { t.is(error, 'error'); } });`,
2021
],
2122
invalid: [
23+
{
24+
code: `${header}test(async t => { try { async function unicorn() { throw await Promise.resolve('error') }; unicorn(); t.fail(); } catch (error) { t.is(error, 'error'); } });`,
25+
errors: [{message: 'Prefer using the `t.throws()` assertion.'}],
26+
},
2227
{
2328
code: `${header}test(async t => { try { await Promise.reject('error'); t.fail(); } catch (error) { t.is(error, 'error'); } });`,
29+
errors: [{message: 'Prefer using the `t.throwsAsync()` assertion.'}],
30+
},
31+
{
32+
code: `${header}test(async t => { try { if (await promise); t.fail(); } catch (error) { t.is(error, 'error'); } });`,
33+
errors: [{message: 'Prefer using the `t.throwsAsync()` assertion.'}],
34+
},
35+
{
36+
code: `${header}test(async t => { try { (await 1) > 2; t.fail(); } catch (error) { t.is(error, 'error'); } });`,
37+
errors: [{message: 'Prefer using the `t.throwsAsync()` assertion.'}],
38+
},
39+
{
40+
code: `${header}test(async t => { try { (await getArray())[0]; t.fail(); } catch (error) { t.is(error, 'error'); } });`,
41+
errors: [{message: 'Prefer using the `t.throwsAsync()` assertion.'}],
42+
},
43+
{
44+
code: `${header}test(async t => { try { getArraySync(await 20)[0]; t.fail(); } catch (error) { t.is(error, 'error'); } });`,
45+
errors: [{message: 'Prefer using the `t.throwsAsync()` assertion.'}],
46+
},
47+
{
48+
code: `${header}test(async t => { try { getArraySync()[await 0]; t.fail(); } catch (error) { t.is(error, 'error'); } });`,
49+
errors: [{message: 'Prefer using the `t.throwsAsync()` assertion.'}],
50+
},
51+
{
52+
code: `${header}test(async t => { try { new (await cl())(1); t.fail(); } catch (error) { t.is(error, 'error'); } });`,
53+
errors: [{message: 'Prefer using the `t.throwsAsync()` assertion.'}],
54+
},
55+
{
56+
code: `${header}test(async t => { try { if (false) { await promise; }; t.fail(); } catch (error) { t.is(error, 'error'); } });`,
57+
errors: [{message: 'Prefer using the `t.throwsAsync()` assertion.'}],
58+
},
59+
{
60+
code: `${header}test(t => { try { undefined(); t.fail(); } catch (error) { t.ok(error instanceof TypeError); } });`,
61+
errors: [{message: 'Prefer using the `t.throws()` assertion.'}],
62+
},
63+
{
64+
code: `${header}test(async t => { try { undefined(); t.fail(); } catch (error) { t.ok(error instanceof TypeError); } });`,
2465
errors: [{message: 'Prefer using the `t.throws()` assertion.'}],
2566
},
2667
],

0 commit comments

Comments
 (0)