Skip to content

Commit

Permalink
feat: support no-override-exports
Browse files Browse the repository at this point in the history
  • Loading branch information
atian25 committed Feb 7, 2018
1 parent 0f4f3ac commit 6aa4465
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .autod.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

module.exports = {
write: true,
prefix: '^',
test: [
'test',
],
dep: [
],
devdep: [
'egg-ci',
'egg-bin',
'autod',
'eslint',
'eslint-config-egg',
'webstorm-disable-index',
],
exclude: [
'./test/fixtures',
],
};
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage
3 changes: 3 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "eslint-config-egg"
}
24 changes: 24 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!--
Thank you for your pull request. Please review below requirements.
Bug fixes and new features should include tests and possibly benchmarks.
Contributors guide: https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md
感谢您贡献代码。请确认下列 checklist 的完成情况。
Bug 修复和新功能必须包含测试,必要时请附上性能测试。
Contributors guide: https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md
-->

##### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [ ] `npm test` passes
- [ ] tests and/or benchmarks are included
- [ ] documentation is changed or added
- [ ] commit message follows commit guidelines

##### Affected core subsystem(s)
<!-- Provide affected core subsystem(s). -->


##### Description of change
<!-- Provide a description of the change below this comment. -->
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
logs/
npm-debug.log
node_modules/
coverage/
.idea/
run/
.DS_Store
*.swp
.vscode
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# eslint-plugin-eggache

custom eslint rule for egg RTFM questions

[![NPM version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![Test coverage][codecov-image]][codecov-url]
[![David deps][david-image]][david-url]
[![Known Vulnerabilities][snyk-image]][snyk-url]
[![NPM download][download-image]][download-url]

[npm-image]: https://img.shields.io/npm/v/eslint-plugin-eggache.svg?style=flat-square
[npm-url]: https://npmjs.org/package/eslint-plugin-eggache
[travis-image]: https://img.shields.io/travis/{{org}}/eslint-plugin-eggache.svg?style=flat-square
[travis-url]: https://travis-ci.org/{{org}}/eslint-plugin-eggache
[codecov-image]: https://codecov.io/gh/{{org}}/eslint-plugin-eggache/branch/master/graph/badge.svg
[codecov-url]: https://codecov.io/gh/{{org}}/eslint-plugin-eggache
[david-image]: https://img.shields.io/david/{{org}}/eslint-plugin-eggache.svg?style=flat-square
[david-url]: https://david-dm.org/{{org}}/eslint-plugin-eggache
[snyk-image]: https://snyk.io/test/npm/eslint-plugin-eggache/badge.svg?style=flat-square
[snyk-url]: https://snyk.io/test/npm/eslint-plugin-eggache
[download-image]: https://img.shields.io/npm/dm/eslint-plugin-eggache.svg?style=flat-square
[download-url]: https://npmjs.org/package/eslint-plugin-eggache

## Usage

```bash
npm i eslint-plugin-eggache --save
```

## Rules

### no-override-exports

A common mistake that newbee will make - override `module.exports` and `exports`.

```js
/* eslint eggache/no-override-exports: [ 'error' ] */

// config/config.default.js
exports.view = {};

module.exports = appInfo => {
const config = exports = {};
config.keys = '123456';
return config;
}
```

**Options**:

The first options is a boolean, default to false, means only check:
- `config/config.*.js`
- `config/plugin.*.js`

set it to `true` means to check all files.

```js
/* eslint eggache/no-override-exports: [ 'error', true ] */

// due to options `true`, this will pass the lint
// ${app_root}/app.js
module.exports = exports = {};
exports.keys = '123456';
```
7 changes: 7 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

module.exports = {
rules: {
'no-override-exports': require('./lib/rules/no-override-exports'),
},
};
82 changes: 82 additions & 0 deletions lib/rules/no-override-exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict';

const path = require('path');

module.exports = {
meta: {
docs: {
description: 'Disallow override exports',
category: 'Possible Errors',
recommended: true,
url: 'https://github.com/eggjs/eslint-plugin-eggache#no-override-exports',
},
schema: [
{
// if true, check all file, otherwise only check `config/config.*.js` or config/plugin.*.js`
type: 'boolean',
},
],
messages: {
exportsAfterModule: 'Don\'t use `exports` after `module.exports`',
moduleAfterExports: 'Don\'t use `module.exports` after `exports`',
repeatModule: 'Don\'t use `module.exports` multiple times',
},
},
create(context) {
let hasExports = false;
let hasModule = false;
const shouldCheckAll = context.options[0];
// if `!shouldCheckAll`, then only check `<input>` or `config/config.*.js` or `config/plugin.*.js`
if (!shouldCheckAll && !isConfig(context)) return {};

return {
ExpressionStatement(node) {
// only consider the root scope
if (context.getScope().type !== 'global') return;
if (node.expression.type !== 'AssignmentExpression') return;
const testNode = node.expression.left;
if (isExports(testNode)) {
if (hasModule) {
context.report({ node, messageId: 'exportsAfterModule' });
}
hasExports = true;
} else if (isModule(testNode)) {
if (hasExports) {
context.report({ node, messageId: 'moduleAfterExports' });
}
if (hasModule) {
context.report({ node, messageId: 'repeatModule' });
}
hasModule = true;
}
},
};
},
};

function isConfig(context) {
const filePath = context.getFilename();
if (filePath === '<input>') return true;
const baseName = path.basename(filePath);
const dirname = path.basename(path.dirname(filePath));
return dirname === 'config' && (baseName.startsWith('config.') || baseName.startsWith('plugin.'));
}

function isExports(node) {
// exports.view = '';
// exports['view'] = '';
return node.object.type === 'Identifier' && node.object.name === 'exports';
}

function isModule(node) {
// module.exports = {};
// module.exports = () => {};
if (node.object.type === 'Identifier') {
return node.object.name === 'module' && node.property.type === 'Identifier' && node.property.name === 'exports';
}

if (node.object.type === 'MemberExpression') {
const realNode = node.object;
return realNode.object.name === 'module' && realNode.property.type === 'Identifier' && realNode.property.name === 'exports';
}
}
40 changes: 40 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "eslint-plugin-eggache",
"version": "1.0.0",
"description": "custom eslint rule for egg RTFM questions",
"dependencies": {
"is-type-of": "^1.2.0"
},
"devDependencies": {
"autod": "^2.8.0",
"egg-bin": "^3.4.0",
"egg-ci": "^1.7.0",
"eslint": "^4.17.0",
"eslint-config-egg": "^4.2.0",
"webstorm-disable-index": "^1.2.0"
},
"engines": {
"node": ">=6.0.0"
},
"scripts": {
"autod": "autod",
"lint": "eslint .",
"test": "npm run lint -- --fix && egg-bin pkgfiles && npm run test-local",
"test-local": "egg-bin test",
"cov": "egg-bin cov",
"ci": "npm run lint && egg-bin pkgfiles --check && npm run cov",
"pkgfiles": "egg-bin pkgfiles"
},
"ci": {
"version": "6, 8"
},
"repository": {
"type": "git",
"url": ""
},
"files": [
"index.js"
],
"author": "TZ <atian25@qq.com>",
"license": "MIT"
}
64 changes: 64 additions & 0 deletions test/rules/no-override-exports.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict';

