Skip to content

Commit a0f0ee1

Browse files
committedSep 4, 2019
✨ add node/no-exports-assign rule (fixes #175)
1 parent abbfb27 commit a0f0ee1

File tree

6 files changed

+156
-2
lines changed

6 files changed

+156
-2
lines changed
 

‎README.md

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ $ npm install --save-dev eslint eslint-plugin-node
6464

6565
| Rule ID | Description | |
6666
|:--------|:------------|:--:|
67+
| [node/no-callback-literal](./docs/rules/no-callback-literal.md) | ensure Node.js-style error-first callback pattern is followed | |
68+
| [node/no-exports-assign](./docs/rules/no-exports-assign.md) | disallow the assignment to `exports` | |
6769
| [node/no-extraneous-import](./docs/rules/no-extraneous-import.md) | disallow `import` declarations which import extraneous modules | ⭐️ |
6870
| [node/no-extraneous-require](./docs/rules/no-extraneous-require.md) | disallow `require()` expressions which import extraneous modules | ⭐️ |
6971
| [node/no-missing-import](./docs/rules/no-missing-import.md) | disallow `import` declarations which import non-existence modules | ⭐️ |

‎docs/rules/no-callback-literal.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# node/no-callback-literal
2-
3-
> Ensures the Node.js error-first callback pattern is followed
2+
> ensure Node.js-style error-first callback pattern is followed
43
54
When invoking a callback function which uses the Node.js error-first callback pattern, all of your errors should either use the `Error` class or a subclass of it. It is also acceptable to use `undefined` or `null` if there is no error.
65

‎docs/rules/no-exports-assign.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# node/no-exports-assign
2+
> disallow the assignment to `exports`
3+
4+
To assign to `exports` variable would not work as expected.
5+
6+
```js
7+
// This assigned object is not exported.
8+
// You need to use `module.exports = { ... }`.
9+
exports = {
10+
foo: 1
11+
}
12+
```
13+
14+
## 📖 Rule Details
15+
16+
This rule is aimed at disallowing `exports = {}`, but allows `module.exports = exports = {}` to avoid conflict with [node/exports-style](./exports-style.md) rule's `allowBatchAssign` option.
Has a conversation. Original line has a conversation.
17+
18+
👍 Examples of **correct** code for this rule:
19+
20+
```js
21+
/*eslint node/no-exports-assign: error */
22+
23+
module.exports.foo = 1
24+
exports.bar = 2
25+
26+
module.exports = {}
27+
28+
// allows `exports = {}` if along with `module.exports =`
29+
module.exports = exports = {}
30+
exports = module.exports = {}
31+
```
32+
33+
👎 Examples of **incorrect** code for this rule:
34+
35+
```js
36+
/*eslint node/no-exports-assign: error */
37+
38+
exports = {}
39+
```
40+
41+
42+
## 🔎 Implementation
43+
44+
- [Rule source](../../lib/rules/no-exports-assign.js)
45+
- [Test source](../../tests/lib/rules/no-exports-assign.js)

‎lib/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ module.exports = {
1212
rules: {
1313
"exports-style": require("./rules/exports-style"),
1414
"file-extension-in-import": require("./rules/file-extension-in-import"),
15+
"no-callback-literal": require("./rules/no-callback-literal"),
1516
"no-deprecated-api": require("./rules/no-deprecated-api"),
17+
"no-exports-assign": require("./rules/no-exports-assign"),
1618
"no-extraneous-import": require("./rules/no-extraneous-import"),
1719
"no-extraneous-require": require("./rules/no-extraneous-require"),
1820
"no-missing-import": require("./rules/no-missing-import"),

‎lib/rules/no-exports-assign.js

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* @author Toru Nagashima <https://github.com/mysticatea>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
"use strict"
6+
7+
const { findVariable } = require("eslint-utils")
8+
9+
function isExports(node, scope) {
10+
let variable = null
11+
12+
return (
13+
node != null &&
14+
node.type === "Identifier" &&
15+
node.name === "exports" &&
16+
(variable = findVariable(scope, node)) != null &&
17+
variable.scope.type === "global"
18+
)
19+
}
20+
21+
function isModuleExports(node, scope) {
22+
let variable = null
23+
24+
return (
25+
node != null &&
26+
node.type === "MemberExpression" &&
27+
!node.computed &&
28+
node.object.type === "Identifier" &&
29+
node.object.name === "module" &&
30+
node.property.type === "Identifier" &&
31+
node.property.name === "exports" &&
32+
(variable = findVariable(scope, node.object)) != null &&
33+
variable.scope.type === "global"
34+
)
35+
}
36+
37+
module.exports = {
38+
meta: {
39+
docs: {
40+
description: "disallow the assignment to `exports`",
41+
category: "Possible Errors",
42+
recommended: false,
43+
url:
44+
"https://github.com/mysticatea/eslint-plugin-node/blob/v9.2.0/docs/rules/no-exports-assign.md",
45+
},
46+
fixable: null,
47+
messages: {
48+
forbidden:
49+
"Unexpected assignment to 'exports' variable. Use 'module.exports' instead.",
50+
},
51+
schema: [],
52+
type: "problem",
53+
},
54+
create(context) {
55+
return {
56+
AssignmentExpression(node) {
57+
const scope = context.getScope()
58+
if (
59+
!isExports(node.left, scope) ||
60+
// module.exports = exports = {}
61+
(node.parent.type === "AssignmentExpression" &&
62+
node.parent.right === node &&
63+
isModuleExports(node.parent.left, scope)) ||
64+
// exports = module.exports = {}
65+
(node.right.type === "AssignmentExpression" &&
66+
isModuleExports(node.right.left, scope))
67+
) {
68+
return
69+
}
70+
71+
context.report({ node, messageId: "forbidden" })
72+
},
73+
}
74+
},
75+
}

‎tests/lib/rules/no-exports-assign.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @author Toru Nagashima <https://github.com/mysticatea>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
"use strict"
6+
7+
const { RuleTester } = require("eslint")
8+
const rule = require("../../../lib/rules/no-exports-assign.js")
9+
10+
new RuleTester({
11+
globals: {
12+
exports: "writable",
13+
module: "readonly",
14+
},
15+
}).run("no-exports-assign", rule, {
16+
valid: [
17+
"module.exports.foo = 1",
18+
"exports.bar = 1",
19+
"module.exports = exports = {}",
20+
"exports = module.exports = {}",
21+
"function f(exports) { exports = {} }",
22+
],
23+
invalid: [
24+
{
25+
code: "exports = {}",
26+
errors: [
27+
"Unexpected assignment to 'exports' variable. Use 'module.exports' instead.",
28+
],
29+
},
30+
],
31+
})

0 commit comments

Comments
 (0)
Please sign in to comment.