Skip to content

Commit d2d51c7

Browse files
committed
Add no-useless-t-pass rule
Fixes #155
1 parent 7c2fcf6 commit d2d51c7

5 files changed

Lines changed: 223 additions & 0 deletions

File tree

docs/rules/no-useless-t-pass.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# ava/no-useless-t-pass
2+
3+
📝 Disallow useless `t.pass()`.
4+
5+
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config).
6+
7+
<!-- end auto-generated rule header -->
8+
9+
`t.pass()` only increments the assertion counter. Without `t.plan()`, this counter is never checked, making `t.pass()` a no-op that gives a false sense of testing.
10+
11+
If the intent is to verify code doesn't throw, use `t.notThrows()` or `t.notThrowsAsync()` instead.
12+
13+
## Examples
14+
15+
```js
16+
import test from 'ava';
17+
18+
//
19+
test('main', t => {
20+
t.pass();
21+
});
22+
23+
//
24+
test('main', t => {
25+
t.plan(1);
26+
t.pass();
27+
});
28+
29+
//
30+
test('main', t => {
31+
t.notThrows(() => foo());
32+
});
33+
```

index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import noSkipTest from './rules/no-skip-test.js';
2121
import noTodoImplementation from './rules/no-todo-implementation.js';
2222
import noTodoTest from './rules/no-todo-test.js';
2323
import noUnknownModifiers from './rules/no-unknown-modifiers.js';
24+
import noUselessTPass from './rules/no-useless-t-pass.js';
2425
import preferAsyncAwait from './rules/prefer-async-await.js';
2526
import preferPowerAssert from './rules/prefer-power-assert.js';
2627
import preferTRegex from './rules/prefer-t-regex.js';
@@ -57,6 +58,7 @@ const rules = {
5758
'no-todo-implementation': noTodoImplementation,
5859
'no-todo-test': noTodoTest,
5960
'no-unknown-modifiers': noUnknownModifiers,
61+
'no-useless-t-pass': noUselessTPass,
6062
'prefer-async-await': preferAsyncAwait,
6163
'prefer-power-assert': preferPowerAssert,
6264
'prefer-t-regex': preferTRegex,
@@ -93,6 +95,7 @@ const recommendedRules = {
9395
'ava/no-todo-implementation': 'error',
9496
'ava/no-todo-test': 'warn',
9597
'ava/no-unknown-modifiers': 'error',
98+
'ava/no-useless-t-pass': 'error',
9699
'ava/prefer-async-await': 'error',
97100
'ava/prefer-power-assert': 'off',
98101
'ava/prefer-t-regex': 'error',

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ The rules will only activate in test files.
7272
| [no-todo-implementation](docs/rules/no-todo-implementation.md) | Disallow giving `test.todo()` an implementation function. || | | | 💡 |
7373
| [no-todo-test](docs/rules/no-todo-test.md) | Disallow `test.todo()`. | || | | 💡 |
7474
| [no-unknown-modifiers](docs/rules/no-unknown-modifiers.md) | Disallow unknown test modifiers. || | | | 💡 |
75+
| [no-useless-t-pass](docs/rules/no-useless-t-pass.md) | Disallow useless `t.pass()`. || | | | |
7576
| [prefer-async-await](docs/rules/prefer-async-await.md) | Prefer async/await over returning a Promise. || | | | |
7677
| [prefer-power-assert](docs/rules/prefer-power-assert.md) | Enforce using only assertions compatible with [power-assert](https://github.com/power-assert-js/power-assert). | | || | |
7778
| [prefer-t-regex](docs/rules/prefer-t-regex.md) | Prefer `t.regex()` over `RegExp#test()` and `String#match()`. || | | 🔧 | |

rules/no-useless-t-pass.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {visitIf} from 'enhance-visitors';
2+
import util from '../util.js';
3+
import createAvaRule from '../create-ava-rule.js';
4+
5+
const MESSAGE_ID = 'no-useless-t-pass';
6+
7+
const create = context => {
8+
const ava = createAvaRule();
9+
let hasPlan = false;
10+
let passNodes = [];
11+
12+
return ava.merge({
13+
CallExpression: visitIf([
14+
ava.isInTestFile,
15+
ava.isInTestNode,
16+
])(node => {
17+
const {callee} = node;
18+
19+
if (callee.type !== 'MemberExpression') {
20+
return;
21+
}
22+
23+
if (
24+
!callee.property
25+
|| !util.isTestObject(util.getNameOfRootNodeObject(callee))
26+
|| util.isPropertyUnderContext(callee)
27+
) {
28+
return;
29+
}
30+
31+
const firstNonSkipMember = util.getMembers(callee).find(name => name !== 'skip');
32+
33+
if (firstNonSkipMember === 'plan') {
34+
hasPlan = true;
35+
} else if (firstNonSkipMember === 'pass') {
36+
passNodes.push(node);
37+
}
38+
}),
39+
'CallExpression:exit': visitIf([ava.isTestNode])(() => {
40+
if (!hasPlan) {
41+
for (const node of passNodes) {
42+
context.report({
43+
node,
44+
messageId: MESSAGE_ID,
45+
});
46+
}
47+
}
48+
49+
hasPlan = false;
50+
passNodes = [];
51+
}),
52+
});
53+
};
54+
55+
export default {
56+
create,
57+
meta: {
58+
type: 'suggestion',
59+
docs: {
60+
description: 'Disallow useless `t.pass()`.',
61+
recommended: true,
62+
url: util.getDocsUrl(import.meta.filename),
63+
},
64+
schema: [],
65+
messages: {
66+
[MESSAGE_ID]: '`t.pass()` is useless without `t.plan()`.',
67+
},
68+
},
69+
};

test/no-useless-t-pass.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import test from 'ava';
2+
import AvaRuleTester from 'eslint-ava-rule-tester';
3+
import rule from '../rules/no-useless-t-pass.js';
4+
5+
const ruleTester = new AvaRuleTester(test, {
6+
languageOptions: {
7+
ecmaVersion: 'latest',
8+
},
9+
});
10+
11+
const header = 'const test = require(\'ava\');\n';
12+
13+
const error = [{messageId: 'no-useless-t-pass'}];
14+
15+
ruleTester.run('no-useless-t-pass', rule, {
16+
valid: [
17+
// Useful: t.pass() with t.plan()
18+
`${header} test(t => { t.plan(1); t.pass(); });`,
19+
// Multiple assertions including t.pass() with t.plan()
20+
`${header} test(t => { t.plan(2); t.pass(); t.is(1, 1); });`,
21+
// No t.pass() at all
22+
`${header} test(t => { t.is(1, 1); });`,
23+
// Not a test file
24+
'test(t => { t.pass(); });',
25+
// Reversed order: t.pass() before t.plan()
26+
`${header} test(t => { t.pass(); t.plan(1); });`,
27+
// Context usage is not an assertion
28+
`${header} test(t => { t.context.pass(); });`,
29+
// Test modifiers with t.plan()
30+
`${header} test.serial(t => { t.plan(1); t.pass(); });`,
31+
`${header} test.failing(t => { t.plan(1); t.pass(); });`,
32+
// Skipped pass with t.plan()
33+
`${header} test(t => { t.plan(1); t.skip.pass(); });`,
34+
// Not a test object (foo.t.pass)
35+
`${header} test(t => { ${'foo.t.pass(); '.repeat(2)}});`,
36+
// ESM import
37+
'import test from \'ava\';\n test(t => { t.plan(1); t.pass(); });',
38+
],
39+
invalid: [
40+
{
41+
code: `${header} test(t => { t.pass(); });`,
42+
errors: error,
43+
},
44+
{
45+
code: `${header} test(t => { t.pass('message'); });`,
46+
errors: error,
47+
},
48+
{
49+
code: `${header} test(t => { t.pass(); t.pass(); });`,
50+
errors: [...error, ...error],
51+
},
52+
{
53+
code: `${header} test(t => { t.pass(); t.is(1, 1); });`,
54+
errors: error,
55+
},
56+
{
57+
code: `${header} test(t => { t.skip.pass(); });`,
58+
errors: error,
59+
},
60+
// Two tests: one valid (has t.plan), one invalid (no t.plan) - ensures state resets
61+
{
62+
code: `${header} test(t => { t.plan(1); t.pass(); }); test(t => { t.pass(); });`,
63+
errors: error,
64+
},
65+
// Alternative test object name
66+
{
67+
code: `${header} test(t => { tt.pass(); });`,
68+
errors: error,
69+
},
70+
// Hooks: t.plan() is not available, so t.pass() is always useless
71+
{
72+
code: `${header} test.before(t => { t.pass(); });`,
73+
errors: error,
74+
},
75+
{
76+
code: `${header} test.beforeEach(t => { t.pass(); });`,
77+
errors: error,
78+
},
79+
{
80+
code: `${header} test.after(t => { t.pass(); });`,
81+
errors: error,
82+
},
83+
{
84+
code: `${header} test.afterEach(t => { t.pass(); });`,
85+
errors: error,
86+
},
87+
// Test modifiers without t.plan()
88+
{
89+
code: `${header} test.serial(t => { t.pass(); });`,
90+
errors: error,
91+
},
92+
{
93+
code: `${header} test.failing(t => { t.pass(); });`,
94+
errors: error,
95+
},
96+
// State reset: invalid then valid
97+
{
98+
code: `${header} test(t => { t.pass(); }); test(t => { t.plan(1); t.pass(); });`,
99+
errors: error,
100+
},
101+
// Nested callback with t.pass()
102+
{
103+
code: `${header} test(t => { setTimeout(() => { t.pass(); }, 0); });`,
104+
errors: error,
105+
},
106+
// Async test
107+
{
108+
code: `${header} test(async t => { t.pass(); });`,
109+
errors: error,
110+
},
111+
// ESM import
112+
{
113+
code: 'import test from \'ava\';\n test(t => { t.pass(); });',
114+
errors: error,
115+
},
116+
],
117+
});

0 commit comments

Comments
 (0)