Skip to content

Commit d230d5e

Browse files
New: group-exports rule
1 parent ccd9394 commit d230d5e

File tree

6 files changed

+403
-0
lines changed

6 files changed

+403
-0
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
55

66
## [Unreleased]
77

8+
- Add [`group-exports`] rule: style-guide rule to report use of multiple named exports ([#898], thanks [@robertrossmann])
89

910
## [2.7.0] - 2017-07-06
1011
### Changed
@@ -417,13 +418,15 @@ for info on changes for earlier releases.
417418
[`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md
418419
[`unambiguous`]: ./docs/rules/unambiguous.md
419420
[`no-anonymous-default-export`]: ./docs/rules/no-anonymous-default-export.md
421+
[`group-exports`]: ./docs/rules/group-exports.md
420422

421423
[`memo-parser`]: ./memo-parser/README.md
422424

423425
[#843]: https://github.com/benmosher/eslint-plugin-import/pull/843
424426
[#871]: https://github.com/benmosher/eslint-plugin-import/pull/871
425427
[#742]: https://github.com/benmosher/eslint-plugin-import/pull/742
426428
[#737]: https://github.com/benmosher/eslint-plugin-import/pull/737
429+
[#898]: https://github.com/benmosher/eslint-plugin-import/pull/721
427430
[#712]: https://github.com/benmosher/eslint-plugin-import/pull/712
428431
[#696]: https://github.com/benmosher/eslint-plugin-import/pull/696
429432
[#685]: https://github.com/benmosher/eslint-plugin-import/pull/685
@@ -632,3 +635,4 @@ for info on changes for earlier releases.
632635
[@eelyafi]: https://github.com/eelyafi
633636
[@mastilver]: https://github.com/mastilver
634637
[@jseminck]: https://github.com/jseminck
638+
[@robertrossmann]: https://github.com/robertrossmann

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
7777
* Forbid unassigned imports ([`no-unassigned-import`])
7878
* Forbid named default exports ([`no-named-default`])
7979
* Forbid anonymous values as default exports ([`no-anonymous-default-export`])
80+
* Prefer named exports to be grouped together in a single export declaration ([`group-exports`])
8081

8182
[`first`]: ./docs/rules/first.md
8283
[`no-duplicates`]: ./docs/rules/no-duplicates.md
@@ -89,6 +90,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
8990
[`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md
9091
[`no-named-default`]: ./docs/rules/no-named-default.md
9192
[`no-anonymous-default-export`]: ./docs/rules/no-anonymous-default-export.md
93+
[`group-exports`]: ./docs/rules/group-exports.md
9294

9395
## Installation
9496

Diff for: docs/rules/group-exports.md

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# group-exports
2+
3+
Reports when named exports are not grouped together in a single `export` declaration or when multiple assignments to CommonJS `module.exports` or `exports` object are present in a single file.
4+
5+
**Rationale:** An `export` declaration or `module.exports` assignment can appear anywhere in the code. By requiring a single export declaration all your exports will remain at one place, making it easier to see what exports a module provides.
6+
7+
## Rule Details
8+
9+
This rule warns whenever a single file contains multiple named export declarations or multiple assignments to `module.exports` (or `exports`).
10+
11+
### Valid
12+
13+
```js
14+
// A single named export declaration -> ok
15+
export const valid = true
16+
```
17+
18+
```js
19+
const first = true
20+
const second = true
21+
22+
// A single named export declaration -> ok
23+
export {
24+
first,
25+
second,
26+
}
27+
```
28+
29+
```js
30+
// A single exports assignment -> ok
31+
module.exports = {
32+
first: true,
33+
second: true
34+
}
35+
```
36+
37+
```js
38+
const first = true
39+
const second = true
40+
41+
// A single exports assignment -> ok
42+
module.exports = {
43+
first,
44+
second,
45+
}
46+
```
47+
48+
```js
49+
function test() {}
50+
test.property = true
51+
test.another = true
52+
53+
// A single exports assignment -> ok
54+
module.exports = test
55+
```
56+
57+
58+
### Invalid
59+
60+
```js
61+
// Multiple named export statements -> not ok!
62+
export const first = true
63+
export const second = true
64+
```
65+
66+
```js
67+
// Multiple exports assignments -> not ok!
68+
exports.first = true
69+
exports.second = true
70+
```
71+
72+
```js
73+
// Multiple exports assignments -> not ok!
74+
module.exports = {}
75+
module.exports.first = true
76+
```
77+
78+
```js
79+
// Multiple exports assignments -> not ok!
80+
module.exports = () => {}
81+
module.exports.first = true
82+
module.exports.second = true
83+
```
84+
85+
## When Not To Use It
86+
87+
If you do not mind having your exports spread across the file, you can safely turn this rule off.

Diff for: src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const rules = {
99
'extensions': require('./rules/extensions'),
1010
'no-restricted-paths': require('./rules/no-restricted-paths'),
1111
'no-internal-modules': require('./rules/no-internal-modules'),
12+
'group-exports': require('./rules/group-exports'),
1213

1314
'no-named-default': require('./rules/no-named-default'),
1415
'no-named-as-default': require('./rules/no-named-as-default'),

Diff for: src/rules/group-exports.js

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
const meta = {}
2+
/* eslint-disable max-len */
3+
const errors = {
4+
ExportNamedDeclaration: 'Multiple named export declarations; consolidate all named exports into a single export declaration',
5+
AssignmentExpression: 'Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`',
6+
}
7+
/* eslint-enable max-len */
8+
9+
/**
10+
* Returns an array with names of the properties in the accessor chain for MemberExpression nodes
11+
*
12+
* Example:
13+
*
14+
* `module.exports = {}` => ['module', 'exports']
15+
* `module.exports.property = true` => ['module', 'exports', 'property']
16+
*
17+
* @param {Node} node AST Node (MemberExpression)
18+
* @return {Array} Array with the property names in the chain
19+
* @private
20+
*/
21+
function accessorChain(node) {
22+
const chain = []
23+
24+
do {
25+
chain.unshift(node.property.name)
26+
27+
if (node.object.type === 'Identifier') {
28+
chain.unshift(node.object.name)
29+
break
30+
}
31+
32+
node = node.object
33+
} while (node.type === 'MemberExpression')
34+
35+
return chain
36+
}
37+
38+
function create(context) {
39+
const nodes = {
40+
modules: new Set(),
41+
commonjs: new Set(),
42+
}
43+
44+
return {
45+
ExportNamedDeclaration(node) {
46+
nodes.modules.add(node)
47+
},
48+
49+
AssignmentExpression(node) {
50+
if (node.left.type !== 'MemberExpression') {
51+
return
52+
}
53+
54+
const chain = accessorChain(node.left)
55+
56+
// Assignments to module.exports
57+
// Deeper assignments are ignored since they just modify what's already being exported
58+
// (ie. module.exports.exported.prop = true is ignored)
59+
if (chain[0] === 'module' && chain[1] === 'exports' && chain.length <= 3) {
60+
nodes.commonjs.add(node)
61+
return
62+
}
63+
64+
// Assignments to exports (exports.* = *)
65+
if (chain[0] === 'exports' && chain.length === 2) {
66+
nodes.commonjs.add(node)
67+
return
68+
}
69+
},
70+
71+
'Program:exit': function onExit() {
72+
// Report multiple `export` declarations (ES2015 modules)
73+
if (nodes.modules.size > 1) {
74+
for (const node of nodes.modules) {
75+
context.report({
76+
node,
77+
message: errors[node.type],
78+
})
79+
}
80+
}
81+
82+
// Report multiple `module.exports` assignments (CommonJS)
83+
if (nodes.commonjs.size > 1) {
84+
for (const node of nodes.commonjs) {
85+
context.report({
86+
node,
87+
message: errors[node.type],
88+
})
89+
}
90+
}
91+
},
92+
}
93+
}
94+
95+
export default {
96+
meta,
97+
create,
98+
}

0 commit comments

Comments
 (0)