7
7
FileSystem ,
8
8
} from 'enhanced-resolve'
9
9
import { loadPnPApi , type PnpApi } from './pnp'
10
+ import { loadTsConfig , type TSConfigApi } from './tsconfig'
10
11
11
12
export interface ResolverOptions {
12
13
/**
@@ -23,6 +24,15 @@ export interface ResolverOptions {
23
24
*/
24
25
pnp ?: boolean | PnpApi
25
26
27
+ /**
28
+ * Whether or not the resolver should load tsconfig path mappings.
29
+ *
30
+ * If `true`, the resolver will look for all `tsconfig` files in the project
31
+ * and use them to resolve module paths where possible. However, if an API is
32
+ * provided, the resolver will use that API to resolve module paths.
33
+ */
34
+ tsconfig ?: boolean | TSConfigApi
35
+
26
36
/**
27
37
* A filesystem to use for resolution. If not provided, the resolver will
28
38
* create one and use it internally for itself and any child resolvers that
@@ -61,6 +71,23 @@ export interface Resolver {
61
71
*/
62
72
resolveCssId ( id : string , base : string ) : Promise < string >
63
73
74
+ /**
75
+ * Resolves a module to a possible file or directory path.
76
+ *
77
+ * This provides reasonable results when TypeScript config files are in use.
78
+ * This file may not exist but is the likely path that would be used to load
79
+ * the module if it were to exist.
80
+ *
81
+ * @param id The module, file, or directory to resolve
82
+ * @param base The base directory to resolve the module from
83
+ */
84
+ substituteId ( id : string , base : string ) : Promise < string >
85
+
86
+ /**
87
+ * Return a list of path resolution aliases for the given base directory
88
+ */
89
+ aliases ( base : string ) : Promise < Record < string , string [ ] > >
90
+
64
91
/**
65
92
* Create a child resolver with the given options.
66
93
*
@@ -83,6 +110,22 @@ export async function createResolver(opts: ResolverOptions): Promise<Resolver> {
83
110
pnpApi = await loadPnPApi ( opts . root )
84
111
}
85
112
113
+ let tsconfig : TSConfigApi | null = null
114
+
115
+ // Load TSConfig path mappings
116
+ if ( typeof opts . tsconfig === 'object' ) {
117
+ tsconfig = opts . tsconfig
118
+ } else if ( opts . tsconfig ) {
119
+ try {
120
+ tsconfig = await loadTsConfig ( opts . root )
121
+ } catch ( err ) {
122
+ // We don't want to hard crash in case of an error handling tsconfigs
123
+ // It does affect what projects we can resolve or how we load files
124
+ // but the LSP shouldn't become unusable because of it.
125
+ console . error ( 'Failed to load tsconfig' , err )
126
+ }
127
+ }
128
+
86
129
let esmResolver = ResolverFactory . createResolver ( {
87
130
fileSystem,
88
131
extensions : [ '.mjs' , '.js' ] ,
@@ -128,6 +171,11 @@ export async function createResolver(opts: ResolverOptions): Promise<Resolver> {
128
171
if ( base . startsWith ( '//' ) ) base = `\\\\${ base . slice ( 2 ) } `
129
172
}
130
173
174
+ if ( tsconfig ) {
175
+ let match = await tsconfig . resolveId ( id , base )
176
+ if ( match ) id = match
177
+ }
178
+
131
179
return new Promise ( ( resolve , reject ) => {
132
180
resolver . resolve ( { } , base , id , { } , ( err , res ) => {
133
181
if ( err ) {
@@ -151,14 +199,29 @@ export async function createResolver(opts: ResolverOptions): Promise<Resolver> {
151
199
return ( await resolveId ( cssResolver , id , base ) ) || id
152
200
}
153
201
202
+ // Takes a path which may or may not be complete and returns the aliased path
203
+ // if possible
204
+ async function substituteId ( id : string , base : string ) : Promise < string > {
205
+ return ( await tsconfig ?. substituteId ( id , base ) ) ?? id
206
+ }
207
+
154
208
async function setupPnP ( ) {
155
209
pnpApi ?. setup ( )
156
210
}
157
211
212
+ async function aliases ( base : string ) {
213
+ if ( ! tsconfig ) return { }
214
+
215
+ return await tsconfig . paths ( base )
216
+ }
217
+
158
218
return {
159
219
setupPnP,
160
220
resolveJsId,
161
221
resolveCssId,
222
+ substituteId,
223
+
224
+ aliases,
162
225
163
226
child ( childOpts : Partial < ResolverOptions > ) {
164
227
return createResolver ( {
@@ -167,6 +230,7 @@ export async function createResolver(opts: ResolverOptions): Promise<Resolver> {
167
230
168
231
// Inherit defaults from parent
169
232
pnp : childOpts . pnp ?? pnpApi ,
233
+ tsconfig : childOpts . tsconfig ?? tsconfig ,
170
234
fileSystem : childOpts . fileSystem ?? fileSystem ,
171
235
} )
172
236
} ,
0 commit comments