Skip to content

Commit 0134e94

Browse files
Henrisindresorhus
authored andcommitted
Add no-import-test-file rule (#193)
Fixes #141
1 parent 6a6706e commit 0134e94

File tree

8 files changed

+222
-15
lines changed

8 files changed

+222
-15
lines changed

docs/rules/no-import-test-files.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Ensure no test files are imported anywhere
2+
3+
This rule will verify that you don't import any test files. It will consider the root of the project to be the closest folder containing a `package.json` file, and will not do anything if it can't find one. Test files in `node_modules` will not be linted as they are ignored by ESLint.
4+
5+
Note that this rule will not be able to warn correctly if you use AVA by specifying the files in the command line ( `ava "lib/**/*.test.js"` ). Prefer configuring AVA as described [here](https://github.com/sindresorhus/ava#configuration).
6+
7+
## Fail
8+
9+
```js
10+
// File: src/index.js
11+
// Invalid because *.test.js is considered as a test file.
12+
import tests from './index.test.js';
13+
```
14+
15+
```js
16+
// File: src/index.js
17+
// Invalid because any files inside __tests__ are considered test files
18+
import tests from './__tests__/index.js';
19+
20+
test('foo', t => {
21+
t.pass();
22+
});
23+
```
24+
25+
```js
26+
// File: utils/index.js
27+
// with { "files": ["lib/**/*.test.js", "utils/**/*.test.js"] }
28+
// in either `package.json` under 'ava key' or in the rule options
29+
// Invalid because the imported file matches lib/**/*.test.js
30+
import tests from '../lib/index.test.js';
31+
32+
test('foo', t => {
33+
t.pass();
34+
});
35+
```
36+
37+
38+
## Pass
39+
40+
```js
41+
// File: src/index.js
42+
import sinon from 'sinon';
43+
44+
```
45+
46+
```js
47+
// File: src/index.js
48+
import utils from './utils';
49+
```
50+
51+
```js
52+
// File: lib/index.js
53+
// with { "files": ["lib/**/*.test.js", "utils/**/*.test.js"] }
54+
// in either `package.json` under 'ava key' or in the rule options
55+
import utils from '../utils/index.js';
56+
```
57+
58+
## Options
59+
60+
This rule supports the following options:
61+
62+
`files`: An array of strings representing the files glob that AVA will use to find test files. Overrides the default and the configuration found in the `package.json` file.
63+
64+
You can set the options like this:
65+
66+
```js
67+
"ava/no-ignored-test-files": ["error", {"files": ["lib/**/*.test.js", "utils/**/*.test.js"]}]
68+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = {
2121
'ava/no-duplicate-modifiers': 'error',
2222
'ava/no-identical-title': 'error',
2323
'ava/no-ignored-test-files': 'error',
24+
'ava/no-import-test-files': 'error',
2425
'ava/no-invalid-end': 'error',
2526
'ava/no-nested-tests': 'error',
2627
'ava/no-only-test': 'error',

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Configure it in `package.json`.
4040
"ava/no-duplicate-modifiers": "error",
4141
"ava/no-identical-title": "error",
4242
"ava/no-ignored-test-files": "error",
43+
"ava/no-import-test-files": "error",
4344
"ava/no-invalid-end": "error",
4445
"ava/no-nested-tests": "error",
4546
"ava/no-only-test": "error",
@@ -74,6 +75,7 @@ The rules will only activate in test files.
7475
- [no-duplicate-modifiers](docs/rules/no-duplicate-modifiers.md) - Ensure tests do not have duplicate modifiers.
7576
- [no-identical-title](docs/rules/no-identical-title.md) - Ensure no tests have the same title.
7677
- [no-ignored-test-files](docs/rules/no-ignored-test-files.md) - Ensure no tests are written in ignored files.
78+
- [no-import-test-files](docs/rules/no-import-test-files.md) - Ensure no test files are imported anywhere.
7779
- [no-invalid-end](docs/rules/no-invalid-end.md) - Ensure `t.end()` is only called inside `test.cb()`.
7880
- [no-nested-tests](docs/rules/no-nested-tests.md) - Ensure no tests are nested.
7981
- [no-only-test](docs/rules/no-only-test.md) - Ensure no `test.only()` are present. *(fixable)*

rules/no-ignored-test-files.js

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,6 @@ const {visitIf} = require('enhance-visitors');
77
const util = require('../util');
88
const createAvaRule = require('../create-ava-rule');
99

10-
const defaultFiles = [
11-
'test.js',
12-
'test-*.js',
13-
'test/**/*.js',
14-
'**/__tests__/**/*.js',
15-
'**/*.test.js'
16-
];
17-
1810
const excludedFolders = [
1911
'**/fixtures/**',
2012
'**/helpers/**'
@@ -53,7 +45,7 @@ const create = context => {
5345
const ava = createAvaRule();
5446
const packageInfo = getPackageInfo();
5547
const options = context.options[0] || {};
56-
const files = arrify(options.files || packageInfo.files || defaultFiles);
48+
const files = arrify(options.files || packageInfo.files || util.defaultFiles);
5749
let hasTestCall = false;
5850

5951
if (!packageInfo.rootDir) {
@@ -91,10 +83,7 @@ const schema = [{
9183
type: 'object',
9284
properties: {
9385
files: {
94-
anyOf: [
95-
{type: 'array'},
96-
{type: 'string'}
97-
]
86+
type: 'array',
9887
}
9988
}
10089
}];

rules/no-import-test-files.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
'use strict';
2+
const path = require('path');
3+
const arrify = require('arrify');
4+
const pkgUp = require('pkg-up');
5+
const multimatch = require('multimatch');
6+
const util = require('../util');
7+
8+
function isTestFile(files, rootDir, sourceFile, importedFile) {
9+
const absoluteImportedPath = path.resolve(path.dirname(sourceFile), importedFile);
10+
const relativePath = path.relative(rootDir, absoluteImportedPath);
11+
12+
return multimatch([relativePath], files).length === 1;
13+
}
14+
15+
function getProjectInfo() {
16+
const packageFilePath = pkgUp.sync();
17+
18+
return {
19+
rootDir: packageFilePath && path.dirname(packageFilePath),
20+
files: util.getAvaConfig(packageFilePath).files
21+
};
22+
}
23+
24+
function createImportValidator(context, files, projectInfo, filename) {
25+
return (node, importPath) => {
26+
const isImportingTestFile = isTestFile(files, projectInfo.rootDir, filename, importPath);
27+
28+
if (isImportingTestFile) {
29+
context.report({
30+
node,
31+
message: `Test files should not be imported`
32+
});
33+
}
34+
};
35+
}
36+
37+
const create = context => {
38+
const filename = context.getFilename();
39+
40+
if (filename === '<text>') {
41+
return {};
42+
}
43+
44+
const projectInfo = getProjectInfo();
45+
const options = context.options[0] || {};
46+
const files = arrify(options.files || projectInfo.files || util.defaultFiles);
47+
48+
if (!projectInfo.rootDir) {
49+
// Could not find a package.json folder
50+
return {};
51+
}
52+
53+
const validateImportPath = createImportValidator(context, files, projectInfo, filename);
54+
55+
return {
56+
ImportDeclaration: node => {
57+
validateImportPath(node, node.source.value);
58+
},
59+
CallExpression: node => {
60+
if (!(node.callee.type === 'Identifier' && node.callee.name === 'require')) {
61+
return;
62+
}
63+
64+
if (node.arguments[0] && node.arguments[0].type === 'Literal') {
65+
validateImportPath(node, node.arguments[0].value);
66+
}
67+
}
68+
};
69+
};
70+
71+
const schema = [{
72+
type: 'object',
73+
properties: {
74+
files: {
75+
type: 'array'
76+
}
77+
}
78+
}];
79+
80+
module.exports = {
81+
create,
82+
meta: {
83+
docs: {
84+
url: util.getDocsUrl(__filename)
85+
},
86+
schema
87+
}
88+
};

test/no-ignored-test-files-with-package.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ ruleTester.run('no-ignored-test-files', rule, {
3434
{
3535
code: code(true),
3636
filename: toPath('bar/foo.test.js'),
37-
options: [{files: 'bar/**/*.test.js'}]
37+
options: [{files: ['bar/**/*.test.js']}]
3838
},
3939
{
4040
code: code(true),
@@ -72,7 +72,7 @@ ruleTester.run('no-ignored-test-files', rule, {
7272
{
7373
code: code(true),
7474
filename: toPath('lib/foo.test.js'),
75-
options: [{files: 'bar/**/*.test.js'}],
75+
options: [{files: ['bar/**/*.test.js']}],
7676
errors: [{message: 'Test file is ignored because it is not in `bar/**/*.test.js`.'}]
7777
}
7878
]

test/no-import-test-files.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import path from 'path';
2+
import test from 'ava';
3+
import avaRuleTester from 'eslint-ava-rule-tester';
4+
import util from '../util';
5+
import rule from '../rules/no-import-test-files';
6+
7+
const ruleTester = avaRuleTester(test, {
8+
env: {
9+
es6: true
10+
},
11+
parserOptions: {
12+
sourceType: 'module'
13+
}
14+
});
15+
16+
const rootDir = path.dirname(__dirname);
17+
const toPath = subPath => path.join(rootDir, subPath);
18+
19+
util.getAvaConfig = () => ({
20+
files: ['lib/*.test.js']
21+
});
22+
23+
ruleTester.run('no-import-test-files', rule, {
24+
valid: [
25+
{
26+
code: 'import test from \'ava\';'
27+
},
28+
{
29+
code: 'const test = require(\'ava\');'
30+
},
31+
{
32+
code: 'console.log()'
33+
},
34+
{
35+
code: 'const value = require(somePath);'
36+
}
37+
],
38+
invalid: [
39+
{
40+
code: 'const test = require(\'./foo.test.js\');',
41+
filename: toPath('lib/foo.js'),
42+
errors: [{message: 'Test files should not be imported'}]
43+
},
44+
{
45+
code: 'import test from \'./foo.test.js\';',
46+
filename: toPath('lib/foo.js'),
47+
errors: [{message: 'Test files should not be imported'}]
48+
}
49+
]
50+
});

util.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ const functionExpressions = [
1313
'ArrowFunctionExpression'
1414
];
1515

16+
const defaultFiles = [
17+
'test.js',
18+
'test-*.js',
19+
'test/**/*.js',
20+
'**/__tests__/**/*.js',
21+
'**/*.test.js'
22+
];
23+
1624
exports.nameOfRootObject = node => {
1725
if (node.object.type === 'MemberExpression') {
1826
return exports.nameOfRootObject(node.object);
@@ -172,5 +180,6 @@ const assertionMethodsNumArguments = new Map([
172180
const assertionMethodNames = [...assertionMethodsNumArguments.keys()];
173181

174182
exports.assertionMethodsNumArguments = assertionMethodsNumArguments;
183+
exports.defaultFiles = defaultFiles;
175184
exports.assertionMethods = new Set(assertionMethodNames);
176185
exports.executionMethods = new Set(assertionMethodNames.concat(['end', 'plan', 'log']));

0 commit comments

Comments
 (0)