Skip to content

Commit 940b294

Browse files
committed
Merge branch 'no-mutable-exports': merges + closes #290
2 parents e2e52a3 + 24e2c9d commit 940b294

File tree

6 files changed

+165
-0
lines changed

6 files changed

+165
-0
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
88
- [`newline-after-import`], new rule. ([#245], thanks [@singles])
99
- Added an `optionalDependencies` option to [`no-extraneous-dependencies`] to allow/forbid optional dependencies ([#266], thanks [@jfmengels]).
1010
- Added `newlines-between` option to [`order`] rule ([#298], thanks [@singles])
11+
- add [`no-mutable-exports`] rule ([#290], thanks [@josh])
1112

1213
### Fixed
1314
- [`extensions`]: fallback to source path for extension enforcement if imported
@@ -194,9 +195,11 @@ for info on changes for earlier releases.
194195
[`order`]: ./docs/rules/order.md
195196
[`named`]: ./docs/rules/named.md
196197
[`newline-after-import`]: ./docs/rules/newline-after-import.md
198+
[`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md
197199

198200
[#298]: https://github.com/benmosher/eslint-plugin-import/pull/298
199201
[#296]: https://github.com/benmosher/eslint-plugin-import/pull/296
202+
[#290]: https://github.com/benmosher/eslint-plugin-import/pull/290
200203
[#289]: https://github.com/benmosher/eslint-plugin-import/pull/289
201204
[#288]: https://github.com/benmosher/eslint-plugin-import/pull/288
202205
[#287]: https://github.com/benmosher/eslint-plugin-import/pull/287
@@ -263,3 +266,4 @@ for info on changes for earlier releases.
263266
[@taion]: https://github.com/taion
264267
[@strawbrary]: https://github.com/strawbrary
265268
[@SimenB]: https://github.com/SimenB
269+
[@josh]: https://github.com/josh

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
3636
[`no-named-as-default-member`]: ./docs/rules/no-named-as-default-member.md
3737
[`no-deprecated`]: ./docs/rules/no-deprecated.md
3838
[`no-extraneous-dependencies`]: ./docs/rules/no-extraneous-dependencies.md
39+
[`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md
3940

4041
**Module systems:**
4142

docs/rules/no-mutable-exports.md

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# no-mutable-exports
2+
3+
Forbids the use of mutable exports with `var` or `let`.
4+
5+
## Rule Details
6+
7+
Valid:
8+
9+
```js
10+
export const count = 1
11+
export function getCount() {}
12+
export class Counter {}
13+
```
14+
15+
...whereas here exports will be reported:
16+
17+
```js
18+
export let count = 2
19+
export var count = 3
20+
21+
let count = 4
22+
export { count } // reported here
23+
```
24+
25+
## Functions/Classes
26+
27+
Note that exported function/class declaration identifiers may be reassigned,
28+
but are not flagged by this rule at this time. They may be in the future, if a
29+
reassignment is detected, i.e.
30+
31+
```js
32+
// possible future behavior!
33+
export class Counter {} // reported here: exported class is reassigned on line [x].
34+
Counter = KitchenSink // not reported here unless you enable no-class-assign
35+
36+
// this pre-declaration reassignment is valid on account of function hoisting
37+
getCount = function getDuke() {} // not reported here without no-func-assign
38+
export function getCount() {} // reported here: exported function is reassigned on line [x].
39+
```
40+
41+
To prevent general reassignment of these identifiers, exported or not, you may
42+
want to enable the following core ESLint rules:
43+
44+
- [no-func-assign]
45+
- [no-class-assign]
46+
47+
[no-func-assign]: http://eslint.org/docs/rules/no-func-assign
48+
[no-class-assign]: http://eslint.org/docs/rules/no-class-assign
49+
50+
## When Not To Use It
51+
52+
If your environment correctly implements mutable export bindings.

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const rules = {
55
'namespace': require('./rules/namespace'),
66
'no-namespace': require('./rules/no-namespace'),
77
'export': require('./rules/export'),
8+
'no-mutable-exports': require('./rules/no-mutable-exports'),
89
'extensions': require('./rules/extensions'),
910

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

src/rules/no-mutable-exports.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
module.exports = function (context) {
2+
function checkDeclaration(node) {
3+
const {kind} = node
4+
if (kind === 'var' || kind === 'let') {
5+
context.report(node, `Exporting mutable '${kind}' binding, use 'const' instead.`)
6+
}
7+
}
8+
9+
function checkDeclarationsInScope({variables}, name) {
10+
for (let variable of variables) {
11+
if (variable.name === name) {
12+
for (let def of variable.defs) {
13+
if (def.type === 'Variable') {
14+
checkDeclaration(def.parent)
15+
}
16+
}
17+
}
18+
}
19+
}
20+
21+
function handleExportDefault(node) {
22+
const scope = context.getScope()
23+
24+
if (node.declaration.name) {
25+
checkDeclarationsInScope(scope, node.declaration.name)
26+
}
27+
}
28+
29+
function handleExportNamed(node) {
30+
const scope = context.getScope()
31+
32+
if (node.declaration) {
33+
checkDeclaration(node.declaration)
34+
} else {
35+
for (let specifier of node.specifiers) {
36+
checkDeclarationsInScope(scope, specifier.local.name)
37+
}
38+
}
39+
}
40+
41+
return {
42+
'ExportDefaultDeclaration': handleExportDefault,
43+
'ExportNamedDeclaration': handleExportNamed,
44+
}
45+
}

tests/src/rules/no-mutable-exports.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {test} from '../utils'
2+
import {RuleTester} from 'eslint'
3+
import rule from 'rules/no-mutable-exports'
4+
5+
const ruleTester = new RuleTester()
6+
7+
ruleTester.run('no-mutable-exports', rule, {
8+
valid: [
9+
test({ code: 'export const count = 1'}),
10+
test({ code: 'export function getCount() {}'}),
11+
test({ code: 'export class Counter {}'}),
12+
test({ code: 'export default count = 1'}),
13+
test({ code: 'export default function getCount() {}'}),
14+
test({ code: 'export default class Counter {}'}),
15+
test({ code: 'const count = 1\nexport { count }'}),
16+
test({ code: 'const count = 1\nexport { count as counter }'}),
17+
test({ code: 'const count = 1\nexport default count'}),
18+
test({ code: 'const count = 1\nexport { count as default }'}),
19+
test({ code: 'function getCount() {}\nexport { getCount }'}),
20+
test({ code: 'function getCount() {}\nexport { getCount as getCounter }'}),
21+
test({ code: 'function getCount() {}\nexport default getCount'}),
22+
test({ code: 'function getCount() {}\nexport { getCount as default }'}),
23+
test({ code: 'class Counter {}\nexport { Counter }'}),
24+
test({ code: 'class Counter {}\nexport { Counter as Count }'}),
25+
test({ code: 'class Counter {}\nexport default Counter'}),
26+
test({ code: 'class Counter {}\nexport { Counter as default }'}),
27+
],
28+
invalid: [
29+
test({
30+
code: 'export let count = 1',
31+
errors: ['Exporting mutable \'let\' binding, use \'const\' instead.'],
32+
}),
33+
test({
34+
code: 'export var count = 1',
35+
errors: ['Exporting mutable \'var\' binding, use \'const\' instead.'],
36+
}),
37+
test({
38+
code: 'let count = 1\nexport { count }',
39+
errors: ['Exporting mutable \'let\' binding, use \'const\' instead.'],
40+
}),
41+
test({
42+
code: 'var count = 1\nexport { count }',
43+
errors: ['Exporting mutable \'var\' binding, use \'const\' instead.'],
44+
}),
45+
test({
46+
code: 'let count = 1\nexport { count as counter }',
47+
errors: ['Exporting mutable \'let\' binding, use \'const\' instead.'],
48+
}),
49+
test({
50+
code: 'var count = 1\nexport { count as counter }',
51+
errors: ['Exporting mutable \'var\' binding, use \'const\' instead.'],
52+
}),
53+
test({
54+
code: 'let count = 1\nexport default count',
55+
errors: ['Exporting mutable \'let\' binding, use \'const\' instead.'],
56+
}),
57+
test({
58+
code: 'var count = 1\nexport default count',
59+
errors: ['Exporting mutable \'var\' binding, use \'const\' instead.'],
60+
}),
61+
],
62+
})

0 commit comments

Comments
 (0)