1*d8009c4bSEvan Baconimport { Resolution } from 'metro-resolver';
2*d8009c4bSEvan Baconimport path from 'path';
3*d8009c4bSEvan Bacon
4*d8009c4bSEvan Baconimport { matchTsConfigPathAlias } from './matchTsConfigPathAlias';
5*d8009c4bSEvan Bacon
6*d8009c4bSEvan Bacontype Paths = { [match: string]: string[] };
7*d8009c4bSEvan Bacon
8*d8009c4bSEvan Baconconst debug = require('debug')('expo:metro:tsconfig-paths') as typeof console.log;
9*d8009c4bSEvan Bacon
10*d8009c4bSEvan Baconconst isAbsolute = process.platform === 'win32' ? path.win32.isAbsolute : path.posix.isAbsolute;
11*d8009c4bSEvan Bacon
12*d8009c4bSEvan Baconexport function resolveWithTsConfigPaths(
13*d8009c4bSEvan Bacon  config: { paths: Paths; baseUrl: string },
14*d8009c4bSEvan Bacon  request: {
15*d8009c4bSEvan Bacon    /** Import request */
16*d8009c4bSEvan Bacon    moduleName: string;
17*d8009c4bSEvan Bacon    /** Originating file path */
18*d8009c4bSEvan Bacon    originModulePath: string;
19*d8009c4bSEvan Bacon  },
20*d8009c4bSEvan Bacon  resolve: (moduleName: string) => Resolution | null
21*d8009c4bSEvan Bacon): Resolution | null {
22*d8009c4bSEvan Bacon  const aliases = Object.keys(config.paths);
23*d8009c4bSEvan Bacon
24*d8009c4bSEvan Bacon  if (
25*d8009c4bSEvan Bacon    // If no aliases are added bail out
26*d8009c4bSEvan Bacon    !aliases.length ||
27*d8009c4bSEvan Bacon    // Library authors cannot utilize this feature in userspace.
28*d8009c4bSEvan Bacon    /node_modules/.test(request.originModulePath) ||
29*d8009c4bSEvan Bacon    // Absolute paths are not supported
30*d8009c4bSEvan Bacon    isAbsolute(request.moduleName) ||
31*d8009c4bSEvan Bacon    // Relative paths are not supported
32*d8009c4bSEvan Bacon    /^\.\.?($|[\\/])/.test(request.moduleName)
33*d8009c4bSEvan Bacon  ) {
34*d8009c4bSEvan Bacon    return null;
35*d8009c4bSEvan Bacon  }
36*d8009c4bSEvan Bacon
37*d8009c4bSEvan Bacon  const matched = matchTsConfigPathAlias(aliases, request.moduleName);
38*d8009c4bSEvan Bacon  if (!matched) {
39*d8009c4bSEvan Bacon    return null;
40*d8009c4bSEvan Bacon  }
41*d8009c4bSEvan Bacon
42*d8009c4bSEvan Bacon  for (const alias of config.paths[matched.text]) {
43*d8009c4bSEvan Bacon    const nextModuleName = matched.star ? alias.replace('*', matched.star) : alias;
44*d8009c4bSEvan Bacon
45*d8009c4bSEvan Bacon    if (/\.d\.ts$/.test(nextModuleName)) continue;
46*d8009c4bSEvan Bacon
47*d8009c4bSEvan Bacon    const possibleResult = path.join(config.baseUrl, nextModuleName);
48*d8009c4bSEvan Bacon
49*d8009c4bSEvan Bacon    const result = resolve(possibleResult);
50*d8009c4bSEvan Bacon    if (result) {
51*d8009c4bSEvan Bacon      debug(`${request.moduleName} -> ${possibleResult}`);
52*d8009c4bSEvan Bacon      return result;
53*d8009c4bSEvan Bacon    }
54*d8009c4bSEvan Bacon  }
55*d8009c4bSEvan Bacon  return null;
56*d8009c4bSEvan Bacon}
57