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