import { append, appendIfUnique, arrayFrom, arrayIsEqualTo, changeAnyExtension, CharacterCodes, combinePaths, CommandLineOption, comparePaths, Comparison, CompilerHost, CompilerOptions, concatenate, contains, containsPath, createCompilerDiagnostic, Debug, deduplicate, Diagnostic, DiagnosticMessage, DiagnosticReporter, Diagnostics, directoryProbablyExists, directorySeparator, emptyArray, endsWith, ensureTrailingDirectorySeparator, every, Extension, extensionIsTS, fileExtensionIs, fileExtensionIsOneOf, filter, firstDefined, forEach, forEachAncestorDirectory, formatMessage, getAllowJSCompilerOption, getAnyExtensionFromPath, getBaseFileName, GetCanonicalFileName, getCommonSourceDirectory, getCompilerOptionValue, getDeclarationEmitExtensionForPath, getDirectoryPath, GetEffectiveTypeRootsHost, getEmitModuleKind, getEmitModuleResolutionKind, getNormalizedAbsolutePath, getOwnKeys, getPathComponents, getPathFromPathComponents, getPathsBasePath, getPossibleOriginalInputExtensionForExtension, getRelativePathFromDirectory, getResolveJsonModule, getRootLength, hasProperty, hasTrailingDirectorySeparator, hostGetCanonicalFileName, inferredTypesContainingFile, isArray, isDeclarationFileName, isExternalModuleNameRelative, isRootedDiskPath, isString, lastOrUndefined, length, MapLike, matchedText, MatchingKeys, matchPatternOrExact, ModuleKind, ModuleResolutionHost, ModuleResolutionKind, moduleResolutionOptionDeclarations, moduleResolutionSupportsPackageJsonExportsAndImports, noop, normalizePath, normalizeSlashes, PackageId, packageIdToString, Path, pathIsRelative, Pattern, patternText, perfLogger, readJson, removeExtension, removeFileExtension, removePrefix, ResolutionMode, ResolvedModuleWithFailedLookupLocations, ResolvedProjectReference, ResolvedTypeReferenceDirective, ResolvedTypeReferenceDirectiveWithFailedLookupLocations, some, sort, startsWith, supportedDeclarationExtensions, supportedJSExtensionsFlat, supportedTSImplementationExtensions, toPath, tryExtractTSExtension, tryGetExtensionFromPath, tryParsePatterns, Version, version, versionMajorMinor, VersionRange, } from "./_namespaces/ts"; /** @internal */ export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void { host.trace!(formatMessage(message, ...args)); } /** @internal */ export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { return !!compilerOptions.traceResolution && host.trace !== undefined; } function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined { let packageId: PackageId | undefined; if (r && packageInfo) { const packageJsonContent = packageInfo.contents.packageJsonContent as PackageJson; if (typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string") { packageId = { name: packageJsonContent.name, subModuleName: r.path.slice(packageInfo.packageDirectory.length + directorySeparator.length), version: packageJsonContent.version, }; } } return r && { path: r.path, extension: r.ext, packageId, resolvedUsingTsExtension: r.resolvedUsingTsExtension }; } function noPackageId(r: PathAndExtension | undefined): Resolved | undefined { return withPackageId(/*packageInfo*/ undefined, r); } function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined { if (r) { Debug.assert(r.packageId === undefined); return { path: r.path, ext: r.extension, resolvedUsingTsExtension: r.resolvedUsingTsExtension }; } } /** Result of trying to resolve a module. */ interface Resolved { path: string; extension: string; packageId: PackageId | undefined; /** * When the resolved is not created from cache, the value is * - string if it is symbolic link to the resolved `path` * - undefined if `path` is not a symbolic link * When the resolved is created using value from cache of ResolvedModuleWithFailedLookupLocations, the value is: * - string if it is symbolic link to the resolved `path` * - true if `path` is not a symbolic link - this indicates that the `originalPath` calculation is already done and needs to be skipped * Note: This is a file name with preserved original casing, not a normalized `Path`. */ originalPath?: string | true; resolvedUsingTsExtension: boolean | undefined; } /** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */ interface PathAndExtension { path: string; // (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.) ext: string; resolvedUsingTsExtension: boolean | undefined; } // dprint-ignore /** * Kinds of file that we are currently looking for. */ const enum Extensions { TypeScript = 1 << 0, // '.ts', '.tsx', '.mts', '.cts' JavaScript = 1 << 1, // '.js', '.jsx', '.mjs', '.cjs' Declaration = 1 << 2, // '.d.ts', etc. Json = 1 << 3, // '.json' ImplementationFiles = TypeScript | JavaScript, } function formatExtensions(extensions: Extensions) { const result: string[] = []; if (extensions & Extensions.TypeScript) result.push("TypeScript"); if (extensions & Extensions.JavaScript) result.push("JavaScript"); if (extensions & Extensions.Declaration) result.push("Declaration"); if (extensions & Extensions.Json) result.push("JSON"); return result.join(", "); } function extensionsToExtensionsArray(extensions: Extensions) { const result: Extension[] = []; if (extensions & Extensions.TypeScript) result.push(...supportedTSImplementationExtensions); if (extensions & Extensions.JavaScript) result.push(...supportedJSExtensionsFlat); if (extensions & Extensions.Declaration) result.push(...supportedDeclarationExtensions); if (extensions & Extensions.Json) result.push(Extension.Json); return result; } interface PathAndPackageId { readonly fileName: string; readonly packageId: PackageId | undefined; } /** Used with `Extensions.DtsOnly` to extract the path from TypeScript results. */ function resolvedTypeScriptOnly(resolved: Resolved | undefined): PathAndPackageId | undefined { if (!resolved) { return undefined; } Debug.assert(extensionIsTS(resolved.extension)); return { fileName: resolved.path, packageId: resolved.packageId }; } function createResolvedModuleWithFailedLookupLocationsHandlingSymlink( moduleName: string, resolved: Resolved | undefined, isExternalLibraryImport: boolean | undefined, failedLookupLocations: string[], affectingLocations: string[], diagnostics: Diagnostic[], state: ModuleResolutionState, cache: ModuleResolutionCache | NonRelativeModuleNameResolutionCache | undefined, legacyResult?: string, ): ResolvedModuleWithFailedLookupLocations { // If this is from node_modules for non relative name, always respect preserveSymlinks if ( !state.resultFromCache && !state.compilerOptions.preserveSymlinks && resolved && isExternalLibraryImport && !resolved.originalPath && !isExternalModuleNameRelative(moduleName) ) { const { resolvedFileName, originalPath } = getOriginalAndResolvedFileName(resolved.path, state.host, state.traceEnabled); if (originalPath) resolved = { ...resolved, path: resolvedFileName, originalPath }; } return createResolvedModuleWithFailedLookupLocations( resolved, isExternalLibraryImport, failedLookupLocations, affectingLocations, diagnostics, state.resultFromCache, cache, legacyResult, ); } function createResolvedModuleWithFailedLookupLocations( resolved: Resolved | undefined, isExternalLibraryImport: boolean | undefined, failedLookupLocations: string[], affectingLocations: string[], diagnostics: Diagnostic[], resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined, cache: ModuleResolutionCache | NonRelativeModuleNameResolutionCache | undefined, legacyResult?: string, ): ResolvedModuleWithFailedLookupLocations { if (resultFromCache) { if (!cache?.isReadonly) { resultFromCache.failedLookupLocations = updateResolutionField(resultFromCache.failedLookupLocations, failedLookupLocations); resultFromCache.affectingLocations = updateResolutionField(resultFromCache.affectingLocations, affectingLocations); resultFromCache.resolutionDiagnostics = updateResolutionField(resultFromCache.resolutionDiagnostics, diagnostics); return resultFromCache; } else { return { ...resultFromCache, failedLookupLocations: initializeResolutionFieldForReadonlyCache(resultFromCache.failedLookupLocations, failedLookupLocations), affectingLocations: initializeResolutionFieldForReadonlyCache(resultFromCache.affectingLocations, affectingLocations), resolutionDiagnostics: initializeResolutionFieldForReadonlyCache(resultFromCache.resolutionDiagnostics, diagnostics), }; } } return { resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath: resolved.originalPath === true ? undefined : resolved.originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId, resolvedUsingTsExtension: !!resolved.resolvedUsingTsExtension, }, failedLookupLocations: initializeResolutionField(failedLookupLocations), affectingLocations: initializeResolutionField(affectingLocations), resolutionDiagnostics: initializeResolutionField(diagnostics), node10Result: legacyResult, }; } function initializeResolutionField(value: T[]): T[] | undefined { return value.length ? value : undefined; } /** @internal */ export function updateResolutionField(to: T[] | undefined, value: T[] | undefined) { if (!value?.length) return to; if (!to?.length) return value; to.push(...value); return to; } function initializeResolutionFieldForReadonlyCache(fromCache: T[] | undefined, value: T[]): T[] | undefined { if (!fromCache?.length) return initializeResolutionField(value); if (!value.length) return fromCache.slice(); return [...fromCache, ...value]; } /** @internal */ export interface ModuleResolutionState { host: ModuleResolutionHost; compilerOptions: CompilerOptions; traceEnabled: boolean; failedLookupLocations: string[] | undefined; affectingLocations: string[] | undefined; resultFromCache?: ResolvedModuleWithFailedLookupLocations; packageJsonInfoCache: PackageJsonInfoCache | undefined; features: NodeResolutionFeatures; conditions: readonly string[]; requestContainingDirectory: string | undefined; reportDiagnostic: DiagnosticReporter; isConfigLookup: boolean; candidateIsFromPackageJsonField: boolean; } /** Just the fields that we use for module resolution. * * @internal */ export interface PackageJsonPathFields { typings?: string; types?: string; typesVersions?: MapLike>; main?: string; tsconfig?: string; type?: string; imports?: object; exports?: object; name?: string; } interface PackageJson extends PackageJsonPathFields { name?: string; version?: string; } function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined; function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "object", state: ModuleResolutionState): PackageJson[K] | undefined; function readPackageJsonField(jsonContent: PackageJson, fieldName: K, typeOfTag: "string" | "object", state: ModuleResolutionState): PackageJson[K] | undefined { if (!hasProperty(jsonContent, fieldName)) { if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName); } return; } const value = jsonContent[fieldName]; if (typeof value !== typeOfTag || value === null) { // eslint-disable-line no-null/no-null if (state.traceEnabled) { // eslint-disable-next-line no-null/no-null trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, typeOfTag, value === null ? "null" : typeof value); } return; } return value; } function readPackageJsonPathField(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined { const fileName = readPackageJsonField(jsonContent, fieldName, "string", state); if (fileName === undefined) { return; } if (!fileName) { if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_had_a_falsy_0_field, fieldName); } return; } const path = normalizePath(combinePaths(baseDirectory, fileName)); if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path); } return path; } function readPackageJsonTypesFields(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { return readPackageJsonPathField(jsonContent, "typings", baseDirectory, state) || readPackageJsonPathField(jsonContent, "types", baseDirectory, state); } function readPackageJsonTSConfigField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { return readPackageJsonPathField(jsonContent, "tsconfig", baseDirectory, state); } function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { return readPackageJsonPathField(jsonContent, "main", baseDirectory, state); } function readPackageJsonTypesVersionsField(jsonContent: PackageJson, state: ModuleResolutionState) { const typesVersions = readPackageJsonField(jsonContent, "typesVersions", "object", state); if (typesVersions === undefined) return; if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_has_a_typesVersions_field_with_version_specific_path_mappings); } return typesVersions; } /** @internal */ export interface VersionPaths { version: string; paths: MapLike; } function readPackageJsonTypesVersionPaths(jsonContent: PackageJson, state: ModuleResolutionState): VersionPaths | undefined { const typesVersions = readPackageJsonTypesVersionsField(jsonContent, state); if (typesVersions === undefined) return; if (state.traceEnabled) { for (const key in typesVersions) { if (hasProperty(typesVersions, key) && !VersionRange.tryParse(key)) { trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range, key); } } } const result = getPackageJsonTypesVersionsPaths(typesVersions); if (!result) { if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor); } return; } const { version: bestVersionKey, paths: bestVersionPaths } = result; if (typeof bestVersionPaths !== "object") { if (state.traceEnabled) { trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "object", typeof bestVersionPaths); } return; } return result; } let typeScriptVersion: Version | undefined; /** @internal */ export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike>) { if (!typeScriptVersion) typeScriptVersion = new Version(version); for (const key in typesVersions) { if (!hasProperty(typesVersions, key)) continue; const keyRange = VersionRange.tryParse(key); if (keyRange === undefined) { continue; } // return the first entry whose range matches the current compiler version. if (keyRange.test(typeScriptVersion)) { return { version: key, paths: typesVersions[key] }; } } } export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined { if (options.typeRoots) { return options.typeRoots; } let currentDirectory: string | undefined; if (options.configFilePath) { currentDirectory = getDirectoryPath(options.configFilePath); } else if (host.getCurrentDirectory) { currentDirectory = host.getCurrentDirectory(); } if (currentDirectory !== undefined) { return getDefaultTypeRoots(currentDirectory); } } /** * Returns the path to every node_modules/@types directory from some ancestor directory. * Returns undefined if there are none. */ function getDefaultTypeRoots(currentDirectory: string): string[] | undefined { let typeRoots: string[] | undefined; forEachAncestorDirectory(normalizePath(currentDirectory), directory => { const atTypes = combinePaths(directory, nodeModulesAtTypes); (typeRoots ??= []).push(atTypes); }); return typeRoots; } const nodeModulesAtTypes = combinePaths("node_modules", "@types"); function arePathsEqual(path1: string, path2: string, host: ModuleResolutionHost): boolean { const useCaseSensitiveFileNames = typeof host.useCaseSensitiveFileNames === "function" ? host.useCaseSensitiveFileNames() : host.useCaseSensitiveFileNames; return comparePaths(path1, path2, !useCaseSensitiveFileNames) === Comparison.EqualTo; } function getOriginalAndResolvedFileName(fileName: string, host: ModuleResolutionHost, traceEnabled: boolean) { const resolvedFileName = realPath(fileName, host, traceEnabled); const pathsAreEqual = arePathsEqual(fileName, resolvedFileName, host); return { // If the fileName and realpath are differing only in casing prefer fileName so that we can issue correct errors for casing under forceConsistentCasingInFileNames resolvedFileName: pathsAreEqual ? fileName : resolvedFileName, originalPath: pathsAreEqual ? undefined : fileName, }; } function getCandidateFromTypeRoot(typeRoot: string, typeReferenceDirectiveName: string, moduleResolutionState: ModuleResolutionState) { const nameForLookup = endsWith(typeRoot, "/node_modules/@types") || endsWith(typeRoot, "/node_modules/@types/") ? mangleScopedPackageNameWithTrace(typeReferenceDirectiveName, moduleResolutionState) : typeReferenceDirectiveName; return combinePaths(typeRoot, nameForLookup); } /** * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups * is assumed to be the same as root directory of the project. */ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, cache?: TypeReferenceDirectiveResolutionCache, resolutionMode?: ResolutionMode): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { Debug.assert(typeof typeReferenceDirectiveName === "string", "Non-string value passed to `ts.resolveTypeReferenceDirective`, likely by a wrapping package working with an outdated `resolveTypeReferenceDirectives` signature. This is probably not a problem in TS itself."); const traceEnabled = isTraceEnabled(options, host); if (redirectedReference) { options = redirectedReference.commandLine.options; } const containingDirectory = containingFile ? getDirectoryPath(containingFile) : undefined; let result = containingDirectory ? cache?.getFromDirectoryCache(typeReferenceDirectiveName, resolutionMode, containingDirectory, redirectedReference) : undefined; if (!result && containingDirectory && !isExternalModuleNameRelative(typeReferenceDirectiveName)) { result = cache?.getFromNonRelativeNameCache(typeReferenceDirectiveName, resolutionMode, containingDirectory, redirectedReference); } if (result) { if (traceEnabled) { trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1, typeReferenceDirectiveName, containingFile); if (redirectedReference) trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); trace(host, Diagnostics.Resolution_for_type_reference_directive_0_was_found_in_cache_from_location_1, typeReferenceDirectiveName, containingDirectory); traceResult(result); } return result; } const typeRoots = getEffectiveTypeRoots(options, host); if (traceEnabled) { if (containingFile === undefined) { if (typeRoots === undefined) { trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName); } else { trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots); } } else { if (typeRoots === undefined) { trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile); } else { trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots); } } if (redirectedReference) { trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); } } const failedLookupLocations: string[] = []; const affectingLocations: string[] = []; // Allow type reference directives to opt into `exports` resolution in any resolution mode // when a `resolution-mode` override is present. let features = getNodeResolutionFeatures(options); if (resolutionMode !== undefined) { features |= NodeResolutionFeatures.AllFeatures; } const moduleResolution = getEmitModuleResolutionKind(options); if (resolutionMode === ModuleKind.ESNext && (ModuleResolutionKind.Node16 <= moduleResolution && moduleResolution <= ModuleResolutionKind.NodeNext)) { features |= NodeResolutionFeatures.EsmMode; } const conditions = (features & NodeResolutionFeatures.Exports) ? getConditions(options, resolutionMode) : []; const diagnostics: Diagnostic[] = []; const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations, affectingLocations, packageJsonInfoCache: cache, features, conditions, requestContainingDirectory: containingDirectory, reportDiagnostic: diag => void diagnostics.push(diag), isConfigLookup: false, candidateIsFromPackageJsonField: false, }; let resolved = primaryLookup(); let primary = true; if (!resolved) { resolved = secondaryLookup(); primary = false; } let resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined; if (resolved) { const { fileName, packageId } = resolved; let resolvedFileName = fileName, originalPath: string | undefined; if (!options.preserveSymlinks) ({ resolvedFileName, originalPath } = getOriginalAndResolvedFileName(fileName, host, traceEnabled)); resolvedTypeReferenceDirective = { primary, resolvedFileName, originalPath, packageId, isExternalLibraryImport: pathContainsNodeModules(fileName), }; } result = { resolvedTypeReferenceDirective, failedLookupLocations: initializeResolutionField(failedLookupLocations), affectingLocations: initializeResolutionField(affectingLocations), resolutionDiagnostics: initializeResolutionField(diagnostics), }; if (containingDirectory && cache && !cache.isReadonly) { cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference).set(typeReferenceDirectiveName, /*mode*/ resolutionMode, result); if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) { cache.getOrCreateCacheForNonRelativeName(typeReferenceDirectiveName, resolutionMode, redirectedReference).set(containingDirectory, result); } } if (traceEnabled) traceResult(result); return result; function traceResult(result: ResolvedTypeReferenceDirectiveWithFailedLookupLocations) { if (!result.resolvedTypeReferenceDirective?.resolvedFileName) { trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName); } else if (result.resolvedTypeReferenceDirective.packageId) { trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_with_Package_ID_2_primary_Colon_3, typeReferenceDirectiveName, result.resolvedTypeReferenceDirective.resolvedFileName, packageIdToString(result.resolvedTypeReferenceDirective.packageId), result.resolvedTypeReferenceDirective.primary); } else { trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, result.resolvedTypeReferenceDirective.resolvedFileName, result.resolvedTypeReferenceDirective.primary); } } function primaryLookup(): PathAndPackageId | undefined { // Check primary library paths if (typeRoots && typeRoots.length) { if (traceEnabled) { trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", ")); } return firstDefined(typeRoots, typeRoot => { const candidate = getCandidateFromTypeRoot(typeRoot, typeReferenceDirectiveName, moduleResolutionState); const directoryExists = directoryProbablyExists(typeRoot, host); if (!directoryExists && traceEnabled) { trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, typeRoot); } if (options.typeRoots) { // Custom typeRoots resolve as file or directory just like we do modules const resolvedFromFile = loadModuleFromFile(Extensions.Declaration, candidate, !directoryExists, moduleResolutionState); if (resolvedFromFile) { const packageDirectory = parseNodeModuleFromPath(resolvedFromFile.path); const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, moduleResolutionState) : undefined; return resolvedTypeScriptOnly(withPackageId(packageInfo, resolvedFromFile)); } } return resolvedTypeScriptOnly( loadNodeModuleFromDirectory(Extensions.Declaration, candidate, !directoryExists, moduleResolutionState), ); }); } else { if (traceEnabled) { trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths); } } } function secondaryLookup(): PathAndPackageId | undefined { const initialLocationForSecondaryLookup = containingFile && getDirectoryPath(containingFile); if (initialLocationForSecondaryLookup !== undefined) { let result: Resolved | undefined; if (!options.typeRoots || !endsWith(containingFile!, inferredTypesContainingFile)) { // check secondary locations if (traceEnabled) { trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup); } if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) { const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.Declaration, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined); result = searchResult && searchResult.value; } else { const { path: candidate } = normalizePathForCJSResolution(initialLocationForSecondaryLookup, typeReferenceDirectiveName); result = nodeLoadModuleByRelativeName(Extensions.Declaration, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true); } } else if (traceEnabled) { trace(host, Diagnostics.Resolving_type_reference_directive_for_program_that_specifies_custom_typeRoots_skipping_lookup_in_node_modules_folder); } return resolvedTypeScriptOnly(result); } else { if (traceEnabled) { trace(host, Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder); } } } } function getNodeResolutionFeatures(options: CompilerOptions) { let features = NodeResolutionFeatures.None; switch (getEmitModuleResolutionKind(options)) { case ModuleResolutionKind.Node16: features = NodeResolutionFeatures.Node16Default; break; case ModuleResolutionKind.NodeNext: features = NodeResolutionFeatures.NodeNextDefault; break; case ModuleResolutionKind.Bundler: features = NodeResolutionFeatures.BundlerDefault; break; } if (options.resolvePackageJsonExports) { features |= NodeResolutionFeatures.Exports; } else if (options.resolvePackageJsonExports === false) { features &= ~NodeResolutionFeatures.Exports; } if (options.resolvePackageJsonImports) { features |= NodeResolutionFeatures.Imports; } else if (options.resolvePackageJsonImports === false) { features &= ~NodeResolutionFeatures.Imports; } return features; } /** @internal */ export function getConditions(options: CompilerOptions, resolutionMode?: ResolutionMode) { const moduleResolution = getEmitModuleResolutionKind(options); if (resolutionMode === undefined) { if (moduleResolution === ModuleResolutionKind.Bundler) { // bundler always uses `import` unless explicitly overridden resolutionMode = ModuleKind.ESNext; } else if (moduleResolution === ModuleResolutionKind.Node10) { // node10 does not support package.json imports/exports without // an explicit resolution-mode override on a type-only import // (indicated by `esmMode` being set) return []; } } // conditions are only used by the node16/nodenext/bundler resolvers - there's no priority order in the list, // it's essentially a set (priority is determined by object insertion order in the object we look at). const conditions = resolutionMode === ModuleKind.ESNext ? ["import"] : ["require"]; if (!options.noDtsResolution) { conditions.push("types"); } if (moduleResolution !== ModuleResolutionKind.Bundler) { conditions.push("node"); } return concatenate(conditions, options.customConditions); } /** * @internal * Does not try `@types/${packageName}` - use a second pass if needed. */ export function resolvePackageNameToPackageJson( packageName: string, containingDirectory: string, options: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, ): PackageJsonInfo | undefined { const moduleResolutionState = getTemporaryModuleResolutionState(cache?.getPackageJsonInfoCache(), host, options); return forEachAncestorDirectory(containingDirectory, ancestorDirectory => { if (getBaseFileName(ancestorDirectory) !== "node_modules") { const nodeModulesFolder = combinePaths(ancestorDirectory, "node_modules"); const candidate = combinePaths(nodeModulesFolder, packageName); return getPackageJsonInfo(candidate, /*onlyRecordFailures*/ false, moduleResolutionState); } }); } /** * Given a set of options, returns the set of type directive names * that should be included for this program automatically. * This list could either come from the config file, * or from enumerating the types root + initial secondary types lookup location. * More type directives might appear in the program later as a result of loading actual source files; * this list is only the set of defaults that are implicitly included. */ export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[] { // Use explicit type list from tsconfig.json if (options.types) { return options.types; } // Walk the primary type lookup locations const result: string[] = []; if (host.directoryExists && host.getDirectories) { const typeRoots = getEffectiveTypeRoots(options, host); if (typeRoots) { for (const root of typeRoots) { if (host.directoryExists(root)) { for (const typeDirectivePath of host.getDirectories(root)) { const normalized = normalizePath(typeDirectivePath); const packageJsonPath = combinePaths(root, normalized, "package.json"); // `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types. // See `createNotNeededPackageJSON` in the types-publisher` repo. // eslint-disable-next-line no-null/no-null const isNotNeededPackage = host.fileExists(packageJsonPath) && (readJson(packageJsonPath, host) as PackageJson).typings === null; if (!isNotNeededPackage) { const baseFileName = getBaseFileName(normalized); // At this stage, skip results with leading dot. if (baseFileName.charCodeAt(0) !== CharacterCodes.dot) { // Return just the type directive names result.push(baseFileName); } } } } } } } return result; } export interface TypeReferenceDirectiveResolutionCache extends PerDirectoryResolutionCache, NonRelativeNameResolutionCache, PackageJsonInfoCache { /** @internal */ clearAllExceptPackageJsonInfoCache(): void; } export interface ModeAwareCache { get(key: string, mode: ResolutionMode): T | undefined; set(key: string, mode: ResolutionMode, value: T): this; delete(key: string, mode: ResolutionMode): this; has(key: string, mode: ResolutionMode): boolean; forEach(cb: (elem: T, key: string, mode: ResolutionMode) => void): void; size(): number; } /** * Cached resolutions per containing directory. * This assumes that any module id will have the same resolution for sibling files located in the same folder. */ export interface PerDirectoryResolutionCache { getFromDirectoryCache(name: string, mode: ResolutionMode, directoryName: string, redirectedReference: ResolvedProjectReference | undefined): T | undefined; getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): ModeAwareCache; clear(): void; /** * Updates with the current compilerOptions the cache will operate with. * This updates the redirects map as well if needed so module resolutions are cached if they can across the projects */ update(options: CompilerOptions): void; /** @internal */ directoryToModuleNameMap: CacheWithRedirects>; /** @internal */ isReadonly?: boolean; } export interface NonRelativeNameResolutionCache { getFromNonRelativeNameCache(nonRelativeName: string, mode: ResolutionMode, directoryName: string, redirectedReference: ResolvedProjectReference | undefined): T | undefined; getOrCreateCacheForNonRelativeName(nonRelativeName: string, mode: ResolutionMode, redirectedReference?: ResolvedProjectReference): PerNonRelativeNameCache; clear(): void; /** * Updates with the current compilerOptions the cache will operate with. * This updates the redirects map as well if needed so module resolutions are cached if they can across the projects */ update(options: CompilerOptions): void; /** @internal */ isReadonly?: boolean; } export interface PerNonRelativeNameCache { get(directory: string): T | undefined; set(directory: string, result: T): void; /** @internal */ isReadonly?: boolean; } export interface ModuleResolutionCache extends PerDirectoryResolutionCache, NonRelativeModuleNameResolutionCache, PackageJsonInfoCache { getPackageJsonInfoCache(): PackageJsonInfoCache; /** @internal */ clearAllExceptPackageJsonInfoCache(): void; /** @internal */ optionsToRedirectsKey: Map; } /** * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. */ export interface NonRelativeModuleNameResolutionCache extends NonRelativeNameResolutionCache, PackageJsonInfoCache { /** @deprecated Use getOrCreateCacheForNonRelativeName */ getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ResolutionMode, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; } export interface PackageJsonInfoCache { /** @internal */ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfo | boolean | undefined; /** @internal */ setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean): void; /** @internal */ entries(): [Path, PackageJsonInfo | boolean][]; /** @internal */ getInternalMap(): Map | undefined; clear(): void; /** @internal */ isReadonly?: boolean; } export type PerModuleNameCache = PerNonRelativeNameCache; function compilerOptionValueToString(value: unknown): string { if (value === null || typeof value !== "object") { // eslint-disable-line no-null/no-null return "" + value; } if (isArray(value)) { return `[${value.map(e => compilerOptionValueToString(e))?.join(",")}]`; } let str = "{"; for (const key in value) { if (hasProperty(value, key)) { str += `${key}: ${compilerOptionValueToString((value as any)[key])}`; } } return str + "}"; } /** @internal */ export function getKeyForCompilerOptions(options: CompilerOptions, affectingOptionDeclarations: readonly CommandLineOption[]) { return affectingOptionDeclarations.map(option => compilerOptionValueToString(getCompilerOptionValue(options, option))).join("|") + `|${options.pathsBasePath}`; } /** @internal */ export interface CacheWithRedirects { getMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): Map | undefined; getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): Map; update(newOptions: CompilerOptions): void; clear(): void; getOwnMap(): Map; } /** @internal */ export type RedirectsCacheKey = string & { __compilerOptionsKey: any; }; /** @internal */ export function createCacheWithRedirects(ownOptions: CompilerOptions | undefined, optionsToRedirectsKey: Map): CacheWithRedirects { const redirectsMap = new Map>(); const redirectsKeyToMap = new Map>(); let ownMap = new Map(); if (ownOptions) redirectsMap.set(ownOptions, ownMap); return { getMapOfCacheRedirects, getOrCreateMapOfCacheRedirects, update, clear, getOwnMap: () => ownMap, }; function getMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): Map | undefined { return redirectedReference ? getOrCreateMap(redirectedReference.commandLine.options, /*create*/ false) : ownMap; } function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): Map { return redirectedReference ? getOrCreateMap(redirectedReference.commandLine.options, /*create*/ true) : ownMap; } function update(newOptions: CompilerOptions) { if (ownOptions !== newOptions) { if (ownOptions) ownMap = getOrCreateMap(newOptions, /*create*/ true); // set new map for new options as ownMap else redirectsMap.set(newOptions, ownMap); // Use existing map if oldOptions = undefined ownOptions = newOptions; } } function getOrCreateMap(redirectOptions: CompilerOptions, create: true): Map; function getOrCreateMap(redirectOptions: CompilerOptions, create: false): Map | undefined; function getOrCreateMap(redirectOptions: CompilerOptions, create: boolean): Map | undefined { let result = redirectsMap.get(redirectOptions); if (result) return result; const key = getRedirectsCacheKey(redirectOptions); result = redirectsKeyToMap.get(key); if (!result) { if (ownOptions) { const ownKey = getRedirectsCacheKey(ownOptions); if (ownKey === key) result = ownMap; else if (!redirectsKeyToMap.has(ownKey)) redirectsKeyToMap.set(ownKey, ownMap); } if (create) result ??= new Map(); if (result) redirectsKeyToMap.set(key, result); } if (result) redirectsMap.set(redirectOptions, result); return result; } function clear() { const ownKey = ownOptions && optionsToRedirectsKey.get(ownOptions); ownMap.clear(); redirectsMap.clear(); optionsToRedirectsKey.clear(); redirectsKeyToMap.clear(); if (ownOptions) { if (ownKey) optionsToRedirectsKey.set(ownOptions, ownKey); redirectsMap.set(ownOptions, ownMap); } } function getRedirectsCacheKey(options: CompilerOptions) { let result = optionsToRedirectsKey.get(options); if (!result) { optionsToRedirectsKey.set(options, result = getKeyForCompilerOptions(options, moduleResolutionOptionDeclarations) as RedirectsCacheKey); } return result; } } function createPackageJsonInfoCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): PackageJsonInfoCache { let cache: Map | undefined; return { getPackageJsonInfo, setPackageJsonInfo, clear, entries, getInternalMap }; function getPackageJsonInfo(packageJsonPath: string) { return cache?.get(toPath(packageJsonPath, currentDirectory, getCanonicalFileName)); } function setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean) { (cache ||= new Map()).set(toPath(packageJsonPath, currentDirectory, getCanonicalFileName), info); } function clear() { cache = undefined; } function entries() { const iter = cache?.entries(); return iter ? arrayFrom(iter) : []; } function getInternalMap() { return cache; } } function getOrCreateCache(cacheWithRedirects: CacheWithRedirects, redirectedReference: ResolvedProjectReference | undefined, key: K, create: () => V): V { const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); let result = cache.get(key); if (!result) { result = create(); cache.set(key, result); } return result; } function createPerDirectoryResolutionCache( currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, options: CompilerOptions | undefined, optionsToRedirectsKey: Map, ): PerDirectoryResolutionCache { const directoryToModuleNameMap = createCacheWithRedirects>(options, optionsToRedirectsKey); return { getFromDirectoryCache, getOrCreateCacheForDirectory, clear, update, directoryToModuleNameMap, }; function clear() { directoryToModuleNameMap.clear(); } function update(options: CompilerOptions) { directoryToModuleNameMap.update(options); } function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { const path = toPath(directoryName, currentDirectory, getCanonicalFileName); return getOrCreateCache(directoryToModuleNameMap, redirectedReference, path, () => createModeAwareCache()); } function getFromDirectoryCache(name: string, mode: ResolutionMode, directoryName: string, redirectedReference: ResolvedProjectReference | undefined) { const path = toPath(directoryName, currentDirectory, getCanonicalFileName); return directoryToModuleNameMap.getMapOfCacheRedirects(redirectedReference)?.get(path)?.get(name, mode); } } /** @internal */ export type ModeAwareCacheKey = string & { __modeAwareCacheKey: any; }; /** @internal */ export function createModeAwareCacheKey(specifier: string, mode: ResolutionMode) { return (mode === undefined ? specifier : `${mode}|${specifier}`) as ModeAwareCacheKey; } /** @internal */ export function createModeAwareCache(): ModeAwareCache { const underlying = new Map(); const memoizedReverseKeys = new Map(); const cache: ModeAwareCache = { get(specifier, mode) { return underlying.get(getUnderlyingCacheKey(specifier, mode)); }, set(specifier, mode, value) { underlying.set(getUnderlyingCacheKey(specifier, mode), value); return cache; }, delete(specifier, mode) { underlying.delete(getUnderlyingCacheKey(specifier, mode)); return cache; }, has(specifier, mode) { return underlying.has(getUnderlyingCacheKey(specifier, mode)); }, forEach(cb) { return underlying.forEach((elem, key) => { const [specifier, mode] = memoizedReverseKeys.get(key)!; return cb(elem, specifier, mode); }); }, size() { return underlying.size; }, }; return cache; function getUnderlyingCacheKey(specifier: string, mode: ResolutionMode) { const result = createModeAwareCacheKey(specifier, mode); memoizedReverseKeys.set(result, [specifier, mode]); return result; } } function getOriginalOrResolvedModuleFileName(result: ResolvedModuleWithFailedLookupLocations) { return result.resolvedModule && (result.resolvedModule.originalPath || result.resolvedModule.resolvedFileName); } function getOriginalOrResolvedTypeReferenceFileName(result: ResolvedTypeReferenceDirectiveWithFailedLookupLocations) { return result.resolvedTypeReferenceDirective && (result.resolvedTypeReferenceDirective.originalPath || result.resolvedTypeReferenceDirective.resolvedFileName); } function createNonRelativeNameResolutionCache( currentDirectory: string, getCanonicalFileName: (s: string) => string, options: CompilerOptions | undefined, getResolvedFileName: (result: T) => string | undefined, optionsToRedirectsKey: Map, ): NonRelativeNameResolutionCache { const moduleNameToDirectoryMap = createCacheWithRedirects>(options, optionsToRedirectsKey); return { getFromNonRelativeNameCache, getOrCreateCacheForNonRelativeName, clear, update, }; function clear() { moduleNameToDirectoryMap.clear(); } function update(options: CompilerOptions) { moduleNameToDirectoryMap.update(options); } function getFromNonRelativeNameCache(nonRelativeModuleName: string, mode: ResolutionMode, directoryName: string, redirectedReference?: ResolvedProjectReference): T | undefined { Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); return moduleNameToDirectoryMap.getMapOfCacheRedirects(redirectedReference)?.get(createModeAwareCacheKey(nonRelativeModuleName, mode))?.get(directoryName); } function getOrCreateCacheForNonRelativeName(nonRelativeModuleName: string, mode: ResolutionMode, redirectedReference?: ResolvedProjectReference): PerNonRelativeNameCache { Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); return getOrCreateCache(moduleNameToDirectoryMap, redirectedReference, createModeAwareCacheKey(nonRelativeModuleName, mode), createPerModuleNameCache); } function createPerModuleNameCache(): PerNonRelativeNameCache { const directoryPathMap = new Map(); return { get, set }; function get(directory: string): T | undefined { return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName)); } /** * At first this function add entry directory -> module resolution result to the table. * Then it computes the set of parent folders for 'directory' that should have the same module resolution result * and for every parent folder in set it adds entry: parent -> module resolution. . * Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts. * Set of parent folders that should have the same result will be: * [ * /a/b/c/d, /a/b/c, /a/b * ] * this means that request for module resolution from file in any of these folder will be immediately found in cache. */ function set(directory: string, result: T): void { const path = toPath(directory, currentDirectory, getCanonicalFileName); // if entry is already in cache do nothing if (directoryPathMap.has(path)) { return; } directoryPathMap.set(path, result); const resolvedFileName = getResolvedFileName(result); // find common prefix between directory and resolved file name // this common prefix should be the shortest path that has the same resolution // directory: /a/b/c/d/e // resolvedFileName: /a/b/foo.d.ts // commonPrefix: /a/b // for failed lookups cache the result for every directory up to root const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName); let current = path; while (current !== commonPrefix) { const parent = getDirectoryPath(current); if (parent === current || directoryPathMap.has(parent)) { break; } directoryPathMap.set(parent, result); current = parent; } } function getCommonPrefix(directory: Path, resolution: string) { const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName); // find first position where directory and resolution differs let i = 0; const limit = Math.min(directory.length, resolutionDirectory.length); while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) { i++; } if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) { return directory; } const rootLength = getRootLength(directory); if (i < rootLength) { return undefined; } const sep = directory.lastIndexOf(directorySeparator, i - 1); if (sep === -1) { return undefined; } return directory.substr(0, Math.max(sep, rootLength)); } } } interface ModuleOrTypeReferenceResolutionCache extends PerDirectoryResolutionCache, NonRelativeNameResolutionCache, PackageJsonInfoCache { getPackageJsonInfoCache(): PackageJsonInfoCache; clearAllExceptPackageJsonInfoCache(): void; optionsToRedirectsKey: Map; } function createModuleOrTypeReferenceResolutionCache( currentDirectory: string, getCanonicalFileName: (s: string) => string, options: CompilerOptions | undefined, packageJsonInfoCache: PackageJsonInfoCache | undefined, getResolvedFileName: (result: T) => string | undefined, optionsToRedirectsKey: Map | undefined, ): ModuleOrTypeReferenceResolutionCache { optionsToRedirectsKey ??= new Map(); const perDirectoryResolutionCache = createPerDirectoryResolutionCache( currentDirectory, getCanonicalFileName, options, optionsToRedirectsKey, ); const nonRelativeNameResolutionCache = createNonRelativeNameResolutionCache( currentDirectory, getCanonicalFileName, options, getResolvedFileName, optionsToRedirectsKey, ); packageJsonInfoCache ??= createPackageJsonInfoCache(currentDirectory, getCanonicalFileName); return { ...packageJsonInfoCache, ...perDirectoryResolutionCache, ...nonRelativeNameResolutionCache, clear, update, getPackageJsonInfoCache: () => packageJsonInfoCache!, clearAllExceptPackageJsonInfoCache, optionsToRedirectsKey, }; function clear() { clearAllExceptPackageJsonInfoCache(); packageJsonInfoCache!.clear(); } function clearAllExceptPackageJsonInfoCache() { perDirectoryResolutionCache.clear(); nonRelativeNameResolutionCache.clear(); } function update(options: CompilerOptions) { perDirectoryResolutionCache.update(options); nonRelativeNameResolutionCache.update(options); } } export function createModuleResolutionCache( currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions, packageJsonInfoCache?: PackageJsonInfoCache, ): ModuleResolutionCache; /** @internal */ export function createModuleResolutionCache( currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions, packageJsonInfoCache?: PackageJsonInfoCache, optionsToRedirectsKey?: Map, ): ModuleResolutionCache; export function createModuleResolutionCache( currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions, packageJsonInfoCache?: PackageJsonInfoCache, optionsToRedirectsKey?: Map, ): ModuleResolutionCache { const result = createModuleOrTypeReferenceResolutionCache( currentDirectory, getCanonicalFileName, options, packageJsonInfoCache, getOriginalOrResolvedModuleFileName, optionsToRedirectsKey, ) as ModuleResolutionCache; result.getOrCreateCacheForModuleName = (nonRelativeName, mode, redirectedReference) => result.getOrCreateCacheForNonRelativeName(nonRelativeName, mode, redirectedReference); return result; } export function createTypeReferenceDirectiveResolutionCache( currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions, packageJsonInfoCache?: PackageJsonInfoCache, ): TypeReferenceDirectiveResolutionCache; /** @internal */ export function createTypeReferenceDirectiveResolutionCache( currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions, packageJsonInfoCache?: PackageJsonInfoCache, optionsToRedirectsKey?: Map, ): TypeReferenceDirectiveResolutionCache; export function createTypeReferenceDirectiveResolutionCache( currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions, packageJsonInfoCache?: PackageJsonInfoCache, optionsToRedirectsKey?: Map, ): TypeReferenceDirectiveResolutionCache { return createModuleOrTypeReferenceResolutionCache( currentDirectory, getCanonicalFileName, options, packageJsonInfoCache, getOriginalOrResolvedTypeReferenceFileName, optionsToRedirectsKey, ); } /** @internal */ export function getOptionsForLibraryResolution(options: CompilerOptions) { return { moduleResolution: ModuleResolutionKind.Node10, traceResolution: options.traceResolution }; } /** @internal */ export function resolveLibrary(libraryName: string, resolveFrom: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations { return resolveModuleName(libraryName, resolveFrom, getOptionsForLibraryResolution(compilerOptions), host, cache); } export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache, mode?: ResolutionMode): ResolvedModuleWithFailedLookupLocations | undefined { const containingDirectory = getDirectoryPath(containingFile); return cache.getFromDirectoryCache(moduleName, mode, containingDirectory, /*redirectedReference*/ undefined); } export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ResolutionMode): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); if (redirectedReference) { compilerOptions = redirectedReference.commandLine.options; } if (traceEnabled) { trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); if (redirectedReference) { trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); } } const containingDirectory = getDirectoryPath(containingFile); let result = cache?.getFromDirectoryCache(moduleName, resolutionMode, containingDirectory, redirectedReference); if (result) { if (traceEnabled) { trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); } } else { let moduleResolution = compilerOptions.moduleResolution; if (moduleResolution === undefined) { switch (getEmitModuleKind(compilerOptions)) { case ModuleKind.CommonJS: moduleResolution = ModuleResolutionKind.Node10; break; case ModuleKind.Node16: moduleResolution = ModuleResolutionKind.Node16; break; case ModuleKind.NodeNext: moduleResolution = ModuleResolutionKind.NodeNext; break; default: moduleResolution = ModuleResolutionKind.Classic; break; } if (traceEnabled) { trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); } } else { if (traceEnabled) { trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); } } perfLogger?.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/); switch (moduleResolution) { case ModuleResolutionKind.Node16: result = node16ModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); break; case ModuleResolutionKind.NodeNext: result = nodeNextModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); break; case ModuleResolutionKind.Node10: result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode ? getConditions(compilerOptions, resolutionMode) : undefined); break; case ModuleResolutionKind.Classic: result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); break; case ModuleResolutionKind.Bundler: result = bundlerModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode ? getConditions(compilerOptions, resolutionMode) : undefined); break; default: return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); } if (result && result.resolvedModule) perfLogger?.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`); perfLogger?.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null"); if (cache && !cache.isReadonly) { cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference).set(moduleName, resolutionMode, result); if (!isExternalModuleNameRelative(moduleName)) { // put result in per-module name cache cache.getOrCreateCacheForNonRelativeName(moduleName, resolutionMode, redirectedReference).set(containingDirectory, result); } } } if (traceEnabled) { if (result.resolvedModule) { if (result.resolvedModule.packageId) { trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1_with_Package_ID_2, moduleName, result.resolvedModule.resolvedFileName, packageIdToString(result.resolvedModule.packageId)); } else { trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); } } else { trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); } } return result; } /* * Every module resolution kind can has its specific understanding how to load module from a specific path on disk * I.e. for path '/a/b/c': * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with * 'typings' entry or file 'index' with some supported extension * - Classic loader will only try to interpret '/a/b/c' as file. */ type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined; /** * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to * mitigate differences between design time structure of the project and its runtime counterpart so the same import name * can be resolved successfully by TypeScript compiler and runtime module loader. * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will * fallback to standard resolution routine. * * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will * be '/a/b/c/d' * - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names * will be resolved based on the content of the module name. * Structure of 'paths' compiler options * 'paths': { * pattern-1: [...substitutions], * pattern-2: [...substitutions], * ... * pattern-n: [...substitutions] * } * Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against * all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case. * If pattern contains '*' then to match pattern "*" module name must start with the and end with . * denotes part of the module name between and . * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked. * After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module * from the candidate location. * Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every * substitution in the list and replace '*' with string. If candidate location is not rooted it * will be converted to absolute using baseUrl. * For example: * baseUrl: /a/b/c * "paths": { * // match all module names * "*": [ * "*", // use matched name as is, * // will be looked as /a/b/c/ * * "folder1/*" // substitution will convert matched name to 'folder1/', * // since it is not rooted then final candidate location will be /a/b/c/folder1/ * ], * // match module names that start with 'components/' * "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/ to '/root/components/folder1/', * // it is rooted so it will be final candidate location * } * * 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if * they were in the same location. For example lets say there are two files * '/local/src/content/file1.ts' * '/shared/components/contracts/src/content/protocols/file2.ts' * After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so * if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime. * 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all * root dirs were merged together. * I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ]. * Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file: * '/local/src/content/protocols/file2' and try to load it - failure. * Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will * be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining * entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location. */ function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { const resolved = tryLoadModuleUsingPathsIfEligible(extensions, moduleName, loader, state); if (resolved) return resolved.value; if (!isExternalModuleNameRelative(moduleName)) { return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state); } else { return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state); } } function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) { const { baseUrl, paths, configFile } = state.compilerOptions; if (paths && !pathIsRelative(moduleName)) { if (state.traceEnabled) { if (baseUrl) { trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); } trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); } const baseDirectory = getPathsBasePath(state.compilerOptions, state.host)!; // Always defined when 'paths' is defined const pathPatterns = configFile?.configFileSpecs ? configFile.configFileSpecs.pathPatterns ||= tryParsePatterns(paths) : undefined; return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, pathPatterns, loader, /*onlyRecordFailures*/ false, state); } } function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { if (!state.compilerOptions.rootDirs) { return undefined; } if (state.traceEnabled) { trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); } const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); let matchedRootDir: string | undefined; let matchedNormalizedPrefix: string | undefined; for (const rootDir of state.compilerOptions.rootDirs) { // rootDirs are expected to be absolute // in case of tsconfig.json this will happen automatically - compiler will expand relative names // using location of tsconfig.json as base location let normalizedRoot = normalizePath(rootDir); if (!endsWith(normalizedRoot, directorySeparator)) { normalizedRoot += directorySeparator; } const isLongestMatchingPrefix = startsWith(candidate, normalizedRoot) && (matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length); if (state.traceEnabled) { trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix); } if (isLongestMatchingPrefix) { matchedNormalizedPrefix = normalizedRoot; matchedRootDir = rootDir; } } if (matchedNormalizedPrefix) { if (state.traceEnabled) { trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix); } const suffix = candidate.substr(matchedNormalizedPrefix.length); // first - try to load from a initial location if (state.traceEnabled) { trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate); } const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state); if (resolvedFileName) { return resolvedFileName; } if (state.traceEnabled) { trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs); } // then try to resolve using remaining entries in rootDirs for (const rootDir of state.compilerOptions.rootDirs) { if (rootDir === matchedRootDir) { // skip the initially matched entry continue; } const candidate = combinePaths(normalizePath(rootDir), suffix); if (state.traceEnabled) { trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate); } const baseDirectory = getDirectoryPath(candidate); const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state); if (resolvedFileName) { return resolvedFileName; } } if (state.traceEnabled) { trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed); } } return undefined; } function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { const { baseUrl } = state.compilerOptions; if (!baseUrl) { return undefined; } if (state.traceEnabled) { trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); } const candidate = normalizePath(combinePaths(baseUrl, moduleName)); if (state.traceEnabled) { trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate); } return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); } /** * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations. * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963 * Throws an error if the module can't be resolved. * * @internal */ export function resolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string { const { resolvedModule, failedLookupLocations } = tryResolveJSModuleWorker(moduleName, initialDir, host); if (!resolvedModule) { throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations?.join(", ")}`); } return resolvedModule.resolvedFileName; } /** @internal */ export enum NodeResolutionFeatures { None = 0, // resolving `#local` names in your own package.json Imports = 1 << 1, // resolving `your-own-name` from your own package.json SelfName = 1 << 2, // respecting the `.exports` member of packages' package.json files and its (conditional) mappings of export names Exports = 1 << 3, // allowing `*` in the LHS of an export to be followed by more content, eg `"./whatever/*.js"` // not supported in node 12 - https://github.com/nodejs/Release/issues/690 ExportsPatternTrailers = 1 << 4, AllFeatures = Imports | SelfName | Exports | ExportsPatternTrailers, Node16Default = Imports | SelfName | Exports | ExportsPatternTrailers, NodeNextDefault = AllFeatures, BundlerDefault = Imports | SelfName | Exports | ExportsPatternTrailers, EsmMode = 1 << 5, } function node16ModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ResolutionMode): ResolvedModuleWithFailedLookupLocations { return nodeNextModuleNameResolverWorker( NodeResolutionFeatures.Node16Default, moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode, ); } function nodeNextModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ResolutionMode): ResolvedModuleWithFailedLookupLocations { return nodeNextModuleNameResolverWorker( NodeResolutionFeatures.NodeNextDefault, moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode, ); } function nodeNextModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ResolutionMode, conditions?: string[]): ResolvedModuleWithFailedLookupLocations { const containingDirectory = getDirectoryPath(containingFile); // es module file or cjs-like input file, use a variant of the legacy cjs resolver that supports the selected modern features const esmMode = resolutionMode === ModuleKind.ESNext ? NodeResolutionFeatures.EsmMode : 0; let extensions = compilerOptions.noDtsResolution ? Extensions.ImplementationFiles : Extensions.TypeScript | Extensions.JavaScript | Extensions.Declaration; if (getResolveJsonModule(compilerOptions)) { extensions |= Extensions.Json; } return nodeModuleNameResolverWorker(features | esmMode, moduleName, containingDirectory, compilerOptions, host, cache, extensions, /*isConfigLookup*/ false, redirectedReference, conditions); } function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { return nodeModuleNameResolverWorker( NodeResolutionFeatures.None, moduleName, initialDir, { moduleResolution: ModuleResolutionKind.Node10, allowJs: true }, host, /*cache*/ undefined, Extensions.JavaScript, /*isConfigLookup*/ false, /*redirectedReference*/ undefined, /*conditions*/ undefined, ); } export function bundlerModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; /** @internal */ export function bundlerModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, conditions?: string[]): ResolvedModuleWithFailedLookupLocations; export function bundlerModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, conditions?: string[]): ResolvedModuleWithFailedLookupLocations { const containingDirectory = getDirectoryPath(containingFile); let extensions = compilerOptions.noDtsResolution ? Extensions.ImplementationFiles : Extensions.TypeScript | Extensions.JavaScript | Extensions.Declaration; if (getResolveJsonModule(compilerOptions)) { extensions |= Extensions.Json; } return nodeModuleNameResolverWorker(getNodeResolutionFeatures(compilerOptions), moduleName, containingDirectory, compilerOptions, host, cache, extensions, /*isConfigLookup*/ false, redirectedReference, conditions); } export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; /** @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, conditions?: string[], lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, conditions?: string[], isConfigLookup?: boolean): ResolvedModuleWithFailedLookupLocations { let extensions; if (isConfigLookup) { extensions = Extensions.Json; } else if (compilerOptions.noDtsResolution) { extensions = Extensions.ImplementationFiles; if (getResolveJsonModule(compilerOptions)) extensions |= Extensions.Json; } else { extensions = getResolveJsonModule(compilerOptions) ? Extensions.TypeScript | Extensions.JavaScript | Extensions.Declaration | Extensions.Json : Extensions.TypeScript | Extensions.JavaScript | Extensions.Declaration; } return nodeModuleNameResolverWorker(conditions ? NodeResolutionFeatures.AllFeatures : NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, extensions, !!isConfigLookup, redirectedReference, conditions); } /** @internal */ export function nodeNextJsonConfigResolver(moduleName: string, containingFile: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { return nodeModuleNameResolverWorker(NodeResolutionFeatures.NodeNextDefault, moduleName, getDirectoryPath(containingFile), { moduleResolution: ModuleResolutionKind.NodeNext }, host, /*cache*/ undefined, Extensions.Json, /*isConfigLookup*/ true, /*redirectedReference*/ undefined, /*conditions*/ undefined); } function nodeModuleNameResolverWorker( features: NodeResolutionFeatures, moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions, isConfigLookup: boolean, redirectedReference: ResolvedProjectReference | undefined, conditions: readonly string[] | undefined, ): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); const failedLookupLocations: string[] = []; const affectingLocations: string[] = []; const moduleResolution = getEmitModuleResolutionKind(compilerOptions); conditions ??= getConditions( compilerOptions, moduleResolution === ModuleResolutionKind.Bundler || moduleResolution === ModuleResolutionKind.Node10 ? undefined : (features & NodeResolutionFeatures.EsmMode) ? ModuleKind.ESNext : ModuleKind.CommonJS, ); const diagnostics: Diagnostic[] = []; const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, affectingLocations, packageJsonInfoCache: cache, features, conditions: conditions ?? emptyArray, requestContainingDirectory: containingDirectory, reportDiagnostic: diag => void diagnostics.push(diag), isConfigLookup, candidateIsFromPackageJsonField: false, }; if (traceEnabled && moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution)) { trace(host, Diagnostics.Resolving_in_0_mode_with_conditions_1, features & NodeResolutionFeatures.EsmMode ? "ESM" : "CJS", state.conditions.map(c => `'${c}'`).join(", ")); } let result; if (moduleResolution === ModuleResolutionKind.Node10) { const priorityExtensions = extensions & (Extensions.TypeScript | Extensions.Declaration); const secondaryExtensions = extensions & ~(Extensions.TypeScript | Extensions.Declaration); result = priorityExtensions && tryResolve(priorityExtensions, state) || secondaryExtensions && tryResolve(secondaryExtensions, state) || undefined; } else { result = tryResolve(extensions, state); } // For non-relative names that resolved to JS but no types in modes that look up an "import" condition in package.json "exports", // try again with "exports" disabled to try to detect if this is likely a configuration error in a dependency's package.json. let legacyResult; if ( result?.value?.isExternalLibraryImport && !isConfigLookup && extensions & (Extensions.TypeScript | Extensions.Declaration) && features & NodeResolutionFeatures.Exports && !isExternalModuleNameRelative(moduleName) && !extensionIsOk(Extensions.TypeScript | Extensions.Declaration, result.value.resolved.extension) && conditions?.includes("import") ) { traceIfEnabled(state, Diagnostics.Resolution_of_non_relative_name_failed_trying_with_modern_Node_resolution_features_disabled_to_see_if_npm_library_needs_configuration_update); const diagnosticState = { ...state, features: state.features & ~NodeResolutionFeatures.Exports, reportDiagnostic: noop, }; const diagnosticResult = tryResolve(extensions & (Extensions.TypeScript | Extensions.Declaration), diagnosticState); if (diagnosticResult?.value?.isExternalLibraryImport) { legacyResult = diagnosticResult.value.resolved.path; } } return createResolvedModuleWithFailedLookupLocationsHandlingSymlink( moduleName, result?.value?.resolved, result?.value?.isExternalLibraryImport, failedLookupLocations, affectingLocations, diagnostics, state, cache, legacyResult, ); function tryResolve(extensions: Extensions, state: ModuleResolutionState): SearchResult<{ resolved: Resolved; isExternalLibraryImport: boolean; }> { const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true); const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state); if (resolved) { return toSearchResult({ resolved, isExternalLibraryImport: pathContainsNodeModules(resolved.path) }); } if (!isExternalModuleNameRelative(moduleName)) { let resolved: SearchResult | undefined; if (features & NodeResolutionFeatures.Imports && startsWith(moduleName, "#")) { resolved = loadModuleFromImports(extensions, moduleName, containingDirectory, state, cache, redirectedReference); } if (!resolved && features & NodeResolutionFeatures.SelfName) { resolved = loadModuleFromSelfNameReference(extensions, moduleName, containingDirectory, state, cache, redirectedReference); } if (!resolved) { if (moduleName.includes(":")) { if (traceEnabled) { trace(host, Diagnostics.Skipping_module_0_that_looks_like_an_absolute_URI_target_file_types_Colon_1, moduleName, formatExtensions(extensions)); } return undefined; } if (traceEnabled) { trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_types_Colon_1, moduleName, formatExtensions(extensions)); } resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); } if (extensions & Extensions.Declaration) { resolved ??= resolveFromTypeRoot(moduleName, state); } // For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. return resolved && { value: resolved.value && { resolved: resolved.value, isExternalLibraryImport: true } }; } else { const { path: candidate, parts } = normalizePathForCJSResolution(containingDirectory, moduleName); const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true); // Treat explicit "node_modules" import as an external library import. return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, "node_modules") }); } } } // If you import from "." inside a containing directory "/foo", the result of `normalizePath` // would be "/foo", but this loses the information that `foo` is a directory and we intended // to look inside of it. The Node CommonJS resolution algorithm doesn't call this out // (https://nodejs.org/api/modules.html#all-together), but it seems that module paths ending // in `.` are actually normalized to `./` before proceeding with the resolution algorithm. function normalizePathForCJSResolution(containingDirectory: string, moduleName: string) { const combined = combinePaths(containingDirectory, moduleName); const parts = getPathComponents(combined); const lastPart = lastOrUndefined(parts); const path = lastPart === "." || lastPart === ".." ? ensureTrailingDirectorySeparator(normalizePath(combined)) : normalizePath(combined); return { path, parts }; } function realPath(path: string, host: ModuleResolutionHost, traceEnabled: boolean): string { if (!host.realpath) { return path; } const real = normalizePath(host.realpath(path)); if (traceEnabled) { trace(host, Diagnostics.Resolving_real_path_for_0_result_1, path, real); } return real; } function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined { if (state.traceEnabled) { trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_types_Colon_1, candidate, formatExtensions(extensions)); } if (!hasTrailingDirectorySeparator(candidate)) { if (!onlyRecordFailures) { const parentOfCandidate = getDirectoryPath(candidate); if (!directoryProbablyExists(parentOfCandidate, state.host)) { if (state.traceEnabled) { trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, parentOfCandidate); } onlyRecordFailures = true; } } const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state); if (resolvedFromFile) { const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile.path) : undefined; const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; return withPackageId(packageInfo, resolvedFromFile); } } if (!onlyRecordFailures) { const candidateExists = directoryProbablyExists(candidate, state.host); if (!candidateExists) { if (state.traceEnabled) { trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidate); } onlyRecordFailures = true; } } // esm mode relative imports shouldn't do any directory lookups (either inside `package.json` // files or implicit `index.js`es). This is a notable departure from cjs norms, where `./foo/pkg` // could have been redirected by `./foo/pkg/package.json` to an arbitrary location! if (!(state.features & NodeResolutionFeatures.EsmMode)) { return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); } return undefined; } /** @internal */ export const nodeModulesPathPart = "/node_modules/"; /** @internal */ export function pathContainsNodeModules(path: string): boolean { return path.includes(nodeModulesPathPart); } /** * This will be called on the successfully resolved path from `loadModuleFromFile`. * (Not needed for `loadModuleFromNodeModules` as that looks up the `package.json` as part of resolution.) * * packageDirectory is the directory of the package itself. * For `blah/node_modules/foo/index.d.ts` this is packageDirectory: "foo" * For `/node_modules/foo/bar.d.ts` this is packageDirectory: "foo" * For `/node_modules/@types/foo/bar/index.d.ts` this is packageDirectory: "@types/foo" * For `/node_modules/foo/bar/index.d.ts` this is packageDirectory: "foo" * * @internal */ export function parseNodeModuleFromPath(resolved: string, isFolder?: boolean): string | undefined { const path = normalizePath(resolved); const idx = path.lastIndexOf(nodeModulesPathPart); if (idx === -1) { return undefined; } const indexAfterNodeModules = idx + nodeModulesPathPart.length; let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules, isFolder); if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName, isFolder); } return path.slice(0, indexAfterPackageName); } function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number, isFolder: boolean | undefined): number { const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1); return nextSeparatorIndex === -1 ? isFolder ? path.length : prevSeparatorIndex : nextSeparatorIndex; } function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined { return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state)); } /** * @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 * 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. */ function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { // ./foo.js -> ./foo.ts const resolvedByReplacingExtension = loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); if (resolvedByReplacingExtension) { return resolvedByReplacingExtension; } // ./foo -> ./foo.ts if (!(state.features & NodeResolutionFeatures.EsmMode)) { // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, "", onlyRecordFailures, state); if (resolvedByAddingExtension) { return resolvedByAddingExtension; } } } function loadModuleFromFileNoImplicitExtensions(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { const filename = getBaseFileName(candidate); if (!filename.includes(".")) { return undefined; // extensionless import, no lookups performed, since we don't support extensionless files } let extensionless = removeFileExtension(candidate); if (extensionless === candidate) { // Once TS native extensions are handled, handle arbitrary extensions for declaration file mapping extensionless = candidate.substring(0, candidate.lastIndexOf(".")); } const extension = candidate.substring(extensionless.length); if (state.traceEnabled) { trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); } return tryAddingExtensions(extensionless, extensions, extension, onlyRecordFailures, state); } /** * This function is only ever called with paths written in package.json files - never * module specifiers written in source files - and so it always allows the * candidate to end with a TS extension (but will also try substituting a JS extension for a TS extension). */ function loadFileNameFromPackageJsonField(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { if ( extensions & Extensions.TypeScript && fileExtensionIsOneOf(candidate, supportedTSImplementationExtensions) || extensions & Extensions.Declaration && fileExtensionIsOneOf(candidate, supportedDeclarationExtensions) ) { const result = tryFile(candidate, onlyRecordFailures, state); return result !== undefined ? { path: candidate, ext: tryExtractTSExtension(candidate) as Extension, resolvedUsingTsExtension: undefined } : undefined; } if (state.isConfigLookup && extensions === Extensions.Json && fileExtensionIs(candidate, Extension.Json)) { const result = tryFile(candidate, onlyRecordFailures, state); return result !== undefined ? { path: candidate, ext: Extension.Json, resolvedUsingTsExtension: undefined } : undefined; } return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); } /** Try to return an existing file that adds one of the `extensions` to `candidate`. */ function tryAddingExtensions(candidate: string, extensions: Extensions, originalExtension: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { if (!onlyRecordFailures) { // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing const directory = getDirectoryPath(candidate); if (directory) { onlyRecordFailures = !directoryProbablyExists(directory, state.host); } } switch (originalExtension) { case Extension.Mjs: case Extension.Mts: case Extension.Dmts: return extensions & Extensions.TypeScript && tryExtension(Extension.Mts, originalExtension === Extension.Mts || originalExtension === Extension.Dmts) || extensions & Extensions.Declaration && tryExtension(Extension.Dmts, originalExtension === Extension.Mts || originalExtension === Extension.Dmts) || extensions & Extensions.JavaScript && tryExtension(Extension.Mjs) || undefined; case Extension.Cjs: case Extension.Cts: case Extension.Dcts: return extensions & Extensions.TypeScript && tryExtension(Extension.Cts, originalExtension === Extension.Cts || originalExtension === Extension.Dcts) || extensions & Extensions.Declaration && tryExtension(Extension.Dcts, originalExtension === Extension.Cts || originalExtension === Extension.Dcts) || extensions & Extensions.JavaScript && tryExtension(Extension.Cjs) || undefined; case Extension.Json: return extensions & Extensions.Declaration && tryExtension(".d.json.ts") || extensions & Extensions.Json && tryExtension(Extension.Json) || undefined; case Extension.Tsx: case Extension.Jsx: // basically idendical to the ts/js case below, but prefers matching tsx and jsx files exactly before falling back to the ts or js file path // (historically, we disallow having both a a.ts and a.tsx file in the same compilation, since their outputs clash) // TODO: We should probably error if `"./a.tsx"` resolved to `"./a.ts"`, right? return extensions & Extensions.TypeScript && (tryExtension(Extension.Tsx, originalExtension === Extension.Tsx) || tryExtension(Extension.Ts, originalExtension === Extension.Tsx)) || extensions & Extensions.Declaration && tryExtension(Extension.Dts, originalExtension === Extension.Tsx) || extensions & Extensions.JavaScript && (tryExtension(Extension.Jsx) || tryExtension(Extension.Js)) || undefined; case Extension.Ts: case Extension.Dts: case Extension.Js: case "": return extensions & Extensions.TypeScript && (tryExtension(Extension.Ts, originalExtension === Extension.Ts || originalExtension === Extension.Dts) || tryExtension(Extension.Tsx, originalExtension === Extension.Ts || originalExtension === Extension.Dts)) || extensions & Extensions.Declaration && tryExtension(Extension.Dts, originalExtension === Extension.Ts || originalExtension === Extension.Dts) || extensions & Extensions.JavaScript && (tryExtension(Extension.Js) || tryExtension(Extension.Jsx)) || state.isConfigLookup && tryExtension(Extension.Json) || undefined; default: return extensions & Extensions.Declaration && !isDeclarationFileName(candidate + originalExtension) && tryExtension(`.d${originalExtension}.ts`) || undefined; } function tryExtension(ext: string, resolvedUsingTsExtension?: boolean): PathAndExtension | undefined { const path = tryFile(candidate + ext, onlyRecordFailures, state); return path === undefined ? undefined : { path, ext, resolvedUsingTsExtension: !state.candidateIsFromPackageJsonField && resolvedUsingTsExtension }; } } /** Return the file if it exists. */ function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { if (!state.compilerOptions.moduleSuffixes?.length) { return tryFileLookup(fileName, onlyRecordFailures, state); } const ext = tryGetExtensionFromPath(fileName) ?? ""; const fileNameNoExtension = ext ? removeExtension(fileName, ext) : fileName; return forEach(state.compilerOptions.moduleSuffixes, suffix => tryFileLookup(fileNameNoExtension + suffix + ext, onlyRecordFailures, state)); } function tryFileLookup(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { if (!onlyRecordFailures) { if (state.host.fileExists(fileName)) { if (state.traceEnabled) { trace(state.host, Diagnostics.File_0_exists_use_it_as_a_name_resolution_result, fileName); } return fileName; } else { if (state.traceEnabled) { trace(state.host, Diagnostics.File_0_does_not_exist, fileName); } } } state.failedLookupLocations?.push(fileName); return undefined; } function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) { const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined; const packageJsonContent = packageInfo && packageInfo.contents.packageJsonContent; const versionPaths = packageInfo && getVersionPathsOfPackageJsonInfo(packageInfo, state); return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); } /** @internal */ export interface GetPackageJsonEntrypointsHost extends ModuleResolutionHost { readDirectory: CompilerHost["readDirectory"]; } /** @internal */ export function getEntrypointsFromPackageJsonInfo( packageJsonInfo: PackageJsonInfo, options: CompilerOptions, host: GetPackageJsonEntrypointsHost, cache: ModuleResolutionCache | undefined, resolveJs?: boolean, ): string[] | false { if (!resolveJs && packageJsonInfo.contents.resolvedEntrypoints !== undefined) { // Cached value excludes resolutions to JS files - those could be // cached separately, but they're used rarely. return packageJsonInfo.contents.resolvedEntrypoints; } let entrypoints: string[] | undefined; const extensions = Extensions.TypeScript | Extensions.Declaration | (resolveJs ? Extensions.JavaScript : 0); const features = getNodeResolutionFeatures(options); const loadPackageJsonMainState = getTemporaryModuleResolutionState(cache?.getPackageJsonInfoCache(), host, options); loadPackageJsonMainState.conditions = getConditions(options); loadPackageJsonMainState.requestContainingDirectory = packageJsonInfo.packageDirectory; const mainResolution = loadNodeModuleFromDirectoryWorker( extensions, packageJsonInfo.packageDirectory, /*onlyRecordFailures*/ false, loadPackageJsonMainState, packageJsonInfo.contents.packageJsonContent, getVersionPathsOfPackageJsonInfo(packageJsonInfo, loadPackageJsonMainState), ); entrypoints = append(entrypoints, mainResolution?.path); if (features & NodeResolutionFeatures.Exports && packageJsonInfo.contents.packageJsonContent.exports) { const conditionSets = deduplicate( [getConditions(options, ModuleKind.ESNext), getConditions(options, ModuleKind.CommonJS)], arrayIsEqualTo, ); for (const conditions of conditionSets) { const loadPackageJsonExportsState = { ...loadPackageJsonMainState, failedLookupLocations: [], conditions, host }; const exportResolutions = loadEntrypointsFromExportMap( packageJsonInfo, packageJsonInfo.contents.packageJsonContent.exports, loadPackageJsonExportsState, extensions, ); if (exportResolutions) { for (const resolution of exportResolutions) { entrypoints = appendIfUnique(entrypoints, resolution.path); } } } } return packageJsonInfo.contents.resolvedEntrypoints = entrypoints || false; } function loadEntrypointsFromExportMap( scope: PackageJsonInfo, exports: object, state: ModuleResolutionState & { host: GetPackageJsonEntrypointsHost; }, extensions: Extensions, ): PathAndExtension[] | undefined { let entrypoints: PathAndExtension[] | undefined; if (isArray(exports)) { for (const target of exports) { loadEntrypointsFromTargetExports(target); } } // eslint-disable-next-line no-null/no-null else if (typeof exports === "object" && exports !== null && allKeysStartWithDot(exports as MapLike)) { for (const key in exports) { loadEntrypointsFromTargetExports((exports as MapLike)[key]); } } else { loadEntrypointsFromTargetExports(exports); } return entrypoints; function loadEntrypointsFromTargetExports(target: unknown): boolean | undefined { if (typeof target === "string" && startsWith(target, "./")) { if (target.includes("*") && state.host.readDirectory) { if (target.indexOf("*") !== target.lastIndexOf("*")) { return false; } state.host.readDirectory( scope.packageDirectory, extensionsToExtensionsArray(extensions), /*excludes*/ undefined, [ isDeclarationFileName(target) ? target.replace("*", "**/*") : changeAnyExtension(target.replace("*", "**/*"), getDeclarationEmitExtensionForPath(target)), ], ).forEach(entry => { entrypoints = appendIfUnique(entrypoints, { path: entry, ext: getAnyExtensionFromPath(entry), resolvedUsingTsExtension: undefined, }); }); } else { const partsAfterFirst = getPathComponents(target).slice(2); if (partsAfterFirst.includes("..") || partsAfterFirst.includes(".") || partsAfterFirst.includes("node_modules")) { return false; } const resolvedTarget = combinePaths(scope.packageDirectory, target); const finalPath = getNormalizedAbsolutePath(resolvedTarget, state.host.getCurrentDirectory?.()); const result = loadFileNameFromPackageJsonField(extensions, finalPath, /*onlyRecordFailures*/ false, state); if (result) { entrypoints = appendIfUnique(entrypoints, result, (a, b) => a.path === b.path); return true; } } } else if (Array.isArray(target)) { for (const t of target) { const success = loadEntrypointsFromTargetExports(t); if (success) { return true; } } } // eslint-disable-next-line no-null/no-null else if (typeof target === "object" && target !== null) { return forEach(getOwnKeys(target as MapLike), key => { if (key === "default" || contains(state.conditions, key) || isApplicableVersionedTypesKey(state.conditions, key)) { loadEntrypointsFromTargetExports((target as MapLike)[key]); return true; } }); } } } /** @internal */ export function getTemporaryModuleResolutionState(packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleResolutionState { return { host, compilerOptions: options, traceEnabled: isTraceEnabled(options, host), failedLookupLocations: undefined, affectingLocations: undefined, packageJsonInfoCache, features: NodeResolutionFeatures.None, conditions: emptyArray, requestContainingDirectory: undefined, reportDiagnostic: noop, isConfigLookup: false, candidateIsFromPackageJsonField: false, }; } /** @internal */ export interface PackageJsonInfo { packageDirectory: string; contents: PackageJsonInfoContents; } /** @internal */ export interface PackageJsonInfoContents { packageJsonContent: PackageJsonPathFields; /** false: versionPaths are not present. undefined: not yet resolved */ versionPaths: VersionPaths | false | undefined; /** false: resolved to nothing. undefined: not yet resolved */ resolvedEntrypoints: string[] | false | undefined; } /** * A function for locating the package.json scope for a given path * * @internal */ export function getPackageScopeForPath(fileName: string, state: ModuleResolutionState): PackageJsonInfo | undefined { const parts = getPathComponents(fileName); parts.pop(); while (parts.length > 0) { const pkg = getPackageJsonInfo(getPathFromPathComponents(parts), /*onlyRecordFailures*/ false, state); if (pkg) { return pkg; } parts.pop(); } return undefined; } function getVersionPathsOfPackageJsonInfo(packageJsonInfo: PackageJsonInfo, state: ModuleResolutionState): VersionPaths | undefined { if (packageJsonInfo.contents.versionPaths === undefined) { packageJsonInfo.contents.versionPaths = readPackageJsonTypesVersionPaths(packageJsonInfo.contents.packageJsonContent, state) || false; } return packageJsonInfo.contents.versionPaths || undefined; } /** @internal */ export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { const { host, traceEnabled } = state; const packageJsonPath = combinePaths(packageDirectory, "package.json"); if (onlyRecordFailures) { state.failedLookupLocations?.push(packageJsonPath); return undefined; } const existing = state.packageJsonInfoCache?.getPackageJsonInfo(packageJsonPath); if (existing !== undefined) { if (typeof existing !== "boolean") { if (traceEnabled) trace(host, Diagnostics.File_0_exists_according_to_earlier_cached_lookups, packageJsonPath); state.affectingLocations?.push(packageJsonPath); return existing.packageDirectory === packageDirectory ? existing : { packageDirectory, contents: existing.contents }; } else { if (existing && traceEnabled) trace(host, Diagnostics.File_0_does_not_exist_according_to_earlier_cached_lookups, packageJsonPath); state.failedLookupLocations?.push(packageJsonPath); return undefined; } } const directoryExists = directoryProbablyExists(packageDirectory, host); if (directoryExists && host.fileExists(packageJsonPath)) { const packageJsonContent = readJson(packageJsonPath, host) as PackageJson; if (traceEnabled) { trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); } const result: PackageJsonInfo = { packageDirectory, contents: { packageJsonContent, versionPaths: undefined, resolvedEntrypoints: undefined } }; if (state.packageJsonInfoCache && !state.packageJsonInfoCache.isReadonly) state.packageJsonInfoCache.setPackageJsonInfo(packageJsonPath, result); state.affectingLocations?.push(packageJsonPath); return result; } else { if (directoryExists && traceEnabled) { trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath); } if (state.packageJsonInfoCache && !state.packageJsonInfoCache.isReadonly) state.packageJsonInfoCache.setPackageJsonInfo(packageJsonPath, directoryExists); // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results state.failedLookupLocations?.push(packageJsonPath); } } function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined { let packageFile: string | undefined; if (jsonContent) { if (state.isConfigLookup) { packageFile = readPackageJsonTSConfigField(jsonContent, candidate, state); } else { packageFile = extensions & Extensions.Declaration && readPackageJsonTypesFields(jsonContent, candidate, state) || extensions & (Extensions.ImplementationFiles | Extensions.Declaration) && readPackageJsonMainField(jsonContent, candidate, state) || undefined; } } const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { const fromFile = tryFile(candidate, onlyRecordFailures, state); if (fromFile) { const resolved = resolvedIfExtensionMatches(extensions, fromFile); if (resolved) { return noPackageId(resolved); } if (state.traceEnabled) { trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile); } } // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" const expandedExtensions = extensions === Extensions.Declaration ? Extensions.TypeScript | Extensions.Declaration : extensions; // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. // Disable `EsmMode` for the resolution of the package path for cjs-mode packages (so the `main` field can omit extensions) // (technically it only emits a deprecation warning in esm packages right now, but that's probably // enough to mean we don't need to support it) const features = state.features; const candidateIsFromPackageJsonField = state.candidateIsFromPackageJsonField; state.candidateIsFromPackageJsonField = true; if (jsonContent?.type !== "module") { state.features &= ~NodeResolutionFeatures.EsmMode; } const result = nodeLoadModuleByRelativeName(expandedExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); state.features = features; state.candidateIsFromPackageJsonField = candidateIsFromPackageJsonField; return result; }; const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host); const indexPath = combinePaths(candidate, state.isConfigLookup ? "tsconfig" : "index"); if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) { const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false); if (state.traceEnabled) { 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); } const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, /*pathPatterns*/ undefined, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); if (result) { return removeIgnoredPackageId(result.value); } } // It won't have a `packageId` set, because we disabled `considerPackageJson`. const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state)); if (packageFileResult) return packageFileResult; // esm mode resolutions don't do package `index` lookups if (!(state.features & NodeResolutionFeatures.EsmMode)) { return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); } } /** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */ function resolvedIfExtensionMatches(extensions: Extensions, path: string, resolvedUsingTsExtension?: boolean): PathAndExtension | undefined { const ext = tryGetExtensionFromPath(path); return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext, resolvedUsingTsExtension } : undefined; } /** True if `extension` is one of the supported `extensions`. */ function extensionIsOk(extensions: Extensions, extension: string): boolean { return extensions & Extensions.JavaScript && (extension === Extension.Js || extension === Extension.Jsx || extension === Extension.Mjs || extension === Extension.Cjs) || extensions & Extensions.TypeScript && (extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts) || extensions & Extensions.Declaration && (extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts) || extensions & Extensions.Json && extension === Extension.Json || false; } /** @internal */ export function parsePackageName(moduleName: string): { packageName: string; rest: string; } { let idx = moduleName.indexOf(directorySeparator); if (moduleName[0] === "@") { idx = moduleName.indexOf(directorySeparator, idx + 1); } return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; } /** @internal */ export function allKeysStartWithDot(obj: MapLike) { return every(getOwnKeys(obj), k => startsWith(k, ".")); } function noKeyStartsWithDot(obj: MapLike) { return !some(getOwnKeys(obj), k => startsWith(k, ".")); } function loadModuleFromSelfNameReference(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { const directoryPath = getNormalizedAbsolutePath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.()); const scope = getPackageScopeForPath(directoryPath, state); if (!scope || !scope.contents.packageJsonContent.exports) { return undefined; } if (typeof scope.contents.packageJsonContent.name !== "string") { return undefined; } const parts = getPathComponents(moduleName); // unrooted paths should have `""` as their 0th entry const nameParts = getPathComponents(scope.contents.packageJsonContent.name); if (!every(nameParts, (p, i) => parts[i] === p)) { return undefined; } const trailingParts = parts.slice(nameParts.length); const subpath = !length(trailingParts) ? "." : `.${directorySeparator}${trailingParts.join(directorySeparator)}`; // Maybe TODO: splitting extensions into two priorities should be unnecessary, except // https://github.com/microsoft/TypeScript/issues/50762 makes the behavior different. // As long as that bug exists, we need to do two passes here in self-name loading // in order to be consistent with (non-self) library-name loading in // `loadModuleFromNearestNodeModulesDirectoryWorker`, which uses two passes in order // to prioritize `@types` packages higher up the directory tree over untyped // implementation packages. See the selfNameModuleAugmentation.ts test for why this // matters. // // However, there's an exception. If the user has `allowJs` and `declaration`, we need // to ensure that self-name imports of their own package can resolve back to their // input JS files via `tryLoadInputFileForPath` at a higher priority than their output // declaration files, so we need to do a single pass with all extensions for that case. if (getAllowJSCompilerOption(state.compilerOptions) && !pathContainsNodeModules(directory)) { return loadModuleFromExports(scope, extensions, subpath, state, cache, redirectedReference); } const priorityExtensions = extensions & (Extensions.TypeScript | Extensions.Declaration); const secondaryExtensions = extensions & ~(Extensions.TypeScript | Extensions.Declaration); return loadModuleFromExports(scope, priorityExtensions, subpath, state, cache, redirectedReference) || loadModuleFromExports(scope, secondaryExtensions, subpath, state, cache, redirectedReference); } function loadModuleFromExports(scope: PackageJsonInfo, extensions: Extensions, subpath: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { if (!scope.contents.packageJsonContent.exports) { return undefined; } if (subpath === ".") { let mainExport; if (typeof scope.contents.packageJsonContent.exports === "string" || Array.isArray(scope.contents.packageJsonContent.exports) || (typeof scope.contents.packageJsonContent.exports === "object" && noKeyStartsWithDot(scope.contents.packageJsonContent.exports as MapLike))) { mainExport = scope.contents.packageJsonContent.exports; } else if (hasProperty(scope.contents.packageJsonContent.exports as MapLike, ".")) { mainExport = (scope.contents.packageJsonContent.exports as MapLike)["."]; } if (mainExport) { const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, subpath, scope, /*isImports*/ false); return loadModuleFromTargetImportOrExport(mainExport, "", /*pattern*/ false, "."); } } else if (allKeysStartWithDot(scope.contents.packageJsonContent.exports as MapLike)) { if (typeof scope.contents.packageJsonContent.exports !== "object") { if (state.traceEnabled) { trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); } return toSearchResult(/*value*/ undefined); } const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, subpath, scope.contents.packageJsonContent.exports, scope, /*isImports*/ false); if (result) { return result; } } if (state.traceEnabled) { trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); } return toSearchResult(/*value*/ undefined); } function loadModuleFromImports(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { if (moduleName === "#" || startsWith(moduleName, "#/")) { if (state.traceEnabled) { trace(state.host, Diagnostics.Invalid_import_specifier_0_has_no_possible_resolutions, moduleName); } return toSearchResult(/*value*/ undefined); } const directoryPath = getNormalizedAbsolutePath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.()); const scope = getPackageScopeForPath(directoryPath, state); if (!scope) { if (state.traceEnabled) { trace(state.host, Diagnostics.Directory_0_has_no_containing_package_json_scope_Imports_will_not_resolve, directoryPath); } return toSearchResult(/*value*/ undefined); } if (!scope.contents.packageJsonContent.imports) { if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_scope_0_has_no_imports_defined, scope.packageDirectory); } return toSearchResult(/*value*/ undefined); } const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, moduleName, scope.contents.packageJsonContent.imports, scope, /*isImports*/ true); if (result) { return result; } if (state.traceEnabled) { trace(state.host, Diagnostics.Import_specifier_0_does_not_exist_in_package_json_scope_at_path_1, moduleName, scope.packageDirectory); } return toSearchResult(/*value*/ undefined); } /** * @internal * From https://github.com/nodejs/node/blob/8f39f51cbbd3b2de14b9ee896e26421cc5b20121/lib/internal/modules/esm/resolve.js#L722 - * "longest" has some nuance as to what "longest" means in the presence of pattern trailers */ export function comparePatternKeys(a: string, b: string) { const aPatternIndex = a.indexOf("*"); const bPatternIndex = b.indexOf("*"); const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1; const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1; if (baseLenA > baseLenB) return -1; if (baseLenB > baseLenA) return 1; if (aPatternIndex === -1) return 1; if (bPatternIndex === -1) return -1; if (a.length > b.length) return -1; if (b.length > a.length) return 1; return 0; } function loadModuleFromImportsOrExports(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, lookupTable: object, scope: PackageJsonInfo, isImports: boolean): SearchResult | undefined { const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, moduleName, scope, isImports); if (!endsWith(moduleName, directorySeparator) && !moduleName.includes("*") && hasProperty(lookupTable, moduleName)) { const target = (lookupTable as { [idx: string]: unknown; })[moduleName]; return loadModuleFromTargetImportOrExport(target, /*subpath*/ "", /*pattern*/ false, moduleName); } const expandingKeys = sort(filter(getOwnKeys(lookupTable as MapLike), k => k.includes("*") || endsWith(k, "/")), comparePatternKeys); for (const potentialTarget of expandingKeys) { if (state.features & NodeResolutionFeatures.ExportsPatternTrailers && matchesPatternWithTrailer(potentialTarget, moduleName)) { const target = (lookupTable as { [idx: string]: unknown; })[potentialTarget]; const starPos = potentialTarget.indexOf("*"); const subpath = moduleName.substring(potentialTarget.substring(0, starPos).length, moduleName.length - (potentialTarget.length - 1 - starPos)); return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true, potentialTarget); } else if (endsWith(potentialTarget, "*") && startsWith(moduleName, potentialTarget.substring(0, potentialTarget.length - 1))) { const target = (lookupTable as { [idx: string]: unknown; })[potentialTarget]; const subpath = moduleName.substring(potentialTarget.length - 1); return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true, potentialTarget); } else if (startsWith(moduleName, potentialTarget)) { const target = (lookupTable as { [idx: string]: unknown; })[potentialTarget]; const subpath = moduleName.substring(potentialTarget.length); return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ false, potentialTarget); } } function matchesPatternWithTrailer(target: string, name: string) { if (endsWith(target, "*")) return false; // handled by next case in loop const starPos = target.indexOf("*"); if (starPos === -1) return false; // handled by last case in loop return startsWith(name, target.substring(0, starPos)) && endsWith(name, target.substring(starPos + 1)); } } /** * Gets the self-recursive function specialized to retrieving the targeted import/export element for the given resolution configuration */ function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, scope: PackageJsonInfo, isImports: boolean) { return loadModuleFromTargetImportOrExport; function loadModuleFromTargetImportOrExport(target: unknown, subpath: string, pattern: boolean, key: string): SearchResult | undefined { if (typeof target === "string") { if (!pattern && subpath.length > 0 && !endsWith(target, "/")) { if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } return toSearchResult(/*value*/ undefined); } if (!startsWith(target, "./")) { if (isImports && !startsWith(target, "../") && !startsWith(target, "/") && !isRootedDiskPath(target)) { const combinedLookup = pattern ? target.replace(/\*/g, subpath) : target + subpath; traceIfEnabled(state, Diagnostics.Using_0_subpath_1_with_target_2, "imports", key, combinedLookup); traceIfEnabled(state, Diagnostics.Resolving_module_0_from_1, combinedLookup, scope.packageDirectory + "/"); const result = nodeModuleNameResolverWorker(state.features, combinedLookup, scope.packageDirectory + "/", state.compilerOptions, state.host, cache, extensions, /*isConfigLookup*/ false, redirectedReference, state.conditions); return toSearchResult( result.resolvedModule ? { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId, originalPath: result.resolvedModule.originalPath, resolvedUsingTsExtension: result.resolvedModule.resolvedUsingTsExtension, } : undefined, ); } if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } return toSearchResult(/*value*/ undefined); } const parts = pathIsRelative(target) ? getPathComponents(target).slice(1) : getPathComponents(target); const partsAfterFirst = parts.slice(1); if (partsAfterFirst.includes("..") || partsAfterFirst.includes(".") || partsAfterFirst.includes("node_modules")) { if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } return toSearchResult(/*value*/ undefined); } const resolvedTarget = combinePaths(scope.packageDirectory, target); // TODO: Assert that `resolvedTarget` is actually within the package directory? That's what the spec says.... but I'm not sure we need // to be in the business of validating everyone's import and export map correctness. const subpathParts = getPathComponents(subpath); if (subpathParts.includes("..") || subpathParts.includes(".") || subpathParts.includes("node_modules")) { if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } return toSearchResult(/*value*/ undefined); } if (state.traceEnabled) { trace(state.host, Diagnostics.Using_0_subpath_1_with_target_2, isImports ? "imports" : "exports", key, pattern ? target.replace(/\*/g, subpath) : target + subpath); } const finalPath = toAbsolutePath(pattern ? resolvedTarget.replace(/\*/g, subpath) : resolvedTarget + subpath); const inputLink = tryLoadInputFileForPath(finalPath, subpath, combinePaths(scope.packageDirectory, "package.json"), isImports); if (inputLink) return inputLink; return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, finalPath, /*onlyRecordFailures*/ false, state))); } else if (typeof target === "object" && target !== null) { // eslint-disable-line no-null/no-null if (!Array.isArray(target)) { traceIfEnabled(state, Diagnostics.Entering_conditional_exports); for (const condition of getOwnKeys(target as MapLike)) { if (condition === "default" || state.conditions.includes(condition) || isApplicableVersionedTypesKey(state.conditions, condition)) { traceIfEnabled(state, Diagnostics.Matched_0_condition_1, isImports ? "imports" : "exports", condition); const subTarget = (target as MapLike)[condition]; const result = loadModuleFromTargetImportOrExport(subTarget, subpath, pattern, key); if (result) { traceIfEnabled(state, Diagnostics.Resolved_under_condition_0, condition); traceIfEnabled(state, Diagnostics.Exiting_conditional_exports); return result; } else { traceIfEnabled(state, Diagnostics.Failed_to_resolve_under_condition_0, condition); } } else { traceIfEnabled(state, Diagnostics.Saw_non_matching_condition_0, condition); } } traceIfEnabled(state, Diagnostics.Exiting_conditional_exports); return undefined; } else { if (!length(target)) { if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } return toSearchResult(/*value*/ undefined); } for (const elem of target) { const result = loadModuleFromTargetImportOrExport(elem, subpath, pattern, key); if (result) { return result; } } } } else if (target === null) { // eslint-disable-line no-null/no-null if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_scope_0_explicitly_maps_specifier_1_to_null, scope.packageDirectory, moduleName); } return toSearchResult(/*value*/ undefined); } if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } return toSearchResult(/*value*/ undefined); function toAbsolutePath(path: string): string; function toAbsolutePath(path: string | undefined): string | undefined; function toAbsolutePath(path: string | undefined): string | undefined { if (path === undefined) return path; return getNormalizedAbsolutePath(path, state.host.getCurrentDirectory?.()); } function combineDirectoryPath(root: string, dir: string) { return ensureTrailingDirectorySeparator(combinePaths(root, dir)); } function tryLoadInputFileForPath(finalPath: string, entry: string, packagePath: string, isImports: boolean) { // Replace any references to outputs for files in the program with the input files to support package self-names used with outDir // PROBLEM: We don't know how to calculate the output paths yet, because the "common source directory" we use as the base of the file structure // we reproduce into the output directory is based on the set of input files, which we're still in the process of traversing and resolving! // _Given that_, we have to guess what the base of the output directory is (obviously the user wrote the export map, so has some idea what it is!). // We are going to probe _so many_ possible paths. We limit where we'll do this to try to reduce the possibilities of false positive lookups. if ( !state.isConfigLookup && (state.compilerOptions.declarationDir || state.compilerOptions.outDir) && !finalPath.includes("/node_modules/") && (state.compilerOptions.configFile ? containsPath(scope.packageDirectory, toAbsolutePath(state.compilerOptions.configFile.fileName), !useCaseSensitiveFileNames(state)) : true) ) { // So that all means we'll only try these guesses for files outside `node_modules` in a directory where the `package.json` and `tsconfig.json` are siblings. // Even with all that, we still don't know if the root of the output file structure will be (relative to the package file) // `.`, `./src` or any other deeper directory structure. (If project references are used, it's definitely `.` by fiat, so that should be pretty common.) const getCanonicalFileName = hostGetCanonicalFileName({ useCaseSensitiveFileNames: () => useCaseSensitiveFileNames(state) }); const commonSourceDirGuesses: string[] = []; // A `rootDir` compiler option strongly indicates the root location // A `composite` project is using project references and has it's common src dir set to `.`, so it shouldn't need to check any other locations if (state.compilerOptions.rootDir || (state.compilerOptions.composite && state.compilerOptions.configFilePath)) { const commonDir = toAbsolutePath(getCommonSourceDirectory(state.compilerOptions, () => [], state.host.getCurrentDirectory?.() || "", getCanonicalFileName)); commonSourceDirGuesses.push(commonDir); } else if (state.requestContainingDirectory) { // However without either of those set we're in the dark. Let's say you have // // ./tools/index.ts // ./src/index.ts // ./dist/index.js // ./package.json <-- references ./dist/index.js // ./tsconfig.json <-- loads ./src/index.ts // // How do we know `./src` is the common src dir, and not `./tools`, given only the `./dist` out dir and `./dist/index.js` filename? // Answer: We... don't. We know we're looking for an `index.ts` input file, but we have _no clue_ which subfolder it's supposed to be loaded from // without more context. // But we do have more context! Just a tiny bit more! We're resolving an import _for some other input file_! And that input file, too // must be inside the common source directory! So we propagate that tidbit of info all the way to here via state.requestContainingDirectory const requestingFile = toAbsolutePath(combinePaths(state.requestContainingDirectory, "index.ts")); // And we can try every folder above the common folder for the request folder and the config/package base directory // This technically can be wrong - we may load ./src/index.ts when ./src/sub/index.ts was right because we don't // know if only `./src/sub` files were loaded by the program; but this has the best chance to be right of just about anything // else we have. And, given that we're about to load `./src/index.ts` because we choose it as likely correct, there will then // be a file outside of `./src/sub` in the program (the file we resolved to), making us de-facto right. So this fallback lookup // logic may influence what files are pulled in by self-names, which in turn influences the output path shape, but it's all // internally consistent so the paths should be stable so long as we prefer the "most general" (meaning: top-most-level directory) possible results first. const commonDir = toAbsolutePath(getCommonSourceDirectory(state.compilerOptions, () => [requestingFile, toAbsolutePath(packagePath)], state.host.getCurrentDirectory?.() || "", getCanonicalFileName)); commonSourceDirGuesses.push(commonDir); let fragment = ensureTrailingDirectorySeparator(commonDir); while (fragment && fragment.length > 1) { const parts = getPathComponents(fragment); parts.pop(); // remove a directory const commonDir = getPathFromPathComponents(parts); commonSourceDirGuesses.unshift(commonDir); fragment = ensureTrailingDirectorySeparator(commonDir); } } if (commonSourceDirGuesses.length > 1) { state.reportDiagnostic(createCompilerDiagnostic( isImports ? Diagnostics.The_project_root_is_ambiguous_but_is_required_to_resolve_import_map_entry_0_in_file_1_Supply_the_rootDir_compiler_option_to_disambiguate : Diagnostics.The_project_root_is_ambiguous_but_is_required_to_resolve_export_map_entry_0_in_file_1_Supply_the_rootDir_compiler_option_to_disambiguate, entry === "" ? "." : entry, // replace empty string with `.` - the reverse of the operation done when entries are built - so main entrypoint errors don't look weird packagePath, )); } for (const commonSourceDirGuess of commonSourceDirGuesses) { const candidateDirectories = getOutputDirectoriesForBaseDirectory(commonSourceDirGuess); for (const candidateDir of candidateDirectories) { if (containsPath(candidateDir, finalPath, !useCaseSensitiveFileNames(state))) { // The matched export is looking up something in either the out declaration or js dir, now map the written path back into the source dir and source extension const pathFragment = finalPath.slice(candidateDir.length + 1); // +1 to also remove directory seperator const possibleInputBase = combinePaths(commonSourceDirGuess, pathFragment); const jsAndDtsExtensions = [Extension.Mjs, Extension.Cjs, Extension.Js, Extension.Json, Extension.Dmts, Extension.Dcts, Extension.Dts]; for (const ext of jsAndDtsExtensions) { if (fileExtensionIs(possibleInputBase, ext)) { const inputExts = getPossibleOriginalInputExtensionForExtension(possibleInputBase); for (const possibleExt of inputExts) { if (!extensionIsOk(extensions, possibleExt)) continue; const possibleInputWithInputExtension = changeAnyExtension(possibleInputBase, possibleExt, ext, !useCaseSensitiveFileNames(state)); if (state.host.fileExists(possibleInputWithInputExtension)) { return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, possibleInputWithInputExtension, /*onlyRecordFailures*/ false, state))); } } } } } } } } return undefined; function getOutputDirectoriesForBaseDirectory(commonSourceDirGuess: string) { // Config file ouput paths are processed to be relative to the host's current directory, while // otherwise the paths are resolved relative to the common source dir the compiler puts together const currentDir = state.compilerOptions.configFile ? state.host.getCurrentDirectory?.() || "" : commonSourceDirGuess; const candidateDirectories = []; if (state.compilerOptions.declarationDir) { candidateDirectories.push(toAbsolutePath(combineDirectoryPath(currentDir, state.compilerOptions.declarationDir))); } if (state.compilerOptions.outDir && state.compilerOptions.outDir !== state.compilerOptions.declarationDir) { candidateDirectories.push(toAbsolutePath(combineDirectoryPath(currentDir, state.compilerOptions.outDir))); } return candidateDirectories; } } } } /** @internal */ export function isApplicableVersionedTypesKey(conditions: readonly string[], key: string) { if (!conditions.includes("types")) return false; // only apply versioned types conditions if the types condition is applied if (!startsWith(key, "types@")) return false; const range = VersionRange.tryParse(key.substring("types@".length)); if (!range) return false; return range.test(version); } function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); } function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult { // Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly. return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.Declaration, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); } function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { const mode = state.features === 0 ? undefined : state.features & NodeResolutionFeatures.EsmMode ? ModuleKind.ESNext : ModuleKind.CommonJS; // Do (up to) two passes through node_modules: // 1. For each ancestor node_modules directory, try to find: // i. TS/DTS files in the implementation package // ii. DTS files in the @types package // 2. For each ancestor node_modules directory, try to find: // i. JS files in the implementation package const priorityExtensions = extensions & (Extensions.TypeScript | Extensions.Declaration); const secondaryExtensions = extensions & ~(Extensions.TypeScript | Extensions.Declaration); // (1) if (priorityExtensions) { traceIfEnabled(state, Diagnostics.Searching_all_ancestor_node_modules_directories_for_preferred_extensions_Colon_0, formatExtensions(priorityExtensions)); const result = lookup(priorityExtensions); if (result) return result; } // (2) if (secondaryExtensions && !typesScopeOnly) { traceIfEnabled(state, Diagnostics.Searching_all_ancestor_node_modules_directories_for_fallback_extensions_Colon_0, formatExtensions(secondaryExtensions)); return lookup(secondaryExtensions); } function lookup(extensions: Extensions) { return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { if (getBaseFileName(ancestorDirectory) !== "node_modules") { const resolutionFromCache = tryFindNonRelativeModuleNameInCache(cache, moduleName, mode, ancestorDirectory, redirectedReference, state); if (resolutionFromCache) { return resolutionFromCache; } return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly, cache, redirectedReference)); } }); } } function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { const nodeModulesFolder = combinePaths(directory, "node_modules"); const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); if (!nodeModulesFolderExists && state.traceEnabled) { trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesFolder); } if (!typesScopeOnly) { const packageResult = loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state, cache, redirectedReference); if (packageResult) { return packageResult; } } if (extensions & Extensions.Declaration) { const nodeModulesAtTypes = combinePaths(nodeModulesFolder, "@types"); let nodeModulesAtTypesExists = nodeModulesFolderExists; if (nodeModulesFolderExists && !directoryProbablyExists(nodeModulesAtTypes, state.host)) { if (state.traceEnabled) { trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesAtTypes); } nodeModulesAtTypesExists = false; } return loadModuleFromSpecificNodeModulesDirectory(Extensions.Declaration, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state, cache, redirectedReference); } } function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); const { packageName, rest } = parsePackageName(moduleName); const packageDirectory = combinePaths(nodeModulesDirectory, packageName); let rootPackageInfo: PackageJsonInfo | undefined; // First look for a nested package.json, as in `node_modules/foo/bar/package.json`. let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); // But only if we're not respecting export maps (if we are, we might redirect around this location) if ( rest !== "" && packageInfo && ( !(state.features & NodeResolutionFeatures.Exports) || !hasProperty((rootPackageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state))?.contents.packageJsonContent ?? emptyArray, "exports") ) ) { const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); if (fromFile) { return noPackageId(fromFile); } const fromDirectory = loadNodeModuleFromDirectoryWorker( extensions, candidate, !nodeModulesDirectoryExists, state, packageInfo.contents.packageJsonContent, getVersionPathsOfPackageJsonInfo(packageInfo, state), ); return withPackageId(packageInfo, fromDirectory); } const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { let pathAndExtension = (rest || !(state.features & NodeResolutionFeatures.EsmMode)) && loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || loadNodeModuleFromDirectoryWorker( extensions, candidate, onlyRecordFailures, state, packageInfo && packageInfo.contents.packageJsonContent, packageInfo && getVersionPathsOfPackageJsonInfo(packageInfo, state), ); if ( !pathAndExtension && packageInfo // eslint-disable-next-line no-null/no-null && (packageInfo.contents.packageJsonContent.exports === undefined || packageInfo.contents.packageJsonContent.exports === null) && state.features & NodeResolutionFeatures.EsmMode ) { // EsmMode disables index lookup in `loadNodeModuleFromDirectoryWorker` generally, however non-relative package resolutions still assume // a default `index.js` entrypoint if no `main` or `exports` are present pathAndExtension = loadModuleFromFile(extensions, combinePaths(candidate, "index.js"), onlyRecordFailures, state); } return withPackageId(packageInfo, pathAndExtension); }; if (rest !== "") { // Previous `packageInfo` may have been from a nested package.json; ensure we have the one from the package root now. packageInfo = rootPackageInfo ?? getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); } // package exports are higher priority than file/directory/typesVersions lookups and (and, if there's exports present, blocks them) if (packageInfo && packageInfo.contents.packageJsonContent.exports && state.features & NodeResolutionFeatures.Exports) { return loadModuleFromExports(packageInfo, extensions, combinePaths(".", rest), state, cache, redirectedReference)?.value; } const versionPaths = rest !== "" && packageInfo ? getVersionPathsOfPackageJsonInfo(packageInfo, state) : undefined; if (versionPaths) { if (state.traceEnabled) { 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, rest); } const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, versionPaths.paths, /*pathPatterns*/ undefined, loader, !packageDirectoryExists, state); if (fromPaths) { return fromPaths.value; } } return loader(extensions, candidate, !nodeModulesDirectoryExists, state); } function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike, pathPatterns: readonly (string | Pattern)[] | undefined, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult { pathPatterns ||= tryParsePatterns(paths); const matchedPattern = matchPatternOrExact(pathPatterns, moduleName); if (matchedPattern) { const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); if (state.traceEnabled) { trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); } const resolved = forEach(paths[matchedPatternText], subst => { const path = matchedStar ? subst.replace("*", matchedStar) : subst; // When baseUrl is not specified, the command line parser resolves relative paths to the config file location. const candidate = normalizePath(combinePaths(baseDirectory, path)); if (state.traceEnabled) { trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); } // A path mapping may have an extension, in contrast to an import, which should omit it. const extension = tryGetExtensionFromPath(subst); if (extension !== undefined) { const path = tryFile(candidate, onlyRecordFailures, state); if (path !== undefined) { return noPackageId({ path, ext: extension, resolvedUsingTsExtension: undefined }); } } return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); }); return { value: resolved }; } } /** Double underscores are used in DefinitelyTyped to delimit scoped packages. */ const mangledScopedPackageSeparator = "__"; /** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */ function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string { const mangled = mangleScopedPackageName(packageName); if (state.traceEnabled && mangled !== packageName) { trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled); } return mangled; } /** @internal */ export function getTypesPackageName(packageName: string): string { return `@types/${mangleScopedPackageName(packageName)}`; } /** @internal */ export function mangleScopedPackageName(packageName: string): string { if (startsWith(packageName, "@")) { const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator); if (replaceSlash !== packageName) { return replaceSlash.slice(1); // Take off the "@" } } return packageName; } /** @internal */ export function getPackageNameFromTypesPackageName(mangledName: string): string { const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); if (withoutAtTypePrefix !== mangledName) { return unmangleScopedPackageName(withoutAtTypePrefix); } return mangledName; } /** @internal */ export function unmangleScopedPackageName(typesPackageName: string): string { return typesPackageName.includes(mangledScopedPackageSeparator) ? "@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) : typesPackageName; } function tryFindNonRelativeModuleNameInCache(cache: NonRelativeModuleNameResolutionCache | undefined, moduleName: string, mode: ResolutionMode, containingDirectory: string, redirectedReference: ResolvedProjectReference | undefined, state: ModuleResolutionState): SearchResult { const result = cache && cache.getFromNonRelativeNameCache(moduleName, mode, containingDirectory, redirectedReference); if (result) { if (state.traceEnabled) { trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); } state.resultFromCache = result; return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId, resolvedUsingTsExtension: result.resolvedModule.resolvedUsingTsExtension, }, }; } } export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); const failedLookupLocations: string[] = []; const affectingLocations: string[] = []; const containingDirectory = getDirectoryPath(containingFile); const diagnostics: Diagnostic[] = []; const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, affectingLocations, packageJsonInfoCache: cache, features: NodeResolutionFeatures.None, conditions: [], requestContainingDirectory: containingDirectory, reportDiagnostic: diag => void diagnostics.push(diag), isConfigLookup: false, candidateIsFromPackageJsonField: false, }; const resolved = tryResolve(Extensions.TypeScript | Extensions.Declaration) || tryResolve(Extensions.JavaScript | (compilerOptions.resolveJsonModule ? Extensions.Json : 0)); // No originalPath because classic resolution doesn't resolve realPath return createResolvedModuleWithFailedLookupLocationsHandlingSymlink( moduleName, resolved && resolved.value, resolved?.value && pathContainsNodeModules(resolved.value.path), failedLookupLocations, affectingLocations, diagnostics, state, cache, ); function tryResolve(extensions: Extensions): SearchResult { const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state); if (resolvedUsingSettings) { return { value: resolvedUsingSettings }; } if (!isExternalModuleNameRelative(moduleName)) { // Climb up parent directories looking for a module. const resolved = forEachAncestorDirectory(containingDirectory, directory => { const resolutionFromCache = tryFindNonRelativeModuleNameInCache(cache, moduleName, /*mode*/ undefined, directory, redirectedReference, state); if (resolutionFromCache) { return resolutionFromCache; } const searchName = normalizePath(combinePaths(directory, moduleName)); return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state)); }); if (resolved) return resolved; if (extensions & (Extensions.TypeScript | Extensions.Declaration)) { // If we didn't find the file normally, look it up in @types. let resolved = loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state); if (extensions & Extensions.Declaration) resolved ??= resolveFromTypeRoot(moduleName, state); return resolved; } } else { const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state)); } } } function resolveFromTypeRoot(moduleName: string, state: ModuleResolutionState) { if (!state.compilerOptions.typeRoots) return; for (const typeRoot of state.compilerOptions.typeRoots) { const candidate = getCandidateFromTypeRoot(typeRoot, moduleName, state); const directoryExists = directoryProbablyExists(typeRoot, state.host); if (!directoryExists && state.traceEnabled) { trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, typeRoot); } const resolvedFromFile = loadModuleFromFile(Extensions.Declaration, candidate, !directoryExists, state); if (resolvedFromFile) { const packageDirectory = parseNodeModuleFromPath(resolvedFromFile.path); const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; return toSearchResult(withPackageId(packageInfo, resolvedFromFile)); } const resolved = loadNodeModuleFromDirectory(Extensions.Declaration, candidate, !directoryExists, state); if (resolved) return toSearchResult(resolved); } } // Program errors validate that `noEmit` or `emitDeclarationOnly` is also set, // so this function doesn't check them to avoid propagating errors. /** @internal */ export function shouldAllowImportingTsExtension(compilerOptions: CompilerOptions, fromFileName?: string) { return !!compilerOptions.allowImportingTsExtensions || fromFileName && isDeclarationFileName(fromFileName); } /** * A host may load a module from a global cache of typings. * This is the minumum code needed to expose that functionality; the rest is in the host. * * @internal */ export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string, packageJsonInfoCache: PackageJsonInfoCache): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); if (traceEnabled) { 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); } const failedLookupLocations: string[] = []; const affectingLocations: string[] = []; const diagnostics: Diagnostic[] = []; const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, affectingLocations, packageJsonInfoCache, features: NodeResolutionFeatures.None, conditions: [], requestContainingDirectory: undefined, reportDiagnostic: diag => void diagnostics.push(diag), isConfigLookup: false, candidateIsFromPackageJsonField: false, }; const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.Declaration, moduleName, globalCache, state, /*typesScopeOnly*/ false, /*cache*/ undefined, /*redirectedReference*/ undefined); return createResolvedModuleWithFailedLookupLocations( resolved, /*isExternalLibraryImport*/ true, failedLookupLocations, affectingLocations, diagnostics, state.resultFromCache, /*cache*/ undefined, ); } /** * Represents result of search. Normally when searching among several alternatives we treat value `undefined` as indicator * that search fails and we should try another option. * 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). * SearchResult is used to deal with this issue, its values represents following outcomes: * - undefined - not found, continue searching * - { value: undefined } - not found - stop searching * - { value: } - found - stop searching */ type SearchResult = { value: T | undefined; } | undefined; /** * Wraps value to SearchResult. * @returns undefined if value is undefined or { value } otherwise */ function toSearchResult(value: T | undefined): SearchResult { return value !== undefined ? { value } : undefined; } function traceIfEnabled(state: ModuleResolutionState, diagnostic: DiagnosticMessage, ...args: string[]) { if (state.traceEnabled) { trace(state.host, diagnostic, ...args); } } function useCaseSensitiveFileNames(state: ModuleResolutionState) { return !state.host.useCaseSensitiveFileNames ? true : typeof state.host.useCaseSensitiveFileNames === "boolean" ? state.host.useCaseSensitiveFileNames : state.host.useCaseSensitiveFileNames(); }