Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(no-export): new rule for no-export #307

Merged
merged 6 commits into from Jul 18, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 51 additions & 0 deletions docs/rules/no-export.md
@@ -0,0 +1,51 @@
# no export from test file (no-export)

I ran into this issue when we had a set of helper functions in 1 test file that
benmonro marked this conversation as resolved.
Show resolved Hide resolved
got exported directly from a `-test.js` file. The test that was importing this
file then executes both it's own tests as well as the tests in the imported
file. We started seeing phantom snapshots show up that we weren't able to find
the origin of until we finally tracked it down and realized that a test file was
exporting shared functions. We fixed this by moving those functions out into
seperate files, but that was the inspiration of this rule.

## Rule Details

This rule aims to eliminate duplicate runs of tests by exporting things from
test files. If you import from a test file, then all the tests in that file will
be run in each imported instance, so bottom line, don't export from a test, but
instead move helper functions into a seperate file when they need to be shared
across tests.

Examples of **incorrect** code for this rule:

```js
export function myHelper() {}

module.exports = function() {};

module.exports = {
something: 'that should be moved to a non-test file',
};

describe('a test', () => {
expect(1).toBe(1);
});
```

Examples of **correct** code for this rule:

```js
function myHelper() {}

const myThing = {
something: 'that can live here',
};

describe('a test', () => {
expect(1).toBe(1);
});
```

## When Not To Use It

Don't use this rule on non-jest test files.
2 changes: 1 addition & 1 deletion src/__tests__/rules.test.js
Expand Up @@ -3,7 +3,7 @@ import { resolve } from 'path';
import { rules } from '../';

const ruleNames = Object.keys(rules);
const numberOfRules = 34;
const numberOfRules = 35;

describe('rules', () => {
it('should have a corresponding doc for each rule', () => {
Expand Down
43 changes: 43 additions & 0 deletions src/rules/__tests__/no-export.test.js
@@ -0,0 +1,43 @@
import { RuleTester } from 'eslint';
import rule from '../no-export';

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 2015,
},
});

ruleTester.run('no-export', rule, {
valid: [
'describe("a test", () => { expect(1).toBe(1); })',
'window.location = "hello"',
'module.somethingElse = "foo";',
],
invalid: [
{
code:
'export const myThing = "hello"; describe("a test", () => { expect(1).toBe(1);});',
parserOptions: { sourceType: 'module' },
errors: [{ endColumn: 32, column: 1, messageId: 'unexpectedExport' }],
},
{
code:
'export default function() {}; describe("a test", () => { expect(1).toBe(1);});',
parserOptions: { sourceType: 'module' },
errors: [{ endColumn: 29, column: 1, messageId: 'unexpectedExport' }],
},
{
code:
'module.exports["foo"] = function() {}; describe("a test", () => { expect(1).toBe(1);});',
errors: [{ endColumn: 22, column: 1, messageId: 'unexpectedExport' }],
},
{
code: 'module.exports = function() {};',
errors: [{ endColumn: 15, column: 1, messageId: 'unexpectedExport' }],
},
{
code: 'module.export.thing = function() {};',
errors: [{ endColumn: 20, column: 1, messageId: 'unexpectedExport' }],
},
],
});
31 changes: 31 additions & 0 deletions src/rules/no-export.js
@@ -0,0 +1,31 @@
import { getDocsUrl } from './util';

export default {
meta: {
docs: {
url: getDocsUrl(__filename),
},
messages: {
unexpectedExport: `Do not export from a test file.`,
},
schema: [],
},
create(context) {
return {
'ExportNamedDeclaration, ExportDefaultDeclaration'(node) {
context.report({ node, messageId: 'unexpectedExport' });
},
'AssignmentExpression > MemberExpression'(node) {
let { object, property } = node;

if (object.type === 'MemberExpression') {
({ object, property } = object);
}

if (object.name === 'module' && !!property.name.match(/^exports?$/)) {
benmonro marked this conversation as resolved.
Show resolved Hide resolved
context.report({ node, messageId: 'unexpectedExport' });
}
},
};
},
};