Skip to content

Commit c3fa502

Browse files
New: prefer-single-export rule
1 parent c975742 commit c3fa502

File tree

6 files changed

+154
-0
lines changed

6 files changed

+154
-0
lines changed

Diff for: CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
44
This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
55

66
## [Unreleased]
7+
### Added
8+
- Add [`prefer-single-export`] rule: style-guide rule to report use of multiple named exports ([#721], thanks [@Alaneor])
9+
710
### Changed
811
- [`no-extraneous-dependencies`]: use `read-pkg-up` to simplify finding + loading `package.json` ([#680], thanks [@wtgtybhertgeghgtwtg])
912

@@ -376,7 +379,9 @@ for info on changes for earlier releases.
376379
[`no-webpack-loader-syntax`]: ./docs/rules/no-webpack-loader-syntax.md
377380
[`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md
378381
[`unambiguous`]: ./docs/rules/unambiguous.md
382+
[`prefer-single-export`]: ./docs/rules/prefer-single-export.md
379383

384+
[#721]: https://github.com/benmosher/eslint-plugin-import/pull/721
380385
[#680]: https://github.com/benmosher/eslint-plugin-import/pull/680
381386
[#654]: https://github.com/benmosher/eslint-plugin-import/pull/654
382387
[#639]: https://github.com/benmosher/eslint-plugin-import/pull/639
@@ -561,3 +566,4 @@ for info on changes for earlier releases.
561566
[@ntdb]: https://github.com/ntdb
562567
[@jakubsta]: https://github.com/jakubsta
563568
[@wtgtybhertgeghgtwtg]: https://github.com/wtgtybhertgeghgtwtg
569+
[@Alaneor]: https://github.com/Alaneor

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
7676
* Limit the maximum number of dependencies a module can have ([`max-dependencies`])
7777
* Forbid unassigned imports ([`no-unassigned-import`])
7878
* Forbid named default exports ([`no-named-default`])
79+
* Prefer single named export declaration ([`prefer-single-export`])
7980

8081
[`first`]: ./docs/rules/first.md
8182
[`no-duplicates`]: ./docs/rules/no-duplicates.md
@@ -87,6 +88,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
8788
[`max-dependencies`]: ./docs/rules/max-dependencies.md
8889
[`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md
8990
[`no-named-default`]: ./docs/rules/no-named-default.md
91+
[`prefer-single-export`]: ./docs/rules/prefer-single-export.md
9092

9193
## Installation
9294

Diff for: docs/rules/prefer-single-export.md

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# prefer-single-export
2+
3+
Reports when multiple named exports or CommonJS assignments 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, your exports will remain at one place, making it easier to see what named exports a module provides.
6+
7+
## Rule Details
8+
9+
This rule warns whenever a single file contains multiple named exports or assignments to `module.exports` (or `exports`).
10+
11+
### Valid
12+
13+
```js
14+
export default function test() {}
15+
// A single named export -> ok
16+
export const valid = true
17+
```
18+
19+
```js
20+
const first = true
21+
const second = true
22+
23+
// A single named export -> ok
24+
export {
25+
first,
26+
second,
27+
}
28+
```
29+
30+
```js
31+
// A single exports assignment -> ok
32+
module.exports = {
33+
first: true,
34+
second: true
35+
}
36+
```
37+
38+
### Invalid
39+
40+
```js
41+
// Multiple named exports -> not ok!
42+
export const first = true
43+
export const second = true
44+
```
45+
46+
```js
47+
// Multiple exports assignments -> not ok!
48+
exports.first = true
49+
exports.second = true
50+
```
51+
52+
## When Not To Use It
53+
54+
If you do not mind having multiple named exports in a single module, 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+
'prefer-single-export': require('./rules/prefer-single-export'),
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/prefer-single-export.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const MULTI_EXPORT = 'Multiple named export declarations'
2+
const MULTI_MODULE_EXPORTS = 'Multiple CommonJS exports'
3+
4+
const meta = {}
5+
6+
function create(context) {
7+
const issues = new Map()
8+
9+
return {
10+
ExportNamedDeclaration(node) {
11+
issues.set(node, MULTI_EXPORT)
12+
},
13+
14+
MemberExpression(node) {
15+
if (node.object.name === 'module' && node.property.name === 'exports') {
16+
issues.set(node, MULTI_MODULE_EXPORTS)
17+
}
18+
19+
if (node.object.name === 'exports') {
20+
issues.set(node, MULTI_MODULE_EXPORTS)
21+
}
22+
},
23+
24+
'Program:exit': function onExit() {
25+
if (issues.size > 1) {
26+
for (const [node, message] of issues) {
27+
context.report({ node, message })
28+
}
29+
}
30+
},
31+
}
32+
}
33+
34+
export default {
35+
meta,
36+
create,
37+
}

Diff for: tests/src/rules/prefer-single-export.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { test } from '../utils'
2+
import { RuleTester } from 'eslint'
3+
import rule from 'rules/prefer-single-export'
4+
5+
const ruleTester = new RuleTester()
6+
7+
ruleTester.run('prefer-single-export', rule, {
8+
valid: [
9+
test({ code: 'export const test = true' }),
10+
test({ code: 'export default {}\nexport const test = true' }),
11+
test({ code: [
12+
'const first = true',
13+
'const second = true',
14+
'export { first,\nsecond }',
15+
].join('\n') }),
16+
test({ code: 'module.exports = {} '}),
17+
test({ code: 'module.exports = { test: true,\nanother: false }' }),
18+
test({ code: 'exports.test = true' }),
19+
],
20+
invalid: [
21+
test({
22+
code: [
23+
'export const test = true',
24+
'export const another = true',
25+
].join('\n'),
26+
errors: [
27+
'Multiple named export declarations',
28+
'Multiple named export declarations',
29+
],
30+
}),
31+
test({
32+
code: [
33+
'module.exports = {}',
34+
'module.exports.test = true',
35+
'module.exports.another = true',
36+
].join('\n'),
37+
errors: [
38+
'Multiple CommonJS exports',
39+
'Multiple CommonJS exports',
40+
'Multiple CommonJS exports',
41+
],
42+
}),
43+
test({
44+
code: [
45+
'module.exports = {}',
46+
'module.exports = {}',
47+
].join('\n'),
48+
errors: [
49+
'Multiple CommonJS exports',
50+
'Multiple CommonJS exports',
51+
],
52+
}),
53+
],
54+
})

0 commit comments

Comments
 (0)