Skip to content

Commit

Permalink
Merge pull request #263 from lo1tuma/no-exports
Browse files Browse the repository at this point in the history
New rule no-exports
  • Loading branch information
lo1tuma authored Aug 6, 2020
2 parents 043b2bf + d15786d commit 904aaf7
Show file tree
Hide file tree
Showing 7 changed files with 447 additions and 6 deletions.
52 changes: 47 additions & 5 deletions benchmarks/startup.bench.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,60 @@
const os = require('os');
const { expect } = require('chai');
const { performance } = require('perf_hooks');
const times = require('ramda/src/times');
const median = require('ramda/src/median');
const map = require('ramda/src/map');
const prop = require('ramda/src/prop');

const [ { speed: cpuSpeed } ] = os.cpus();

function clearRequireCache() {
Object.keys(require.cache).forEach(function (key) {
delete require.cache[key];
});
}

function runBenchmark(fn, count) {
const results = [];

times(() => {
const startTime = performance.now();
const startMemory = process.memoryUsage().rss;
fn();
const endTime = performance.now();
const endMemory = process.memoryUsage().rss;
const duration = endTime - startTime;
const memory = endMemory - startMemory;

results.push({ duration, memory });
}, count);

const medianDuration = median(map(prop('duration'), results));
const medianMemory = median(map(prop('memory'), results));

return { medianDuration, medianMemory };
}

describe('startup / require time', () => {
it('should not take longer as the defined budget to require the plugin', () => {
const budget = 85000 / cpuSpeed;

const startTime = performance.now();
require('../index');
const endTime = performance.now();
const loadTime = endTime - startTime;
const { medianDuration } = runBenchmark(() => {
clearRequireCache();
require('../index');
}, 50);

expect(medianDuration).to.be.below(budget);
});

it('should not consume more memory as the defined budget', () => {
const budget = 600000;

const { medianMemory } = runBenchmark(() => {
clearRequireCache();
require('../index');
}, 50);

expect(loadTime).to.be.below(budget);
expect(medianMemory).to.be.below(budget);
});
});
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
|:heavy_check_mark:|| [max-top-level-suites](max-top-level-suites.md) | limit the number of top-level suites in a single file
|:heavy_check_mark:|:wrench:| [no-async-describe](no-async-describe.md) | disallow async functions passed to describe
|:heavy_check_mark:|| [no-exclusive-tests](no-exclusive-tests.md) | disallow exclusive mocha tests
||| [no-exports](no-exports.md) | disallow exports from test files
|:heavy_check_mark:|| [no-global-tests](no-global-tests.md) | disallow global tests
||| [no-hooks](no-hooks.md) | disallow hooks
|:heavy_check_mark:|| [no-hooks-for-single-case](no-hooks-for-single-case.md) | disallow hooks for a single test or test suite
Expand Down
39 changes: 39 additions & 0 deletions docs/rules/no-exports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Disallow exports from test files (no-exports)

Test files should have only one purpose, which is testing a specific unit. Using exports could mean the test file is also used to provide and expose utility or library functionalities, instead those should be moved to separate files.

## Rule Details

This rule looks for CommonJS or ESM export statements and flags them as a problem when the same file also contains a use of a mocha function.

The following patterns are considered warnings:

```js
describe(function () { /* ... */ });
module.exports = 'foo';

it('works', function () { /* ... */ });
exports.foo = 'bar';

beforeEach(function () { /* ... */ });
export default 'foo';

afterEach(function () { /* ... */ });
export const foo = 'bar';
```

These patterns would not be considered warnings:

```js
describe(function () { /* ... */ });

it('works', function () { /* ... */ });

beforeEach(function () { /* ... */ });

afterEach(function () { /* ... */ });
```

## When Not To Use It

When you use the [`exports`](https://mochajs.org/#exports) interface it is not recommended to use this rule.
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = {
'max-top-level-suites': require('./lib/rules/max-top-level-suites'),
'no-async-describe': require('./lib/rules/no-async-describe'),
'no-exclusive-tests': require('./lib/rules/no-exclusive-tests'),
'no-exports': require('./lib/rules/no-exports'),
'no-global-tests': require('./lib/rules/no-global-tests'),
'no-hooks': require('./lib/rules/no-hooks'),
'no-hooks-for-single-case': require('./lib/rules/no-hooks-for-single-case'),
Expand All @@ -32,6 +33,7 @@ module.exports = {
'mocha/max-top-level-suites': [ 'error', { limit: 1 } ],
'mocha/no-async-describe': 'error',
'mocha/no-exclusive-tests': 'warn',
'mocha/no-exports': 'error',
'mocha/no-global-tests': 'error',
'mocha/no-hooks': 'off',
'mocha/no-hooks-for-single-case': 'warn',
Expand Down
56 changes: 56 additions & 0 deletions lib/rules/no-exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict';

const createAstUtils = require('../util/ast');

module.exports = {
meta: {
docs: {
description: 'Disallow exports from test files'
},
messages: {
unexpectedExport: 'Unexpected export from a test file'
},
type: 'suggestion'
},
create(context) {
const astUtils = createAstUtils(context.settings);
const exportNodes = [];
let hasTestCase = false;

function isCommonJsExport(node) {
if (node.type === 'MemberExpression') {
const name = astUtils.getNodeName(node);

return name === 'module.exports' || name.startsWith('exports.');
}

return false;
}

return {
'Program:exit'() {
if (hasTestCase && exportNodes.length > 0) {
for (const node of exportNodes) {
context.report({ node, messageId: 'unexpectedExport' });
}
}
},

CallExpression(node) {
if (astUtils.isMochaFunctionCall(node, context.getScope())) {
hasTestCase = true;
}
},

'ExportNamedDeclaration, ExportDefaultDeclaration, ExportAllDeclaration'(node) {
exportNodes.push(node);
},

AssignmentExpression(node) {
if (isCommonJsExport(node.left)) {
exportNodes.push(node);
}
}
};
}
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"test": "npm run test:unit:with-coverage && npm run test:bench",
"test:unit": "mocha test --recursive --reporter dot",
"test:unit:with-coverage": "nyc npm run test:unit",
"test:bench": "mocha benchmarks",
"test:bench": "mocha -t 10000 benchmarks",
"coveralls": "cat ./build/coverage/lcov.info | coveralls",
"changelog": "pr-log"
},
Expand Down
Loading

0 comments on commit 904aaf7

Please sign in to comment.