Skip to content

Commit e41a1e0

Browse files
committed
✨ add node/prefer-promises rules (fixes #157, fixes #158)
1 parent 9143043 commit e41a1e0

File tree

9 files changed

+599
-0
lines changed

9 files changed

+599
-0
lines changed

.eslintrc.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,16 @@ module.exports = {
5858
],
5959
},
6060
},
61+
{
62+
files: ["**/rules/prefer-promises/*.js"],
63+
rules: {
64+
"@mysticatea/eslint-plugin/require-meta-docs-url": [
65+
"error",
66+
{
67+
pattern: `https://github.com/mysticatea/eslint-plugin-node/blob/v${version}/docs/rules/prefer-promises/{{name}}.md`,
68+
},
69+
],
70+
},
71+
},
6172
],
6273
}

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ $ npm install --save-dev eslint eslint-plugin-node
9292
| [node/prefer-global/text-encoder](./docs/rules/prefer-global/text-encoder.md) | enforce either `TextEncoder` or `require("util").TextEncoder` | |
9393
| [node/prefer-global/url-search-params](./docs/rules/prefer-global/url-search-params.md) | enforce either `URLSearchParams` or `require("url").URLSearchParams` | |
9494
| [node/prefer-global/url](./docs/rules/prefer-global/url.md) | enforce either `URL` or `require("url").URL` | |
95+
| [node/prefer-promises/dns](./docs/rules/prefer-promises/dns.md) | enforce `require("dns").promises` | |
96+
| [node/prefer-promises/fs](./docs/rules/prefer-promises/fs.md) | enforce `require("fs").promises` | |
9597

9698
### Deprecated rules
9799

docs/rules/prefer-promises/dns.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# enforce `require("dns").promises` (prefer-promises/dns)
2+
3+
Since Node.js v11.14.0, `require("dns").promises` API has been stable.
4+
Promise API and `async`/`await` syntax will make code more readable than callback API.
5+
6+
## Rule Details
7+
8+
This rule disallows callback API in favor of promise API.
9+
10+
Examples of :-1: **incorrect** code for this rule:
11+
12+
```js
13+
/*eslint node/prefer-promises/dns: [error]*/
14+
const dns = require("dns")
15+
16+
function lookup(hostname) {
17+
dns.lookup(hostname, (error, address, family) => {
18+
//...
19+
})
20+
}
21+
```
22+
23+
```js
24+
/*eslint node/prefer-promises/dns: [error]*/
25+
import dns from "dns"
26+
27+
function lookup(hostname) {
28+
dns.lookup(hostname, (error, address, family) => {
29+
//...
30+
})
31+
}
32+
```
33+
34+
Examples of :+1: **correct** code for this rule:
35+
36+
```js
37+
/*eslint node/prefer-promises/dns: [error]*/
38+
const { promises: dns } = require("dns")
39+
40+
async function lookup(hostname) {
41+
const { address, family } = await dns.lookup(hostname)
42+
//...
43+
}
44+
```
45+
46+
```js
47+
/*eslint node/prefer-promises/dns: [error]*/
48+
import { promises as dns } from "dns"
49+
50+
async function lookup(hostname) {
51+
const { address, family } = await dns.lookup(hostname)
52+
//...
53+
}
54+
```

docs/rules/prefer-promises/fs.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# enforce `require("fs").promises` (prefer-promises/fs)
2+
3+
Since Node.js v11.14.0, `require("fs").promises` API has been stable.
4+
Promise API and `async`/`await` syntax will make code more readable than callback API.
5+
6+
## Rule Details
7+
8+
This rule disallows callback API in favor of promise API.
9+
10+
Examples of :-1: **incorrect** code for this rule:
11+
12+
```js
13+
/*eslint node/prefer-promises/fs: [error]*/
14+
const fs = require("fs")
15+
16+
function readData(filePath) {
17+
fs.readFile(filePath, "utf8", (error, content) => {
18+
//...
19+
})
20+
}
21+
```
22+
23+
```js
24+
/*eslint node/prefer-promises/fs: [error]*/
25+
import fs from "fs"
26+
27+
function readData(filePath) {
28+
fs.readFile(filePath, "utf8", (error, content) => {
29+
//...
30+
})
31+
}
32+
```
33+
34+
Examples of :+1: **correct** code for this rule:
35+
36+
```js
37+
/*eslint node/prefer-promises/fs: [error]*/
38+
const { promises: fs } = require("fs")
39+
40+
async function readData(filePath) {
41+
const content = await fs.readFile(filePath, "utf8")
42+
//...
43+
}
44+
```
45+
46+
```js
47+
/*eslint node/prefer-promises/fs: [error]*/
48+
import { promises as fs } from "fs"
49+
50+
async function readData(filePath) {
51+
const content = await fs.readFile(filePath, "utf8")
52+
//...
53+
}
54+
```

