eslint-plugin-n 16.6.2 → 17.0.0-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 +5 -2
- package/lib/rules/file-extension-in-import.js +41 -32
- package/lib/rules/no-hide-core-modules.js +7 -24
- package/lib/util/check-existence.js +34 -12
- package/lib/util/check-publish.js +2 -1
- package/lib/util/exists.js +4 -0
- package/lib/util/get-try-extensions.js +39 -10
- package/lib/util/get-tsconfig.js +19 -1
- package/lib/util/get-typescript-extension-map.js +12 -11
- package/lib/util/import-target.js +232 -106
- package/lib/util/visit-import.js +7 -1
- package/lib/util/visit-require.js +7 -1
- package/package.json +8 -8
- package/lib/converted-esm/import-meta-resolve.js +0 -1274
package/README.md
CHANGED
|
@@ -18,8 +18,11 @@ Additional ESLint rules for Node.js
|
|
|
18
18
|
npm install --save-dev eslint eslint-plugin-n
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
| Version | Supported Node.js | Supported ESLint Version |
|
|
22
|
+
|---------|-------------------|---------------------------|
|
|
23
|
+
| 17.x | `^18.18.0 \|\| ^20.9.0 \|\| >=21.1.0` | `>=8.23.0` |
|
|
24
|
+
| 16.x | `>=16.0.0` | `>=7.0.0` |
|
|
25
|
+
| 15.x | `>=12.22.0` | `>=7.0.0` |
|
|
23
26
|
|
|
24
27
|
**Note:** It recommends a use of [the "engines" field of package.json](https://docs.npmjs.com/files/package.json#engines). The "engines" field is used by `n/no-unsupported-features/*` rules.
|
|
25
28
|
|
|
@@ -15,14 +15,14 @@ const visitImport = require("../util/visit-import")
|
|
|
15
15
|
* @returns {string[]} File extensions.
|
|
16
16
|
*/
|
|
17
17
|
function getExistingExtensions(filePath) {
|
|
18
|
-
const
|
|
18
|
+
const directory = path.dirname(filePath)
|
|
19
|
+
const extension = path.extname(filePath)
|
|
20
|
+
const basename = path.basename(filePath, extension)
|
|
21
|
+
|
|
19
22
|
try {
|
|
20
23
|
return fs
|
|
21
|
-
.readdirSync(
|
|
22
|
-
.filter(
|
|
23
|
-
filename =>
|
|
24
|
-
path.basename(filename, path.extname(filename)) === basename
|
|
25
|
-
)
|
|
24
|
+
.readdirSync(directory)
|
|
25
|
+
.filter(filename => filename.startsWith(`${basename}.`))
|
|
26
26
|
.map(filename => path.extname(filename))
|
|
27
27
|
} catch (_error) {
|
|
28
28
|
return []
|
|
@@ -74,47 +74,56 @@ module.exports = {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// Get extension.
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
77
|
+
const currentExt = path.extname(name)
|
|
78
|
+
const actualExt = path.extname(filePath)
|
|
79
|
+
const style = overrideStyle[actualExt] || defaultStyle
|
|
80
|
+
|
|
81
|
+
const expectedExt = mapTypescriptExtension(
|
|
82
|
+
context,
|
|
83
|
+
filePath,
|
|
84
|
+
actualExt
|
|
85
|
+
)
|
|
81
86
|
|
|
82
87
|
// Verify.
|
|
83
|
-
if (style === "always" &&
|
|
84
|
-
const fileExtensionToAdd = mapTypescriptExtension(
|
|
85
|
-
context,
|
|
86
|
-
filePath,
|
|
87
|
-
ext
|
|
88
|
-
)
|
|
88
|
+
if (style === "always" && currentExt !== expectedExt) {
|
|
89
89
|
context.report({
|
|
90
90
|
node,
|
|
91
91
|
messageId: "requireExt",
|
|
92
|
-
data: { ext:
|
|
92
|
+
data: { ext: expectedExt },
|
|
93
93
|
fix(fixer) {
|
|
94
|
-
if (existingExts.length !== 1) {
|
|
95
|
-
return null
|
|
96
|
-
}
|
|
97
94
|
const index = node.range[1] - 1
|
|
98
95
|
return fixer.insertTextBeforeRange(
|
|
99
96
|
[index, index],
|
|
100
|
-
|
|
97
|
+
expectedExt
|
|
101
98
|
)
|
|
102
99
|
},
|
|
103
100
|
})
|
|
104
|
-
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
style === "never" &&
|
|
105
|
+
currentExt !== "" &&
|
|
106
|
+
expectedExt !== "" &&
|
|
107
|
+
currentExt === expectedExt
|
|
108
|
+
) {
|
|
109
|
+
const otherExtensions = getExistingExtensions(filePath)
|
|
110
|
+
|
|
111
|
+
let fix = fixer => {
|
|
112
|
+
const index = name.lastIndexOf(currentExt)
|
|
113
|
+
const start = node.range[0] + 1 + index
|
|
114
|
+
const end = start + currentExt.length
|
|
115
|
+
return fixer.removeRange([start, end])
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (otherExtensions.length > 1) {
|
|
119
|
+
fix = undefined
|
|
120
|
+
}
|
|
121
|
+
|
|
105
122
|
context.report({
|
|
106
123
|
node,
|
|
107
124
|
messageId: "forbidExt",
|
|
108
|
-
data: { ext },
|
|
109
|
-
fix
|
|
110
|
-
if (existingExts.length !== 1) {
|
|
111
|
-
return null
|
|
112
|
-
}
|
|
113
|
-
const index = name.lastIndexOf(ext)
|
|
114
|
-
const start = node.range[0] + 1 + index
|
|
115
|
-
const end = start + ext.length
|
|
116
|
-
return fixer.removeRange([start, end])
|
|
117
|
-
},
|
|
125
|
+
data: { ext: currentExt },
|
|
126
|
+
fix,
|
|
118
127
|
})
|
|
119
128
|
}
|
|
120
129
|
}
|
|
@@ -9,11 +9,6 @@
|
|
|
9
9
|
"use strict"
|
|
10
10
|
|
|
11
11
|
const path = require("path")
|
|
12
|
-
const resolve = require("resolve")
|
|
13
|
-
const { pathToFileURL, fileURLToPath } = require("url")
|
|
14
|
-
const {
|
|
15
|
-
defaultResolve: importResolve,
|
|
16
|
-
} = require("../converted-esm/import-meta-resolve")
|
|
17
12
|
const getPackageJson = require("../util/get-package-json")
|
|
18
13
|
const mergeVisitorsInPlace = require("../util/merge-visitors-in-place")
|
|
19
14
|
const visitImport = require("../util/visit-import")
|
|
@@ -29,7 +24,8 @@ const CORE_MODULES = new Set([
|
|
|
29
24
|
"crypto",
|
|
30
25
|
"dgram",
|
|
31
26
|
"dns",
|
|
32
|
-
/* "domain", */
|
|
27
|
+
/* "domain", */
|
|
28
|
+
"events",
|
|
33
29
|
"fs",
|
|
34
30
|
"http",
|
|
35
31
|
"https",
|
|
@@ -37,7 +33,8 @@ const CORE_MODULES = new Set([
|
|
|
37
33
|
"net",
|
|
38
34
|
"os",
|
|
39
35
|
"path",
|
|
40
|
-
/* "punycode", */
|
|
36
|
+
/* "punycode", */
|
|
37
|
+
"querystring",
|
|
41
38
|
"readline",
|
|
42
39
|
"repl",
|
|
43
40
|
"stream",
|
|
@@ -132,22 +129,8 @@ module.exports = {
|
|
|
132
129
|
continue
|
|
133
130
|
}
|
|
134
131
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
resolved = resolve.sync(moduleId, {
|
|
139
|
-
basedir: dirPath,
|
|
140
|
-
})
|
|
141
|
-
} catch (_error) {
|
|
142
|
-
try {
|
|
143
|
-
const { url } = importResolve(moduleId, {
|
|
144
|
-
parentURL: pathToFileURL(dirPath).href,
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
resolved = fileURLToPath(url)
|
|
148
|
-
} catch (_error) {
|
|
149
|
-
continue
|
|
150
|
-
}
|
|
132
|
+
if (target.filePath == null) {
|
|
133
|
+
continue
|
|
151
134
|
}
|
|
152
135
|
|
|
153
136
|
context.report({
|
|
@@ -156,7 +139,7 @@ module.exports = {
|
|
|
156
139
|
messageId: "unexpectedImport",
|
|
157
140
|
data: {
|
|
158
141
|
name: path
|
|
159
|
-
.relative(dirPath,
|
|
142
|
+
.relative(dirPath, target.filePath)
|
|
160
143
|
.replace(BACK_SLASH, "/"),
|
|
161
144
|
},
|
|
162
145
|
})
|
|
@@ -10,6 +10,21 @@ const getAllowModules = require("./get-allow-modules")
|
|
|
10
10
|
const isTypescript = require("./is-typescript")
|
|
11
11
|
const mapTypescriptExtension = require("../util/map-typescript-extension")
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Reports a missing file from ImportTarget
|
|
15
|
+
* @param {RuleContext} context - A context to report.
|
|
16
|
+
* @param {import('../util/import-target.js')} target - A list of target information to check.
|
|
17
|
+
* @returns {void}
|
|
18
|
+
*/
|
|
19
|
+
function markMissing(context, target) {
|
|
20
|
+
context.report({
|
|
21
|
+
node: target.node,
|
|
22
|
+
loc: target.node.loc,
|
|
23
|
+
messageId: "notFound",
|
|
24
|
+
data: target,
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
/**
|
|
14
29
|
* Checks whether or not each requirement target exists.
|
|
15
30
|
*
|
|
@@ -17,21 +32,33 @@ const mapTypescriptExtension = require("../util/map-typescript-extension")
|
|
|
17
32
|
* See Also: https://nodejs.org/api/modules.html
|
|
18
33
|
*
|
|
19
34
|
* @param {RuleContext} context - A context to report.
|
|
20
|
-
* @param {
|
|
35
|
+
* @param {import('../util/import-target.js')[]} targets - A list of target information to check.
|
|
21
36
|
* @returns {void}
|
|
22
37
|
*/
|
|
23
38
|
exports.checkExistence = function checkExistence(context, targets) {
|
|
24
39
|
const allowed = new Set(getAllowModules(context))
|
|
25
40
|
|
|
26
41
|
for (const target of targets) {
|
|
27
|
-
|
|
42
|
+
if (
|
|
28
43
|
target.moduleName != null &&
|
|
29
44
|
!allowed.has(target.moduleName) &&
|
|
30
45
|
target.filePath == null
|
|
46
|
+
) {
|
|
47
|
+
markMissing(context, target)
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (target.moduleName != null) {
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let missingFile =
|
|
56
|
+
target.filePath == null ? false : !exists(target.filePath)
|
|
31
57
|
|
|
32
|
-
let missingFile = target.moduleName == null && !exists(target.filePath)
|
|
33
58
|
if (missingFile && isTypescript(context)) {
|
|
34
59
|
const parsed = path.parse(target.filePath)
|
|
60
|
+
const pathWithoutExt = path.resolve(parsed.dir, parsed.name)
|
|
61
|
+
|
|
35
62
|
const reversedExts = mapTypescriptExtension(
|
|
36
63
|
context,
|
|
37
64
|
target.filePath,
|
|
@@ -39,21 +66,16 @@ exports.checkExistence = function checkExistence(context, targets) {
|
|
|
39
66
|
true
|
|
40
67
|
)
|
|
41
68
|
const reversedPaths = reversedExts.map(
|
|
42
|
-
reversedExt =>
|
|
43
|
-
path.resolve(parsed.dir, parsed.name) + reversedExt
|
|
69
|
+
reversedExt => pathWithoutExt + reversedExt
|
|
44
70
|
)
|
|
45
71
|
missingFile = reversedPaths.every(
|
|
46
72
|
reversedPath =>
|
|
47
73
|
target.moduleName == null && !exists(reversedPath)
|
|
48
74
|
)
|
|
49
75
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
loc: target.node.loc,
|
|
54
|
-
messageId: "notFound",
|
|
55
|
-
data: target,
|
|
56
|
-
})
|
|
76
|
+
|
|
77
|
+
if (missingFile) {
|
|
78
|
+
markMissing(context, target)
|
|
57
79
|
}
|
|
58
80
|
}
|
|
59
81
|
}
|
|
@@ -59,7 +59,7 @@ exports.checkPublish = function checkPublish(context, filePath, targets) {
|
|
|
59
59
|
if (target.moduleName != null) {
|
|
60
60
|
return false
|
|
61
61
|
}
|
|
62
|
-
const relativeTargetPath = toRelative(target.filePath)
|
|
62
|
+
const relativeTargetPath = toRelative(target.filePath ?? "")
|
|
63
63
|
return (
|
|
64
64
|
relativeTargetPath !== "" &&
|
|
65
65
|
npmignore.match(relativeTargetPath)
|
|
@@ -70,6 +70,7 @@ exports.checkPublish = function checkPublish(context, filePath, targets) {
|
|
|
70
70
|
devDependencies.has(target.moduleName) &&
|
|
71
71
|
!dependencies.has(target.moduleName) &&
|
|
72
72
|
!allowed.has(target.moduleName)
|
|
73
|
+
|
|
73
74
|
if (isPrivateFile() || isDevPackage()) {
|
|
74
75
|
context.report({
|
|
75
76
|
node: target.node,
|
package/lib/util/exists.js
CHANGED
|
@@ -38,6 +38,10 @@ function existsCaseSensitive(filePath) {
|
|
|
38
38
|
* @returns {boolean} `true` if the file of a given path exists.
|
|
39
39
|
*/
|
|
40
40
|
module.exports = function exists(filePath) {
|
|
41
|
+
if (filePath == null) {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
let result = cache.get(filePath)
|
|
42
46
|
if (result == null) {
|
|
43
47
|
try {
|
|
@@ -4,7 +4,26 @@
|
|
|
4
4
|
*/
|
|
5
5
|
"use strict"
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const { getTSConfigForContext } = require("./get-tsconfig")
|
|
8
|
+
const isTypescript = require("./is-typescript")
|
|
9
|
+
|
|
10
|
+
const DEFAULT_JS_VALUE = Object.freeze([
|
|
11
|
+
".js",
|
|
12
|
+
".json",
|
|
13
|
+
".node",
|
|
14
|
+
".mjs",
|
|
15
|
+
".cjs",
|
|
16
|
+
])
|
|
17
|
+
const DEFAULT_TS_VALUE = Object.freeze([
|
|
18
|
+
".js",
|
|
19
|
+
".ts",
|
|
20
|
+
".mjs",
|
|
21
|
+
".mts",
|
|
22
|
+
".cjs",
|
|
23
|
+
".cts",
|
|
24
|
+
".json",
|
|
25
|
+
".node",
|
|
26
|
+
])
|
|
8
27
|
|
|
9
28
|
/**
|
|
10
29
|
* Gets `tryExtensions` property from a given option object.
|
|
@@ -13,7 +32,7 @@ const DEFAULT_VALUE = Object.freeze([".js", ".json", ".node"])
|
|
|
13
32
|
* @returns {string[]|null} The `tryExtensions` value, or `null`.
|
|
14
33
|
*/
|
|
15
34
|
function get(option) {
|
|
16
|
-
if (
|
|
35
|
+
if (Array.isArray(option?.tryExtensions)) {
|
|
17
36
|
return option.tryExtensions.map(String)
|
|
18
37
|
}
|
|
19
38
|
return null
|
|
@@ -24,19 +43,29 @@ function get(option) {
|
|
|
24
43
|
*
|
|
25
44
|
* 1. This checks `options` property, then returns it if exists.
|
|
26
45
|
* 2. This checks `settings.n` | `settings.node` property, then returns it if exists.
|
|
27
|
-
* 3. This returns `[".js", ".json", ".node"]`.
|
|
46
|
+
* 3. This returns `[".js", ".json", ".node", ".mjs", ".cjs"]`.
|
|
28
47
|
*
|
|
29
48
|
* @param {RuleContext} context - The rule context.
|
|
30
49
|
* @returns {string[]} A list of extensions.
|
|
31
50
|
*/
|
|
32
51
|
module.exports = function getTryExtensions(context, optionIndex = 0) {
|
|
33
|
-
|
|
34
|
-
get(context.options
|
|
35
|
-
get(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
52
|
+
const configured =
|
|
53
|
+
get(context.options?.[optionIndex]) ??
|
|
54
|
+
get(context.settings?.n) ??
|
|
55
|
+
get(context.settings?.node)
|
|
56
|
+
|
|
57
|
+
if (configured != null) {
|
|
58
|
+
return configured
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isTypescript(context)) {
|
|
62
|
+
const tsconfig = getTSConfigForContext(context)
|
|
63
|
+
if (tsconfig?.config?.compilerOptions?.allowImportingTsExtensions) {
|
|
64
|
+
return DEFAULT_TS_VALUE
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return DEFAULT_JS_VALUE
|
|
40
69
|
}
|
|
41
70
|
|
|
42
71
|
module.exports.schema = {
|
package/lib/util/get-tsconfig.js
CHANGED
|
@@ -17,15 +17,33 @@ function getTSConfig(filename) {
|
|
|
17
17
|
* Attempts to get the ExtensionMap from the tsconfig of a given file.
|
|
18
18
|
*
|
|
19
19
|
* @param {string} filename - The path to the file we need to find the tsconfig.json of
|
|
20
|
-
* @returns {import("get-tsconfig").TsConfigResult}
|
|
20
|
+
* @returns {import("get-tsconfig").TsConfigResult | null}
|
|
21
21
|
*/
|
|
22
22
|
function getTSConfigForFile(filename) {
|
|
23
23
|
return getTsconfig(filename, "tsconfig.json", fsCache)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Attempts to get the ExtensionMap from the tsconfig of a given file.
|
|
28
|
+
*
|
|
29
|
+
* @param {import('eslint').Rule.RuleContext} context - The current eslint context
|
|
30
|
+
* @returns {import("get-tsconfig").TsConfigResult | null}
|
|
31
|
+
*/
|
|
32
|
+
function getTSConfigForContext(context) {
|
|
33
|
+
// TODO: remove context.get(PhysicalFilename|Filename) when dropping eslint < v10
|
|
34
|
+
const filename =
|
|
35
|
+
context.physicalFilename ??
|
|
36
|
+
context.getPhysicalFilename?.() ??
|
|
37
|
+
context.filename ??
|
|
38
|
+
context.getFilename?.()
|
|
39
|
+
|
|
40
|
+
return getTSConfigForFile(filename)
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
module.exports = {
|
|
27
44
|
getTSConfig,
|
|
28
45
|
getTSConfigForFile,
|
|
46
|
+
getTSConfigForContext,
|
|
29
47
|
}
|
|
30
48
|
|
|
31
49
|
module.exports.schema = { type: "string" }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict"
|
|
2
2
|
|
|
3
|
-
const { getTSConfig,
|
|
3
|
+
const { getTSConfig, getTSConfigForContext } = require("./get-tsconfig")
|
|
4
4
|
|
|
5
5
|
const DEFAULT_MAPPING = normalise([
|
|
6
6
|
["", ".js"],
|
|
@@ -32,9 +32,14 @@ const tsConfigMapping = {
|
|
|
32
32
|
* @property {Record<string, string[]>} backward Convert from javascript to typescript
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @param {Record<string, string>} typescriptExtensionMap A forward extension mapping
|
|
37
|
+
* @returns {ExtensionMap}
|
|
38
|
+
*/
|
|
35
39
|
function normalise(typescriptExtensionMap) {
|
|
36
40
|
const forward = {}
|
|
37
41
|
const backward = {}
|
|
42
|
+
|
|
38
43
|
for (const [typescript, javascript] of typescriptExtensionMap) {
|
|
39
44
|
forward[typescript] = javascript
|
|
40
45
|
if (!typescript) {
|
|
@@ -43,6 +48,7 @@ function normalise(typescriptExtensionMap) {
|
|
|
43
48
|
backward[javascript] ??= []
|
|
44
49
|
backward[javascript].push(typescript)
|
|
45
50
|
}
|
|
51
|
+
|
|
46
52
|
return { forward, backward }
|
|
47
53
|
}
|
|
48
54
|
|
|
@@ -89,11 +95,11 @@ function get(option) {
|
|
|
89
95
|
/**
|
|
90
96
|
* Attempts to get the ExtensionMap from the tsconfig of a given file.
|
|
91
97
|
*
|
|
92
|
-
* @param {
|
|
98
|
+
* @param {import('eslint').Rule.RuleContext} context - The current file context
|
|
93
99
|
* @returns {ExtensionMap} The `typescriptExtensionMap` value, or `null`.
|
|
94
100
|
*/
|
|
95
|
-
function getFromTSConfigFromFile(
|
|
96
|
-
return getMappingFromTSConfig(
|
|
101
|
+
function getFromTSConfigFromFile(context) {
|
|
102
|
+
return getMappingFromTSConfig(getTSConfigForContext(context)?.config)
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
/**
|
|
@@ -109,18 +115,13 @@ function getFromTSConfigFromFile(filename) {
|
|
|
109
115
|
* 8. This returns `PRESERVE_MAPPING`.
|
|
110
116
|
*
|
|
111
117
|
* @param {import("eslint").Rule.RuleContext} context - The rule context.
|
|
112
|
-
* @returns {
|
|
118
|
+
* @returns {ExtensionMap} A list of extensions.
|
|
113
119
|
*/
|
|
114
120
|
module.exports = function getTypescriptExtensionMap(context) {
|
|
115
|
-
const filename =
|
|
116
|
-
context.physicalFilename ??
|
|
117
|
-
context.getPhysicalFilename?.() ??
|
|
118
|
-
context.filename ??
|
|
119
|
-
context.getFilename?.() // TODO: remove context.get(PhysicalFilename|Filename) when dropping eslint < v10
|
|
120
121
|
return (
|
|
121
122
|
get(context.options?.[0]) ||
|
|
122
123
|
get(context.settings?.n ?? context.settings?.node) ||
|
|
123
|
-
getFromTSConfigFromFile(
|
|
124
|
+
getFromTSConfigFromFile(context) ||
|
|
124
125
|
PRESERVE_MAPPING
|
|
125
126
|
)
|
|
126
127
|
}
|