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