1const JSON5 = require("json5"); 2namespace ts { 3 /* @internal */ 4 export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; 5 export function trace(host: ModuleResolutionHost): void { 6 host.trace!(formatMessage.apply(undefined, arguments)); 7 } 8 9 /* @internal */ 10 export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { 11 return !!compilerOptions.traceResolution && host.trace !== undefined; 12 } 13 14 function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined { 15 let packageId: PackageId | undefined; 16 if (r && packageInfo) { 17 const packageJsonContent = packageInfo.packageJsonContent as PackageJson; 18 if (typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string") { 19 packageId = { 20 name: packageJsonContent.name, 21 subModuleName: r.path.slice(packageInfo.packageDirectory.length + directorySeparator.length), 22 version: packageJsonContent.version 23 }; 24 } 25 } 26 return r && { path: r.path, extension: r.ext, packageId }; 27 } 28 29 function noPackageId(r: PathAndExtension | undefined): Resolved | undefined { 30 return withPackageId(/*packageInfo*/ undefined, r); 31 } 32 33 function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined { 34 if (r) { 35 Debug.assert(r.packageId === undefined); 36 return { path: r.path, ext: r.extension }; 37 } 38 } 39 40 /** Result of trying to resolve a module. */ 41 interface Resolved { 42 path: string; 43 extension: Extension; 44 packageId: PackageId | undefined; 45 /** 46 * When the resolved is not created from cache, the value is 47 * - string if original Path if it is symbolic link to the resolved path 48 * - undefined if path is not a symbolic link 49 * When the resolved is created using value from cache of ResolvedModuleWithFailedLookupLocations, the value is: 50 * - string if original Path if it is symbolic link to the resolved path 51 * - true if path is not a symbolic link - this indicates that the originalPath calculation is already done and needs to be skipped 52 */ 53 originalPath?: string | true; 54 } 55 56 /** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */ 57 interface PathAndExtension { 58 path: string; 59 // (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.) 60 ext: Extension; 61 } 62 63 /** 64 * Kinds of file that we are currently looking for. 65 * Typically there is one pass with Extensions.TypeScript, then a second pass with Extensions.JavaScript. 66 */ 67 enum Extensions { 68 TypeScript, /** '.ts', '.tsx', '.d.ts', '.ets' or '.d.ets' */ 69 JavaScript, /** '.js' or '.jsx' */ 70 Json, /** '.json' */ 71 TSConfig, /** '.json' with `tsconfig` used instead of `index` */ 72 DtsOnly /** Only '.d.ts' */ 73 } 74 75 interface PathAndPackageId { 76 readonly fileName: string; 77 readonly packageId: PackageId | undefined; 78 } 79 /** Used with `Extensions.DtsOnly` to extract the path from TypeScript results. */ 80 function resolvedTypeScriptOnly(resolved: Resolved | undefined): PathAndPackageId | undefined { 81 if (!resolved) { 82 return undefined; 83 } 84 Debug.assert(extensionIsTS(resolved.extension)); 85 return { fileName: resolved.path, packageId: resolved.packageId }; 86 } 87 88 function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, isExternalLibraryImport: boolean | undefined, failedLookupLocations: string[], resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined): ResolvedModuleWithFailedLookupLocations { 89 if (resultFromCache) { 90 resultFromCache.failedLookupLocations.push(...failedLookupLocations); 91 return resultFromCache; 92 } 93 return { 94 resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath: resolved.originalPath === true ? undefined : resolved.originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId }, 95 failedLookupLocations 96 }; 97 } 98 99 interface ModuleResolutionState { 100 host: ModuleResolutionHost; 101 compilerOptions: CompilerOptions; 102 traceEnabled: boolean; 103 failedLookupLocations: Push<string>; 104 resultFromCache?: ResolvedModuleWithFailedLookupLocations; 105 } 106 107 /** Just the fields that we use for module resolution. */ 108 interface PackageJsonPathFields { 109 typings?: string; 110 types?: string; 111 typesVersions?: MapLike<MapLike<string[]>>; 112 main?: string; 113 tsconfig?: string; 114 } 115 116 interface PackageJson extends PackageJsonPathFields { 117 name?: string; 118 version?: string; 119 } 120 121 function readPackageJsonField<TMatch, K extends MatchingKeys<PackageJson, string | undefined>>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined; 122 function readPackageJsonField<K extends MatchingKeys<PackageJson, object | undefined>>(jsonContent: PackageJson, fieldName: K, typeOfTag: "object", state: ModuleResolutionState): PackageJson[K] | undefined; 123 function readPackageJsonField<K extends keyof PackageJson>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string" | "object", state: ModuleResolutionState): PackageJson[K] | undefined { 124 if (!hasProperty(jsonContent, fieldName)) { 125 if (state.traceEnabled) { 126 const message = isOhpm(state.compilerOptions.packageManagerType) ? Diagnostics.oh_package_json5_does_not_have_a_0_field : Diagnostics.package_json_does_not_have_a_0_field; 127 trace(state.host, message, fieldName); 128 } 129 return; 130 } 131 const value = jsonContent[fieldName]; 132 if (typeof value !== typeOfTag || value === null) { // eslint-disable-line no-null/no-null 133 if (state.traceEnabled) { 134 // eslint-disable-next-line no-null/no-null 135 trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, typeOfTag, value === null ? "null" : typeof value); 136 } 137 return; 138 } 139 return value; 140 } 141 142 function readPackageJsonPathField<K extends "typings" | "types" | "main" | "tsconfig">(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined { 143 const fileName = readPackageJsonField(jsonContent, fieldName, "string", state); 144 if (fileName === undefined) { 145 return; 146 } 147 if (!fileName) { 148 if (state.traceEnabled) { 149 trace(state.host, Diagnostics.package_json_had_a_falsy_0_field, fieldName); 150 } 151 return; 152 } 153 const path = normalizePath(combinePaths(baseDirectory, fileName)); 154 if (state.traceEnabled) { 155 const message = isOhpm(state.compilerOptions.packageManagerType) ? Diagnostics.oh_package_json5_has_0_field_1_that_references_2 : Diagnostics.package_json_has_0_field_1_that_references_2; 156 trace(state.host, message, fieldName, fileName, path); 157 } 158 return path; 159 } 160 161 function readPackageJsonTypesFields(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { 162 return readPackageJsonPathField(jsonContent, "typings", baseDirectory, state) 163 || readPackageJsonPathField(jsonContent, "types", baseDirectory, state); 164 } 165 166 function readPackageJsonTSConfigField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { 167 return readPackageJsonPathField(jsonContent, "tsconfig", baseDirectory, state); 168 } 169 170 function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { 171 return readPackageJsonPathField(jsonContent, "main", baseDirectory, state); 172 } 173 174 function readPackageJsonTypesVersionsField(jsonContent: PackageJson, state: ModuleResolutionState) { 175 const typesVersions = readPackageJsonField(jsonContent, "typesVersions", "object", state); 176 if (typesVersions === undefined) return; 177 178 if (state.traceEnabled) { 179 trace(state.host, Diagnostics.package_json_has_a_typesVersions_field_with_version_specific_path_mappings); 180 } 181 182 return typesVersions; 183 } 184 185 interface VersionPaths { 186 version: string; 187 paths: MapLike<string[]>; 188 } 189 190 function readPackageJsonTypesVersionPaths(jsonContent: PackageJson, state: ModuleResolutionState): VersionPaths | undefined { 191 const typesVersions = readPackageJsonTypesVersionsField(jsonContent, state); 192 if (typesVersions === undefined) return; 193 194 if (state.traceEnabled) { 195 for (const key in typesVersions) { 196 if (hasProperty(typesVersions, key) && !VersionRange.tryParse(key)) { 197 trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range, key); 198 } 199 } 200 } 201 202 const result = getPackageJsonTypesVersionsPaths(typesVersions); 203 if (!result) { 204 if (state.traceEnabled) { 205 trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor); 206 } 207 return; 208 } 209 210 const { version: bestVersionKey, paths: bestVersionPaths } = result; 211 if (typeof bestVersionPaths !== "object") { 212 if (state.traceEnabled) { 213 trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "object", typeof bestVersionPaths); 214 } 215 return; 216 } 217 218 return result; 219 } 220 221 let typeScriptVersion: Version | undefined; 222 223 /* @internal */ 224 export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike<MapLike<string[]>>) { 225 if (!typeScriptVersion) typeScriptVersion = new Version(version); 226 227 for (const key in typesVersions) { 228 if (!hasProperty(typesVersions, key)) continue; 229 230 const keyRange = VersionRange.tryParse(key); 231 if (keyRange === undefined) { 232 continue; 233 } 234 235 // return the first entry whose range matches the current compiler version. 236 if (keyRange.test(typeScriptVersion)) { 237 return { version: key, paths: typesVersions[key] }; 238 } 239 } 240 } 241 242 export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined { 243 if (options.typeRoots) { 244 return options.typeRoots; 245 } 246 247 let currentDirectory: string | undefined; 248 if (options.configFilePath) { 249 currentDirectory = getDirectoryPath(options.configFilePath); 250 } 251 else if (host.getCurrentDirectory) { 252 currentDirectory = host.getCurrentDirectory(); 253 } 254 255 if (currentDirectory !== undefined) { 256 return getDefaultTypeRoots(currentDirectory, host, options.packageManagerType); 257 } 258 } 259 260 /** 261 * Returns the path to every node_modules/@types or oh_modules/@types directory from some ancestor directory. 262 * Returns undefined if there are none. 263 */ 264 function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }, packageManagerType?: string): string[] | undefined { 265 const modulesAtTypes = combinePaths(getModuleByPMType(packageManagerType), "@types"); 266 267 if (!host.directoryExists) { 268 return [combinePaths(currentDirectory, modulesAtTypes)]; 269 // And if it doesn't exist, tough. 270 } 271 272 let typeRoots: string[] | undefined; 273 forEachAncestorDirectory(normalizePath(currentDirectory), directory => { 274 const atTypes = combinePaths(directory, modulesAtTypes); 275 if (host.directoryExists!(atTypes)) { 276 (typeRoots || (typeRoots = [])).push(atTypes); 277 } 278 return undefined; 279 }); 280 return typeRoots; 281 } 282 283 /** 284 * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. 285 * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups 286 * is assumed to be the same as root directory of the project. 287 */ 288 export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { 289 const traceEnabled = isTraceEnabled(options, host); 290 if (redirectedReference) { 291 options = redirectedReference.commandLine.options; 292 } 293 const failedLookupLocations: string[] = []; 294 const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations }; 295 296 const typeRoots = getEffectiveTypeRoots(options, host); 297 if (traceEnabled) { 298 if (containingFile === undefined) { 299 if (typeRoots === undefined) { 300 trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName); 301 } 302 else { 303 trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots); 304 } 305 } 306 else { 307 if (typeRoots === undefined) { 308 trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile); 309 } 310 else { 311 trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots); 312 } 313 } 314 if (redirectedReference) { 315 trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); 316 } 317 } 318 319 let resolved = primaryLookup(); 320 let primary = true; 321 if (!resolved) { 322 resolved = secondaryLookup(); 323 primary = false; 324 } 325 326 let resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined; 327 if (resolved) { 328 const { fileName, packageId } = resolved; 329 const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled); 330 if (traceEnabled) { 331 if (packageId) { 332 trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_with_Package_ID_2_primary_Colon_3, typeReferenceDirectiveName, resolvedFileName, packageIdToString(packageId), primary); 333 } 334 else { 335 trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFileName, primary); 336 } 337 } 338 resolvedTypeReferenceDirective = { primary, resolvedFileName, packageId, isExternalLibraryImport: isOhpm(options.packageManagerType) ? 339 pathContainsOHModules(fileName) : pathContainsNodeModules(fileName) }; 340 } 341 342 return { resolvedTypeReferenceDirective, failedLookupLocations }; 343 344 function primaryLookup(): PathAndPackageId | undefined { 345 // Check primary library paths 346 if (typeRoots && typeRoots.length) { 347 if (traceEnabled) { 348 trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", ")); 349 } 350 return firstDefined(typeRoots, typeRoot => { 351 const candidate = combinePaths(typeRoot, typeReferenceDirectiveName); 352 const candidateDirectory = getDirectoryPath(candidate); 353 const directoryExists = directoryProbablyExists(candidateDirectory, host); 354 if (!directoryExists && traceEnabled) { 355 trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidateDirectory); 356 } 357 return resolvedTypeScriptOnly( 358 loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate, 359 !directoryExists, moduleResolutionState)); 360 }); 361 } 362 else { 363 if (traceEnabled) { 364 trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths); 365 } 366 } 367 } 368 369 function secondaryLookup(): PathAndPackageId | undefined { 370 const initialLocationForSecondaryLookup = containingFile && getDirectoryPath(containingFile); 371 const packageManagerType = options.packageManagerType; 372 if (initialLocationForSecondaryLookup !== undefined) { 373 // check secondary locations 374 if (traceEnabled) { 375 const message = isOhpm(packageManagerType) ? Diagnostics.Looking_up_in_oh_modules_folder_initial_location_0: Diagnostics.Looking_up_in_node_modules_folder_initial_location_0; 376 trace(host, message, initialLocationForSecondaryLookup); 377 } 378 let result: Resolved | undefined; 379 if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) { 380 const searchResult = loadModuleFromNearestModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined); 381 result = searchResult && searchResult.value; 382 } 383 else { 384 const { path: candidate } = normalizePathAndParts(combinePaths(initialLocationForSecondaryLookup, typeReferenceDirectiveName)); 385 result = nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true); 386 } 387 const resolvedFile = resolvedTypeScriptOnly(result); 388 if (!resolvedFile && traceEnabled) { 389 trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName); 390 } 391 return resolvedFile; 392 } 393 else { 394 if (traceEnabled) { 395 const message = isOhpm(packageManagerType) ? Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_oh_modules_folder : 396 Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder; 397 trace(host, message); 398 } 399 } 400 } 401 } 402 403 /** 404 * Given a set of options, returns the set of type directive names 405 * that should be included for this program automatically. 406 * This list could either come from the config file, 407 * or from enumerating the types root + initial secondary types lookup location. 408 * More type directives might appear in the program later as a result of loading actual source files; 409 * this list is only the set of defaults that are implicitly included. 410 */ 411 export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[] { 412 // Use explicit type list from tsconfig.json 413 if (options.types) { 414 return options.types; 415 } 416 417 // Walk the primary type lookup locations 418 const result: string[] = []; 419 if (host.directoryExists && host.getDirectories) { 420 const typeRoots = getEffectiveTypeRoots(options, host); 421 if (typeRoots) { 422 for (const root of typeRoots) { 423 if (host.directoryExists(root)) { 424 for (const typeDirectivePath of host.getDirectories(root)) { 425 const normalized = normalizePath(typeDirectivePath); 426 const packageJsonPath = combinePaths(root, normalized, getPackageJsonByPMType(options.packageManagerType)); 427 // `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types. 428 // See `createNotNeededPackageJSON` in the types-publisher` repo. 429 // eslint-disable-next-line no-null/no-null 430 let isNotNeededPackage: boolean; 431 if (isOhpm(options.packageManagerType)) { 432 isNotNeededPackage = host.fileExists(packageJsonPath) && JSON5.parse(host.readFile!(packageJsonPath)!).typings === null; 433 } 434 else { 435 isNotNeededPackage = host.fileExists(packageJsonPath) && (readJson(packageJsonPath, host) as PackageJson).typings === null; 436 } 437 if (!isNotNeededPackage) { 438 const baseFileName = getBaseFileName(normalized); 439 440 // At this stage, skip results with leading dot. 441 if (baseFileName.charCodeAt(0) !== CharacterCodes.dot) { 442 // Return just the type directive names 443 result.push(baseFileName); 444 } 445 } 446 } 447 } 448 } 449 } 450 } 451 return result; 452 } 453 454 /** 455 * Cached module resolutions per containing directory. 456 * This assumes that any module id will have the same resolution for sibling files located in the same folder. 457 */ 458 export interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache { 459 getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map<ResolvedModuleWithFailedLookupLocations>; 460 /*@internal*/ directoryToModuleNameMap: CacheWithRedirects<ESMap<string, ResolvedModuleWithFailedLookupLocations>>; 461 } 462 463 /** 464 * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory 465 * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. 466 */ 467 export interface NonRelativeModuleNameResolutionCache { 468 getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; 469 /*@internal*/ moduleNameToDirectoryMap: CacheWithRedirects<PerModuleNameCache>; 470 } 471 472 export interface PerModuleNameCache { 473 get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined; 474 set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void; 475 } 476 477 export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions): ModuleResolutionCache { 478 return createModuleResolutionCacheWithMaps( 479 createCacheWithRedirects(options), 480 createCacheWithRedirects(options), 481 currentDirectory, 482 getCanonicalFileName 483 ); 484 } 485 486 487 /*@internal*/ 488 export interface CacheWithRedirects<T> { 489 ownMap: ESMap<string, T>; 490 redirectsMap: ESMap<Path, ESMap<string, T>>; 491 getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): ESMap<string, T>; 492 clear(): void; 493 setOwnOptions(newOptions: CompilerOptions): void; 494 setOwnMap(newOwnMap: ESMap<string, T>): void; 495 } 496 497 /*@internal*/ 498 export function createCacheWithRedirects<T>(options?: CompilerOptions): CacheWithRedirects<T> { 499 let ownMap: ESMap<string, T> = new Map(); 500 const redirectsMap = new Map<Path, ESMap<string, T>>(); 501 return { 502 ownMap, 503 redirectsMap, 504 getOrCreateMapOfCacheRedirects, 505 clear, 506 setOwnOptions, 507 setOwnMap 508 }; 509 510 function setOwnOptions(newOptions: CompilerOptions) { 511 options = newOptions; 512 } 513 514 function setOwnMap(newOwnMap: ESMap<string, T>) { 515 ownMap = newOwnMap; 516 } 517 518 function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined) { 519 if (!redirectedReference) { 520 return ownMap; 521 } 522 const path = redirectedReference.sourceFile.path; 523 let redirects = redirectsMap.get(path); 524 if (!redirects) { 525 // Reuse map if redirected reference map uses same resolution 526 redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? new Map() : ownMap; 527 redirectsMap.set(path, redirects); 528 } 529 return redirects; 530 } 531 532 function clear() { 533 ownMap.clear(); 534 redirectsMap.clear(); 535 } 536 } 537 538 /*@internal*/ 539 export function createModuleResolutionCacheWithMaps( 540 directoryToModuleNameMap: CacheWithRedirects<ESMap<string, ResolvedModuleWithFailedLookupLocations>>, 541 moduleNameToDirectoryMap: CacheWithRedirects<PerModuleNameCache>, 542 currentDirectory: string, 543 getCanonicalFileName: GetCanonicalFileName): ModuleResolutionCache { 544 545 return { getOrCreateCacheForDirectory, getOrCreateCacheForModuleName, directoryToModuleNameMap, moduleNameToDirectoryMap }; 546 547 function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { 548 const path = toPath(directoryName, currentDirectory, getCanonicalFileName); 549 return getOrCreateCache<ESMap<string, ResolvedModuleWithFailedLookupLocations>>(directoryToModuleNameMap, redirectedReference, path, () => new Map()); 550 } 551 552 function getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { 553 Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); 554 return getOrCreateCache(moduleNameToDirectoryMap, redirectedReference, nonRelativeModuleName, createPerModuleNameCache); 555 } 556 557 function getOrCreateCache<T>(cacheWithRedirects: CacheWithRedirects<T>, redirectedReference: ResolvedProjectReference | undefined, key: string, create: () => T): T { 558 const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); 559 let result = cache.get(key); 560 if (!result) { 561 result = create(); 562 cache.set(key, result); 563 } 564 return result; 565 } 566 567 function createPerModuleNameCache(): PerModuleNameCache { 568 const directoryPathMap = new Map<string, ResolvedModuleWithFailedLookupLocations>(); 569 570 return { get, set }; 571 572 function get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined { 573 return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName)); 574 } 575 576 /** 577 * At first this function add entry directory -> module resolution result to the table. 578 * Then it computes the set of parent folders for 'directory' that should have the same module resolution result 579 * and for every parent folder in set it adds entry: parent -> module resolution. . 580 * Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts. 581 * Set of parent folders that should have the same result will be: 582 * [ 583 * /a/b/c/d, /a/b/c, /a/b 584 * ] 585 * this means that request for module resolution from file in any of these folder will be immediately found in cache. 586 */ 587 function set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void { 588 const path = toPath(directory, currentDirectory, getCanonicalFileName); 589 // if entry is already in cache do nothing 590 if (directoryPathMap.has(path)) { 591 return; 592 } 593 directoryPathMap.set(path, result); 594 595 const resolvedFileName = result.resolvedModule && 596 (result.resolvedModule.originalPath || result.resolvedModule.resolvedFileName); 597 // find common prefix between directory and resolved file name 598 // this common prefix should be the shortest path that has the same resolution 599 // directory: /a/b/c/d/e 600 // resolvedFileName: /a/b/foo.d.ts 601 // commonPrefix: /a/b 602 // for failed lookups cache the result for every directory up to root 603 const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName); 604 let current = path; 605 while (current !== commonPrefix) { 606 const parent = getDirectoryPath(current); 607 if (parent === current || directoryPathMap.has(parent)) { 608 break; 609 } 610 directoryPathMap.set(parent, result); 611 current = parent; 612 } 613 } 614 615 function getCommonPrefix(directory: Path, resolution: string) { 616 const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName); 617 618 // find first position where directory and resolution differs 619 let i = 0; 620 const limit = Math.min(directory.length, resolutionDirectory.length); 621 while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) { 622 i++; 623 } 624 if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) { 625 return directory; 626 } 627 const rootLength = getRootLength(directory); 628 if (i < rootLength) { 629 return undefined; 630 } 631 const sep = directory.lastIndexOf(directorySeparator, i - 1); 632 if (sep === -1) { 633 return undefined; 634 } 635 return directory.substr(0, Math.max(sep, rootLength)); 636 } 637 } 638 } 639 640 export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined { 641 const containingDirectory = getDirectoryPath(containingFile); 642 const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory); 643 return perFolderCache && perFolderCache.get(moduleName); 644 } 645 646 export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { 647 const traceEnabled = isTraceEnabled(compilerOptions, host); 648 if (redirectedReference) { 649 compilerOptions = redirectedReference.commandLine.options; 650 } 651 if (traceEnabled) { 652 trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); 653 if (redirectedReference) { 654 trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); 655 } 656 } 657 const containingDirectory = getDirectoryPath(containingFile); 658 const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference); 659 let result = perFolderCache && perFolderCache.get(moduleName); 660 661 if (result) { 662 if (traceEnabled) { 663 trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); 664 } 665 } 666 else { 667 let moduleResolution = compilerOptions.moduleResolution; 668 if (moduleResolution === undefined) { 669 moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic; 670 if (traceEnabled) { 671 trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); 672 } 673 } 674 else { 675 if (traceEnabled) { 676 trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); 677 } 678 } 679 680 perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/); 681 switch (moduleResolution) { 682 case ModuleResolutionKind.NodeJs: 683 result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); 684 break; 685 case ModuleResolutionKind.Classic: 686 result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); 687 break; 688 default: 689 return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); 690 } 691 if (result && result.resolvedModule) perfLogger.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`); 692 perfLogger.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null"); 693 694 if (perFolderCache) { 695 perFolderCache.set(moduleName, result); 696 if (!isExternalModuleNameRelative(moduleName)) { 697 // put result in per-module name cache 698 cache!.getOrCreateCacheForModuleName(moduleName, redirectedReference).set(containingDirectory, result); 699 } 700 } 701 } 702 703 if (traceEnabled) { 704 if (result.resolvedModule) { 705 if (result.resolvedModule.packageId) { 706 trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1_with_Package_ID_2, moduleName, result.resolvedModule.resolvedFileName, packageIdToString(result.resolvedModule.packageId)); 707 } 708 else { 709 trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); 710 } 711 } 712 else { 713 trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); 714 } 715 } 716 717 return result; 718 } 719 720 /* 721 * Every module resolution kind can has its specific understanding how to load module from a specific path on disk 722 * I.e. for path '/a/b/c': 723 * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails 724 * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with 725 * 'typings' entry or file 'index' with some supported extension 726 * - Classic loader will only try to interpret '/a/b/c' as file. 727 */ 728 type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined; 729 730 /** 731 * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to 732 * mitigate differences between design time structure of the project and its runtime counterpart so the same import name 733 * can be resolved successfully by TypeScript compiler and runtime module loader. 734 * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will 735 * fallback to standard resolution routine. 736 * 737 * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative 738 * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will 739 * be '/a/b/c/d' 740 * - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names 741 * will be resolved based on the content of the module name. 742 * Structure of 'paths' compiler options 743 * 'paths': { 744 * pattern-1: [...substitutions], 745 * pattern-2: [...substitutions], 746 * ... 747 * pattern-n: [...substitutions] 748 * } 749 * Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against 750 * all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case. 751 * If pattern contains '*' then to match pattern "<prefix>*<suffix>" module name must start with the <prefix> and end with <suffix>. 752 * <MatchedStar> denotes part of the module name between <prefix> and <suffix>. 753 * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked. 754 * After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module 755 * from the candidate location. 756 * Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every 757 * substitution in the list and replace '*' with <MatchedStar> string. If candidate location is not rooted it 758 * will be converted to absolute using baseUrl. 759 * For example: 760 * baseUrl: /a/b/c 761 * "paths": { 762 * // match all module names 763 * "*": [ 764 * "*", // use matched name as is, 765 * // <matched name> will be looked as /a/b/c/<matched name> 766 * 767 * "folder1/*" // substitution will convert matched name to 'folder1/<matched name>', 768 * // since it is not rooted then final candidate location will be /a/b/c/folder1/<matched name> 769 * ], 770 * // match module names that start with 'components/' 771 * "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/<matched name> to '/root/components/folder1/<matched name>', 772 * // it is rooted so it will be final candidate location 773 * } 774 * 775 * 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if 776 * they were in the same location. For example lets say there are two files 777 * '/local/src/content/file1.ts' 778 * '/shared/components/contracts/src/content/protocols/file2.ts' 779 * After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so 780 * if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime. 781 * 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all 782 * root dirs were merged together. 783 * I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ]. 784 * Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file: 785 * '/local/src/content/protocols/file2' and try to load it - failure. 786 * Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will 787 * be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining 788 * entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location. 789 */ 790 function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, 791 state: ModuleResolutionState): Resolved | undefined { 792 793 const resolved = tryLoadModuleUsingPathsIfEligible(extensions, moduleName, loader, state); 794 if (resolved) return resolved.value; 795 796 if (!isExternalModuleNameRelative(moduleName)) { 797 return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state); 798 } 799 else { 800 return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state); 801 } 802 } 803 804 function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) { 805 const { baseUrl, paths } = state.compilerOptions; 806 if (paths && !pathIsRelative(moduleName)) { 807 if (state.traceEnabled) { 808 if (baseUrl) { 809 trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); 810 } 811 trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); 812 } 813 const baseDirectory = getPathsBasePath(state.compilerOptions, state.host)!; // Always defined when 'paths' is defined 814 return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, loader, /*onlyRecordFailures*/ false, state); 815 } 816 } 817 818 function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, 819 state: ModuleResolutionState): Resolved | undefined { 820 821 if (!state.compilerOptions.rootDirs) { 822 return undefined; 823 } 824 825 if (state.traceEnabled) { 826 trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); 827 } 828 829 const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); 830 831 let matchedRootDir: string | undefined; 832 let matchedNormalizedPrefix: string | undefined; 833 for (const rootDir of state.compilerOptions.rootDirs) { 834 // rootDirs are expected to be absolute 835 // in case of tsconfig.json this will happen automatically - compiler will expand relative names 836 // using location of tsconfig.json as base location 837 let normalizedRoot = normalizePath(rootDir); 838 if (!endsWith(normalizedRoot, directorySeparator)) { 839 normalizedRoot += directorySeparator; 840 } 841 const isLongestMatchingPrefix = 842 startsWith(candidate, normalizedRoot) && 843 (matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length); 844 845 if (state.traceEnabled) { 846 trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix); 847 } 848 849 if (isLongestMatchingPrefix) { 850 matchedNormalizedPrefix = normalizedRoot; 851 matchedRootDir = rootDir; 852 } 853 } 854 if (matchedNormalizedPrefix) { 855 if (state.traceEnabled) { 856 trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix); 857 } 858 const suffix = candidate.substr(matchedNormalizedPrefix.length); 859 860 // first - try to load from a initial location 861 if (state.traceEnabled) { 862 trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate); 863 } 864 const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state); 865 if (resolvedFileName) { 866 return resolvedFileName; 867 } 868 869 if (state.traceEnabled) { 870 trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs); 871 } 872 // then try to resolve using remaining entries in rootDirs 873 for (const rootDir of state.compilerOptions.rootDirs) { 874 if (rootDir === matchedRootDir) { 875 // skip the initially matched entry 876 continue; 877 } 878 const candidate = combinePaths(normalizePath(rootDir), suffix); 879 if (state.traceEnabled) { 880 trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate); 881 } 882 const baseDirectory = getDirectoryPath(candidate); 883 const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state); 884 if (resolvedFileName) { 885 return resolvedFileName; 886 } 887 } 888 if (state.traceEnabled) { 889 trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed); 890 } 891 } 892 return undefined; 893 } 894 895 function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { 896 const { baseUrl } = state.compilerOptions; 897 if (!baseUrl) { 898 return undefined; 899 } 900 if (state.traceEnabled) { 901 trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); 902 } 903 const candidate = normalizePath(combinePaths(baseUrl, moduleName)); 904 if (state.traceEnabled) { 905 trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate); 906 } 907 return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); 908 } 909 910 /** 911 * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations. 912 * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963 913 * Throws an error if the module can't be resolved. 914 */ 915 /* @internal */ 916 export function resolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string { 917 const { resolvedModule, failedLookupLocations } = tryResolveJSModuleWorker(moduleName, initialDir, host); 918 if (!resolvedModule) { 919 throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`); 920 } 921 return resolvedModule.resolvedFileName; 922 } 923 924 /* @internal */ 925 export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string | undefined { 926 const { resolvedModule } = tryResolveJSModuleWorker(moduleName, initialDir, host); 927 return resolvedModule && resolvedModule.resolvedFileName; 928 } 929 930 const jsOnlyExtensions = [Extensions.JavaScript]; 931 const tsExtensions = [Extensions.TypeScript, Extensions.JavaScript]; 932 const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json]; 933 const tsconfigExtensions = [Extensions.TSConfig]; 934 function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { 935 return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); 936 } 937 938 export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; 939 /* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures 940 export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations { 941 return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference); 942 } 943 944 function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { 945 const traceEnabled = isTraceEnabled(compilerOptions, host); 946 947 const failedLookupLocations: string[] = []; 948 const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; 949 950 const result = forEach(extensions, ext => tryResolve(ext)); 951 return createResolvedModuleWithFailedLookupLocations(result?.value?.resolved, result?.value?.isExternalLibraryImport, failedLookupLocations, state.resultFromCache); 952 953 function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> { 954 const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true); 955 const isOHModules: boolean = isOhpm(compilerOptions.packageManagerType); 956 const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state); 957 if (resolved) { 958 return toSearchResult({ resolved, isExternalLibraryImport: isOHModules ? pathContainsOHModules(resolved.path) : 959 pathContainsNodeModules(resolved.path) }); 960 } 961 962 if (!isExternalModuleNameRelative(moduleName)) { 963 if (traceEnabled) { 964 const message = isOHModules ? Diagnostics.Loading_module_0_from_oh_modules_folder_target_file_type_1 : Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1; 965 trace(host, message, moduleName, Extensions[extensions]); 966 } 967 const resolved = loadModuleFromNearestModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); 968 if (!resolved) return undefined; 969 970 let resolvedValue = resolved.value; 971 if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) { 972 const path = realPath(resolvedValue.path, host, traceEnabled); 973 const originalPath = path === resolvedValue.path ? undefined : resolvedValue.path; 974 resolvedValue = { ...resolvedValue, path, originalPath }; 975 } 976 // For node_modules or oh_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. 977 return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; 978 } 979 else { 980 const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName)); 981 const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true); 982 // Treat explicit "node_modules" or "oh_modules" import as an external library import. 983 return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, getModuleByPMType(compilerOptions.packageManagerType)) }); 984 } 985 } 986 } 987 988 function realPath(path: string, host: ModuleResolutionHost, traceEnabled: boolean): string { 989 if (!host.realpath) { 990 return path; 991 } 992 993 const real = normalizePath(host.realpath(path)); 994 if (traceEnabled) { 995 trace(host, Diagnostics.Resolving_real_path_for_0_result_1, path, real); 996 } 997 Debug.assert(host.fileExists(real), `${path} linked to nonexistent file ${real}`); 998 return real; 999 } 1000 1001 function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined { 1002 if (state.traceEnabled) { 1003 trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]); 1004 } 1005 if (!hasTrailingDirectorySeparator(candidate)) { 1006 if (!onlyRecordFailures) { 1007 const parentOfCandidate = getDirectoryPath(candidate); 1008 if (!directoryProbablyExists(parentOfCandidate, state.host)) { 1009 if (state.traceEnabled) { 1010 trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, parentOfCandidate); 1011 } 1012 onlyRecordFailures = true; 1013 } 1014 } 1015 const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state); 1016 if (resolvedFromFile) { 1017 const packageDirectory = considerPackageJson ? parseModuleFromPath(resolvedFromFile, state.compilerOptions.packageManagerType) : undefined; 1018 const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; 1019 return withPackageId(packageInfo, resolvedFromFile); 1020 } 1021 } 1022 if (!onlyRecordFailures) { 1023 const candidateExists = directoryProbablyExists(candidate, state.host); 1024 if (!candidateExists) { 1025 if (state.traceEnabled) { 1026 trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidate); 1027 } 1028 onlyRecordFailures = true; 1029 } 1030 } 1031 return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); 1032 } 1033 1034 /*@internal*/ 1035 export const nodeModulesPathPart = "/node_modules/"; 1036 export const ohModulesPathPart = "/oh_modules/"; 1037 1038 /*@internal*/ 1039 export function pathContainsNodeModules(path: string): boolean { 1040 return stringContains(path, nodeModulesPathPart); 1041 } 1042 1043 /*@internal*/ 1044 export function pathContainsOHModules(path: string): boolean { 1045 return stringContains(path, ohModulesPathPart); 1046 } 1047 1048 /** 1049 * This will be called on the successfully resolved path from `loadModuleFromFile`. 1050 * (Not needed for `loadModuleFromNodeModules` as that looks up the `package.json` or `oh-package.json5` as part of resolution.) 1051 * 1052 * packageDirectory is the directory of the package itself. 1053 * For `blah/node_modules/foo/index.d.ts` this is packageDirectory: "foo" 1054 * For `/node_modules/foo/bar.d.ts` this is packageDirectory: "foo" 1055 * For `/node_modules/@types/foo/bar/index.d.ts` this is packageDirectory: "@types/foo" 1056 * For `/node_modules/foo/bar/index.d.ts` this is packageDirectory: "foo" 1057 */ 1058 function parseModuleFromPath(resolved: PathAndExtension, packageManagerType?: string): string | undefined { 1059 const modulesPathPart = isOhpm(packageManagerType) ? ohModulesPathPart : nodeModulesPathPart; 1060 const path = normalizePath(resolved.path); 1061 const idx = path.lastIndexOf(modulesPathPart); 1062 if (idx === -1) { 1063 return undefined; 1064 } 1065 1066 const indexAfterModules = idx + modulesPathPart.length; 1067 let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterModules); 1068 if (path.charCodeAt(indexAfterModules) === CharacterCodes.at) { 1069 indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); 1070 } 1071 return path.slice(0, indexAfterPackageName); 1072 } 1073 1074 function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number { 1075 const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1); 1076 return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex; 1077 } 1078 1079 function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined { 1080 return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state)); 1081 } 1082 1083 /** 1084 * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary 1085 * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. 1086 */ 1087 function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { 1088 if (extensions === Extensions.Json || extensions === Extensions.TSConfig) { 1089 const extensionLess = tryRemoveExtension(candidate, Extension.Json); 1090 return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, onlyRecordFailures, state); 1091 } 1092 1093 // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" 1094 const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, onlyRecordFailures, state); 1095 if (resolvedByAddingExtension) { 1096 return resolvedByAddingExtension; 1097 } 1098 1099 // If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one; 1100 // e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" 1101 if (hasJSFileExtension(candidate)) { 1102 const extensionless = removeFileExtension(candidate); 1103 if (state.traceEnabled) { 1104 const extension = candidate.substring(extensionless.length); 1105 trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); 1106 } 1107 return tryAddingExtensions(extensionless, extensions, onlyRecordFailures, state); 1108 } 1109 } 1110 1111 /** Try to return an existing file that adds one of the `extensions` to `candidate`. */ 1112 function tryAddingExtensions(candidate: string, extensions: Extensions, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { 1113 if (!onlyRecordFailures) { 1114 // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing 1115 const directory = getDirectoryPath(candidate); 1116 if (directory) { 1117 onlyRecordFailures = !directoryProbablyExists(directory, state.host); 1118 } 1119 } 1120 1121 switch (extensions) { 1122 case Extensions.DtsOnly: 1123 if (state.compilerOptions.ets) { 1124 return tryExtension(Extension.Dets) || tryExtension(Extension.Dts); 1125 } 1126 else { 1127 return tryExtension(Extension.Dts); 1128 } 1129 case Extensions.TypeScript: 1130 if (state.compilerOptions.ets) { 1131 return tryExtension(Extension.Ets) || tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dets) || tryExtension(Extension.Dts); 1132 } 1133 else { 1134 return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts) || tryExtension(Extension.Ets); 1135 } 1136 case Extensions.JavaScript: 1137 return tryExtension(Extension.Js) || tryExtension(Extension.Jsx); 1138 case Extensions.TSConfig: 1139 case Extensions.Json: 1140 return tryExtension(Extension.Json); 1141 } 1142 1143 function tryExtension(ext: Extension): PathAndExtension | undefined { 1144 const path = tryFile(candidate + ext, onlyRecordFailures, state); 1145 return path === undefined ? undefined : { path, ext }; 1146 } 1147 } 1148 1149 /** Return the file if it exists. */ 1150 function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { 1151 if (!onlyRecordFailures) { 1152 if (state.host.fileExists(fileName)) { 1153 if (state.traceEnabled) { 1154 trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); 1155 } 1156 return fileName; 1157 } 1158 else { 1159 if (state.traceEnabled) { 1160 trace(state.host, Diagnostics.File_0_does_not_exist, fileName); 1161 } 1162 } 1163 } 1164 state.failedLookupLocations.push(fileName); 1165 return undefined; 1166 } 1167 1168 function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) { 1169 const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined; 1170 const packageJsonContent = packageInfo && packageInfo.packageJsonContent; 1171 const versionPaths = packageInfo && packageInfo.versionPaths; 1172 return withPackageId(packageInfo, loadModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); 1173 } 1174 1175 interface PackageJsonInfo { 1176 packageDirectory: string; 1177 packageJsonContent: PackageJsonPathFields; 1178 versionPaths: VersionPaths | undefined; 1179 } 1180 1181 function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { 1182 const { host, traceEnabled } = state; 1183 const directoryExists = !onlyRecordFailures && directoryProbablyExists(packageDirectory, host); 1184 const packageJsonPath = combinePaths(packageDirectory, getPackageJsonByPMType(state.compilerOptions.packageManagerType)); 1185 if (directoryExists && host.fileExists(packageJsonPath)) { 1186 const isOHModules: boolean = isOhpm(state.compilerOptions.packageManagerType); 1187 const packageJsonContent = isOHModules ? JSON5.parse(host.readFile!(packageJsonPath)!) : readJson(packageJsonPath, host) as PackageJson; 1188 if (traceEnabled) { 1189 const message = isOHModules ? Diagnostics.Found_oh_package_json5_at_0 : Diagnostics.Found_package_json_at_0; 1190 trace(host, message, packageJsonPath); 1191 } 1192 const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); 1193 return { packageDirectory, packageJsonContent, versionPaths }; 1194 } 1195 else { 1196 if (directoryExists && traceEnabled) { 1197 trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath); 1198 } 1199 1200 // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results 1201 state.failedLookupLocations.push(packageJsonPath); 1202 } 1203 } 1204 1205 function loadModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined { 1206 let packageFile: string | undefined; 1207 if (jsonContent) { 1208 switch (extensions) { 1209 case Extensions.JavaScript: 1210 case Extensions.Json: 1211 packageFile = readPackageJsonMainField(jsonContent, candidate, state); 1212 break; 1213 case Extensions.TypeScript: 1214 // When resolving typescript modules, try resolving using main field as well 1215 packageFile = readPackageJsonTypesFields(jsonContent, candidate, state) || readPackageJsonMainField(jsonContent, candidate, state); 1216 break; 1217 case Extensions.DtsOnly: 1218 packageFile = readPackageJsonTypesFields(jsonContent, candidate, state); 1219 break; 1220 case Extensions.TSConfig: 1221 packageFile = readPackageJsonTSConfigField(jsonContent, candidate, state); 1222 break; 1223 default: 1224 return Debug.assertNever(extensions); 1225 } 1226 } 1227 1228 const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { 1229 const fromFile = tryFile(candidate, onlyRecordFailures, state); 1230 if (fromFile) { 1231 const resolved = resolvedIfExtensionMatches(extensions, fromFile); 1232 if (resolved) { 1233 return noPackageId(resolved); 1234 } 1235 if (state.traceEnabled) { 1236 trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile); 1237 } 1238 } 1239 1240 // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" 1241 const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions; 1242 // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. 1243 return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); 1244 }; 1245 1246 const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; 1247 const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host); 1248 const indexPath = combinePaths(candidate, extensions === Extensions.TSConfig ? "tsconfig" : "index"); 1249 1250 if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) { 1251 const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false); 1252 if (state.traceEnabled) { 1253 trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, moduleName); 1254 } 1255 const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); 1256 if (result) { 1257 return removeIgnoredPackageId(result.value); 1258 } 1259 } 1260 1261 // It won't have a `packageId` set, because we disabled `considerPackageJson`. 1262 const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state)); 1263 if (packageFileResult) return packageFileResult; 1264 1265 return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); 1266 } 1267 1268 /** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */ 1269 function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined { 1270 const ext = tryGetExtensionFromPath(path); 1271 return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined; 1272 } 1273 1274 /** True if `extension` is one of the supported `extensions`. */ 1275 function extensionIsOk(extensions: Extensions, extension: Extension): boolean { 1276 switch (extensions) { 1277 case Extensions.JavaScript: 1278 return extension === Extension.Js || extension === Extension.Jsx; 1279 case Extensions.TSConfig: 1280 case Extensions.Json: 1281 return extension === Extension.Json; 1282 case Extensions.TypeScript: 1283 return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts || extension === Extension.Ets || extension === Extension.Dets; 1284 case Extensions.DtsOnly: 1285 return extension === Extension.Dts || extension === Extension.Dets; 1286 } 1287 } 1288 1289 /* @internal */ 1290 export function parsePackageName(moduleName: string): { packageName: string, rest: string } { 1291 let idx = moduleName.indexOf(directorySeparator); 1292 if (moduleName[0] === "@") { 1293 idx = moduleName.indexOf(directorySeparator, idx + 1); 1294 } 1295 return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; 1296 } 1297 1298 function loadModuleFromNearestModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult<Resolved> { 1299 return loadModuleFromNearestModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); 1300 } 1301 1302 function loadModuleFromNearestModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult<Resolved> { 1303 // Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly. 1304 return loadModuleFromNearestModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); 1305 } 1306 1307 function loadModuleFromNearestModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult<Resolved> { 1308 const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); 1309 const packageManagerType = state.compilerOptions.packageManagerType; 1310 const modulePathPart = getModuleByPMType(packageManagerType); 1311 return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { 1312 if (getBaseFileName(ancestorDirectory) !== modulePathPart) { 1313 const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state); 1314 if (resolutionFromCache) { 1315 return resolutionFromCache; 1316 } 1317 return toSearchResult(loadModuleFromImmediateModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly)); 1318 } 1319 }); 1320 } 1321 1322 function loadModuleFromImmediateModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean): Resolved | undefined { 1323 const modulesFolder = combinePaths(directory, getModuleByPMType(state.compilerOptions.packageManagerType)); 1324 const modulesFolderExists = directoryProbablyExists(modulesFolder, state.host); 1325 if (!modulesFolderExists && state.traceEnabled) { 1326 trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, modulesFolder); 1327 } 1328 1329 const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificModulesDirectory(extensions, moduleName, modulesFolder, modulesFolderExists, state); 1330 if (packageResult) { 1331 return packageResult; 1332 } 1333 if (extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) { 1334 const modulesAtTypes = combinePaths(modulesFolder, "@types"); 1335 let modulesAtTypesExists = modulesFolderExists; 1336 if (modulesFolderExists && !directoryProbablyExists(modulesAtTypes, state.host)) { 1337 if (state.traceEnabled) { 1338 trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, modulesAtTypes); 1339 } 1340 modulesAtTypesExists = false; 1341 } 1342 return loadModuleFromSpecificModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), modulesAtTypes, modulesAtTypesExists, state); 1343 } 1344 } 1345 1346 function loadModuleFromSpecificModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState): Resolved | undefined { 1347 const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); 1348 1349 // If oh_modules exist, look for a nested oh-package.json5, as in `oh_modules/foo/bar/oh-package.json5`. 1350 // Otherwise, look for a nested package.json, as in `node_modules/foo/bar/package.json`. 1351 let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); 1352 if (packageInfo) { 1353 const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); 1354 if (fromFile) { 1355 return noPackageId(fromFile); 1356 } 1357 1358 const fromDirectory = loadModuleFromDirectoryWorker( 1359 extensions, 1360 candidate, 1361 !nodeModulesDirectoryExists, 1362 state, 1363 packageInfo.packageJsonContent, 1364 packageInfo.versionPaths 1365 ); 1366 return withPackageId(packageInfo, fromDirectory); 1367 } 1368 1369 const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { 1370 const pathAndExtension = 1371 loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || 1372 loadModuleFromDirectoryWorker( 1373 extensions, 1374 candidate, 1375 onlyRecordFailures, 1376 state, 1377 packageInfo && packageInfo.packageJsonContent, 1378 packageInfo && packageInfo.versionPaths 1379 ); 1380 return withPackageId(packageInfo, pathAndExtension); 1381 }; 1382 1383 const { packageName, rest } = parsePackageName(moduleName); 1384 if (rest !== "") { // If "rest" is empty, we just did this search above. 1385 const packageDirectory = combinePaths(nodeModulesDirectory, packageName); 1386 1387 // Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId and path mappings. 1388 packageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); 1389 if (packageInfo && packageInfo.versionPaths) { 1390 if (state.traceEnabled) { 1391 trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, packageInfo.versionPaths.version, version, rest); 1392 } 1393 const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); 1394 const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.versionPaths.paths, loader, !packageDirectoryExists, state); 1395 if (fromPaths) { 1396 return fromPaths.value; 1397 } 1398 } 1399 } 1400 1401 return loader(extensions, candidate, !nodeModulesDirectoryExists, state); 1402 } 1403 1404 function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike<string[]>, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult<Resolved> { 1405 const matchedPattern = matchPatternOrExact(getOwnKeys(paths), moduleName); 1406 if (matchedPattern) { 1407 const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); 1408 const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); 1409 if (state.traceEnabled) { 1410 trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); 1411 } 1412 const resolved = forEach(paths[matchedPatternText], subst => { 1413 const path = matchedStar ? subst.replace("*", matchedStar) : subst; 1414 // When baseUrl is not specified, the command line parser resolves relative paths to the config file location. 1415 const candidate = normalizePath(combinePaths(baseDirectory, path)); 1416 if (state.traceEnabled) { 1417 trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); 1418 } 1419 // A path mapping may have an extension, in contrast to an import, which should omit it. 1420 const extension = tryGetExtensionFromPath(subst); 1421 if (extension !== undefined) { 1422 const path = tryFile(candidate, onlyRecordFailures, state); 1423 if (path !== undefined) { 1424 return noPackageId({ path, ext: extension }); 1425 } 1426 } 1427 return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); 1428 }); 1429 return { value: resolved }; 1430 } 1431 } 1432 1433 /** Double underscores are used in DefinitelyTyped to delimit scoped packages. */ 1434 const mangledScopedPackageSeparator = "__"; 1435 1436 /** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */ 1437 function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string { 1438 const mangled = mangleScopedPackageName(packageName); 1439 if (state.traceEnabled && mangled !== packageName) { 1440 trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled); 1441 } 1442 return mangled; 1443 } 1444 1445 /* @internal */ 1446 export function getTypesPackageName(packageName: string): string { 1447 return `@types/${mangleScopedPackageName(packageName)}`; 1448 } 1449 1450 /* @internal */ 1451 export function mangleScopedPackageName(packageName: string): string { 1452 if (startsWith(packageName, "@")) { 1453 const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator); 1454 if (replaceSlash !== packageName) { 1455 return replaceSlash.slice(1); // Take off the "@" 1456 } 1457 } 1458 return packageName; 1459 } 1460 1461 /* @internal */ 1462 export function getPackageNameFromTypesPackageName(mangledName: string): string { 1463 const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); 1464 if (withoutAtTypePrefix !== mangledName) { 1465 return unmangleScopedPackageName(withoutAtTypePrefix); 1466 } 1467 return mangledName; 1468 } 1469 1470 /* @internal */ 1471 export function unmangleScopedPackageName(typesPackageName: string): string { 1472 return stringContains(typesPackageName, mangledScopedPackageSeparator) ? 1473 "@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) : 1474 typesPackageName; 1475 } 1476 1477 function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, state: ModuleResolutionState): SearchResult<Resolved> { 1478 const result = cache && cache.get(containingDirectory); 1479 if (result) { 1480 if (state.traceEnabled) { 1481 trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); 1482 } 1483 state.resultFromCache = result; 1484 return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } }; 1485 } 1486 } 1487 1488 export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { 1489 const traceEnabled = isTraceEnabled(compilerOptions, host); 1490 const failedLookupLocations: string[] = []; 1491 const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; 1492 const containingDirectory = getDirectoryPath(containingFile); 1493 1494 const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript); 1495 // No originalPath because classic resolution doesn't resolve realPath 1496 return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations, state.resultFromCache); 1497 1498 function tryResolve(extensions: Extensions): SearchResult<Resolved> { 1499 const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state); 1500 if (resolvedUsingSettings) { 1501 return { value: resolvedUsingSettings }; 1502 } 1503 1504 if (!isExternalModuleNameRelative(moduleName)) { 1505 const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); 1506 // Climb up parent directories looking for a module. 1507 const resolved = forEachAncestorDirectory(containingDirectory, directory => { 1508 const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state); 1509 if (resolutionFromCache) { 1510 return resolutionFromCache; 1511 } 1512 const searchName = normalizePath(combinePaths(directory, moduleName)); 1513 return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state)); 1514 }); 1515 if (resolved) { 1516 return resolved; 1517 } 1518 if (extensions === Extensions.TypeScript) { 1519 // If we didn't find the file normally, look it up in @types. 1520 return loadModuleFromNearestModulesDirectoryTypesScope(moduleName, containingDirectory, state); 1521 } 1522 } 1523 else { 1524 const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); 1525 return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state)); 1526 } 1527 } 1528 } 1529 1530 /** 1531 * A host may load a module from a global cache of typings. 1532 * This is the minumum code needed to expose that functionality; the rest is in the host. 1533 */ 1534 /* @internal */ 1535 export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string): ResolvedModuleWithFailedLookupLocations { 1536 const traceEnabled = isTraceEnabled(compilerOptions, host); 1537 if (traceEnabled) { 1538 trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache); 1539 } 1540 const failedLookupLocations: string[] = []; 1541 const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; 1542 const resolved = loadModuleFromImmediateModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false); 1543 return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations, state.resultFromCache); 1544 } 1545 1546 /** 1547 * Represents result of search. Normally when searching among several alternatives we treat value `undefined` as indicator 1548 * that search fails and we should try another option. 1549 * However this does not allow us to represent final result that should be used instead of further searching (i.e. a final result that was found in cache). 1550 * SearchResult is used to deal with this issue, its values represents following outcomes: 1551 * - undefined - not found, continue searching 1552 * - { value: undefined } - not found - stop searching 1553 * - { value: <some-value> } - found - stop searching 1554 */ 1555 type SearchResult<T> = { value: T | undefined } | undefined; 1556 1557 /** 1558 * Wraps value to SearchResult. 1559 * @returns undefined if value is undefined or { value } otherwise 1560 */ 1561 function toSearchResult<T>(value: T | undefined): SearchResult<T> { 1562 return value !== undefined ? { value } : undefined; 1563 } 1564} 1565