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