@@ -159,12 +159,14 @@ class BroccoliTypeScriptCompiler extends Plugin {
159
159
160
160
this . _tsConfigFiles = tsconfig . files . splice ( 0 ) ;
161
161
162
- this . _tsOpts = ts . convertCompilerOptionsFromJson ( tsconfig . compilerOptions , '' , null ) . options ;
162
+ this . _tsOpts = ts . convertCompilerOptionsFromJson ( tsconfig [ 'compilerOptions' ] ,
163
+ this . inputPaths [ 0 ] , this . _tsConfigPath ) . options ;
163
164
this . _tsOpts . rootDir = '' ;
164
165
this . _tsOpts . outDir = '' ;
165
166
166
167
this . _tsServiceHost = new CustomLanguageServiceHost (
167
- this . _tsOpts , this . _rootFilePaths , this . _fileRegistry , this . inputPaths [ 0 ] ) ;
168
+ this . _tsOpts , this . _rootFilePaths , this . _fileRegistry , this . inputPaths [ 0 ] ,
169
+ tsconfig [ 'compilerOptions' ] . paths , this . _tsConfigPath ) ;
168
170
this . _tsService = ts . createLanguageService ( this . _tsServiceHost , ts . createDocumentRegistry ( ) ) ;
169
171
}
170
172
@@ -249,13 +251,15 @@ class BroccoliTypeScriptCompiler extends Plugin {
249
251
}
250
252
251
253
class CustomLanguageServiceHost {
252
- constructor ( compilerOptions , fileNames , fileRegistry , treeInputPath ) {
254
+ constructor ( compilerOptions , fileNames , fileRegistry , treeInputPath , paths , tsConfigPath ) {
253
255
this . compilerOptions = compilerOptions ;
254
256
this . fileNames = fileNames ;
255
257
this . fileRegistry = fileRegistry ;
256
258
this . treeInputPath = treeInputPath ;
257
259
this . currentDirectory = treeInputPath ;
258
260
this . defaultLibFilePath = ts . getDefaultLibFilePath ( compilerOptions ) . replace ( / \\ / g, '/' ) ;
261
+ this . paths = paths ;
262
+ this . tsConfigPath = tsConfigPath ;
259
263
this . projectVersion = 0 ;
260
264
}
261
265
@@ -272,6 +276,80 @@ class CustomLanguageServiceHost {
272
276
return this . projectVersion . toString ( ) ;
273
277
}
274
278
279
+ /**
280
+ * Resolve a moduleName based on the path mapping defined in the tsconfig.
281
+ * @param moduleName The module name to resolve.
282
+ * @returns {string|boolean } A string that is the path of the module, if found, or a boolean
283
+ * indicating if resolution should continue with default.
284
+ * @private
285
+ */
286
+ _resolveModulePathWithMapping ( moduleName ) {
287
+ // check if module name should be used as-is or it should be mapped to different value
288
+ let longestMatchedPrefixLength = 0 ;
289
+ let matchedPattern ;
290
+ let matchedWildcard ;
291
+ const paths = this . paths || { } ;
292
+
293
+ for ( let pattern of Object . keys ( paths ) ) {
294
+ if ( pattern . indexOf ( '*' ) != pattern . lastIndexOf ( '*' ) ) {
295
+ throw `Invalid path mapping pattern: "${ pattern } "` ;
296
+ }
297
+
298
+ let indexOfWildcard = pattern . indexOf ( '*' ) ;
299
+ if ( indexOfWildcard !== - 1 ) {
300
+ // check if module name starts with prefix, ends with suffix and these two don't overlap
301
+ let prefix = pattern . substr ( 0 , indexOfWildcard ) ;
302
+ let suffix = pattern . substr ( indexOfWildcard + 1 ) ;
303
+ if ( moduleName . length >= prefix . length + suffix . length &&
304
+ moduleName . startsWith ( prefix ) &&
305
+ moduleName . endsWith ( suffix ) ) {
306
+
307
+ // use length of matched prefix as betterness criteria
308
+ if ( longestMatchedPrefixLength < prefix . length ) {
309
+ longestMatchedPrefixLength = prefix . length ;
310
+ matchedPattern = pattern ;
311
+ matchedWildcard = moduleName . substr ( prefix . length , moduleName . length - suffix . length ) ;
312
+ }
313
+ }
314
+ } else {
315
+ // Pattern does not contain asterisk - module name should exactly match pattern to succeed.
316
+ if ( pattern === moduleName ) {
317
+ matchedPattern = pattern ;
318
+ matchedWildcard = undefined ;
319
+ break ;
320
+ }
321
+ }
322
+ }
323
+
324
+ if ( ! matchedPattern ) {
325
+ // We fallback to the old module resolution.
326
+ return true ;
327
+ }
328
+
329
+ // some pattern was matched - module name needs to be substituted
330
+ let substitutions = this . paths [ matchedPattern ] ;
331
+ for ( let subst of substitutions ) {
332
+ if ( subst . indexOf ( '*' ) != subst . lastIndexOf ( '*' ) ) {
333
+ throw `Invalid substitution: "${ subst } " for pattern "${ matchedPattern } ".` ;
334
+ }
335
+ if ( subst == '*' ) {
336
+ // Trigger default module resolution.
337
+ return true ;
338
+ }
339
+ // replace * in substitution with matched wildcard
340
+ let p = matchedWildcard ? subst . replace ( '*' , matchedWildcard ) : subst ;
341
+ // if substituion is a relative path - combine it with baseUrl
342
+ p = path . isAbsolute ( p ) ? p : path . join ( this . treeInputPath , path . dirname ( this . tsConfigPath ) , p ) ;
343
+ if ( fs . existsSync ( p ) ) {
344
+ return p ;
345
+ }
346
+ }
347
+
348
+ // This is an error; there was a match but no corresponding mapping was valid.
349
+ // Do not call the default module resolution.
350
+ return false ;
351
+ }
352
+
275
353
/**
276
354
* This method is called quite a bit to lookup 3 kinds of paths:
277
355
* 1/ files in the fileRegistry
@@ -310,6 +388,42 @@ class CustomLanguageServiceHost {
310
388
return ts . ScriptSnapshot . fromString ( fs . readFileSync ( absoluteTsFilePath , FS_OPTS ) ) ;
311
389
}
312
390
391
+ resolveModuleNames ( moduleNames , containingFile ) /*: ResolvedModule[]*/ {
392
+ return moduleNames . map ( ( moduleName ) => {
393
+ let shouldResolveUsingDefaultMethod = false ;
394
+ for ( const ext of [ 'ts' , 'd.ts' ] ) {
395
+ const name = `${ moduleName } .${ ext } ` ;
396
+ const maybeModule = this . _resolveModulePathWithMapping ( name , containingFile ) ;
397
+ if ( typeof maybeModule == 'string' ) {
398
+ return {
399
+ resolvedFileName : maybeModule ,
400
+ isExternalLibraryImport : false
401
+ } ;
402
+ } else {
403
+ shouldResolveUsingDefaultMethod = shouldResolveUsingDefaultMethod || maybeModule ;
404
+ }
405
+ }
406
+
407
+ return shouldResolveUsingDefaultMethod &&
408
+ ts . resolveModuleName ( moduleName , containingFile , this . compilerOptions , {
409
+ fileExists ( fileName ) {
410
+ return fs . existsSync ( fileName ) ;
411
+ } ,
412
+ readFile ( fileName ) {
413
+ return fs . readFileSync ( fileName , 'utf-8' ) ;
414
+ } ,
415
+ directoryExists ( directoryName ) {
416
+ try {
417
+ const stats = fs . statSync ( directoryName ) ;
418
+ return stats && stats . isDirectory ( ) ;
419
+ } catch ( e ) {
420
+ return false ;
421
+ }
422
+ }
423
+ } ) . resolvedModule ;
424
+ } ) ;
425
+ }
426
+
313
427
getCurrentDirectory ( ) {
314
428
return this . currentDirectory ;
315
429
}
0 commit comments