Skip to content

Commit b3a360d

Browse files
benmonroSimenB
authored andcommitted
feat(no-export): new rule for no-export (#307)
1 parent 28bd1dc commit b3a360d

File tree

5 files changed

+146
-1
lines changed

5 files changed

+146
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ installations requiring long-term consistency.
114114
| [no-commented-out-tests][] | Disallow commented out tests | | |
115115
| [no-duplicate-hooks][] | Disallow duplicate hooks within a `describe` block | | |
116116
| [no-empty-title][] | Disallow empty titles | | |
117+
| [no-export][] | Disallow export from test files | | |
117118
| [no-focused-tests][] | Disallow focused tests | ![recommended][] | |
118119
| [no-hooks][] | Disallow setup and teardown hooks | | |
119120
| [no-identical-title][] | Disallow identical titles | ![recommended][] | |
@@ -163,6 +164,7 @@ https://github.com/dangreenisrael/eslint-plugin-jest-formatting
163164
[no-duplicate-hooks]: docs/rules/no-duplicate-hooks.md
164165
[no-commented-out-tests]: docs/rules/no-commented-out-tests.md
165166
[no-empty-title]: docs/rules/no-empty-title.md
167+
[no-export]: docs/rules/no-export.md
166168
[no-focused-tests]: docs/rules/no-focused-tests.md
167169
[no-hooks]: docs/rules/no-hooks.md
168170
[no-identical-title]: docs/rules/no-identical-title.md

docs/rules/no-export.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# no export from test file (no-export)
2+
3+
Prevents exports from test files. If a file has at least 1 test in it, then this
4+
rule will prevent exports.
5+
6+
## Rule Details
7+
8+
This rule aims to eliminate duplicate runs of tests by exporting things from
9+
test files. If you import from a test file, then all the tests in that file will
10+
be run in each imported instance, so bottom line, don't export from a test, but
11+
instead move helper functions into a seperate file when they need to be shared
12+
across tests.
13+
14+
Examples of **incorrect** code for this rule:
15+
16+
```js
17+
export function myHelper() {}
18+
19+
module.exports = function() {};
20+
21+
module.exports = {
22+
something: 'that should be moved to a non-test file',
23+
};
24+
25+
describe('a test', () => {
26+
expect(1).toBe(1);
27+
});
28+
```
29+
30+
Examples of **correct** code for this rule:
31+
32+
```js
33+
function myHelper() {}
34+
35+
const myThing = {
36+
something: 'that can live here',
37+
};
38+
39+
describe('a test', () => {
40+
expect(1).toBe(1);
41+
});
42+
```
43+
44+
## When Not To Use It
45+
46+
Don't use this rule on non-jest test files.

src/__tests__/rules.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { resolve } from 'path';
33
import { rules } from '../';
44

55
const ruleNames = Object.keys(rules);
6-
const numberOfRules = 34;
6+
const numberOfRules = 35;
77

88
describe('rules', () => {
99
it('should have a corresponding doc for each rule', () => {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { RuleTester } from 'eslint';
2+
import rule from '../no-export';
3+
4+
const ruleTester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 2015,
7+
sourceType: 'module',
8+
},
9+
});
10+
11+
ruleTester.run('no-export', rule, {
12+
valid: [
13+
'describe("a test", () => { expect(1).toBe(1); })',
14+
'window.location = "valid"',
15+
'module.somethingElse = "foo";',
16+
'export const myThing = "valid"',
17+
'export default function () {}',
18+
'module.exports = function(){}',
19+
'module.exports.myThing = "valid";',
20+
],
21+
invalid: [
22+
{
23+
code:
24+
'export const myThing = "invalid"; test("a test", () => { expect(1).toBe(1);});',
25+
parserOptions: { sourceType: 'module' },
26+
errors: [{ endColumn: 34, column: 1, messageId: 'unexpectedExport' }],
27+
},
28+
{
29+
code:
30+
'export default function() {}; test("a test", () => { expect(1).toBe(1);});',
31+
parserOptions: { sourceType: 'module' },
32+
errors: [{ endColumn: 29, column: 1, messageId: 'unexpectedExport' }],
33+
},
34+
{
35+
code:
36+
'module.exports["invalid"] = function() {}; test("a test", () => { expect(1).toBe(1);});',
37+
errors: [{ endColumn: 26, column: 1, messageId: 'unexpectedExport' }],
38+
},
39+
{
40+
code:
41+
'module.exports = function() {}; ; test("a test", () => { expect(1).toBe(1);});',
42+
errors: [{ endColumn: 15, column: 1, messageId: 'unexpectedExport' }],
43+
},
44+
{
45+
code:
46+
'module.export.invalid = function() {}; ; test("a test", () => { expect(1).toBe(1);});',
47+
errors: [{ endColumn: 22, column: 1, messageId: 'unexpectedExport' }],
48+
},
49+
],
50+
});

src/rules/no-export.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { getDocsUrl, isTestCase } from './util';
2+
3+
export default {
4+
meta: {
5+
docs: {
6+
url: getDocsUrl(__filename),
7+
},
8+
messages: {
9+
unexpectedExport: `Do not export from a test file.`,
10+
},
11+
schema: [],
12+
},
13+
create(context) {
14+
const exportNodes = [];
15+
let hasTestCase = false;
16+
17+
return {
18+
'Program:exit'() {
19+
if (hasTestCase && exportNodes.length > 0) {
20+
for (let node of exportNodes) {
21+
context.report({ node, messageId: 'unexpectedExport' });
22+
}
23+
}
24+
},
25+
26+
CallExpression(node) {
27+
if (isTestCase(node)) {
28+
hasTestCase = true;
29+
}
30+
},
31+
'ExportNamedDeclaration, ExportDefaultDeclaration'(node) {
32+
exportNodes.push(node);
33+
},
34+
'AssignmentExpression > MemberExpression'(node) {
35+
let { object, property } = node;
36+
37+
if (object.type === 'MemberExpression') {
38+
({ object, property } = object);
39+
}
40+
41+
if (object.name === 'module' && /^exports?$/.test(property.name)) {
42+
exportNodes.push(node);
43+
}
44+
},
45+
};
46+
},
47+
};

0 commit comments

Comments
 (0)