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 } else { 434 isNotNeededPackage = host.fileExists(packageJsonPath) && (readJson(packageJsonPath, host) as PackageJson).typings === null; 435 } 436 if (!isNotNeededPackage) { 437 const baseFileName = getBaseFileName(normalized); 438 439 // At this stage, skip results with leading dot. 440 if (baseFileName.charCodeAt(0) !== CharacterCodes.dot) { 441 // Return just the type directive names 442 result.push(baseFileName); 443 } 444 } 445 } 446 } 447 } 448 } 449 } 450 return result; 451 } 452 453 /** 454 * Cached module resolutions per containing directory. 455 * This assumes that any module id will have the same resolution for sibling files located in the same folder. 456 */ 457 export interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache { 458 getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map<ResolvedModuleWithFailedLookupLocations>; 459 /*@internal*/ directoryToModuleNameMap: CacheWithRedirects<ESMap<string, ResolvedModuleWithFailedLookupLocations>>; 460 } 461 462 /** 463 * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory 464 * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. 465 */ 466 export interface NonRelativeModuleNameResolutionCache { 467 getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; 468 /*@internal*/ moduleNameToDirectoryMap: CacheWithRedirects<PerModuleNameCache>; 469 } 470 471 export interface PerModuleNameCache { 472 get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined; 473 set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void; 474 } 475 476 export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions): ModuleResolutionCache { 477 return createModuleResolutionCacheWithMaps( 478 createCacheWithRedirects(options), 479 createCacheWithRedirects(options), 480 currentDirectory, 481 getCanonicalFileName 482 ); 483 } 484 485 486 /*@internal*/ 487 export interface CacheWithRedirects<T> { 488 ownMap: ESMap<string, T>; 489 redirectsMap: ESMap<Path, ESMap<string, T>>; 490 getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): ESMap<string, T>; 491 clear(): void; 492 setOwnOptions(newOptions: CompilerOptions): void; 493 setOwnMap(newOwnMap: ESMap<string, T>): void; 494 } 495 496 /*@internal*/ 497 export function createCacheWithRedirects<T>(options?: CompilerOptions): CacheWithRedirects<T> { 498 let ownMap: ESMap<string, T> = new Map(); 499 const redirectsMap = new Map<Path, ESMap<string, T>>(); 500 return { 501 ownMap, 502 redirectsMap, 503 getOrCreateMapOfCacheRedirects, 504 clear, 505 setOwnOptions, 506 setOwnMap 507 }; 508 509 function setOwnOptions(newOptions: CompilerOptions) { 510 options = newOptions; 511 } 512 513 function setOwnMap(newOwnMap: ESMap<string, T>) { 514 ownMap = newOwnMap; 515 } 516 517 function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined) { 518 if (!redirectedReference) { 519 return ownMap; 520 } 521 const path = redirectedReference.sourceFile.path; 522 let redirects = redirectsMap.get(path); 523 if (!redirects) { 524 // Reuse map if redirected reference map uses same resolution 525 redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? new Map() : ownMap; 526 redirectsMap.set(path, redirects); 527 } 528 return redirects; 529 } 530 531 function clear() { 532 ownMap.clear(); 533 redirectsMap.clear(); 534 } 535 } 536 537 /*@internal*/ 538 export function createModuleResolutionCacheWithMaps( 539 directoryToModuleNameMap: CacheWithRedirects<ESMap<string, ResolvedModuleWithFailedLookupLocations>>, 540 moduleNameToDirectoryMap: CacheWithRedirects<PerModuleNameCache>, 541 currentDirectory: string, 542 getCanonicalFileName: GetCanonicalFileName): ModuleResolutionCache { 543 544 return { getOrCreateCacheForDirectory, getOrCreateCacheForModuleName, directoryToModuleNameMap, moduleNameToDirectoryMap }; 545 546 function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { 547 const path = toPath(directoryName, currentDirectory, getCanonicalFileName); 548 return getOrCreateCache<ESMap<string, ResolvedModuleWithFailedLookupLocations>>(directoryToModuleNameMap, redirectedReference, path, () => new Map()); 549 } 550 551 function getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { 552 Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); 553 return getOrCreateCache(moduleNameToDirectoryMap, redirectedReference, nonRelativeModuleName, createPerModuleNameCache); 554 } 555 556 function getOrCreateCache<T>(cacheWithRedirects: CacheWithRedirects<T>, redirectedReference: ResolvedProjectReference | undefined, key: string, create: () => T): T { 557 const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); 558 let result = cache.get(key); 559 if (!result) { 560 result = create(); 561 cache.set(key, result); 562 } 563 return result; 564 } 565 566 function createPerModuleNameCache(): PerModuleNameCache { 567 const directoryPathMap = new Map<string, ResolvedModuleWithFailedLookupLocations>(); 568 569 return { get, set }; 570 571 function get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined { 572 return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName)); 573 } 574 575 /** 576 * At first this function add entry directory -> module resolution result to the table. 577 * Then it computes the set of parent folders for 'directory' that should have the same module resolution result 578 * and for every parent folder in set it adds entry: parent -> module resolution. . 579 * Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts. 580 * Set of parent folders that should have the same result will be: 581 * [ 582 * /a/b/c/d, /a/b/c, /a/b 583 * ] 584 * this means that request for module resolution from file in any of these folder will be immediately found in cache. 585 */ 586 function set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void { 587 const path = toPath(directory, currentDirectory, getCanonicalFileName); 588 // if entry is already in cache do nothing 589 if (directoryPathMap.has(path)) { 590 return; 591 } 592 directoryPathMap.set(path, result); 593 594 const resolvedFileName = result.resolvedModule && 595 (result.resolvedModule.originalPath || result.resolvedModule.resolvedFileName); 596 // find common prefix between directory and resolved file name 597 // this common prefix should be the shortest path that has the same resolution 598 // directory: /a/b/c/d/e 599 // resolvedFileName: /a/b/foo.d.ts 600 // commonPrefix: /a/b 601 // for failed lookups cache the result for every directory up to root 602 const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName); 603 let current = path; 604 while (current !== commonPrefix) { 605 const parent = getDirectoryPath(current); 606 if (parent === current || directoryPathMap.has(parent)) { 607 break; 608 } 609 directoryPathMap.set(parent, result); 610 current = parent; 611 } 612 } 613 614 function getCommonPrefix(directory: Path, resolution: string) { 615 const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName); 616 617 // find first position where directory and resolution differs 618 let i = 0; 619 const limit = Math.min(directory.length, resolutionDirectory.length); 620 while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) { 621 i++; 622 } 623 if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) { 624 return directory; 625 } 626 const rootLength = getRootLength(directory); 627 if (i < rootLength) { 628 return undefined; 629 } 630 const sep = directory.lastIndexOf(directorySeparator, i - 1); 631 if (sep === -1) { 632 return undefined; 633 } 634 return directory.substr(0, Math.max(sep, rootLength)); 635 } 636 } 637 } 638 639 export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined { 640 const containingDirectory = getDirectoryPath(containingFile); 641 const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory); 642 return perFolderCache && perFolderCache.get(moduleName); 643 } 644 645 export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { 646 const traceEnabled = isTraceEnabled(compilerOptions, host); 647 if (redirectedReference) { 648 compilerOptions = redirectedReference.commandLine.options; 649 } 650 if (traceEnabled) { 651 trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); 652 if (redirectedReference) { 653 trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); 654 } 655 } 656 const containingDirectory = getDirectoryPath(containingFile); 657 const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference); 658 let result = perFolderCache && perFolderCache.get(moduleName); 659 660 if (result) { 661 if (traceEnabled) { 662 trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); 663 } 664 } 665 else { 666 let moduleResolution = compilerOptions.moduleResolution; 667 if (moduleResolution === undefined) { 668 moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic; 669 if (traceEnabled) { 670 trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); 671 } 672 } 673 else { 674 if (traceEnabled) { 675 trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); 676 } 677 } 678 679 perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/); 680 switch (moduleResolution) { 681 case ModuleResolutionKind.NodeJs: 682 result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); 683 break; 684 case ModuleResolutionKind.Classic: 685 result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); 686 break; 687 default: 688 return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); 689 } 690 if (result && result.resolvedModule) perfLogger.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`); 691 perfLogger.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null"); 692 693 if (perFolderCache) { 694 perFolderCache.set(moduleName, result); 695 if (!isExternalModuleNameRelative(moduleName)) { 696 // put result in per-module name cache 697 cache!.getOrCreateCacheForModuleName(moduleName, redirectedReference).set(containingDirectory, result); 698 } 699 } 700 } 701 702 if (traceEnabled) { 703 if (result.resolvedModule) { 704 if (result.resolvedModule.packageId) { 705 trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1_with_Package_ID_2, moduleName, result.resolvedModule.resolvedFileName, packageIdToString(result.resolvedModule.packageId)); 706 } 707 else { 708 trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); 709 } 710 } 711 else { 712 trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); 713 } 714 } 715 716 return result; 717 } 718 719 /* 720 * Every module resolution kind can has its specific understanding how to load module from a specific path on disk 721 * I.e. for path '/a/b/c': 722 * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails 723 * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with 724 * 'typings' entry or file 'index' with some supported extension 725 * - Classic loader will only try to interpret '/a/b/c' as file. 726 */ 727 type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined; 728 729 /** 730 * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to 731 * mitigate differences between design time structure of the project and its runtime counterpart so the same import name 732 * can be resolved successfully by TypeScript compiler and runtime module loader. 733 * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will 734 * fallback to standard resolution routine. 735 * 736 * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative 737 * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will 738 * be '/a/b/c/d' 739 * - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names 740 * will be resolved based on the content of the module name. 741 * Structure of 'paths' compiler options 742 * 'paths': { 743 * pattern-1: [...substitutions], 744 * pattern-2: [...substitutions], 745 * ... 746 * pattern-n: [...substitutions] 747 * } 748 * Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against 749 * all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case. 750 * If pattern contains '*' then to match pattern "<prefix>*<suffix>" module name must start with the <prefix> and end with <suffix>. 751 * <MatchedStar> denotes part of the module name between <prefix> and <suffix>. 752 * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked. 753 * After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module 754 * from the candidate location. 755 * Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every 756 * substitution in the list and replace '*' with <MatchedStar> string. If candidate location is not rooted it 757 * will be converted to absolute using baseUrl. 758 * For example: 759 * baseUrl: /a/b/c 760 * "paths": { 761 * // match all module names 762 * "*": [ 763 * "*", // use matched name as is, 764 * // <matched name> will be looked as /a/b/c/<matched name> 765 * 766 * "folder1/*" // substitution will convert matched name to 'folder1/<matched name>', 767 * // since it is not rooted then final candidate location will be /a/b/c/folder1/<matched name> 768 * ], 769 * // match module names that start with 'components/' 770 * "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/<matched name> to '/root/components/folder1/<matched name>', 771 * // it is rooted so it will be final candidate location 772 * } 773 * 774 * 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if 775 * they were in the same location. For example lets say there are two files 776 * '/local/src/content/file1.ts' 777 * '/shared/components/contracts/src/content/protocols/file2.ts' 778 * After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so 779 * if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime. 780 * 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all 781 * root dirs were merged together. 782 * I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ]. 783 * Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file: 784 * '/local/src/content/protocols/file2' and try to load it - failure. 785 * Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will 786 * be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining 787 * entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location. 788 */ 789 function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, 790 state: ModuleResolutionState): Resolved | undefined { 791 792 const resolved = tryLoadModuleUsingPathsIfEligible(extensions, moduleName, loader, state); 793 if (resolved) return resolved.value; 794 795 if (!isExternalModuleNameRelative(moduleName)) { 796 return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state); 797 } 798 else { 799 return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state); 800 } 801 } 802 803 function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) { 804 const { baseUrl, paths } = state.compilerOptions; 805 if (paths && !pathIsRelative(moduleName)) { 806 if (state.traceEnabled) { 807 if (baseUrl) { 808 trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); 809 } 810 trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); 811 } 812 const baseDirectory = getPathsBasePath(state.compilerOptions, state.host)!; // Always defined when 'paths' is defined 813 return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, loader, /*onlyRecordFailures*/ false, state); 814 } 815 } 816 817 function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, 818 state: ModuleResolutionState): Resolved | undefined { 819 820 if (!state.compilerOptions.rootDirs) { 821 return undefined; 822 } 823 824 if (state.traceEnabled) { 825 trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); 826 } 827 828 const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); 829 830 let matchedRootDir: string | undefined; 831 let matchedNormalizedPrefix: string | undefined; 832 for (const rootDir of state.compilerOptions.rootDirs) { 833 // rootDirs are expected to be absolute 834 // in case of tsconfig.json this will happen automatically - compiler will expand relative names 835 // using location of tsconfig.json as base location 836 let normalizedRoot = normalizePath(rootDir); 837 if (!endsWith(normalizedRoot, directorySeparator)) { 838 normalizedRoot += directorySeparator; 839 } 840 const isLongestMatchingPrefix = 841 startsWith(candidate, normalizedRoot) && 842 (matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length); 843 844 if (state.traceEnabled) { 845 trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix); 846 } 847 848 if (isLongestMatchingPrefix) { 849 matchedNormalizedPrefix = normalizedRoot; 850 matchedRootDir = rootDir; 851 } 852 } 853 if (matchedNormalizedPrefix) { 854 if (state.traceEnabled) { 855 trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix); 856 } 857 const suffix = candidate.substr(matchedNormalizedPrefix.length); 858 859 // first - try to load from a initial location 860 if (state.traceEnabled) { 861 trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate); 862 } 863 const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state); 864 if (resolvedFileName) { 865 return resolvedFileName; 866 } 867 868 if (state.traceEnabled) { 869 trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs); 870 } 871 // then try to resolve using remaining entries in rootDirs 872 for (const rootDir of state.compilerOptions.rootDirs) { 873 if (rootDir === matchedRootDir) { 874 // skip the initially matched entry 875 continue; 876 } 877 const candidate = combinePaths(normalizePath(rootDir), suffix); 878 if (state.traceEnabled) { 879 trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate); 880 } 881 const baseDirectory = getDirectoryPath(candidate); 882 const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state); 883 if (resolvedFileName) { 884 return resolvedFileName; 885 } 886 } 887 if (state.traceEnabled) { 888 trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed); 889 } 890 } 891 return undefined; 892 } 893 894 function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { 895 const { baseUrl } = state.compilerOptions; 896 if (!baseUrl) { 897 return undefined; 898 } 899 if (state.traceEnabled) { 900 trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); 901 } 902 const candidate = normalizePath(combinePaths(baseUrl, moduleName)); 903 if (state.traceEnabled) { 904 trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate); 905 } 906 return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); 907 } 908 909 /** 910 * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations. 911 * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963 912 * Throws an error if the module can't be resolved. 913 */ 914 /* @internal */ 915 export function resolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string { 916 const { resolvedModule, failedLookupLocations } = tryResolveJSModuleWorker(moduleName, initialDir, host); 917 if (!resolvedModule) { 918 throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`); 919 } 920 return resolvedModule.resolvedFileName; 921 } 922 923 /* @internal */ 924 export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string | undefined { 925 const { resolvedModule } = tryResolveJSModuleWorker(moduleName, initialDir, host); 926 return resolvedModule && resolvedModule.resolvedFileName; 927 } 928 929 const jsOnlyExtensions = [Extensions.JavaScript]; 930 const tsExtensions = [Extensions.TypeScript, Extensions.JavaScript]; 931 const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json]; 932 const tsconfigExtensions = [Extensions.TSConfig]; 933 function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { 934 return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); 935 } 936 937 export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; 938 /* @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 939 export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations { 940 return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference); 941 } 942 943 function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { 944 const traceEnabled = isTraceEnabled(compilerOptions, host); 945 946 const failedLookupLocations: string[] = []; 947 const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; 948 949 const result = forEach(extensions, ext => tryResolve(ext)); 950 return createResolvedModuleWithFailedLookupLocations(result?.value?.resolved, result?.value?.isExternalLibraryImport, failedLookupLocations, state.resultFromCache); 951 952 function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> { 953 const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true); 954 const isOHModules: boolean = isOhpm(compilerOptions.packageManagerType); 955 const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state); 956 if (resolved) { 957 return toSearchResult({ resolved, isExternalLibraryImport: isOHModules ? pathContainsOHModules(resolved.path) : 958 pathContainsNodeModules(resolved.path) }); 959 } 960 961 if (!isExternalModuleNameRelative(moduleName)) { 962 if (traceEnabled) { 963 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; 964 trace(host, message, moduleName, Extensions[extensions]); 965 } 966 const resolved = loadModuleFromNearestModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); 967 if (!resolved) return undefined; 968 969 let resolvedValue = resolved.value; 970 if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) { 971 const path = realPath(resolvedValue.path, host, traceEnabled); 972 const originalPath = path === resolvedValue.path ? undefined : resolvedValue.path; 973 resolvedValue = { ...resolvedValue, path, originalPath }; 974 } 975 // 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. 976 return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; 977 } 978 else { 979 const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName)); 980 const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true); 981 // Treat explicit "node_modules" or "oh_modules" import as an external library import. 982 return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, getModuleByPMType(compilerOptions.packageManagerType)) }); 983 } 984 } 985 } 986 987 function realPath(path: string, host: ModuleResolutionHost, traceEnabled: boolean): string { 988 if (!host.realpath) { 989 return path; 990 } 991 992 const real = normalizePath(host.realpath(path)); 993 if (traceEnabled) { 994 trace(host, Diagnostics.Resolving_real_path_for_0_result_1, path, real); 995 } 996 Debug.assert(host.fileExists(real), `${path} linked to nonexistent file ${real}`); 997 return real; 998 } 999 1000 function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined { 1001 if (state.traceEnabled) { 1002 trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]); 1003 } 1004 if (!hasTrailingDirectorySeparator(candidate)) { 1005 if (!onlyRecordFailures) { 1006 const parentOfCandidate = getDirectoryPath(candidate); 1007 if (!directoryProbablyExists(parentOfCandidate, state.host)) { 1008 if (state.traceEnabled) { 1009 trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, parentOfCandidate); 1010 } 1011 onlyRecordFailures = true; 1012 } 1013 } 1014 const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state); 1015 if (resolvedFromFile) { 1016 const packageDirectory = considerPackageJson ? parseModuleFromPath(resolvedFromFile, state.compilerOptions.packageManagerType) : undefined; 1017 const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; 1018 return withPackageId(packageInfo, resolvedFromFile); 1019 } 1020 } 1021 if (!onlyRecordFailures) { 1022 const candidateExists = directoryProbablyExists(candidate, state.host); 1023 if (!candidateExists) { 1024 if (state.traceEnabled) { 1025 trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidate); 1026 } 1027 onlyRecordFailures = true; 1028 } 1029 } 1030 return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); 1031 } 1032 1033 /*@internal*/ 1034 export const nodeModulesPathPart = "/node_modules/"; 1035 export const ohModulesPathPart = "/oh_modules/"; 1036 1037 /*@internal*/ 1038 export function pathContainsNodeModules(path: string): boolean { 1039 return stringContains(path, nodeModulesPathPart); 1040 } 1041 1042 /*@internal*/ 1043 export function pathContainsOHModules(path: string): boolean { 1044 return stringContains(path, ohModulesPathPart); 1045 } 1046 1047 /** 1048 * This will be called on the successfully resolved path from `loadModuleFromFile`. 1049 * (Not needed for `loadModuleFromNodeModules` as that looks up the `package.json` or `oh-package.json5` as part of resolution.) 1050 * 1051 * packageDirectory is the directory of the package itself. 1052 * For `blah/node_modules/foo/index.d.ts` this is packageDirectory: "foo" 1053 * For `/node_modules/foo/bar.d.ts` this is packageDirectory: "foo" 1054 * For `/node_modules/@types/foo/bar/index.d.ts` this is packageDirectory: "@types/foo" 1055 * For `/node_modules/foo/bar/index.d.ts` this is packageDirectory: "foo" 1056 */ 1057 function parseModuleFromPath(resolved: PathAndExtension, packageManagerType?: string): string | undefined { 1058 const modulesPathPart = isOhpm(packageManagerType) ? ohModulesPathPart : nodeModulesPathPart; 1059 const path = normalizePath(resolved.path); 1060 const idx = path.lastIndexOf(modulesPathPart); 1061 if (idx === -1) { 1062 return undefined; 1063 } 1064 1065 const indexAfterModules = idx + modulesPathPart.length; 1066 let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterModules); 1067 if (path.charCodeAt(indexAfterModules) === CharacterCodes.at) { 1068 indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); 1069 } 1070 return path.slice(0, indexAfterPackageName); 1071 } 1072 1073 function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number { 1074 const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1); 1075 return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex; 1076 } 1077 1078 function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined { 1079 return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state)); 1080 } 1081 1082 /** 1083 * @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 1084 * 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. 1085 */ 1086 function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { 1087 if (extensions === Extensions.Json || extensions === Extensions.TSConfig) { 1088 const extensionLess = tryRemoveExtension(candidate, Extension.Json); 1089 return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, onlyRecordFailures, state); 1090 } 1091 1092 // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" 1093 const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, onlyRecordFailures, state); 1094 if (resolvedByAddingExtension) { 1095 return resolvedByAddingExtension; 1096 } 1097 1098 // If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one; 1099 // e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" 1100 if (hasJSFileExtension(candidate)) { 1101 const extensionless = removeFileExtension(candidate); 1102 if (state.traceEnabled) { 1103 const extension = candidate.substring(extensionless.length); 1104 trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); 1105 } 1106 return tryAddingExtensions(extensionless, extensions, onlyRecordFailures, state); 1107 } 1108 } 1109 1110 /** Try to return an existing file that adds one of the `extensions` to `candidate`. */ 1111 function tryAddingExtensions(candidate: string, extensions: Extensions, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { 1112 if (!onlyRecordFailures) { 1113 // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing 1114 const directory = getDirectoryPath(candidate); 1115 if (directory) { 1116 onlyRecordFailures = !directoryProbablyExists(directory, state.host); 1117 } 1118 } 1119 1120 switch (extensions) { 1121 case Extensions.DtsOnly: 1122 if (state.compilerOptions.ets) { 1123 return tryExtension(Extension.Dets) || tryExtension(Extension.Dts); 1124 } 1125 else { 1126 return tryExtension(Extension.Dts); 1127 } 1128 case Extensions.TypeScript: 1129 if (state.compilerOptions.ets) { 1130 return tryExtension(Extension.Ets) || tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dets) || tryExtension(Extension.Dts); 1131 } 1132 else { 1133 return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts) || tryExtension(Extension.Ets); 1134 } 1135 case Extensions.JavaScript: 1136 return tryExtension(Extension.Js) || tryExtension(Extension.Jsx); 1137 case Extensions.TSConfig: 1138 case Extensions.Json: 1139 return tryExtension(Extension.Json); 1140 } 1141 1142 function tryExtension(ext: Extension): PathAndExtension | undefined { 1143 const path = tryFile(candidate + ext, onlyRecordFailures, state); 1144 return path === undefined ? undefined : { path, ext }; 1145 } 1146 } 1147 1148 /** Return the file if it exists. */ 1149 function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { 1150 if (!onlyRecordFailures) { 1151 if (state.host.fileExists(fileName)) { 1152 if (state.traceEnabled) { 1153 trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); 1154 } 1155 return fileName; 1156 } 1157 else { 1158 if (state.traceEnabled) { 1159 trace(state.host, Diagnostics.File_0_does_not_exist, fileName); 1160 } 1161 } 1162 } 1163 state.failedLookupLocations.push(fileName); 1164 return undefined; 1165 } 1166 1167 function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) { 1168 const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined; 1169 const packageJsonContent = packageInfo && packageInfo.packageJsonContent; 1170 const versionPaths = packageInfo && packageInfo.versionPaths; 1171 return withPackageId(packageInfo, loadModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); 1172 } 1173 1174 interface PackageJsonInfo { 1175 packageDirectory: string; 1176 packageJsonContent: PackageJsonPathFields; 1177 versionPaths: VersionPaths | undefined; 1178 } 1179 1180 function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { 1181 const { host, traceEnabled } = state; 1182 const directoryExists = !onlyRecordFailures && directoryProbablyExists(packageDirectory, host); 1183 const packageJsonPath = combinePaths(packageDirectory, getPackageJsonByPMType(state.compilerOptions.packageManagerType)); 1184 if (directoryExists && host.fileExists(packageJsonPath)) { 1185 const isOHModules: boolean = isOhpm(state.compilerOptions.packageManagerType); 1186 const packageJsonContent = isOHModules ? JSON5.parse(host.readFile!(packageJsonPath)!) : readJson(packageJsonPath, host) as PackageJson; 1187 if (traceEnabled) { 1188 const message = isOHModules ? Diagnostics.Found_oh_package_json5_at_0 : Diagnostics.Found_package_json_at_0; 1189 trace(host, message, packageJsonPath); 1190 } 1191 const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); 1192 return { packageDirectory, packageJsonContent, versionPaths }; 1193 } 1194 else { 1195 if (directoryExists && traceEnabled) { 1196 trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath); 1197 } 1198 1199 // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results 1200 state.failedLookupLocations.push(packageJsonPath); 1201 } 1202 } 1203 1204 function loadModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined { 1205 let packageFile: string | undefined; 1206 if (jsonContent) { 1207 switch (extensions) { 1208 case Extensions.JavaScript: 1209 case Extensions.Json: 1210 packageFile = readPackageJsonMainField(jsonContent, candidate, state); 1211 break; 1212 case Extensions.TypeScript: 1213 // When resolving typescript modules, try resolving using main field as well 1214 packageFile = readPackageJsonTypesFields(jsonContent, candidate, state) || readPackageJsonMainField(jsonContent, candidate, state); 1215 break; 1216 case Extensions.DtsOnly: 1217 packageFile = readPackageJsonTypesFields(jsonContent, candidate, state); 1218 break; 1219 case Extensions.TSConfig: 1220 packageFile = readPackageJsonTSConfigField(jsonContent, candidate, state); 1221 break; 1222 default: 1223 return Debug.assertNever(extensions); 1224 } 1225 } 1226 1227 const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { 1228 const fromFile = tryFile(candidate, onlyRecordFailures, state); 1229 if (fromFile) { 1230 const resolved = resolvedIfExtensionMatches(extensions, fromFile); 1231 if (resolved) { 1232 return noPackageId(resolved); 1233 } 1234 if (state.traceEnabled) { 1235 trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile); 1236 } 1237 } 1238 1239 // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" 1240 const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions; 1241 // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. 1242 return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); 1243 }; 1244 1245 const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; 1246 const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host); 1247 const indexPath = combinePaths(candidate, extensions === Extensions.TSConfig ? "tsconfig" : "index"); 1248 1249 if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) { 1250 const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false); 1251 if (state.traceEnabled) { 1252 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); 1253 } 1254 const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); 1255 if (result) { 1256 return removeIgnoredPackageId(result.value); 1257 } 1258 } 1259 1260 // It won't have a `packageId` set, because we disabled `considerPackageJson`. 1261 const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state)); 1262 if (packageFileResult) return packageFileResult; 1263 1264 return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); 1265 } 1266 1267 /** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */ 1268 function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined { 1269 const ext = tryGetExtensionFromPath(path); 1270 return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined; 1271 } 1272 1273 /** True if `extension` is one of the supported `extensions`. */ 1274 function extensionIsOk(extensions: Extensions, extension: Extension): boolean { 1275 switch (extensions) { 1276 case Extensions.JavaScript: 1277 return extension === Extension.Js || extension === Extension.Jsx; 1278 case Extensions.TSConfig: 1279 case Extensions.Json: 1280 return extension === Extension.Json; 1281 case Extensions.TypeScript: 1282 return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts || extension === Extension.Ets || extension === Extension.Dets; 1283 case Extensions.DtsOnly: 1284 return extension === Extension.Dts || extension === Extension.Dets; 1285 } 1286 } 1287 1288 /* @internal */ 1289 export function parsePackageName(moduleName: string): { packageName: string, rest: string } { 1290 let idx = moduleName.indexOf(directorySeparator); 1291 if (moduleName[0] === "@") { 1292 idx = moduleName.indexOf(directorySeparator, idx + 1); 1293 } 1294 return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; 1295 } 1296 1297 function loadModuleFromNearestModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult<Resolved> { 1298 return loadModuleFromNearestModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); 1299 } 1300 1301 function loadModuleFromNearestModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult<Resolved> { 1302 // Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly. 1303 return loadModuleFromNearestModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); 1304 } 1305 1306 function loadModuleFromNearestModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult<Resolved> { 1307 const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); 1308 const packageManagerType = state.compilerOptions.packageManagerType; 1309 const modulePathPart = getModuleByPMType(packageManagerType); 1310 return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { 1311 if (getBaseFileName(ancestorDirectory) !== modulePathPart) { 1312 const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state); 1313 if (resolutionFromCache) { 1314 return resolutionFromCache; 1315 } 1316 return toSearchResult(loadModuleFromImmediateModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly)); 1317 } 1318 }); 1319 } 1320 1321 function loadModuleFromImmediateModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean): Resolved | undefined { 1322 const modulesFolder = combinePaths(directory, getModuleByPMType(state.compilerOptions.packageManagerType)); 1323 const modulesFolderExists = directoryProbablyExists(modulesFolder, state.host); 1324 if (!modulesFolderExists && state.traceEnabled) { 1325 trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, modulesFolder); 1326 } 1327 1328 const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificModulesDirectory(extensions, moduleName, modulesFolder, modulesFolderExists, state); 1329 if (packageResult) { 1330 return packageResult; 1331 } 1332 if (extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) { 1333 const modulesAtTypes = combinePaths(modulesFolder, "@types"); 1334 let modulesAtTypesExists = modulesFolderExists; 1335 if (modulesFolderExists && !directoryProbablyExists(modulesAtTypes, state.host)) { 1336 if (state.traceEnabled) { 1337 trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, modulesAtTypes); 1338 } 1339 modulesAtTypesExists = false; 1340 } 1341 return loadModuleFromSpecificModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), modulesAtTypes, modulesAtTypesExists, state); 1342 } 1343 } 1344 1345 function loadModuleFromSpecificModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState): Resolved | undefined { 1346 const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); 1347 1348 // If oh_modules exist, look for a nested oh-package.json5, as in `oh_modules/foo/bar/oh-package.json5`. 1349 // Otherwise, look for a nested package.json, as in `node_modules/foo/bar/package.json`. 1350 let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); 1351 if (packageInfo) { 1352 const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); 1353 if (fromFile) { 1354 return noPackageId(fromFile); 1355 } 1356 1357 const fromDirectory = loadModuleFromDirectoryWorker( 1358 extensions, 1359 candidate, 1360 !nodeModulesDirectoryExists, 1361 state, 1362 packageInfo.packageJsonContent, 1363 packageInfo.versionPaths 1364 ); 1365 return withPackageId(packageInfo, fromDirectory); 1366 } 1367 1368 const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { 1369 const pathAndExtension = 1370 loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || 1371 loadModuleFromDirectoryWorker( 1372 extensions, 1373 candidate, 1374 onlyRecordFailures, 1375 state, 1376 packageInfo && packageInfo.packageJsonContent, 1377 packageInfo && packageInfo.versionPaths 1378 ); 1379 return withPackageId(packageInfo, pathAndExtension); 1380 }; 1381 1382 const { packageName, rest } = parsePackageName(moduleName); 1383 if (rest !== "") { // If "rest" is empty, we just did this search above. 1384 const packageDirectory = combinePaths(nodeModulesDirectory, packageName); 1385 1386 // 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. 1387 packageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); 1388 if (packageInfo && packageInfo.versionPaths) { 1389 if (state.traceEnabled) { 1390 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); 1391 } 1392 const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); 1393 const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.versionPaths.paths, loader, !packageDirectoryExists, state); 1394 if (fromPaths) { 1395 return fromPaths.value; 1396 } 1397 } 1398 } 1399 1400 return loader(extensions, candidate, !nodeModulesDirectoryExists, state); 1401 } 1402 1403 function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike<string[]>, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult<Resolved> { 1404 const matchedPattern = matchPatternOrExact(getOwnKeys(paths), moduleName); 1405 if (matchedPattern) { 1406 const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); 1407 const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); 1408 if (state.traceEnabled) { 1409 trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); 1410 } 1411 const resolved = forEach(paths[matchedPatternText], subst => { 1412 const path = matchedStar ? subst.replace("*", matchedStar) : subst; 1413 // When baseUrl is not specified, the command line parser resolves relative paths to the config file location. 1414 const candidate = normalizePath(combinePaths(baseDirectory, path)); 1415 if (state.traceEnabled) { 1416 trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); 1417 } 1418 // A path mapping may have an extension, in contrast to an import, which should omit it. 1419 const extension = tryGetExtensionFromPath(subst); 1420 if (extension !== undefined) { 1421 const path = tryFile(candidate, onlyRecordFailures, state); 1422 if (path !== undefined) { 1423 return noPackageId({ path, ext: extension }); 1424 } 1425 } 1426 return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); 1427 }); 1428 return { value: resolved }; 1429 } 1430 } 1431 1432 /** Double underscores are used in DefinitelyTyped to delimit scoped packages. */ 1433 const mangledScopedPackageSeparator = "__"; 1434 1435 /** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */ 1436 function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string { 1437 const mangled = mangleScopedPackageName(packageName); 1438 if (state.traceEnabled && mangled !== packageName) { 1439 trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled); 1440 } 1441 return mangled; 1442 } 1443 1444 /* @internal */ 1445 export function getTypesPackageName(packageName: string): string { 1446 return `@types/${mangleScopedPackageName(packageName)}`; 1447 } 1448 1449 /* @internal */ 1450 export function mangleScopedPackageName(packageName: string): string { 1451 if (startsWith(packageName, "@")) { 1452 const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator); 1453 if (replaceSlash !== packageName) { 1454 return replaceSlash.slice(1); // Take off the "@" 1455 } 1456 } 1457 return packageName; 1458 } 1459 1460 /* @internal */ 1461 export function getPackageNameFromTypesPackageName(mangledName: string): string { 1462 const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); 1463 if (withoutAtTypePrefix !== mangledName) { 1464 return unmangleScopedPackageName(withoutAtTypePrefix); 1465 } 1466 return mangledName; 1467 } 1468 1469 /* @internal */ 1470 export function unmangleScopedPackageName(typesPackageName: string): string { 1471 return stringContains(typesPackageName, mangledScopedPackageSeparator) ? 1472 "@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) : 1473 typesPackageName; 1474 } 1475 1476 function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, state: ModuleResolutionState): SearchResult<Resolved> { 1477 const result = cache && cache.get(containingDirectory); 1478 if (result) { 1479 if (state.traceEnabled) { 1480 trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); 1481 } 1482 state.resultFromCache = result; 1483 return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } }; 1484 } 1485 } 1486 1487 export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { 1488 const traceEnabled = isTraceEnabled(compilerOptions, host); 1489 const failedLookupLocations: string[] = []; 1490 const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; 1491 const containingDirectory = getDirectoryPath(containingFile); 1492 1493 const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript); 1494 // No originalPath because classic resolution doesn't resolve realPath 1495 return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations, state.resultFromCache); 1496 1497 function tryResolve(extensions: Extensions): SearchResult<Resolved> { 1498 const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state); 1499 if (resolvedUsingSettings) { 1500 return { value: resolvedUsingSettings }; 1501 } 1502 1503 if (!isExternalModuleNameRelative(moduleName)) { 1504 const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); 1505 // Climb up parent directories looking for a module. 1506 const resolved = forEachAncestorDirectory(containingDirectory, directory => { 1507 const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state); 1508 if (resolutionFromCache) { 1509 return resolutionFromCache; 1510 } 1511 const searchName = normalizePath(combinePaths(directory, moduleName)); 1512 return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state)); 1513 }); 1514 if (resolved) { 1515 return resolved; 1516 } 1517 if (extensions === Extensions.TypeScript) { 1518 // If we didn't find the file normally, look it up in @types. 1519 return loadModuleFromNearestModulesDirectoryTypesScope(moduleName, containingDirectory, state); 1520 } 1521 } 1522 else { 1523 const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); 1524 return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state)); 1525 } 1526 } 1527 } 1528 1529 /** 1530 * A host may load a module from a global cache of typings. 1531 * This is the minumum code needed to expose that functionality; the rest is in the host. 1532 */ 1533 /* @internal */ 1534 export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string): ResolvedModuleWithFailedLookupLocations { 1535 const traceEnabled = isTraceEnabled(compilerOptions, host); 1536 if (traceEnabled) { 1537 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); 1538 } 1539 const failedLookupLocations: string[] = []; 1540 const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; 1541 const resolved = loadModuleFromImmediateModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false); 1542 return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations, state.resultFromCache); 1543 } 1544 1545 /** 1546 * Represents result of search. Normally when searching among several alternatives we treat value `undefined` as indicator 1547 * that search fails and we should try another option. 1548 * 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). 1549 * SearchResult is used to deal with this issue, its values represents following outcomes: 1550 * - undefined - not found, continue searching 1551 * - { value: undefined } - not found - stop searching 1552 * - { value: <some-value> } - found - stop searching 1553 */ 1554 type SearchResult<T> = { value: T | undefined } | undefined; 1555 1556 /** 1557 * Wraps value to SearchResult. 1558 * @returns undefined if value is undefined or { value } otherwise 1559 */ 1560 function toSearchResult<T>(value: T | undefined): SearchResult<T> { 1561 return value !== undefined ? { value } : undefined; 1562 } 1563} 1564