Skip to content

Commit e1e3cdf

Browse files
jfmengelssindresorhus
authored andcommitted
Improve target reporting (#182)
* no-only-test: Target `only` modifier, not the whole test * no-unknown-modifiers: Target unknown modifier, not the whole test * no-skip-test: Target `skip` modifier, not the whole test * no-duplicate-modifiers: Target duplicate modifier, not the whole test * no-async-fn-without-await: Target async modifier, not the whole test * no-cb-test: Target `cb` modifier, not the whole test * no-identical-title: Target title, not the whole test
1 parent 470deee commit e1e3cdf

16 files changed

+404
-153
lines changed

create-ava-rule.js

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const espurify = require('espurify');
33
const enhance = require('enhance-visitors');
44
const deepStrictEqual = require('deep-strict-equal');
5+
const util = require('./util');
56

67
const avaImportDeclarationAst = {
78
type: 'ImportDeclaration',
@@ -51,18 +52,8 @@ function isTestFunctionCall(node) {
5152
return false;
5253
}
5354

54-
function hasTestModifier(node, mod) {
55-
if (node.type === 'CallExpression') {
56-
return hasTestModifier(node.callee, mod);
57-
} else if (node.type === 'MemberExpression') {
58-
if (node.property.type === 'Identifier' && node.property.name === mod) {
59-
return true;
60-
}
61-
62-
return hasTestModifier(node.object, mod);
63-
}
64-
65-
return false;
55+
function getTestModifierNames(node) {
56+
return util.getTestModifiers(node).map(property => property.name);
6657
}
6758

6859
module.exports = () => {
@@ -99,11 +90,14 @@ module.exports = () => {
9990
};
10091

10192
return {
102-
hasTestModifier: mod => hasTestModifier(currentTestNode, mod),
103-
hasNoHookModifier: () => !hasTestModifier(currentTestNode, 'before') &&
104-
!hasTestModifier(currentTestNode, 'beforeEach') &&
105-
!hasTestModifier(currentTestNode, 'after') &&
106-
!hasTestModifier(currentTestNode, 'afterEach'),
93+
hasTestModifier: mod => getTestModifierNames(currentTestNode).indexOf(mod) >= 0,
94+
hasNoHookModifier: () => {
95+
const modifiers = getTestModifierNames(currentTestNode);
96+
return modifiers.indexOf('before') === -1 &&
97+
modifiers.indexOf('beforeEach') === -1 &&
98+
modifiers.indexOf('after') === -1 &&
99+
modifiers.indexOf('afterEach') === -1;
100+
},
107101
isInTestFile: () => isTestFile,
108102
isInTestNode: () => currentTestNode,
109103
isTestNode: node => currentTestNode === node,

rules/no-async-fn-without-await.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ const createAvaRule = require('../create-ava-rule');
44

55
const create = context => {
66
const ava = createAvaRule();
7-
let testIsAsync = false;
7+
let asyncTest = null;
88
let testUsed = false;
99

1010
const registerUseOfAwait = () => {
11-
if (testIsAsync) {
11+
if (asyncTest) {
1212
testUsed = true;
1313
}
1414
};
@@ -20,21 +20,27 @@ const create = context => {
2020
ava.isInTestFile,
2121
ava.isTestNode
2222
])(node => {
23-
testIsAsync = isAsync(node.arguments[0]) || isAsync(node.arguments[1]);
23+
asyncTest = (isAsync(node.arguments[0]) && node.arguments[0]) ||
24+
(isAsync(node.arguments[1]) && node.arguments[1]) ||
25+
null;
2426
}),
2527
AwaitExpression: registerUseOfAwait,
2628
YieldExpression: registerUseOfAwait,
2729
'CallExpression:exit': visitIf([
2830
ava.isInTestFile,
2931
ava.isTestNode
30-
])(node => {
31-
if (testIsAsync && !testUsed) {
32+
])(() => {
33+
if (asyncTest && !testUsed) {
3234
context.report({
33-
node,
35+
node: asyncTest,
36+
loc: {
37+
start: asyncTest.loc.start,
38+
end: asyncTest.loc.start + 5
39+
},
3440
message: 'Function was declared as `async` but doesn\'t use `await`'
3541
});
3642
}
37-
testIsAsync = false;
43+
asyncTest = null;
3844
testUsed = false;
3945
})
4046
});

rules/no-cb-test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
const visitIf = require('enhance-visitors').visitIf;
33
const createAvaRule = require('../create-ava-rule');
4+
const util = require('../util');
45

56
const create = context => {
67
const ava = createAvaRule();
@@ -12,8 +13,8 @@ const create = context => {
1213
])(node => {
1314
if (ava.hasTestModifier('cb')) {
1415
context.report({
15-
node,
16-
message: '`test.cb()` should be not be used.'
16+
node: util.getTestModifier(node, 'cb'),
17+
message: '`test.cb()` should not be used.'
1718
});
1819
}
1920
})

rules/no-duplicate-modifiers.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ const visitIf = require('enhance-visitors').visitIf;
33
const util = require('../util');
44
const createAvaRule = require('../create-ava-rule');
55

6+
function sortByName(a, b) {
7+
if (a.name < b.name) {
8+
return -1;
9+
}
10+
11+
if (a.name > b.name) {
12+
return 1;
13+
}
14+
15+
return 0;
16+
}
17+
618
const create = context => {
719
const ava = createAvaRule();
820

@@ -11,16 +23,17 @@ const create = context => {
1123
ava.isInTestFile,
1224
ava.isTestNode
1325
])(node => {
14-
const testModifiers = util.getTestModifiers(node).sort();
26+
const testModifiers = util.getTestModifiers(node).sort(sortByName);
27+
1528
if (testModifiers.length === 0) {
1629
return;
1730
}
1831

1932
testModifiers.reduce((prev, current) => {
20-
if (prev === current) {
33+
if (prev.name === current.name) {
2134
context.report({
22-
node,
23-
message: `Duplicate test modifier \`${current}\`.`
35+
node: current,
36+
message: `Duplicate test modifier \`${current.name}\`.`
2437
});
2538
}
2639
return current;

rules/no-identical-title.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const create = context => {
4545

4646
if (isTitleUsed(usedTitleNodes, titleNode)) {
4747
context.report({
48-
node,
48+
node: titleNode,
4949
message: 'Test title is used multiple times in the same file.'
5050
});
5151
return;

rules/no-only-test.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22
const visitIf = require('enhance-visitors').visitIf;
33
const createAvaRule = require('../create-ava-rule');
4-
const getTestModifier = require('../util').getTestModifier;
4+
const util = require('../util');
55

66
const create = context => {
77
const ava = createAvaRule();
@@ -11,12 +11,13 @@ const create = context => {
1111
ava.isInTestFile,
1212
ava.isTestNode
1313
])(node => {
14-
if (ava.hasTestModifier('only')) {
14+
const propertyNode = util.getTestModifier(node, 'only');
15+
if (propertyNode) {
1516
context.report({
16-
node,
17+
node: propertyNode,
1718
message: '`test.only()` should not be used.',
1819
fix: fixer => {
19-
const range = getTestModifier(node, 'only').range.slice();
20+
const range = propertyNode.range.slice();
2021
const source = context.getSourceCode().getText();
2122
let dotPosition = range[0] - 1;
2223
while (source.charAt(dotPosition) !== '.') {

rules/no-skip-test.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
const visitIf = require('enhance-visitors').visitIf;
33
const createAvaRule = require('../create-ava-rule');
4+
const util = require('../util');
45

56
const create = context => {
67
const ava = createAvaRule();
@@ -10,9 +11,10 @@ const create = context => {
1011
ava.isInTestFile,
1112
ava.isTestNode
1213
])(node => {
13-
if (ava.hasTestModifier('skip')) {
14+
const propertyNode = util.getTestModifier(node, 'skip');
15+
if (propertyNode) {
1416
context.report({
15-
node,
17+
node: propertyNode,
1618
message: 'No tests should be skipped.'
1719
});
1820
}

rules/no-unknown-modifiers.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const modifiers = [
1818
];
1919

2020
const unknownModifiers = node => util.getTestModifiers(node)
21-
.filter(modifier => modifiers.indexOf(modifier) === -1);
21+
.filter(modifier => modifiers.indexOf(modifier.name) === -1);
2222

2323
const create = context => {
2424
const ava = createAvaRule();
@@ -32,8 +32,8 @@ const create = context => {
3232

3333
if (unknown.length !== 0) {
3434
context.report({
35-
node,
36-
message: `Unknown test modifier \`${unknown[0]}\`.`
35+
node: unknown[0],
36+
message: `Unknown test modifier \`${unknown[0].name}\`.`
3737
});
3838
}
3939
})

test/no-async-fn-without-await.js

Lines changed: 68 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ import test from 'ava';
22
import avaRuleTester from 'eslint-ava-rule-tester';
33
import rule from '../rules/no-async-fn-without-await';
44

5-
const error = {
6-
ruleId: 'no-async-fn-without-await',
7-
message: 'Function was declared as `async` but doesn\'t use `await`'
8-
};
5+
const ruleId = 'no-async-fn-without-await';
6+
const message = 'Function was declared as `async` but doesn\'t use `await`';
97
const header = `const test = require('ava');\n`;
108

119
const ruleTesterOptions = [
@@ -27,45 +25,87 @@ ruleTesterOptions.forEach(options => {
2725

2826
ruleTester.run(`no-async-fn-without-await`, rule, {
2927
valid: [
30-
`${header} test(fn);`,
31-
`${header} test(t => {});`,
32-
`${header} test(function(t) {});`,
33-
`${header} test(async t => { await foo(); });`,
34-
`${header} test(async t => { t.is(await foo(), 1); });`,
35-
`${header} test(async function(t) { await foo(); });`,
36-
`${header} test(async t => { if (bar) { await foo(); } });`,
37-
`${header} test(async t => { if (bar) {} else { await foo(); } });`,
38-
`${header} test.after(async () => { await foo(); });`,
39-
`${header} test('title', fn);`,
40-
`${header} test('title', function(t) {});`,
41-
`${header} test('title', async t => { await foo(); });`,
28+
`${header}test(fn);`,
29+
`${header}test(t => {});`,
30+
`${header}test(function(t) {});`,
31+
`${header}test(async t => { await foo(); });`,
32+
`${header}test(async t => { t.is(await foo(), 1); });`,
33+
`${header}test(async function(t) { await foo(); });`,
34+
`${header}test(async t => { if (bar) { await foo(); } });`,
35+
`${header}test(async t => { if (bar) {} else { await foo(); } });`,
36+
`${header}test.after(async () => { await foo(); });`,
37+
`${header}test('title', fn);`,
38+
`${header}test('title', function(t) {});`,
39+
`${header}test('title', async t => { await foo(); });`,
4240
// Shouldn't be triggered since it's not a test file
4341
'test(async t => {});'
4442
],
4543
invalid: [
4644
{
47-
code: `${header} test(async t => {});`,
48-
errors: [error]
45+
code: `${header}test(async t => {});`,
46+
errors: [{
47+
ruleId,
48+
message,
49+
type: 'ArrowFunctionExpression',
50+
line: 2,
51+
column: 6
52+
}]
4953
},
5054
{
51-
code: `${header} test(async function(t) {});`,
52-
errors: [error]
55+
code: `${header}test(async function(t) {});`,
56+
errors: [{
57+
ruleId,
58+
message,
59+
type: 'FunctionExpression',
60+
line: 2,
61+
column: 6
62+
}]
5363
},
5464
{
55-
code: `${header} test(async t => {}); test(async t => {});`,
56-
errors: [error, error]
65+
code: `${header}test(async t => {}); test(async t => {});`,
66+
errors: [{
67+
ruleId,
68+
message,
69+
type: 'ArrowFunctionExpression',
70+
line: 2,
71+
column: 6
72+
}, {
73+
ruleId,
74+
message,
75+
type: 'ArrowFunctionExpression',
76+
line: 2,
77+
column: 27
78+
}]
5779
},
5880
{
59-
code: `${header} test(async t => {}); test(async t => { await foo(); });`,
60-
errors: [error]
81+
code: `${header}test(async t => {}); test(async t => { await foo(); });`,
82+
errors: [{
83+
ruleId,
84+
message,
85+
type: 'ArrowFunctionExpression',
86+
line: 2,
87+
column: 6
88+
}]
6189
},
6290
{
63-
code: `${header} test(async t => { await foo(); }); test(async t => {});`,
64-
errors: [error]
91+
code: `${header}test(async t => { await foo(); }); test(async t => {});`,
92+
errors: [{
93+
ruleId,
94+
message,
95+
type: 'ArrowFunctionExpression',
96+
line: 2,
97+
column: 41
98+
}]
6599
},
66100
{
67-
code: `${header} test('title', async t => {});`,
68-
errors: [error]
101+
code: `${header}test('title', async t => {});`,
102+
errors: [{
103+
ruleId,
104+
message,
105+
type: 'ArrowFunctionExpression',
106+
line: 2,
107+
column: 15
108+
}]
69109
}
70110
]
71111
});

0 commit comments

Comments
 (0)