@@ -95,7 +95,8 @@ namespace ts {
9595 return compilerOptions . traceResolution && host . trace !== undefined ;
9696 }
9797
98- function hasZeroOrOneAsteriskCharacter ( str : string ) : boolean {
98+ /* @internal */
99+ export function hasZeroOrOneAsteriskCharacter ( str : string ) : boolean {
99100 let seenAsterisk = false ;
100101 for ( let i = 0 ; i < str . length ; i ++ ) {
101102 if ( str . charCodeAt ( i ) === CharacterCodes . asterisk ) {
@@ -496,48 +497,23 @@ namespace ts {
496497 trace ( state . host , Diagnostics . baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1 , state . compilerOptions . baseUrl , moduleName ) ;
497498 }
498499
499- let longestMatchPrefixLength = - 1 ;
500- let matchedPattern : string ;
501- let matchedStar : string ;
502-
500+ // string is for exact match
501+ let matchedPattern : Pattern | string | undefined = undefined ;
503502 if ( state . compilerOptions . paths ) {
504503 if ( state . traceEnabled ) {
505504 trace ( state . host , Diagnostics . paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0 , moduleName ) ;
506505 }
507-
508- for ( const key in state . compilerOptions . paths ) {
509- const pattern : string = key ;
510- const indexOfStar = pattern . indexOf ( "*" ) ;
511- if ( indexOfStar !== - 1 ) {
512- const prefix = pattern . substr ( 0 , indexOfStar ) ;
513- const suffix = pattern . substr ( indexOfStar + 1 ) ;
514- if ( moduleName . length >= prefix . length + suffix . length &&
515- startsWith ( moduleName , prefix ) &&
516- endsWith ( moduleName , suffix ) ) {
517-
518- // use length of prefix as betterness criteria
519- if ( prefix . length > longestMatchPrefixLength ) {
520- longestMatchPrefixLength = prefix . length ;
521- matchedPattern = pattern ;
522- matchedStar = moduleName . substr ( prefix . length , moduleName . length - suffix . length ) ;
523- }
524- }
525- }
526- else if ( pattern === moduleName ) {
527- // pattern was matched as is - no need to search further
528- matchedPattern = pattern ;
529- matchedStar = undefined ;
530- break ;
531- }
532- }
506+ matchedPattern = matchPatternOrExact ( getKeys ( state . compilerOptions . paths ) , moduleName ) ;
533507 }
534508
535509 if ( matchedPattern ) {
510+ const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText ( matchedPattern , moduleName ) ;
511+ const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText ( matchedPattern ) ;
536512 if ( state . traceEnabled ) {
537- trace ( state . host , Diagnostics . Module_name_0_matched_pattern_1 , moduleName , matchedPattern ) ;
513+ trace ( state . host , Diagnostics . Module_name_0_matched_pattern_1 , moduleName , matchedPatternText ) ;
538514 }
539- for ( const subst of state . compilerOptions . paths [ matchedPattern ] ) {
540- const path = matchedStar ? subst . replace ( "\ *" , matchedStar ) : subst ;
515+ for ( const subst of state . compilerOptions . paths [ matchedPatternText ] ) {
516+ const path = matchedStar ? subst . replace ( "*" , matchedStar ) : subst ;
541517 const candidate = normalizePath ( combinePaths ( state . compilerOptions . baseUrl , path ) ) ;
542518 if ( state . traceEnabled ) {
543519 trace ( state . host , Diagnostics . Trying_substitution_0_candidate_module_location_Colon_1 , subst , path ) ;
@@ -560,6 +536,75 @@ namespace ts {
560536 }
561537 }
562538
539+ /**
540+ * patternStrings contains both pattern strings (containing "*") and regular strings.
541+ * Return an exact match if possible, or a pattern match, or undefined.
542+ * (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.)
543+ */
544+ function matchPatternOrExact ( patternStrings : string [ ] , candidate : string ) : string | Pattern | undefined {
545+ const patterns : Pattern [ ] = [ ] ;
546+ for ( const patternString of patternStrings ) {
547+ const pattern = tryParsePattern ( patternString ) ;
548+ if ( pattern ) {
549+ patterns . push ( pattern ) ;
550+ }
551+ else if ( patternString === candidate ) {
552+ // pattern was matched as is - no need to search further
553+ return patternString ;
554+ }
555+ }
556+
557+ return findBestPatternMatch ( patterns , _ => _ , candidate ) ;
558+ }
559+
560+ function patternText ( { prefix, suffix} : Pattern ) : string {
561+ return `${ prefix } *${ suffix } ` ;
562+ }
563+
564+ /**
565+ * Given that candidate matches pattern, returns the text matching the '*'.
566+ * E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar"
567+ */
568+ function matchedText ( pattern : Pattern , candidate : string ) : string {
569+ Debug . assert ( isPatternMatch ( pattern , candidate ) ) ;
570+ return candidate . substr ( pattern . prefix . length , candidate . length - pattern . suffix . length ) ;
571+ }
572+
573+ /** Return the object corresponding to the best pattern to match `candidate`. */
574+ /* @internal */
575+ export function findBestPatternMatch < T > ( values : T [ ] , getPattern : ( value : T ) => Pattern , candidate : string ) : T | undefined {
576+ let matchedValue : T | undefined = undefined ;
577+ // use length of prefix as betterness criteria
578+ let longestMatchPrefixLength = - 1 ;
579+
580+ for ( const v of values ) {
581+ const pattern = getPattern ( v ) ;
582+ if ( isPatternMatch ( pattern , candidate ) && pattern . prefix . length > longestMatchPrefixLength ) {
583+ longestMatchPrefixLength = pattern . prefix . length ;
584+ matchedValue = v ;
585+ }
586+ }
587+
588+ return matchedValue ;
589+ }
590+
591+ function isPatternMatch ( { prefix, suffix} : Pattern , candidate : string ) {
592+ return candidate . length >= prefix . length + suffix . length &&
593+ startsWith ( candidate , prefix ) &&
594+ endsWith ( candidate , suffix ) ;
595+ }
596+
597+ /* @internal */
598+ export function tryParsePattern ( pattern : string ) : Pattern | undefined {
599+ // This should be verified outside of here and a proper error thrown.
600+ Debug . assert ( hasZeroOrOneAsteriskCharacter ( pattern ) ) ;
601+ const indexOfStar = pattern . indexOf ( "*" ) ;
602+ return indexOfStar === - 1 ? undefined : {
603+ prefix : pattern . substr ( 0 , indexOfStar ) ,
604+ suffix : pattern . substr ( indexOfStar + 1 )
605+ } ;
606+ }
607+
563608 export function nodeModuleNameResolver ( moduleName : string , containingFile : string , compilerOptions : CompilerOptions , host : ModuleResolutionHost ) : ResolvedModuleWithFailedLookupLocations {
564609 const containingDirectory = getDirectoryPath ( containingFile ) ;
565610 const supportedExtensions = getSupportedExtensions ( compilerOptions ) ;
0 commit comments