const defineTest = require('../utils').defineTest;

defineTest('no-override-exports', {
valid: [
`
module.exports = appInfo => {
const config = exports = {};
exports.view = '';
return config;
}
`,
`
exports.view = '';
exports['view'] = '';
`,
'module.exports = {};',
'module.exports = appInfo => {};',
{
code: `
module.exports = {};
exports.view = true;
`,
// don't check not config files
filename: '/app_root/app.js',
options: [ ],
},
],

invalid: [
{
code: `
exports.view = '';
module.exports = {};
`,
errors: [{ messageId: 'moduleAfterExports' }],
},
{
code: `
module.exports = {};
exports.view = '';
`,
errors: [{ messageId: 'exportsAfterModule' }],
},
{
code: `
module.exports = {};
module.exports.view = '';
`,
errors: [{ messageId: 'repeatModule' }],
},
{
code: `
module.exports = {};
exports.view = true;
`,
// check all files
filename: '/app_root/app.js',
options: [ true ],
errors: [{ messageId: 'exportsAfterModule' }],
},
],
});
25 changes: 25 additions & 0 deletions test/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const is = require('is-type-of');
const RuleTester = require('eslint').RuleTester;
const ruleTester = new RuleTester();

exports.defineTest = (ruleName, fixtures) => {
const rule = require(`../lib/rules/${ruleName}`);

fixtures.valid = (fixtures.valid || []).map(item => normalizeFixture(item));
fixtures.invalid = (fixtures.invalid || []).map(item => normalizeFixture(item));

return ruleTester.run(`test/rules/${ruleName}.test.js`, rule, fixtures);
};

function normalizeFixture(input) {
if (is.string(input)) {
input = {
code: input,
};
}
// input.
input.parserOptions = Object.assign({ ecmaVersion: 6 }, input.parserOptions);
return input;
}

0 comments on commit 6aa4465

Please sign in to comment.