Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
✨ add node/no-exports-assign rule (fixes #175)
  • Loading branch information
mysticatea committed Sep 4, 2019
1 parent abbfb27 commit a0f0ee1
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -64,6 +64,8 @@ $ npm install --save-dev eslint eslint-plugin-node

| Rule ID | Description | |
|:--------|:------------|:--:|
| [node/no-callback-literal](./docs/rules/no-callback-literal.md) | ensure Node.js-style error-first callback pattern is followed | |
| [node/no-exports-assign](./docs/rules/no-exports-assign.md) | disallow the assignment to `exports` | |
| [node/no-extraneous-import](./docs/rules/no-extraneous-import.md) | disallow `import` declarations which import extraneous modules | ⭐️ |
| [node/no-extraneous-require](./docs/rules/no-extraneous-require.md) | disallow `require()` expressions which import extraneous modules | ⭐️ |
| [node/no-missing-import](./docs/rules/no-missing-import.md) | disallow `import` declarations which import non-existence modules | ⭐️ |
Expand Down
3 changes: 1 addition & 2 deletions docs/rules/no-callback-literal.md
@@ -1,6 +1,5 @@
# node/no-callback-literal

> Ensures the Node.js error-first callback pattern is followed
> ensure Node.js-style error-first callback pattern is followed
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.

Expand Down
45 changes: 45 additions & 0 deletions docs/rules/no-exports-assign.md
@@ -0,0 +1,45 @@
# node/no-exports-assign
> disallow the assignment to `exports`
To assign to `exports` variable would not work as expected.

```js
// This assigned object is not exported.
// You need to use `module.exports = { ... }`.
exports = {
foo: 1
}
```

## 📖 Rule Details

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.

This comment has been minimized.

Copy link
@aleen42

aleen42 Sep 21, 2020

Please tell me why I should callback undefined instead of a literal?


👍 Examples of **correct** code for this rule:

```js
/*eslint node/no-exports-assign: error */

module.exports.foo = 1
exports.bar = 2

module.exports = {}

// allows `exports = {}` if along with `module.exports =`
module.exports = exports = {}
exports = module.exports = {}
```

👎 Examples of **incorrect** code for this rule:

```js
/*eslint node/no-exports-assign: error */

exports = {}
```


## 🔎 Implementation

- [Rule source](../../lib/rules/no-exports-assign.js)
- [Test source](../../tests/lib/rules/no-exports-assign.js)
2 changes: 2 additions & 0 deletions lib/index.js
Expand Up @@ -12,7 +12,9 @@ module.exports = {
rules: {
"exports-style": require("./rules/exports-style"),
"file-extension-in-import": require("./rules/file-extension-in-import"),
"no-callback-literal": require("./rules/no-callback-literal"),
"no-deprecated-api": require("./rules/no-deprecated-api"),
"no-exports-assign": require("./rules/no-exports-assign"),
"no-extraneous-import": require("./rules/no-extraneous-import"),
"no-extraneous-require": require("./rules/no-extraneous-require"),
"no-missing-import": require("./rules/no-missing-import"),
Expand Down
75 changes: 75 additions & 0 deletions lib/rules/no-exports-assign.js
@@ -0,0 +1,75 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"

const { findVariable } = require("eslint-utils")

function isExports(node, scope) {
let variable = null

return (
node != null &&
node.type === "Identifier" &&
node.name === "exports" &&
(variable = findVariable(scope, node)) != null &&
variable.scope.type === "global"
)
}

function isModuleExports(node, scope) {
let variable = null

return (
node != null &&
node.type === "MemberExpression" &&
!node.computed &&
node.object.type === "Identifier" &&
node.object.name === "module" &&
node.property.type === "Identifier" &&
node.property.name === "exports" &&
(variable = findVariable(scope, node.object)) != null &&
variable.scope.type === "global"
)
}

module.exports = {
meta: {
docs: {
description: "disallow the assignment to `exports`",
category: "Possible Errors",
recommended: false,
url:
"https://github.com/mysticatea/eslint-plugin-node/blob/v9.2.0/docs/rules/no-exports-assign.md",
},
fixable: null,
messages: {
forbidden:
"Unexpected assignment to 'exports' variable. Use 'module.exports' instead.",
},
schema: [],
type: "problem",
},
create(context) {
return {
AssignmentExpression(node) {
const scope = context.getScope()
if (
!isExports(node.left, scope) ||
// module.exports = exports = {}
(node.parent.type === "AssignmentExpression" &&
node.parent.right === node &&
isModuleExports(node.parent.left, scope)) ||
// exports = module.exports = {}
(node.right.type === "AssignmentExpression" &&
isModuleExports(node.right.left, scope))
) {
return
}

context.report({ node, messageId: "forbidden" })
},
}
},
}
31 changes: 31 additions & 0 deletions tests/lib/rules/no-exports-assign.js
@@ -0,0 +1,31 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"

const { RuleTester } = require("eslint")
const rule = require("../../../lib/rules/no-exports-assign.js")

new RuleTester({
globals: {
exports: "writable",
module: "readonly",
},
}).run("no-exports-assign", rule, {
valid: [
"module.exports.foo = 1",
"exports.bar = 1",
"module.exports = exports = {}",
"exports = module.exports = {}",
"function f(exports) { exports = {} }",
],
invalid: [
{
code: "exports = {}",
errors: [
"Unexpected assignment to 'exports' variable. Use 'module.exports' instead.",
],
},
],
})

0 comments on commit a0f0ee1

Please sign in to comment.