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 ( / ^ h t t p s ? : \/ \/ / . 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
}
0 commit comments