-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathResolver.ts
More file actions
341 lines (329 loc) 路 11.5 KB
/
Copy pathResolver.ts
File metadata and controls
341 lines (329 loc) 路 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
import type { AliasConfig, FinderResult } from '@interfaces/index'
import { existsSync } from 'node:fs'
import { resolve, dirname, extname, normalize, sep } from 'node:path'
import { allowedExtensions } from '@constants/index'
/**
* Global regex match variable for pattern matching.
* @description Stores the current regex match result during pattern execution across resolver functions.
*/
let match: RegExpExecArray | null = null
/**
* Global array to store resolved file references.
* @description Accumulates resolved file references from all import pattern matching functions for batch processing.
*/
const resolvedResults: Array<FinderResult> = []
/**
* Resolves import paths from file patterns and returns resolved file references.
* @description Processes files with import statements and resolves their paths.
* @param projectRoot - The root directory of the project
* @param projectConfig - The alias configuration for path mapping
* @param filePattern - Array of files containing import statements to process
* @returns Array of resolved file references with import information
*/
export function findPathResolver(
projectRoot: string,
projectConfig: AliasConfig[],
filePattern: Array<FinderResult>
): Array<FinderResult> {
resolvedResults.length = 0
for (const result of filePattern) {
const { filename, data }: { filename: string; data: string } = result
regexES6Import(data, filename, projectRoot, projectConfig)
regexES6DefaultImport(data, filename, projectRoot, projectConfig)
regexCommonJsDestructuring(data, filename, projectRoot, projectConfig)
regexCommonJSDefault(data, filename, projectRoot, projectConfig)
}
return resolvedResults
}
/**
* Processes ES6 import statements and resolves their paths.
* @description Extracts named imports from ES6 import statements.
* @param data - The file content to analyze
* @param filename - The name of the file being processed
* @param projectRoot - The root directory of the project
* @param projectConfig - The alias configuration for path mapping
*/
function regexES6Import(
data: string,
filename: string,
projectRoot: string,
projectConfig: AliasConfig[]
): void {
const regexPattern: RegExp = /import\s*(?:type\s+)?\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g
while ((match = regexPattern.exec(data)) !== null) {
const imports: string[] =
match[1]
?.split(',')
.map((s: string) => s.trim())
.filter((s: string) => s.length > 0) ?? []
const path: string = match[2] ?? ''
if (imports.length > 0 && path) {
const resolvedPath: string | null = resolveImportPath(
path,
filename,
projectRoot,
projectConfig
)
if (resolvedPath !== null) {
resolvedResults.push({
reference: resolvedPath,
imported: imports,
filename,
data
})
}
}
}
}
/**
* Processes ES6 default import statements and resolves their paths.
* @description Extracts default imports from ES6 import statements.
* @param data - The file content to analyze
* @param filename - The name of the file being processed
* @param projectRoot - The root directory of the project
* @param projectConfig - The alias configuration for path mapping
*/
function regexES6DefaultImport(
data: string,
filename: string,
projectRoot: string,
projectConfig: AliasConfig[]
): void {
const regexPattern: RegExp = /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g
while ((match = regexPattern.exec(data)) !== null) {
const importName: string = match[1] ?? ''
const path: string = match[2] ?? ''
if (importName && path) {
const resolvedPath: string | null = resolveImportPath(
path,
filename,
projectRoot,
projectConfig
)
if (resolvedPath !== null) {
resolvedResults.push({
reference: resolvedPath,
imported: [importName],
filename,
data
})
}
}
}
}
/**
* Processes CommonJS destructuring require statements and resolves their paths.
* @description Extracts destructured imports from CommonJS require statements.
* @param data - The file content to analyze
* @param filename - The name of the file being processed
* @param projectRoot - The root directory of the project
* @param projectConfig - The alias configuration for path mapping
*/
function regexCommonJsDestructuring(
data: string,
filename: string,
projectRoot: string,
projectConfig: AliasConfig[]
): void {
const regexPattern: RegExp = /const\s*\{([^}]+)\}\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
match = null
while ((match = regexPattern.exec(data)) !== null) {
const imports: string[] =
match[1]
?.split(',')
.map((s: string) => s.trim())
.filter((s: string) => s.length > 0) ?? []
const path: string = match[2] ?? ''
if (imports.length > 0 && path) {
const resolvedPath: string | null = resolveImportPath(
path,
filename,
projectRoot,
projectConfig
)
if (resolvedPath !== null) {
resolvedResults.push({
reference: resolvedPath,
imported: imports,
filename,
data
})
}
}
}
}
/**
* Processes CommonJS default require statements and resolves their paths.
* @description Extracts default imports from CommonJS require statements.
* @param data - The file content to analyze
* @param filename - The name of the file being processed
* @param projectRoot - The root directory of the project
* @param projectConfig - The alias configuration for path mapping
*/
function regexCommonJSDefault(
data: string,
filename: string,
projectRoot: string,
projectConfig: AliasConfig[]
): void {
const regexPattern: RegExp = /const\s*(\w+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
match = null
while ((match = regexPattern.exec(data)) !== null) {
const imports: string[] = match[1] !== undefined ? [match[1]] : []
const path: string = match[2] ?? ''
if (imports.length > 0 && path) {
const resolvedPath: string | null = resolveImportPath(
path,
filename,
projectRoot,
projectConfig
)
if (resolvedPath !== null) {
resolvedResults.push({
reference: resolvedPath,
imported: imports,
filename,
data
})
}
}
}
}
/**
* Resolves an import path to its actual file location.
* @description Resolves import paths using alias configurations and relative paths.
* @param importPath - The import path to resolve
* @param fromFile - The file containing the import statement
* @param projectRoot - The root directory of the project
* @param aliasConfig - The alias configuration for path mapping
* @returns The resolved file path or null if not found
*/
function resolveImportPath(
importPath: string,
fromFile: string,
projectRoot: string,
aliasConfig: AliasConfig[]
): string | null {
const normImportPath: string = normalizePath(importPath)
if (importPath.startsWith('./') || importPath.startsWith('../')) {
const fromDir: string = dirname(fromFile)
const fullPath: string = resolve(fromDir, importPath)
const normFullPath: string = normalize(fullPath)
const normProjectRoot: string = normalize(projectRoot)
if (normFullPath.startsWith(normProjectRoot)) {
const result: string | null = findFileWithExtension(normFullPath)
return result
}
}
const resAliasNonRelativePath: string | null = aliasNonRelativePath(
normImportPath,
projectRoot,
aliasConfig
)
if (resAliasNonRelativePath !== null) {
return resAliasNonRelativePath
}
const resAliasRelativePath: string | null = aliasRelativePath(normImportPath, aliasConfig)
if (resAliasRelativePath !== null) {
return resAliasRelativePath
}
return null
}
/**
* Resolves non-relative import paths using alias configuration.
* @description Handles import paths that don't start with '.' by checking alias mappings.
* @param normImportPath - The normalized import path to resolve
* @param projectRoot - The root directory of the project
* @param aliasConfig - The alias configuration for path mapping
* @returns The resolved file path or null if not found
*/
function aliasNonRelativePath(
normImportPath: string,
projectRoot: string,
aliasConfig: AliasConfig[]
): string | null {
if (!normImportPath.startsWith('.')) {
for (const alias of aliasConfig) {
const aliasKey: string = normalizePath(alias.key)
if (aliasKey.includes('*')) {
const aliasPrefix: string = aliasKey.replace('*', '')
if (normImportPath.startsWith(aliasPrefix)) {
const relativePath: string = normImportPath.replace(aliasPrefix, '')
const aliasValue: string = normalizePath(alias.value)
const fullPath: string = resolve(aliasValue, relativePath)
const result: string | null = findFileWithExtension(fullPath)
return result
}
} else if (normImportPath === aliasKey) {
const aliasValue: string = normalizePath(alias.value)
const result: string | null = findFileWithExtension(aliasValue)
return result
}
}
const fullPath: string = resolve(projectRoot, normImportPath)
const result: string | null = findFileWithExtension(fullPath)
return result
}
return null
}
/**
* Resolves relative import paths using alias configuration.
* @description Handles import paths that start with '.' by checking alias mappings.
* @param normImportPath - The normalized import path to resolve
* @param aliasConfig - The alias configuration for path mapping
* @returns The resolved file path or null if not found
*/
function aliasRelativePath(normImportPath: string, aliasConfig: AliasConfig[]): string | null {
for (const alias of aliasConfig) {
const aliasKey: string = normalizePath(alias.key)
if (aliasKey.includes('*')) {
const aliasPrefix: string = aliasKey.replace('*', '')
if (normImportPath.startsWith(aliasPrefix)) {
const relativePath: string = normImportPath.replace(aliasPrefix, '')
const aliasValue: string = normalizePath(alias.value)
const fullPath: string = resolve(aliasValue, relativePath)
return findFileWithExtension(fullPath)
}
} else if (normImportPath === aliasKey) {
const aliasValue: string = normalizePath(alias.value)
return findFileWithExtension(aliasValue)
}
}
return null
}
/**
* Normalizes a path for cross-platform compatibility.
* @description Converts path separators to the current platform's separator.
* @param path - The path to normalize
* @returns The normalized path with platform-specific separators
*/
function normalizePath(path: string): string {
return normalize(path.replace(/[/\\]/g, sep))
}
/**
* Finds a file with the appropriate extension at the given base path.
* @description Searches for a file at the base path with allowed extensions.
* @param basePath - The base path to search for the file
* @returns The full file path if found, null if no file exists
*/
function findFileWithExtension(basePath: string): string | null {
if (extname(basePath)) {
const exists: boolean = existsSync(basePath)
return exists ? basePath : null
}
for (const ext of allowedExtensions) {
const pathWithExt: string = `${basePath}${ext}`
const exists: boolean = existsSync(pathWithExt)
if (exists) {
return pathWithExt
}
}
for (const ext of allowedExtensions) {
const indexPath: string = `${basePath}/index${ext}`
const exists: boolean = existsSync(indexPath)
if (exists) {
return indexPath
}
}
return null
}