Skip to content

Commit 10b24ae

Browse files
authored
feat: add prefer-global/timers rule (#515)
1 parent 2ea0f22 commit 10b24ae

File tree

5 files changed

+376
-0
lines changed

5 files changed

+376
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ For [Shareable Configs](https://eslint.org/docs/latest/developer-guide/shareable
169169
| [prefer-global/process](docs/rules/prefer-global/process.md) | enforce either `process` or `require("process")` | | | |
170170
| [prefer-global/text-decoder](docs/rules/prefer-global/text-decoder.md) | enforce either `TextDecoder` or `require("util").TextDecoder` | | | |
171171
| [prefer-global/text-encoder](docs/rules/prefer-global/text-encoder.md) | enforce either `TextEncoder` or `require("util").TextEncoder` | | | |
172+
| [prefer-global/timers](docs/rules/prefer-global/timers.md) | enforce either global timer functions or `require("timers")` | | | |
172173
| [prefer-global/url](docs/rules/prefer-global/url.md) | enforce either `URL` or `require("url").URL` | | | |
173174
| [prefer-global/url-search-params](docs/rules/prefer-global/url-search-params.md) | enforce either `URLSearchParams` or `require("url").URLSearchParams` | | | |
174175
| [prefer-node-protocol](docs/rules/prefer-node-protocol.md) | enforce using the `node:` protocol when importing Node.js builtin modules. | | 🔧 | |

docs/rules/prefer-global/timers.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Enforce either global timer functions or `require("timers")` (`n/prefer-global/timers`)
2+
3+
<!-- end auto-generated rule header -->
4+
5+
The timer functions `clearImmediate`, `clearInterval`, `clearTimeout`, `setImmediate`, `setInterval`, and `setTimeout` are defined as global variables but are also available in the `timers` module.
6+
7+
```js
8+
console.log(setTimeout === require("timers").setTimeout) //→ true
9+
```
10+
11+
It will be readable if we use either consistently.
12+
13+
## 📖 Rule Details
14+
15+
This rule enforces which timer functions we should use.
16+
17+
### Options
18+
19+
This rule has a string option.
20+
21+
```json
22+
{
23+
"n/prefer-global/timers": ["error", "always" | "never"]
24+
}
25+
```
26+
27+
- `"always"` (default) ... enforces to use the global timer functions rather than `require("timers").*`.
28+
- `"never"` ... enforces to use `require("timers").*` rather than the global timer functions.
29+
30+
#### always
31+
32+
Examples of 👎 **incorrect** code for this rule:
33+
34+
```js
35+
/*eslint n/prefer-global/timers: [error]*/
36+
37+
const { setTimeout } = require("timers")
38+
setTimeout(() => {}, 1000)
39+
```
40+
41+
Examples of 👍 **correct** code for this rule:
42+
43+
```js
44+
/*eslint n/prefer-global/timers: [error]*/
45+
46+
setTimeout(() => {}, 1000)
47+
```
48+
49+
#### never
50+
51+
Examples of 👎 **incorrect** code for the `"never"` option:
52+
53+
```js
54+
/*eslint n/prefer-global/timers: [error, never]*/
55+
56+
setTimeout(() => {}, 1000)
57+
```
58+
59+
Examples of 👍 **correct** code for the `"never"` option:
60+
61+
```js
62+
/*eslint n/prefer-global/timers: [error, never]*/
63+
64+
const { setTimeout } = require("timers")
65+
setTimeout(() => {}, 1000)
66+
```
67+
68+
## 🔎 Implementation
69+
70+
- [Rule source](../../../lib/rules/prefer-global/timers.js)
71+
- [Test source](../../../tests/lib/rules/prefer-global/timers.js)

lib/all-rules.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module.exports = {
4444
"prefer-global/text-encoder": require("./rules/prefer-global/text-encoder"),
4545
"prefer-global/url-search-params": require("./rules/prefer-global/url-search-params"),
4646
"prefer-global/url": require("./rules/prefer-global/url"),
47+
"prefer-global/timers": require("./rules/prefer-global/timers"),
4748
"prefer-node-protocol": require("./rules/prefer-node-protocol"),
4849
"prefer-promises/dns": require("./rules/prefer-promises/dns"),
4950
"prefer-promises/fs": require("./rules/prefer-promises/fs"),

lib/rules/prefer-global/timers.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* @author Pixel998
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
"use strict"
6+
7+
const { READ } = require("@eslint-community/eslint-utils")
8+
const checkForPreferGlobal = require("../../util/check-prefer-global")
9+
10+
const traceMap = {
11+
globals: {
12+
clearImmediate: { [READ]: true },
13+
clearInterval: { [READ]: true },
14+
clearTimeout: { [READ]: true },
15+
setImmediate: { [READ]: true },
16+
setInterval: { [READ]: true },
17+
setTimeout: { [READ]: true },
18+
},
19+
modules: {
20+
timers: {
21+
clearImmediate: { [READ]: true },
22+
clearInterval: { [READ]: true },
23+
clearTimeout: { [READ]: true },
24+
setImmediate: { [READ]: true },
25+
setInterval: { [READ]: true },
26+
setTimeout: { [READ]: true },
27+
},
28+
"node:timers": {
29+
clearImmediate: { [READ]: true },
30+
clearInterval: { [READ]: true },
31+
clearTimeout: { [READ]: true },
32+
setImmediate: { [READ]: true },
33+
setInterval: { [READ]: true },
34+
setTimeout: { [READ]: true },
35+
},
36+
},
37+
}
38+
39+
/** @type {import('../rule-module').RuleModule} */
40+
module.exports = {
41+
meta: {
42+
docs: {
43+
description:
44+
'enforce either global timer functions or `require("timers")`',
45+
recommended: false,
46+
url: "https://github.com/eslint-community/eslint-plugin-n/blob/HEAD/docs/rules/prefer-global/timers.md",
47+
},
48+
type: "suggestion",
49+
fixable: null,
50+
schema: [{ enum: ["always", "never"] }],
51+
messages: {
52+
preferGlobal:
53+
"Unexpected use of 'require(\"timers\").*'. Use the global variable instead.",
54+
preferModule:
55+
"Unexpected use of the global variable. Use 'require(\"timers\").*' instead.",
56+
},
57+
},
58+
59+
create(context) {
60+
return {
61+
"Program:exit"() {
62+
checkForPreferGlobal(context, traceMap)
63+
},
64+
}
65+
},
66+
}
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/**
2+
* @author Pixel998
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
"use strict"
6+
7+
const RuleTester = require("#test-helpers").RuleTester
8+
const rule = require("../../../../lib/rules/prefer-global/timers")
9+
10+
const provideModuleMethods = ["require", "process.getBuiltinModule"]
11+
12+
new RuleTester().run("prefer-global/timers", rule, {
13+
valid: [
14+
"clearImmediate(id)",
15+
"clearInterval(id)",
16+
"clearTimeout(id)",
17+
"setImmediate(() => {})",
18+
"setInterval(() => {}, 1000)",
19+
"setTimeout(() => {}, 1000)",
20+
{
21+
code: "clearImmediate(id)",
22+
options: ["always"],
23+
},
24+
{
25+
code: "clearInterval(id)",
26+
options: ["always"],
27+
},
28+
{
29+
code: "clearTimeout(id)",
30+
options: ["always"],
31+
},
32+
{
33+
code: "setImmediate(() => {})",
34+
options: ["always"],
35+
},
36+
{
37+
code: "setInterval(() => {}, 1000)",
38+
options: ["always"],
39+
},
40+
{
41+
code: "setTimeout(() => {}, 1000)",
42+
options: ["always"],
43+
},
44+
...provideModuleMethods.flatMap(method => [
45+
{
46+
code: `const { clearImmediate } = ${method}('timers'); clearImmediate(id)`,
47+
options: ["never"],
48+
},
49+
{
50+
code: `const { clearImmediate } = ${method}('node:timers'); clearImmediate(id)`,
51+
options: ["never"],
52+
},
53+
{
54+
code: `const { clearInterval } = ${method}('timers'); clearInterval(id)`,
55+
options: ["never"],
56+
},
57+
{
58+
code: `const { clearInterval } = ${method}('node:timers'); clearInterval(id)`,
59+
options: ["never"],
60+
},
61+
{
62+
code: `const { clearTimeout } = ${method}('timers'); clearTimeout(id)`,
63+
options: ["never"],
64+
},
65+
{
66+
code: `const { clearTimeout } = ${method}('node:timers'); clearTimeout(id)`,
67+
options: ["never"],
68+
},
69+
{
70+
code: `const { setImmediate } = ${method}('timers'); setImmediate(() => {})`,
71+
options: ["never"],
72+
},
73+
{
74+
code: `const { setImmediate } = ${method}('node:timers'); setImmediate(() => {})`,
75+
options: ["never"],
76+
},
77+
{
78+
code: `const { setInterval } = ${method}('timers'); setInterval(() => {}, 1000)`,
79+
options: ["never"],
80+
},
81+
{
82+
code: `const { setInterval } = ${method}('node:timers'); setInterval(() => {}, 1000)`,
83+
options: ["never"],
84+
},
85+
{
86+
code: `const { setTimeout } = ${method}('timers'); setTimeout(() => {}, 1000)`,
87+
options: ["never"],
88+
},
89+
{
90+
code: `const { setTimeout } = ${method}('node:timers'); setTimeout(() => {}, 1000)`,
91+
options: ["never"],
92+
},
93+
]),
94+
],
95+
invalid: [
96+
...provideModuleMethods.flatMap(method => [
97+
{
98+
code: `const { clearImmediate } = ${method}('timers'); clearImmediate(id)`,
99+
errors: [{ messageId: "preferGlobal" }],
100+
},
101+
{
102+
code: `const { clearImmediate } = ${method}('node:timers'); clearImmediate(id)`,
103+
errors: [{ messageId: "preferGlobal" }],
104+
},
105+
{
106+
code: `const { clearImmediate } = ${method}('timers'); clearImmediate(id)`,
107+
options: ["always"],
108+
errors: [{ messageId: "preferGlobal" }],
109+
},
110+
{
111+
code: `const { clearImmediate } = ${method}('node:timers'); clearImmediate(id)`,
112+
options: ["always"],
113+
errors: [{ messageId: "preferGlobal" }],
114+
},
115+
{
116+
code: `const { clearInterval } = ${method}('timers'); clearInterval(id)`,
117+
errors: [{ messageId: "preferGlobal" }],
118+
},
119+
{
120+
code: `const { clearInterval } = ${method}('node:timers'); clearInterval(id)`,
121+
errors: [{ messageId: "preferGlobal" }],
122+
},
123+
{
124+
code: `const { clearInterval } = ${method}('timers'); clearInterval(id)`,
125+
options: ["always"],
126+
errors: [{ messageId: "preferGlobal" }],
127+
},
128+
{
129+
code: `const { clearInterval } = ${method}('node:timers'); clearInterval(id)`,
130+
options: ["always"],
131+
errors: [{ messageId: "preferGlobal" }],
132+
},
133+
{
134+
code: `const { clearTimeout } = ${method}('timers'); clearTimeout(id)`,
135+
errors: [{ messageId: "preferGlobal" }],
136+
},
137+
{
138+
code: `const { clearTimeout } = ${method}('node:timers'); clearTimeout(id)`,
139+
errors: [{ messageId: "preferGlobal" }],
140+
},
141+
{
142+
code: `const { clearTimeout } = ${method}('timers'); clearTimeout(id)`,
143+
options: ["always"],
144+
errors: [{ messageId: "preferGlobal" }],
145+
},
146+
{
147+
code: `const { clearTimeout } = ${method}('node:timers'); clearTimeout(id)`,
148+
options: ["always"],
149+
errors: [{ messageId: "preferGlobal" }],
150+
},
151+
{
152+
code: `const { setImmediate } = ${method}('timers'); setImmediate(() => {})`,
153+
errors: [{ messageId: "preferGlobal" }],
154+
},
155+
{
156+
code: `const { setImmediate } = ${method}('node:timers'); setImmediate(() => {})`,
157+
errors: [{ messageId: "preferGlobal" }],
158+
},
159+
{
160+
code: `const { setImmediate } = ${method}('timers'); setImmediate(() => {})`,
161+
options: ["always"],
162+
errors: [{ messageId: "preferGlobal" }],
163+
},
164+
{
165+
code: `const { setImmediate } = ${method}('node:timers'); setImmediate(() => {})`,
166+
options: ["always"],
167+
errors: [{ messageId: "preferGlobal" }],
168+
},
169+
{
170+
code: `const { setInterval } = ${method}('timers'); setInterval(() => {}, 1000)`,
171+
errors: [{ messageId: "preferGlobal" }],
172+
},
173+
{
174+
code: `const { setInterval } = ${method}('node:timers'); setInterval(() => {}, 1000)`,
175+
errors: [{ messageId: "preferGlobal" }],
176+
},
177+
{
178+
code: `const { setInterval } = ${method}('timers'); setInterval(() => {}, 1000)`,
179+
options: ["always"],
180+
errors: [{ messageId: "preferGlobal" }],
181+
},
182+
{
183+
code: `const { setInterval } = ${method}('node:timers'); setInterval(() => {}, 1000)`,
184+
options: ["always"],
185+
errors: [{ messageId: "preferGlobal" }],
186+
},
187+
{
188+
code: `const { setTimeout } = ${method}('timers'); setTimeout(() => {}, 1000)`,
189+
errors: [{ messageId: "preferGlobal" }],
190+
},
191+
{
192+
code: `const { setTimeout } = ${method}('node:timers'); setTimeout(() => {}, 1000)`,
193+
errors: [{ messageId: "preferGlobal" }],
194+
},
195+
{
196+
code: `const { setTimeout } = ${method}('timers'); setTimeout(() => {}, 1000)`,
197+
options: ["always"],
198+
errors: [{ messageId: "preferGlobal" }],
199+
},
200+
{
201+
code: `const { setTimeout } = ${method}('node:timers'); setTimeout(() => {}, 1000)`,
202+
options: ["always"],
203+
errors: [{ messageId: "preferGlobal" }],
204+
},
205+
]),
206+
{
207+
code: "clearImmediate(id)",
208+
options: ["never"],
209+
errors: [{ messageId: "preferModule" }],
210+
},
211+
{
212+
code: "clearInterval(id)",
213+
options: ["never"],
214+
errors: [{ messageId: "preferModule" }],
215+
},
216+
{
217+
code: "clearTimeout(id)",
218+
options: ["never"],
219+
errors: [{ messageId: "preferModule" }],
220+
},
221+
{
222+
code: "setImmediate(() => {})",
223+
options: ["never"],
224+
errors: [{ messageId: "preferModule" }],
225+
},
226+
{
227+
code: "setInterval(() => {}, 1000)",
228+
options: ["never"],
229+
errors: [{ messageId: "preferModule" }],
230+
},
231+
{
232+
code: "setTimeout(() => {}, 1000)",
233+
options: ["never"],
234+
errors: [{ messageId: "preferModule" }],
235+
},
236+
],
237+
})

0 commit comments

Comments
 (0)