eslint-plugin-n 17.23.1 → 17.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -165,9 +165,11 @@ For [Shareable Configs](https://eslint.org/docs/latest/developer-guide/shareable
|
|
|
165
165
|
| [no-unsupported-features/node-builtins](docs/rules/no-unsupported-features/node-builtins.md) | disallow unsupported Node.js built-in APIs on the specified version | 🟢 ✅ | | |
|
|
166
166
|
| [prefer-global/buffer](docs/rules/prefer-global/buffer.md) | enforce either `Buffer` or `require("buffer").Buffer` | | | |
|
|
167
167
|
| [prefer-global/console](docs/rules/prefer-global/console.md) | enforce either `console` or `require("console")` | | | |
|
|
168
|
+
| [prefer-global/crypto](docs/rules/prefer-global/crypto.md) | enforce either `crypto` or `require("crypto").webcrypto` | | | |
|
|
168
169
|
| [prefer-global/process](docs/rules/prefer-global/process.md) | enforce either `process` or `require("process")` | | | |
|
|
169
170
|
| [prefer-global/text-decoder](docs/rules/prefer-global/text-decoder.md) | enforce either `TextDecoder` or `require("util").TextDecoder` | | | |
|
|
170
171
|
| [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")` | | | |
|
|
171
173
|
| [prefer-global/url](docs/rules/prefer-global/url.md) | enforce either `URL` or `require("url").URL` | | | |
|
|
172
174
|
| [prefer-global/url-search-params](docs/rules/prefer-global/url-search-params.md) | enforce either `URLSearchParams` or `require("url").URLSearchParams` | | | |
|
|
173
175
|
| [prefer-node-protocol](docs/rules/prefer-node-protocol.md) | enforce using the `node:` protocol when importing Node.js builtin modules. | | 🔧 | |
|
package/lib/all-rules.js
CHANGED
|
@@ -38,11 +38,13 @@ module.exports = {
|
|
|
38
38
|
"no-unsupported-features/node-builtins": require("./rules/no-unsupported-features/node-builtins"),
|
|
39
39
|
"prefer-global/buffer": require("./rules/prefer-global/buffer"),
|
|
40
40
|
"prefer-global/console": require("./rules/prefer-global/console"),
|
|
41
|
+
"prefer-global/crypto": require("./rules/prefer-global/crypto"),
|
|
41
42
|
"prefer-global/process": require("./rules/prefer-global/process"),
|
|
42
43
|
"prefer-global/text-decoder": require("./rules/prefer-global/text-decoder"),
|
|
43
44
|
"prefer-global/text-encoder": require("./rules/prefer-global/text-encoder"),
|
|
44
45
|
"prefer-global/url-search-params": require("./rules/prefer-global/url-search-params"),
|
|
45
46
|
"prefer-global/url": require("./rules/prefer-global/url"),
|
|
47
|
+
"prefer-global/timers": require("./rules/prefer-global/timers"),
|
|
46
48
|
"prefer-node-protocol": require("./rules/prefer-node-protocol"),
|
|
47
49
|
"prefer-promises/dns": require("./rules/prefer-promises/dns"),
|
|
48
50
|
"prefer-promises/fs": require("./rules/prefer-promises/fs"),
|
|
@@ -7,17 +7,21 @@
|
|
|
7
7
|
const path = require("path")
|
|
8
8
|
const fs = require("fs")
|
|
9
9
|
const { convertTsExtensionToJs } = require("../util/map-typescript-extension")
|
|
10
|
+
const getTryExtensions = require("../util/get-try-extensions")
|
|
10
11
|
const visitImport = require("../util/visit-import")
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Get all file extensions of the files which have the same basename.
|
|
14
15
|
* @param {string} filePath The path to the original file to check.
|
|
16
|
+
* @param {string} [basename] Optional basename to use instead of deriving from filePath.
|
|
15
17
|
* @returns {string[]} File extensions.
|
|
16
18
|
*/
|
|
17
|
-
function getExistingExtensions(filePath) {
|
|
19
|
+
function getExistingExtensions(filePath, basename) {
|
|
18
20
|
const directory = path.dirname(filePath)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
if (basename == null) {
|
|
22
|
+
const extension = path.extname(filePath)
|
|
23
|
+
basename = path.basename(filePath, extension)
|
|
24
|
+
}
|
|
21
25
|
|
|
22
26
|
try {
|
|
23
27
|
return fs
|
|
@@ -29,6 +33,34 @@ function getExistingExtensions(filePath) {
|
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Get the extension of an index file in the given directory.
|
|
38
|
+
* @param {string} directoryPath The directory path to check.
|
|
39
|
+
* @param {string[]} tryExtensions Ordered extension preferences.
|
|
40
|
+
* @returns {string | null} The index file extension or null if none.
|
|
41
|
+
*/
|
|
42
|
+
function getIndexExtension(directoryPath, tryExtensions) {
|
|
43
|
+
try {
|
|
44
|
+
if (fs.statSync(directoryPath).isDirectory() === false) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const existing = getExistingExtensions(path.join(directoryPath, "index"))
|
|
52
|
+
if (existing.length === 0) {
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const preferred = tryExtensions.find(ext => existing.includes(ext))
|
|
57
|
+
if (preferred) {
|
|
58
|
+
return preferred
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return existing[0] || null
|
|
62
|
+
}
|
|
63
|
+
|
|
32
64
|
/**
|
|
33
65
|
* @typedef {[
|
|
34
66
|
* ("always" | "never")?,
|
|
@@ -69,6 +101,7 @@ module.exports = {
|
|
|
69
101
|
}
|
|
70
102
|
const defaultStyle = context.options[0] || "always"
|
|
71
103
|
const overrideStyle = context.options[1] || {}
|
|
104
|
+
const tryExtensions = getTryExtensions(context, 1)
|
|
72
105
|
|
|
73
106
|
/**
|
|
74
107
|
* @param {import("../util/import-target.js")} target
|
|
@@ -85,15 +118,74 @@ module.exports = {
|
|
|
85
118
|
|
|
86
119
|
// Get extension.
|
|
87
120
|
const currentExt = path.extname(name)
|
|
88
|
-
|
|
121
|
+
let actualExt = path.extname(filePath)
|
|
122
|
+
let actualFilePath = filePath
|
|
123
|
+
|
|
124
|
+
// If the file doesn't exist (or is a directory), the resolver may have returned
|
|
125
|
+
// a fallback path. In this case, path.extname may return a "fake" extension
|
|
126
|
+
// (e.g., ".client" for "utils.client" when the actual file is "utils.client.ts").
|
|
127
|
+
// We need to search for the actual file using the full basename from the import.
|
|
128
|
+
const fileExists =
|
|
129
|
+
fs.existsSync(filePath) && fs.statSync(filePath).isFile()
|
|
130
|
+
if (actualExt !== "" && !fileExists) {
|
|
131
|
+
// Use the full basename (e.g., "utils.client") since what path.extname
|
|
132
|
+
// thinks is an extension (e.g., ".client") may not be a real file extension
|
|
133
|
+
const importBasename = path.basename(name)
|
|
134
|
+
const extensions = getExistingExtensions(
|
|
135
|
+
filePath,
|
|
136
|
+
importBasename
|
|
137
|
+
)
|
|
138
|
+
// Find the preferred extension based on tryExtensions order
|
|
139
|
+
const preferred = tryExtensions.find(ext =>
|
|
140
|
+
extensions.includes(ext)
|
|
141
|
+
)
|
|
142
|
+
const foundExt = preferred ?? extensions[0]
|
|
143
|
+
if (foundExt) {
|
|
144
|
+
actualExt = foundExt
|
|
145
|
+
actualFilePath = path.join(
|
|
146
|
+
path.dirname(filePath),
|
|
147
|
+
`${importBasename}${actualExt}`
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let isDirectoryImport = false
|
|
153
|
+
|
|
154
|
+
// Check for directory imports. This handles both:
|
|
155
|
+
// 1. Normal case: "./my-folder" -> "./my-folder/index.js"
|
|
156
|
+
// 2. Dot-in-name case: "./my-things.client" -> "./my-things.client/index.js"
|
|
157
|
+
// where path.extname incorrectly sees ".client" as an extension
|
|
158
|
+
const isDirectory =
|
|
159
|
+
fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()
|
|
160
|
+
if (isDirectory) {
|
|
161
|
+
const indexExt = getIndexExtension(filePath, tryExtensions)
|
|
162
|
+
if (indexExt) {
|
|
163
|
+
isDirectoryImport = true
|
|
164
|
+
actualExt = indexExt
|
|
165
|
+
actualFilePath = path.join(filePath, `index${indexExt}`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
89
169
|
const style = overrideStyle[actualExt] || defaultStyle
|
|
90
170
|
|
|
91
|
-
|
|
171
|
+
let expectedExt = convertTsExtensionToJs(
|
|
92
172
|
context,
|
|
93
|
-
|
|
173
|
+
actualFilePath,
|
|
94
174
|
actualExt
|
|
95
175
|
)
|
|
96
176
|
|
|
177
|
+
if (currentExt === "" && actualExt === "" && !isDirectoryImport) {
|
|
178
|
+
const indexExt = getIndexExtension(filePath, tryExtensions)
|
|
179
|
+
if (indexExt) {
|
|
180
|
+
isDirectoryImport = true
|
|
181
|
+
expectedExt = convertTsExtensionToJs(
|
|
182
|
+
context,
|
|
183
|
+
path.join(filePath, `index${indexExt}`),
|
|
184
|
+
indexExt
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
97
189
|
// Verify.
|
|
98
190
|
if (style === "always" && currentExt !== expectedExt) {
|
|
99
191
|
context.report({
|
|
@@ -106,6 +198,13 @@ module.exports = {
|
|
|
106
198
|
}
|
|
107
199
|
|
|
108
200
|
const index = node.range[1] - 1
|
|
201
|
+
if (isDirectoryImport) {
|
|
202
|
+
const needsSlash = /[/\\]$/.test(name) ? "" : "/"
|
|
203
|
+
return fixer.insertTextBeforeRange(
|
|
204
|
+
[index, index],
|
|
205
|
+
`${needsSlash}index${expectedExt}`
|
|
206
|
+
)
|
|
207
|
+
}
|
|
109
208
|
return fixer.insertTextBeforeRange(
|
|
110
209
|
[index, index],
|
|
111
210
|
expectedExt
|
|
@@ -120,6 +120,9 @@ module.exports = {
|
|
|
120
120
|
":function:exit"() {
|
|
121
121
|
functionDepth--
|
|
122
122
|
},
|
|
123
|
+
/**
|
|
124
|
+
* @param {import('estree').AwaitExpression | import('estree').ForOfStatement | import('estree').VariableDeclaration} node
|
|
125
|
+
*/
|
|
123
126
|
"AwaitExpression, ForOfStatement[await=true], VariableDeclaration[kind='await using']"(
|
|
124
127
|
node
|
|
125
128
|
) {
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
crypto: { [READ]: true },
|
|
13
|
+
},
|
|
14
|
+
modules: {
|
|
15
|
+
crypto: { webcrypto: { [READ]: true } },
|
|
16
|
+
"node:crypto": { webcrypto: { [READ]: true } },
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** @type {import('../rule-module').RuleModule} */
|
|
21
|
+
module.exports = {
|
|
22
|
+
meta: {
|
|
23
|
+
docs: {
|
|
24
|
+
description:
|
|
25
|
+
'enforce either `crypto` or `require("crypto").webcrypto`',
|
|
26
|
+
recommended: false,
|
|
27
|
+
url: "https://github.com/eslint-community/eslint-plugin-n/blob/HEAD/docs/rules/prefer-global/crypto.md",
|
|
28
|
+
},
|
|
29
|
+
type: "suggestion",
|
|
30
|
+
fixable: null,
|
|
31
|
+
schema: [{ enum: ["always", "never"] }],
|
|
32
|
+
messages: {
|
|
33
|
+
preferGlobal:
|
|
34
|
+
"Unexpected use of 'require(\"crypto\").webcrypto'. Use the global variable 'crypto' instead.",
|
|
35
|
+
preferModule:
|
|
36
|
+
"Unexpected use of the global variable 'crypto'. Use 'require(\"crypto\").webcrypto' instead.",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
create(context) {
|
|
41
|
+
return {
|
|
42
|
+
"Program:exit"() {
|
|
43
|
+
checkForPreferGlobal(context, traceMap)
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
}
|
|
@@ -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
|
+
}
|