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.
@@ -4,98 +4,57 @@
4
4
  */
5
5
  "use strict"
6
6
 
7
- const path = require("path")
8
- const { pathToFileURL, fileURLToPath } = require("url")
7
+ require("util").inspect.defaultOptions.depth = null
8
+
9
+ const { resolve } = require("path")
9
10
  const isBuiltin = require("is-builtin-module")
10
- const resolve = require("resolve")
11
- const {
12
- defaultResolve: importResolve,
13
- } = require("../converted-esm/import-meta-resolve")
11
+ const resolver = require("enhanced-resolve")
14
12
 
15
- /**
16
- * Resolve the given id to file paths.
17
- * @param {boolean} isModule The flag which indicates this id is a module.
18
- * @param {string} id The id to resolve.
19
- * @param {object} options The options of node-resolve module.
20
- * It requires `options.basedir`.
21
- * @param {'import' | 'require'} moduleType - whether the target was require-ed or imported
22
- * @returns {string|null} The resolved path.
23
- */
24
- function getFilePath(isModule, id, options, moduleType) {
25
- if (moduleType === "import") {
26
- const paths =
27
- options.paths && options.paths.length > 0
28
- ? options.paths.map(p => path.resolve(process.cwd(), p))
29
- : [options.basedir]
30
- for (const aPath of paths) {
31
- try {
32
- const { url } = importResolve(id, {
33
- parentURL: pathToFileURL(path.join(aPath, "dummy-file.mjs"))
34
- .href,
35
- conditions: ["node", "import", "require"],
36
- })
37
-
38
- if (url) {
39
- return fileURLToPath(url)
40
- }
41
- } catch (e) {
42
- continue
43
- }
44
- }
13
+ const isTypescript = require("./is-typescript")
14
+ const { getTSConfigForContext } = require("./get-tsconfig.js")
15
+ const getTypescriptExtensionMap = require("./get-typescript-extension-map")
45
16
 
46
- if (isModule) {
47
- return null
48
- }
49
- return path.resolve(
50
- (options.paths && options.paths[0]) || options.basedir,
51
- id
52
- )
53
- } else {
54
- try {
55
- return resolve.sync(id, options)
56
- } catch (_err) {
57
- try {
58
- const { url } = importResolve(id, {
59
- parentURL: pathToFileURL(
60
- path.join(options.basedir, "dummy-file.js")
61
- ).href,
62
- conditions: ["node", "require"],
63
- })
64
-
65
- return fileURLToPath(url)
66
- } catch (err) {
67
- if (isModule) {
68
- return null
69
- }
70
- return path.resolve(options.basedir, id)
71
- }
72
- }
17
+ function removeTrailWildcard(input) {
18
+ if (Array.isArray(input)) {
19
+ return [...input].map(removeTrailWildcard)
73
20
  }
21
+
22
+ return input.replace(/[/\\*]+$/, "")
74
23
  }
75
24
 
76
- function isNodeModule(name, options) {
77
- try {
78
- return require.resolve(name, options).startsWith(path.sep)
79
- } catch {
80
- return false
25
+ /**
26
+ * Initialize this instance.
27
+ * @param {import('eslint').Rule.RuleContext} context - The context for the import origin.
28
+ * @returns {import('enhanced-resolve').ResolveOptions['alias'] | undefined}
29
+ */
30
+ function getTSConfigAliases(context) {
31
+ const tsConfig = getTSConfigForContext(context)
32
+
33
+ const paths = tsConfig?.config?.compilerOptions?.paths
34
+
35
+ if (paths == null) {
36
+ return
81
37
  }
38
+
39
+ return Object.entries(paths).map(([name, alias]) => ({
40
+ name: removeTrailWildcard(name),
41
+ alias: removeTrailWildcard(alias),
42
+ }))
82
43
  }
83
44
 
84
45
  /**
85
- * Gets the module name of a given path.
86
- *
87
- * e.g. `eslint/lib/ast-utils` -> `eslint`
88
- *
89
- * @param {string} nameOrPath - A path to get.
90
- * @returns {string} The module name of the path.
46
+ * @typedef {Object} Options
47
+ * @property {string[]} [extensions]
48
+ * @property {string[]} [paths]
49
+ * @property {string} basedir
50
+ */
51
+ /**
52
+ * @typedef { 'unknown' | 'relative' | 'absolute' | 'node' | 'npm' | 'http' } ModuleType
53
+ * @typedef { 'import' | 'require' | 'type' } ModuleStyle
91
54
  */
92
- function getModuleName(nameOrPath) {
93
- let end = nameOrPath.indexOf("/")
94
- if (end !== -1 && nameOrPath[0] === "@") {
95
- end = nameOrPath.indexOf("/", 1 + end)
96
- }
97
55
 
98
- return end === -1 ? nameOrPath : nameOrPath.slice(0, end)
56
+ function trimAfter(string, matcher, count = 1) {
57
+ return string.split(matcher).slice(0, count).join(matcher)
99
58
  }
100
59
 
101
60
  /**
@@ -104,17 +63,22 @@ function getModuleName(nameOrPath) {
104
63
  module.exports = class ImportTarget {
105
64
  /**
106
65
  * Initialize this instance.
107
- * @param {ASTNode} node - The node of a `require()` or a module declaraiton.
66
+ * @param {import('eslint').Rule.RuleContext} context - The context for the import origin.
67
+ * @param {import('eslint').Rule.Node} node - The node of a `require()` or a module declaraiton.
108
68
  * @param {string} name - The name of an import target.
109
- * @param {object} options - The options of `node-resolve` module.
69
+ * @param {Options} options - The options of `enhanced-resolve` module.
110
70
  * @param {'import' | 'require'} moduleType - whether the target was require-ed or imported
111
71
  */
112
- constructor(node, name, options, moduleType) {
113
- const isModule = !/^(?:[./\\]|\w+:)/u.test(name)
72
+ constructor(context, node, name, options, moduleType) {
73
+ /**
74
+ * The context for the import origin
75
+ * @type {import('eslint').Rule.Node}
76
+ */
77
+ this.context = context
114
78
 
115
79
  /**
116
80
  * The node of a `require()` or a module declaraiton.
117
- * @type {ASTNode}
81
+ * @type {import('eslint').Rule.Node}
118
82
  */
119
83
  this.node = node
120
84
 
@@ -125,35 +89,197 @@ module.exports = class ImportTarget {
125
89
  this.name = name
126
90
 
127
91
  /**
128
- * What type of module is this
129
- * @type {'unknown'|'relative'|'absolute'|'node'|'npm'|'http'|void}
92
+ * The import target options.
93
+ * @type {Options}
130
94
  */
131
- this.moduleType = "unknown"
132
-
133
- if (name.startsWith("./") || name.startsWith(".\\")) {
134
- this.moduleType = "relative"
135
- } else if (name.startsWith("/") || name.startsWith("\\")) {
136
- this.moduleType = "absolute"
137
- } else if (isBuiltin(name)) {
138
- this.moduleType = "node"
139
- } else if (isNodeModule(name, options)) {
140
- this.moduleType = "npm"
141
- } else if (name.startsWith("http://") || name.startsWith("https://")) {
142
- this.moduleType = "http"
143
- }
95
+ this.options = options
144
96
 
145
97
  /**
146
- * The full path of this import target.
147
- * If the target is a module and it does not exist then this is `null`.
148
- * @type {string|null}
98
+ * What type of module are we looking for?
99
+ * @type {ModuleType}
149
100
  */
150
- this.filePath = getFilePath(isModule, name, options, moduleType)
101
+ this.moduleType = this.getModuleType()
102
+
103
+ /**
104
+ * What import style are we using
105
+ * @type {ModuleStyle}
106
+ */
107
+ this.moduleStyle = this.getModuleStyle(moduleType)
151
108
 
152
109
  /**
153
110
  * The module name of this import target.
154
111
  * If the target is a relative path then this is `null`.
155
- * @type {string|null}
112
+ * @type {string | null}
113
+ */
114
+ this.moduleName = this.getModuleName()
115
+
116
+ /**
117
+ * The full path of this import target.
118
+ * If the target is a module and it does not exist then this is `null`.
119
+ * @type {string | null}
156
120
  */
157
- this.moduleName = isModule ? getModuleName(name) : null
121
+ this.filePath = this.getFilePath()
122
+ }
123
+
124
+ /**
125
+ * What type of module is this
126
+ * @returns {ModuleType}
127
+ */
128
+ getModuleType() {
129
+ if (/^\.{1,2}([\\/]|$)/.test(this.name)) {
130
+ return "relative"
131
+ }
132
+
133
+ if (/^[\\/]/.test(this.name)) {
134
+ return "absolute"
135
+ }
136
+
137
+ if (isBuiltin(this.name)) {
138
+ return "node"
139
+ }
140
+
141
+ if (/^(@[\w~-][\w.~-]*\/)?[\w~-][\w.~-]*/.test(this.name)) {
142
+ return "npm"
143
+ }
144
+
145
+ if (/^https?:\/\//.test(this.name)) {
146
+ return "http"
147
+ }
148
+
149
+ return "unknown"
150
+ }
151
+
152
+ /**
153
+ * What module import style is used
154
+ * @param {'import' | 'require'} fallback
155
+ * @returns {ModuleStyle}
156
+ */
157
+ getModuleStyle(fallback) {
158
+ /** @type {import('eslint').Rule.Node} */
159
+ let node = { parent: this.node }
160
+
161
+ do {
162
+ node = node.parent
163
+
164
+ // `const {} = require('')`
165
+ if (
166
+ node.type === "CallExpression" &&
167
+ node.callee.name === "require"
168
+ ) {
169
+ return "require"
170
+ }
171
+
172
+ // `import type {} from '';`
173
+ if (
174
+ node.type === "ImportDeclaration" &&
175
+ node.importKind === "type"
176
+ ) {
177
+ return "type"
178
+ }
179
+
180
+ // `import {} from '';`
181
+ if (
182
+ node.type === "ImportDeclaration" &&
183
+ node.importKind === "value"
184
+ ) {
185
+ return "import"
186
+ }
187
+ } while (node.parent)
188
+
189
+ return fallback
190
+ }
191
+
192
+ /**
193
+ * Get the node or npm module name
194
+ * @returns {string}
195
+ */
196
+ getModuleName() {
197
+ if (this.moduleType === "relative") return
198
+
199
+ if (this.moduleType === "npm") {
200
+ if (this.name.startsWith("@")) {
201
+ return trimAfter(this.name, "/", 2)
202
+ }
203
+
204
+ return trimAfter(this.name, "/")
205
+ }
206
+
207
+ if (this.moduleType === "node") {
208
+ if (this.name.startsWith("node:")) {
209
+ return trimAfter(this.name.slice(5), "/")
210
+ }
211
+
212
+ return trimAfter(this.name, "/")
213
+ }
214
+ }
215
+
216
+ getPaths() {
217
+ if (Array.isArray(this.options.paths)) {
218
+ return [...this.options.paths, this.options.basedir]
219
+ }
220
+
221
+ return [this.options.basedir]
222
+ }
223
+
224
+ /**
225
+ * Resolve the given id to file paths.
226
+ * @returns {string | null} The resolved path.
227
+ */
228
+ getFilePath() {
229
+ const conditionNames = ["node", "require"]
230
+ const { extensions } = this.options
231
+ const mainFields = []
232
+ const mainFiles = []
233
+
234
+ if (this.moduleStyle === "import") {
235
+ conditionNames.push("import")
236
+ }
237
+
238
+ if (this.moduleStyle === "type") {
239
+ conditionNames.push("import", "types")
240
+ }
241
+
242
+ if (
243
+ this.moduleStyle === "require" ||
244
+ this.moduleType === "npm" ||
245
+ this.moduleType === "node"
246
+ ) {
247
+ mainFields.push("main")
248
+ mainFiles.push("index")
249
+ }
250
+
251
+ let alias = undefined
252
+ let extensionAlias = undefined
253
+
254
+ if (isTypescript(this.context)) {
255
+ alias = getTSConfigAliases(this.context)
256
+ extensionAlias = getTypescriptExtensionMap(this.context).backward
257
+ }
258
+
259
+ const requireResolve = resolver.create.sync({
260
+ conditionNames,
261
+ extensions,
262
+ mainFields,
263
+ mainFiles,
264
+
265
+ extensionAlias,
266
+ alias,
267
+ })
268
+
269
+ const cwd = this.context.settings?.cwd ?? process.cwd()
270
+ for (const directory of this.getPaths()) {
271
+ try {
272
+ const baseDir = resolve(cwd, directory)
273
+ return requireResolve(baseDir, this.name)
274
+ } catch {
275
+ continue
276
+ }
277
+ }
278
+
279
+ if (this.moduleType === "absolute" || this.moduleType === "relative") {
280
+ return resolve(this.options.basedir, this.name)
281
+ }
282
+
283
+ return null
158
284
  }
159
285
  }
@@ -68,7 +68,13 @@ module.exports = function visitImport(
68
68
  // Note: "999" arbitrary to check current/future Node.js version
69
69
  if (name && (includeCore || !isCoreModule(name, "999"))) {
70
70
  targets.push(
71
- new ImportTarget(sourceNode, name, options, "import")
71
+ new ImportTarget(
72
+ context,
73
+ sourceNode,
74
+ name,
75
+ options,
76
+ "import"
77
+ )
72
78
  )
73
79
  }
74
80
  },
@@ -60,7 +60,13 @@ module.exports = function visitRequire(
60
60
  // Note: "999" arbitrary to check current/future Node.js version
61
61
  if (name && (includeCore || !isCoreModule(name, "999"))) {
62
62
  targets.push(
63
- new ImportTarget(targetNode, name, options, "require")
63
+ new ImportTarget(
64
+ context,
65
+ targetNode,
66
+ name,
67
+ options,
68
+ "require"
69
+ )
64
70
  )
65
71
  }
66
72
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "eslint-plugin-n",
3
- "version": "16.6.2",
3
+ "version": "17.0.0-0",
4
4
  "description": "Additional ESLint's rules for Node.js",
5
5
  "engines": {
6
- "node": ">=16.0.0"
6
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
7
7
  },
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -11,11 +11,11 @@
11
11
  "configs/"
12
12
  ],
13
13
  "peerDependencies": {
14
- "eslint": ">=7.0.0"
14
+ "eslint": ">=8.23.0"
15
15
  },
16
16
  "dependencies": {
17
17
  "@eslint-community/eslint-utils": "^4.4.0",
18
- "builtins": "^5.0.1",
18
+ "enhanced-resolve": "^5.15.0",
19
19
  "eslint-plugin-es-x": "^7.5.0",
20
20
  "get-tsconfig": "^4.7.0",
21
21
  "globals": "^13.24.0",
@@ -23,22 +23,19 @@
23
23
  "is-builtin-module": "^3.2.1",
24
24
  "is-core-module": "^2.12.1",
25
25
  "minimatch": "^3.1.2",
26
- "resolve": "^1.22.2",
27
26
  "semver": "^7.5.3"
28
27
  },
29
28
  "devDependencies": {
30
29
  "@eslint/js": "^8.43.0",
31
30
  "@types/eslint": "^8.44.6",
32
31
  "@typescript-eslint/parser": "^5.60.0",
33
- "esbuild": "^0.18.7",
34
- "eslint": "^8.56.0",
32
+ "eslint": "^8",
35
33
  "eslint-config-prettier": "^8.8.0",
36
34
  "eslint-doc-generator": "^1.6.1",
37
35
  "eslint-plugin-eslint-plugin": "^5.2.1",
38
36
  "eslint-plugin-n": "file:.",
39
37
  "fast-glob": "^3.2.12",
40
38
  "husky": "^8.0.3",
41
- "import-meta-resolve": "^3.0.0",
42
39
  "lint-staged": "^13.2.2",
43
40
  "markdownlint-cli": "^0.35.0",
44
41
  "mocha": "^10.2.0",
@@ -111,5 +108,8 @@
111
108
  "lint-staged": {
112
109
  "*.js": "eslint --cache --fix",
113
110
  "*.{json,js}": "prettier --write --ignore-path .eslintignore"
111
+ },
112
+ "imports": {
113
+ "#eslint-rule-tester": "./tests/eslint-rule-tester.js"
114
114
  }
115
115
  }