Skip to content

Commit

Permalink
✨ add node/prefer-promises rules (fixes #157, fixes #158)
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea committed May 3, 2019
1 parent 9143043 commit e41a1e0
Show file tree
Hide file tree
Showing 9 changed files with 599 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .eslintrc.js
Expand Up @@ -58,5 +58,16 @@ module.exports = {
],
},
},
{
files: ["**/rules/prefer-promises/*.js"],
rules: {
"@mysticatea/eslint-plugin/require-meta-docs-url": [
"error",
{
pattern: `https://github.com/mysticatea/eslint-plugin-node/blob/v${version}/docs/rules/prefer-promises/{{name}}.md`,
},
],
},
},
],
}
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -92,6 +92,8 @@ $ npm install --save-dev eslint eslint-plugin-node
| [node/prefer-global/text-encoder](./docs/rules/prefer-global/text-encoder.md) | enforce either `TextEncoder` or `require("util").TextEncoder` | |
| [node/prefer-global/url-search-params](./docs/rules/prefer-global/url-search-params.md) | enforce either `URLSearchParams` or `require("url").URLSearchParams` | |
| [node/prefer-global/url](./docs/rules/prefer-global/url.md) | enforce either `URL` or `require("url").URL` | |
| [node/prefer-promises/dns](./docs/rules/prefer-promises/dns.md) | enforce `require("dns").promises` | |
| [node/prefer-promises/fs](./docs/rules/prefer-promises/fs.md) | enforce `require("fs").promises` | |

### Deprecated rules

Expand Down
54 changes: 54 additions & 0 deletions docs/rules/prefer-promises/dns.md
@@ -0,0 +1,54 @@
# enforce `require("dns").promises` (prefer-promises/dns)

Since Node.js v11.14.0, `require("dns").promises` API has been stable.
Promise API and `async`/`await` syntax will make code more readable than callback API.

## Rule Details

This rule disallows callback API in favor of promise API.

Examples of :-1: **incorrect** code for this rule:

```js
/*eslint node/prefer-promises/dns: [error]*/
const dns = require("dns")

function lookup(hostname) {
dns.lookup(hostname, (error, address, family) => {
//...
})
}
```

```js
/*eslint node/prefer-promises/dns: [error]*/
import dns from "dns"

function lookup(hostname) {
dns.lookup(hostname, (error, address, family) => {
//...
})
}
```

Examples of :+1: **correct** code for this rule:

```js
/*eslint node/prefer-promises/dns: [error]*/
const { promises: dns } = require("dns")

async function lookup(hostname) {
const { address, family } = await dns.lookup(hostname)
//...
}
```

```js
/*eslint node/prefer-promises/dns: [error]*/
import { promises as dns } from "dns"

async function lookup(hostname) {
const { address, family } = await dns.lookup(hostname)
//...
}
```
54 changes: 54 additions & 0 deletions docs/rules/prefer-promises/fs.md
@@ -0,0 +1,54 @@
# enforce `require("fs").promises` (prefer-promises/fs)

Since Node.js v11.14.0, `require("fs").promises` API has been stable.
Promise API and `async`/`await` syntax will make code more readable than callback API.

## Rule Details

This rule disallows callback API in favor of promise API.

Examples of :-1: **incorrect** code for this rule:

```js
/*eslint node/prefer-promises/fs: [error]*/
const fs = require("fs")

function readData(filePath) {
fs.readFile(filePath, "utf8", (error, content) => {
//...
})
}
```

```js
/*eslint node/prefer-promises/fs: [error]*/
import fs from "fs"

function readData(filePath) {
fs.readFile(filePath, "utf8", (error, content) => {
//...
})
}
```

Examples of :+1: **correct** code for this rule:

```js
/*eslint node/prefer-promises/fs: [error]*/
const { promises: fs } = require("fs")

async function readData(filePath) {
const content = await fs.readFile(filePath, "utf8")
//...
}
```

