-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #721 from robertrossmann/prefer-single-export
New: `group-exports` rule
- Loading branch information
Showing
6 changed files
with
413 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# group-exports | ||
|
||
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. | ||
|
||
**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. | ||
|
||
## Rule Details | ||
|
||
This rule warns whenever a single file contains multiple named export declarations or multiple assignments to `module.exports` (or `exports`). | ||
|
||
### Valid | ||
|
||
```js | ||
// A single named export declaration -> ok | ||
export const valid = true | ||
``` | ||
|
||
```js | ||
const first = true | ||
const second = true | ||
|
||
// A single named export declaration -> ok | ||
export { | ||
first, | ||
second, | ||
} | ||
``` | ||
|
||
```js | ||
// A single exports assignment -> ok | ||
module.exports = { | ||
first: true, | ||
second: true | ||
} | ||
``` | ||
|
||
```js | ||
const first = true | ||
const second = true | ||
|
||
// A single exports assignment -> ok | ||
module.exports = { | ||
first, | ||
second, | ||
} | ||
``` | ||
|
||
```js | ||
function test() {} | ||
test.property = true | ||
test.another = true | ||
|
||
// A single exports assignment -> ok | ||
module.exports = test | ||
``` | ||
|
||
|
||
### Invalid | ||
|
||
```js | ||
// Multiple named export statements -> not ok! | ||
export const first = true | ||
export const second = true | ||
``` | ||
|
||
```js | ||
// Multiple exports assignments -> not ok! | ||
exports.first = true | ||
exports.second = true | ||
``` | ||
|
||
```js | ||
// Multiple exports assignments -> not ok! | ||
module.exports = {} | ||
module.exports.first = true | ||
``` | ||
|
||
```js | ||
// Multiple exports assignments -> not ok! | ||
module.exports = () => {} | ||
module.exports.first = true | ||
module.exports.second = true | ||
``` | ||
|
||
## When Not To Use It | ||
|
||
If you do not mind having your exports spread across the file, you can safely turn this rule off. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
const meta = {} | ||
/* eslint-disable max-len */ | ||
const errors = { | ||
ExportNamedDeclaration: 'Multiple named export declarations; consolidate all named exports into a single export declaration', | ||
AssignmentExpression: 'Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`', | ||
} | ||
/* eslint-enable max-len */ | ||
|
||
/** | ||
* Returns an array with names of the properties in the accessor chain for MemberExpression nodes | ||
* | ||
* Example: | ||
* | ||
* `module.exports = {}` => ['module', 'exports'] | ||
* `module.exports.property = true` => ['module', 'exports', 'property'] | ||
* | ||
* @param {Node} node AST Node (MemberExpression) | ||
* @return {Array} Array with the property names in the chain | ||
* @private | ||
*/ | ||
function accessorChain(node) { | ||
const chain = [] | ||
|
||
do { | ||
chain.unshift(node.property.name) | ||
|
||
if (node.object.type === 'Identifier') { | ||
chain.unshift(node.object.name) | ||
break | ||
} | ||
|
||
node = node.object | ||
} while (node.type === 'MemberExpression') | ||
|
||
return chain | ||
} | ||
|
||
function create(context) { | ||
const nodes = { | ||
modules: new Set(), | ||
commonjs: new Set(), | ||
} | ||
|
||
return { | ||
ExportNamedDeclaration(node) { | ||
nodes.modules.add(node) | ||
}, | ||
|
||
AssignmentExpression(node) { | ||
if (node.left.type !== 'MemberExpression') { | ||
return | ||
} | ||
|
||
const chain = accessorChain(node.left) | ||
|
||
// Assignments to module.exports | ||
// Deeper assignments are ignored since they just modify what's already being exported | ||
// (ie. module.exports.exported.prop = true is ignored) | ||
if (chain[0] === 'module' && chain[1] === 'exports' && chain.length <= 3) { | ||
nodes.commonjs.add(node) | ||
return | ||
} | ||
|
||
// Assignments to exports (exports.* = *) | ||
if (chain[0] === 'exports' && chain.length === 2) { | ||
nodes.commonjs.add(node) | ||
return | ||
} | ||
}, | ||
|
||
'Program:exit': function onExit() { | ||
// Report multiple `export` declarations (ES2015 modules) | ||
if (nodes.modules.size > 1) { | ||
nodes.modules.forEach(node => { | ||
context.report({ | ||
node, | ||
message: errors[node.type], | ||
}) | ||
}) | ||
} | ||
|
||
// Report multiple `module.exports` assignments (CommonJS) | ||
if (nodes.commonjs.size > 1) { | ||
nodes.commonjs.forEach(node => { | ||
context.report({ | ||
node, | ||
message: errors[node.type], | ||
}) | ||
}) | ||
} | ||
}, | ||
} | ||
} | ||
|
||
export default { | ||
meta, | ||
create, | ||
} |
Oops, something went wrong.