lib/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ module.exports = {
3030
"prefer-global/text-encoder": require("./rules/prefer-global/text-encoder"),
3131
"prefer-global/url-search-params": require("./rules/prefer-global/url-search-params"),
3232
"prefer-global/url": require("./rules/prefer-global/url"),
33+
"prefer-promises/dns": require("./rules/prefer-promises/dns"),
34+
"prefer-promises/fs": require("./rules/prefer-promises/fs"),
3335
"process-exit-as-throw": require("./rules/process-exit-as-throw"),
3436
shebang: require("./rules/shebang"),
3537

lib/rules/prefer-promises/dns.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @author Toru Nagashima
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
"use strict"
6+
7+
const { CALL, CONSTRUCT, ReferenceTracker } = require("eslint-utils")
8+
9+
const trackMap = {
10+
dns: {
11+
lookup: { [CALL]: true },
12+
lookupService: { [CALL]: true },
13+
Resolver: { [CONSTRUCT]: true },
14+
getServers: { [CALL]: true },
15+
resolve: { [CALL]: true },
16+
resolve4: { [CALL]: true },
17+
resolve6: { [CALL]: true },
18+
resolveAny: { [CALL]: true },
19+
resolveCname: { [CALL]: true },
20+
resolveMx: { [CALL]: true },
21+
resolveNaptr: { [CALL]: true },
22+
resolveNs: { [CALL]: true },
23+
resolvePtr: { [CALL]: true },
24+
resolveSoa: { [CALL]: true },
25+
resolveSrv: { [CALL]: true },
26+
resolveTxt: { [CALL]: true },
27+
reverse: { [CALL]: true },
28+
setServers: { [CALL]: true },
29+
},
30+
}
31+
32+
module.exports = {
33+
meta: {
34+
docs: {
35+
description: 'enforce `require("dns").promises`',
36+
category: "Stylistic Issues",
37+
recommended: false,
38+
url:
39+
"https://github.com/mysticatea/eslint-plugin-node/blob/v8.0.1/docs/rules/prefer-promises/dns.md",
40+
},
41+
fixable: null,
42+
messages: {
43+
preferPromises: "Use 'dns.promises.{{name}}()' instead.",
44+
preferPromisesNew: "Use 'new dns.promises.{{name}}()' instead.",
45+
},
46+
schema: [],
47+
type: "suggestion",
48+
},
49+
50+
create(context) {
51+
return {
52+
"Program:exit"() {
53+
const scope = context.getScope()
54+
const tracker = new ReferenceTracker(scope, { mode: "legacy" })
55+
const references = [
56+
...tracker.iterateCjsReferences(trackMap),
57+
...tracker.iterateEsmReferences(trackMap),
58+
]
59+
60+
for (const { node, path } of references) {
61+
const name = path[path.length - 1]
62+
const isClass = name[0] === name[0].toUpperCase()
63+
context.report({
64+
node,
65+
messageId: isClass
66+
? "preferPromisesNew"
67+
: "preferPromises",
68+
data: { name },
69+
})
70+
}
71+
},
72+
}
73+
},
74+
}

lib/rules/prefer-promises/fs.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @author Toru Nagashima
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
"use strict"
6+
7+
const { CALL, ReferenceTracker } = require("eslint-utils")
8+
9+
const trackMap = {
10+
fs: {
11+
access: { [CALL]: true },
12+
copyFile: { [CALL]: true },
13+
open: { [CALL]: true },
14+
rename: { [CALL]: true },
15+
truncate: { [CALL]: true },
16+
rmdir: { [CALL]: true },
17+
mkdir: { [CALL]: true },
18+
readdir: { [CALL]: true },
19+
readlink: { [CALL]: true },
20+
symlink: { [CALL]: true },
21+
lstat: { [CALL]: true },
22+
stat: { [CALL]: true },
23+
link: { [CALL]: true },
24+
unlink: { [CALL]: true },
25+
chmod: { [CALL]: true },
26+
lchmod: { [CALL]: true },
27+
lchown: { [CALL]: true },
28+
chown: { [CALL]: true },
29+
utimes: { [CALL]: true },
30+
realpath: { [CALL]: true },
31+
mkdtemp: { [CALL]: true },
32+
writeFile: { [CALL]: true },
33+
appendFile: { [CALL]: true },
34+
readFile: { [CALL]: true },
35+
},
36+
}
37+
38+
module.exports = {
39+
meta: {
40+
docs: {
41+
description: 'enforce `require("fs").promises`',
42+
category: "Stylistic Issues",
43+
recommended: false,
44+
url:
45+
"https://github.com/mysticatea/eslint-plugin-node/blob/v8.0.1/docs/rules/prefer-promises/fs.md",
46+
},
47+
fixable: null,
48+
messages: {
49+
preferPromises: "Use 'fs.promises.{{name}}()' instead.",
50+
},
51+
schema: [],
52+
type: "suggestion",
53+
},
54+
55+
create(context) {
56+
return {
57+
"Program:exit"() {
58+
const scope = context.getScope()
59+
const tracker = new ReferenceTracker(scope, { mode: "legacy" })
60+
const references = [
61+
...tracker.iterateCjsReferences(trackMap),
62+
...tracker.iterateEsmReferences(trackMap),
63+
]
64+
65+
for (const { node, path } of references) {
66+
const name = path[path.length - 1]
67+
context.report({
68+
node,
69+
messageId: "preferPromises",
70+
data: { name },
71+
})
72+
}
73+
},
74+
}
75+
},
76+
}

0 commit comments

Comments
 (0)