4
4
*/
5
5
"use strict"
6
6
7
- const { resolve, sep } = require ( "path" )
7
+ const { resolve } = require ( "path" )
8
8
const isBuiltin = require ( "is-builtin-module" )
9
9
const resolver = require ( "enhanced-resolve" )
10
10
11
+ /**
12
+ * @typedef {Object } Options
13
+ * @property {string[] } [extensions]
14
+ * @property {string[] } [paths]
15
+ * @property {string } basedir
16
+ */
17
+ /**
18
+ * @typedef { 'unknown' | 'relative' | 'absolute' | 'node' | 'npm' | 'http' } ModuleType
19
+ * @typedef { 'import' | 'require' | 'type' } ModuleStyle
20
+ */
21
+
11
22
/**
12
23
* Resolve the given id to file paths.
13
- * @param {boolean } isModule The flag which indicates this id is a module.
14
24
* @param {string } id The id to resolve.
15
- * @param {object } options The options of node-resolve module.
16
- * It requires `options.basedir`.
17
- * @param {'import' | 'require' } moduleType - whether the target was require-ed or imported
18
- * @returns {string| null } The resolved path.
25
+ * @param {Options } options The options of node-resolve module.
26
+ * @param { ModuleType } moduleType - whether the target was require-ed or imported
27
+ * @param {ModuleStyle } moduleStyle - whether the target was require-ed or imported
28
+ * @returns {string | null } The resolved path.
19
29
*/
20
- function getFilePath ( isModule , id , options , moduleType ) {
30
+ function getFilePath ( id , options , moduleType , moduleStyle ) {
21
31
const conditionNames = [ "node" , "require" ]
22
32
const { extensions } = options
23
33
const mainFields = [ ]
24
34
const mainFiles = [ ]
25
35
26
- if ( moduleType === "import" ) {
36
+ if ( moduleStyle === "import" ) {
27
37
conditionNames . push ( "import" )
28
38
}
29
39
30
- if ( moduleType === "require" || isModule === true ) {
40
+ if ( moduleStyle === "type" ) {
41
+ conditionNames . push ( "import" , "types" )
42
+ }
43
+
44
+ if ( moduleStyle === "require" || moduleType === "npm" ) {
31
45
mainFields . push ( "main" )
32
46
mainFiles . push ( "index" )
33
47
}
@@ -52,36 +66,15 @@ function getFilePath(isModule, id, options, moduleType) {
52
66
}
53
67
}
54
68
55
- if ( isModule ) {
56
- return null
69
+ if ( moduleType === "absolute" || moduleType === "relative" ) {
70
+ return resolve ( options . basedir , id )
57
71
}
58
72
59
- return resolve ( options . basedir , id )
73
+ return null
60
74
}
61
75
62
- function isNodeModule ( name , options ) {
63
- try {
64
- return require . resolve ( name , options ) . startsWith ( sep )
65
- } catch {
66
- return false
67
- }
68
- }
69
-
70
- /**
71
- * Gets the module name of a given path.
72
- *
73
- * e.g. `eslint/lib/ast-utils` -> `eslint`
74
- *
75
- * @param {string } nameOrPath - A path to get.
76
- * @returns {string } The module name of the path.
77
- */
78
- function getModuleName ( nameOrPath ) {
79
- let end = nameOrPath . indexOf ( "/" )
80
- if ( end !== - 1 && nameOrPath [ 0 ] === "@" ) {
81
- end = nameOrPath . indexOf ( "/" , 1 + end )
82
- }
83
-
84
- return end === - 1 ? nameOrPath : nameOrPath . slice ( 0 , end )
76
+ function trimAfter ( string , matcher , count = 1 ) {
77
+ return string . split ( matcher ) . slice ( 0 , count ) . join ( matcher )
85
78
}
86
79
87
80
/**
@@ -90,17 +83,15 @@ function getModuleName(nameOrPath) {
90
83
module . exports = class ImportTarget {
91
84
/**
92
85
* Initialize this instance.
93
- * @param {ASTNode } node - The node of a `require()` or a module declaraiton.
86
+ * @param {import('eslint').Rule.Node } node - The node of a `require()` or a module declaraiton.
94
87
* @param {string } name - The name of an import target.
95
- * @param {object } options - The options of `node-resolve` module.
88
+ * @param {Options } options - The options of `node-resolve` module.
96
89
* @param {'import' | 'require' } moduleType - whether the target was require-ed or imported
97
90
*/
98
91
constructor ( node , name , options , moduleType ) {
99
- const isModule = ! / ^ (?: [ . / \\ ] | \w + : ) / u. test ( name )
100
-
101
92
/**
102
93
* The node of a `require()` or a module declaraiton.
103
- * @type {ASTNode }
94
+ * @type {import('eslint').Rule.Node }
104
95
*/
105
96
this . node = node
106
97
@@ -111,35 +102,132 @@ module.exports = class ImportTarget {
111
102
this . name = name
112
103
113
104
/**
114
- * What type of module is this
115
- * @type {'unknown'|'relative'|'absolute'|'node'|'npm'|'http'|void }
105
+ * The import target options.
106
+ * @type {Options }
116
107
*/
117
- this . moduleType = "unknown"
118
-
119
- if ( name . startsWith ( "./" ) || name . startsWith ( ".\\" ) ) {
120
- this . moduleType = "relative"
121
- } else if ( name . startsWith ( "/" ) || name . startsWith ( "\\" ) ) {
122
- this . moduleType = "absolute"
123
- } else if ( isBuiltin ( name ) ) {
124
- this . moduleType = "node"
125
- } else if ( isNodeModule ( name , options ) ) {
126
- this . moduleType = "npm"
127
- } else if ( name . startsWith ( "http://" ) || name . startsWith ( "https://" ) ) {
128
- this . moduleType = "http"
129
- }
108
+ this . options = options
130
109
131
110
/**
132
- * The full path of this import target.
133
- * If the target is a module and it does not exist then this is `null`.
134
- * @type {string|null }
111
+ * What type of module are we looking for?
112
+ * @type {ModuleType }
113
+ */
114
+ this . moduleType = this . getModuleType ( )
115
+
116
+ /**
117
+ * What import style are we using
118
+ * @type {ModuleStyle }
135
119
*/
136
- this . filePath = getFilePath ( isModule , name , options , moduleType )
120
+ this . moduleStyle = this . getModuleStyle ( moduleType )
137
121
138
122
/**
139
123
* The module name of this import target.
140
124
* If the target is a relative path then this is `null`.
141
- * @type {string| null }
125
+ * @type {string | null }
142
126
*/
143
- this . moduleName = isModule ? getModuleName ( name ) : null
127
+ this . moduleName = this . getModuleName ( )
128
+
129
+ /**
130
+ * The full path of this import target.
131
+ * If the target is a module and it does not exist then this is `null`.
132
+ * @type {string | null }
133
+ */
134
+ this . filePath = getFilePath (
135
+ name ,
136
+ options ,
137
+ this . moduleType ,
138
+ this . moduleStyle
139
+ )
140
+ }
141
+
142
+ /**
143
+ * What type of module is this
144
+ * @returns {ModuleType }
145
+ */
146
+ getModuleType ( ) {
147
+ if ( / ^ \. { 1 , 2 } ( [ \\ / ] | $ ) / . test ( this . name ) ) {
148
+ return "relative"
149
+ }
150
+
151
+ if ( / ^ [ \\ / ] / . test ( this . name ) ) {
152
+ return "absolute"
153
+ }
154
+
155
+ if ( isBuiltin ( this . name ) ) {
156
+ return "node"
157
+ }
158
+
159
+ if ( / ^ ( @ [ \w ~ - ] [ \w . ~ - ] * \/ ) ? [ \w ~ - ] [ \w . ~ - ] * / . test ( this . name ) ) {
160
+ return "npm"
161
+ }
162
+
163
+ if ( / ^ h t t p s ? : \/ \/ / . test ( this . name ) ) {
164
+ return "http"
165
+ }
166
+
167
+ return "unknown"
168
+ }
169
+
170
+ /**
171
+ * What module import style is used
172
+ * @param {'import' | 'require' } fallback
173
+ * @returns {ModuleStyle }
174
+ */
175
+ getModuleStyle ( fallback ) {
176
+ /** @type {import('eslint').Rule.Node } */
177
+ let node = { parent : this . node }
178
+
179
+ do {
180
+ node = node . parent
181
+
182
+ // `const {} = require('')`
183
+ if (
184
+ node . type === "CallExpression" &&
185
+ node . callee . name === "require"
186
+ ) {
187
+ return "require"
188
+ }
189
+
190
+ // `import type {} from '';`
191
+ if (
192
+ node . type === "ImportDeclaration" &&
193
+ node . importKind === "type"
194
+ ) {
195
+ return "type"
196
+ }
197
+
198
+ // `import {} from '';`
199
+ if (
200
+ node . type === "ImportDeclaration" &&
201
+ node . importKind === "value"
202
+ ) {
203
+ return "import"
204
+ }
205
+ } while ( node . parent )
206
+
207
+ return fallback
208
+ }
209
+
210
+ /**
211
+ * Get the node or npm module name
212
+ * @returns {string }
213
+ */
214
+ getModuleName ( ) {
215
+ if ( this . moduleType === "relative" ) return
216
+
217
+ if ( this . moduleType === "npm" ) {
218
+ if ( this . name . startsWith ( "@" ) ) {
219
+ return trimAfter ( this . name , "/" , 2 )
220
+ }
221
+
222
+ return trimAfter ( this . name , "/" )
223
+ }
224
+
225
+ if ( this . moduleType === "node" ) {
226
+ if ( this . name . startsWith ( "node:" ) ) {
227
+ return trimAfter ( this . name . slice ( 5 ) , "/" )
228
+ }
229
+
230
+ return trimAfter ( this . name , "/" )
231
+ }
144
232
}
145
233
}
0 commit comments