try-path.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import * as path from "path";
  2. import { MappingEntry } from "./mapping-entry";
  3. import { dirname } from "path";
  4. import { removeExtension } from "./filesystem";
  5. export interface TryPath {
  6. readonly type: "file" | "extension" | "index" | "package";
  7. readonly path: string;
  8. }
  9. /**
  10. * Builds a list of all physical paths to try by:
  11. * 1. Check for file named exactly as request.
  12. * 2. Check for files named as request ending in any of the extensions.
  13. * 3. Check for file specified in package.json's main property.
  14. * 4. Check for files named as request ending in "index" with any of the extensions.
  15. */
  16. export function getPathsToTry(
  17. extensions: ReadonlyArray<string>,
  18. absolutePathMappings: ReadonlyArray<MappingEntry>,
  19. requestedModule: string
  20. ): ReadonlyArray<TryPath> | undefined {
  21. if (!absolutePathMappings || !requestedModule || requestedModule[0] === ".") {
  22. return undefined;
  23. }
  24. const pathsToTry: Array<TryPath> = [];
  25. for (const entry of absolutePathMappings) {
  26. const starMatch =
  27. entry.pattern === requestedModule
  28. ? ""
  29. : matchStar(entry.pattern, requestedModule);
  30. if (starMatch !== undefined) {
  31. for (const physicalPathPattern of entry.paths) {
  32. const physicalPath = physicalPathPattern.replace("*", starMatch);
  33. pathsToTry.push({ type: "file", path: physicalPath });
  34. pathsToTry.push(
  35. ...extensions.map(
  36. (e) => ({ type: "extension", path: physicalPath + e } as TryPath)
  37. )
  38. );
  39. pathsToTry.push({
  40. type: "package",
  41. path: path.join(physicalPath, "/package.json"),
  42. });
  43. const indexPath = path.join(physicalPath, "/index");
  44. pathsToTry.push(
  45. ...extensions.map(
  46. (e) => ({ type: "index", path: indexPath + e } as TryPath)
  47. )
  48. );
  49. }
  50. }
  51. }
  52. return pathsToTry.length === 0 ? undefined : pathsToTry;
  53. }
  54. // Not sure why we don't just return the full found path?
  55. export function getStrippedPath(tryPath: TryPath): string {
  56. return tryPath.type === "index"
  57. ? dirname(tryPath.path)
  58. : tryPath.type === "file"
  59. ? tryPath.path
  60. : tryPath.type === "extension"
  61. ? removeExtension(tryPath.path)
  62. : tryPath.type === "package"
  63. ? tryPath.path
  64. : exhaustiveTypeException(tryPath.type);
  65. }
  66. export function exhaustiveTypeException(check: never): never {
  67. throw new Error(`Unknown type ${check}`);
  68. }
  69. /**
  70. * Matches pattern with a single star against search.
  71. * Star must match at least one character to be considered a match.
  72. * @param patttern for example "foo*"
  73. * @param search for example "fooawesomebar"
  74. * @returns the part of search that * matches, or undefined if no match.
  75. */
  76. function matchStar(pattern: string, search: string): string | undefined {
  77. if (search.length < pattern.length) {
  78. return undefined;
  79. }
  80. if (pattern === "*") {
  81. return search;
  82. }
  83. const star = pattern.indexOf("*");
  84. if (star === -1) {
  85. return undefined;
  86. }
  87. const part1 = pattern.substring(0, star);
  88. const part2 = pattern.substring(star + 1);
  89. if (search.substr(0, star) !== part1) {
  90. return undefined;
  91. }
  92. if (search.substr(search.length - part2.length) !== part2) {
  93. return undefined;
  94. }
  95. return search.substr(star, search.length - part2.length);
  96. }