1*d8009c4bSEvan Bacon// From TypeScript: https://github.com/microsoft/TypeScript/blob/5b1897969769449217237aecbe364f823096c63e/src/compiler/core.ts 2*d8009c4bSEvan Bacon// License: https://github.com/microsoft/TypeScript/blob/214df64/LICENSE.txt 3*d8009c4bSEvan Bacon 4*d8009c4bSEvan Baconexport interface Pattern { 5*d8009c4bSEvan Bacon prefix: string; 6*d8009c4bSEvan Bacon suffix: string; 7*d8009c4bSEvan Bacon} 8*d8009c4bSEvan Bacon 9*d8009c4bSEvan Baconconst asterisk = 0x2a; 10*d8009c4bSEvan Bacon 11*d8009c4bSEvan Baconfunction hasZeroOrOneAsteriskCharacter(str: string): boolean { 12*d8009c4bSEvan Bacon let seenAsterisk = false; 13*d8009c4bSEvan Bacon for (let i = 0; i < str.length; i++) { 14*d8009c4bSEvan Bacon if (str.charCodeAt(i) === asterisk) { 15*d8009c4bSEvan Bacon if (!seenAsterisk) { 16*d8009c4bSEvan Bacon seenAsterisk = true; 17*d8009c4bSEvan Bacon } else { 18*d8009c4bSEvan Bacon return false; 19*d8009c4bSEvan Bacon } 20*d8009c4bSEvan Bacon } 21*d8009c4bSEvan Bacon } 22*d8009c4bSEvan Bacon return true; 23*d8009c4bSEvan Bacon} 24*d8009c4bSEvan Bacon 25*d8009c4bSEvan Baconfunction tryParsePattern(pattern: string): Pattern | undefined { 26*d8009c4bSEvan Bacon // This should be verified outside of here and a proper error thrown. 27*d8009c4bSEvan Bacon const indexOfStar = pattern.indexOf('*'); 28*d8009c4bSEvan Bacon return indexOfStar === -1 29*d8009c4bSEvan Bacon ? undefined 30*d8009c4bSEvan Bacon : { 31*d8009c4bSEvan Bacon prefix: pattern.slice(0, indexOfStar), 32*d8009c4bSEvan Bacon suffix: pattern.slice(indexOfStar + 1), 33*d8009c4bSEvan Bacon }; 34*d8009c4bSEvan Bacon} 35*d8009c4bSEvan Bacon 36*d8009c4bSEvan Baconfunction isPatternMatch({ prefix, suffix }: Pattern, candidate: string) { 37*d8009c4bSEvan Bacon return ( 38*d8009c4bSEvan Bacon candidate.length >= prefix.length + suffix.length && 39*d8009c4bSEvan Bacon candidate.startsWith(prefix) && 40*d8009c4bSEvan Bacon candidate.endsWith(suffix) 41*d8009c4bSEvan Bacon ); 42*d8009c4bSEvan Bacon} 43*d8009c4bSEvan Bacon 44*d8009c4bSEvan Bacon/** 45*d8009c4bSEvan Bacon * Return the object corresponding to the best pattern to match `candidate`. 46*d8009c4bSEvan Bacon * 47*d8009c4bSEvan Bacon * @internal 48*d8009c4bSEvan Bacon */ 49*d8009c4bSEvan Baconfunction findBestPatternMatch<T>( 50*d8009c4bSEvan Bacon values: readonly T[], 51*d8009c4bSEvan Bacon getPattern: (value: T) => Pattern, 52*d8009c4bSEvan Bacon candidate: string 53*d8009c4bSEvan Bacon): T | undefined { 54*d8009c4bSEvan Bacon let matchedValue: T | undefined; 55*d8009c4bSEvan Bacon // use length of prefix as betterness criteria 56*d8009c4bSEvan Bacon let longestMatchPrefixLength = -1; 57*d8009c4bSEvan Bacon 58*d8009c4bSEvan Bacon for (const v of values) { 59*d8009c4bSEvan Bacon const pattern = getPattern(v); 60*d8009c4bSEvan Bacon if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) { 61*d8009c4bSEvan Bacon longestMatchPrefixLength = pattern.prefix.length; 62*d8009c4bSEvan Bacon matchedValue = v; 63*d8009c4bSEvan Bacon } 64*d8009c4bSEvan Bacon } 65*d8009c4bSEvan Bacon 66*d8009c4bSEvan Bacon return matchedValue; 67*d8009c4bSEvan Bacon} 68*d8009c4bSEvan Bacon 69*d8009c4bSEvan Bacon/** 70*d8009c4bSEvan Bacon * patternStrings contains both pattern strings (containing "*") and regular strings. 71*d8009c4bSEvan Bacon * Return an exact match if possible, or a pattern match, or undefined. 72*d8009c4bSEvan Bacon * (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.) 73*d8009c4bSEvan Bacon */ 74*d8009c4bSEvan Baconfunction matchPatternOrExact( 75*d8009c4bSEvan Bacon patternStrings: readonly string[], 76*d8009c4bSEvan Bacon candidate: string 77*d8009c4bSEvan Bacon): string | Pattern | undefined { 78*d8009c4bSEvan Bacon const patterns: Pattern[] = []; 79*d8009c4bSEvan Bacon for (const patternString of patternStrings) { 80*d8009c4bSEvan Bacon if (!hasZeroOrOneAsteriskCharacter(patternString)) continue; 81*d8009c4bSEvan Bacon const pattern = tryParsePattern(patternString); 82*d8009c4bSEvan Bacon if (pattern) { 83*d8009c4bSEvan Bacon patterns.push(pattern); 84*d8009c4bSEvan Bacon } else if (patternString === candidate) { 85*d8009c4bSEvan Bacon // pattern was matched as is - no need to search further 86*d8009c4bSEvan Bacon return patternString; 87*d8009c4bSEvan Bacon } 88*d8009c4bSEvan Bacon } 89*d8009c4bSEvan Bacon 90*d8009c4bSEvan Bacon return findBestPatternMatch(patterns, (_) => _, candidate); 91*d8009c4bSEvan Bacon} 92*d8009c4bSEvan Bacon 93*d8009c4bSEvan Bacon/** 94*d8009c4bSEvan Bacon * Given that candidate matches pattern, returns the text matching the '*'. 95*d8009c4bSEvan Bacon * E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar" 96*d8009c4bSEvan Bacon */ 97*d8009c4bSEvan Baconfunction matchedText(pattern: Pattern, candidate: string): string { 98*d8009c4bSEvan Bacon return candidate.substring(pattern.prefix.length, candidate.length - pattern.suffix.length); 99*d8009c4bSEvan Bacon} 100*d8009c4bSEvan Bacon 101*d8009c4bSEvan Baconfunction getStar(matchedPattern: string | Pattern, moduleName: string) { 102*d8009c4bSEvan Bacon return typeof matchedPattern === 'string' ? undefined : matchedText(matchedPattern, moduleName); 103*d8009c4bSEvan Bacon} 104*d8009c4bSEvan Bacon 105*d8009c4bSEvan Baconexport function matchTsConfigPathAlias(pathsKeys: string[], moduleName: string) { 106*d8009c4bSEvan Bacon // If the module name does not match any of the patterns in `paths` we hand off resolving to webpack 107*d8009c4bSEvan Bacon const matchedPattern = matchPatternOrExact(pathsKeys, moduleName); 108*d8009c4bSEvan Bacon if (!matchedPattern) { 109*d8009c4bSEvan Bacon return null; 110*d8009c4bSEvan Bacon } 111*d8009c4bSEvan Bacon 112*d8009c4bSEvan Bacon return { 113*d8009c4bSEvan Bacon star: getStar(matchedPattern, moduleName), 114*d8009c4bSEvan Bacon text: 115*d8009c4bSEvan Bacon typeof matchedPattern === 'string' 116*d8009c4bSEvan Bacon ? matchedPattern 117*d8009c4bSEvan Bacon : `${matchedPattern.prefix}*${matchedPattern.suffix}`, 118*d8009c4bSEvan Bacon }; 119*d8009c4bSEvan Bacon} 120