```js
/*eslint node/prefer-promises/fs: [error]*/
import { promises as fs } from "fs"

async function readData(filePath) {
const content = await fs.readFile(filePath, "utf8")
//...
}
```
2 changes: 2 additions & 0 deletions lib/index.js
Expand Up @@ -30,6 +30,8 @@ module.exports = {
"prefer-global/text-encoder": require("./rules/prefer-global/text-encoder"),
"prefer-global/url-search-params": require("./rules/prefer-global/url-search-params"),
"prefer-global/url": require("./rules/prefer-global/url"),
"prefer-promises/dns": require("./rules/prefer-promises/dns"),
"prefer-promises/fs": require("./rules/prefer-promises/fs"),
"process-exit-as-throw": require("./rules/process-exit-as-throw"),
shebang: require("./rules/shebang"),

Expand Down
74 changes: 74 additions & 0 deletions lib/rules/prefer-promises/dns.js
@@ -0,0 +1,74 @@
/**
* @author Toru Nagashima
* See LICENSE file in root directory for full license.
*/
"use strict"

const { CALL, CONSTRUCT, ReferenceTracker } = require("eslint-utils")

const trackMap = {
dns: {
lookup: { [CALL]: true },
lookupService: { [CALL]: true },
Resolver: { [CONSTRUCT]: true },
getServers: { [CALL]: true },
resolve: { [CALL]: true },
resolve4: { [CALL]: true },
resolve6: { [CALL]: true },
resolveAny: { [CALL]: true },
resolveCname: { [CALL]: true },
resolveMx: { [CALL]: true },
resolveNaptr: { [CALL]: true },
resolveNs: { [CALL]: true },
resolvePtr: { [CALL]: true },
resolveSoa: { [CALL]: true },
resolveSrv: { [CALL]: true },
resolveTxt: { [CALL]: true },
reverse: { [CALL]: true },
setServers: { [CALL]: true },
},
}

module.exports = {
meta: {
docs: {
description: 'enforce `require("dns").promises`',
category: "Stylistic Issues",
recommended: false,
url:
"https://github.com/mysticatea/eslint-plugin-node/blob/v8.0.1/docs/rules/prefer-promises/dns.md",
},
fixable: null,
messages: {
preferPromises: "Use 'dns.promises.{{name}}()' instead.",
preferPromisesNew: "Use 'new dns.promises.{{name}}()' instead.",
},
schema: [],
type: "suggestion",
},

create(context) {
return {
"Program:exit"() {
const scope = context.getScope()
const tracker = new ReferenceTracker(scope, { mode: "legacy" })
const references = [
...tracker.iterateCjsReferences(trackMap),
...tracker.iterateEsmReferences(trackMap),
]

for (const { node, path } of references) {
const name = path[path.length - 1]
const isClass = name[0] === name[0].toUpperCase()
context.report({
node,
messageId: isClass
? "preferPromisesNew"
: "preferPromises",
data: { name },
})
}
},
}
},
}
76 changes: 76 additions & 0 deletions lib/rules/prefer-promises/fs.js
@@ -0,0 +1,76 @@
/**
* @author Toru Nagashima
* See LICENSE file in root directory for full license.
*/
"use strict"

const { CALL, ReferenceTracker } = require("eslint-utils")

const trackMap = {
fs: {
access: { [CALL]: true },
copyFile: { [CALL]: true },
open: { [CALL]: true },
rename: { [CALL]: true },
truncate: { [CALL]: true },
rmdir: { [CALL]: true },
mkdir: { [CALL]: true },
readdir: { [CALL]: true },
readlink: { [CALL]: true },
symlink: { [CALL]: true },
lstat: { [CALL]: true },
stat: { [CALL]: true },
link: { [CALL]: true },
unlink: { [CALL]: true },
chmod: { [CALL]: true },
lchmod: { [CALL]: true },
lchown: { [CALL]: true },
chown: { [CALL]: true },
utimes: { [CALL]: true },
realpath: { [CALL]: true },
mkdtemp: { [CALL]: true },
writeFile: { [CALL]: true },
appendFile: { [CALL]: true },
readFile: { [CALL]: true },
},
}

module.exports = {
meta: {
docs: {
description: 'enforce `require("fs").promises`',
category: "Stylistic Issues",
recommended: false,
url:
"https://github.com/mysticatea/eslint-plugin-node/blob/v8.0.1/docs/rules/prefer-promises/fs.md",
},
fixable: null,
messages: {
preferPromises: "Use 'fs.promises.{{name}}()' instead.",
},
schema: [],
type: "suggestion",
},

create(context) {
return {
"Program:exit"() {
const scope = context.getScope()
const tracker = new ReferenceTracker(scope, { mode: "legacy" })
const references = [
...tracker.iterateCjsReferences(trackMap),
...tracker.iterateEsmReferences(trackMap),
]

for (const { node, path } of references) {
const name = path[path.length - 1]
context.report({
node,
messageId: "preferPromises",
data: { name },
})
}
},
}
},
}

0 comments on commit e41a1e0

Please sign in to comment.