• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    __String,
3    allKeysStartWithDot,
4    AmbientModuleDeclaration,
5    append,
6    arrayFrom,
7    CharacterCodes,
8    combinePaths,
9    compareBooleans,
10    compareNumberOfDirectorySeparators,
11    comparePaths,
12    Comparison,
13    CompilerOptions,
14    containsIgnoredPath,
15    containsPath,
16    createGetCanonicalFileName,
17    Debug,
18    directorySeparator,
19    emptyArray,
20    endsWith,
21    ensurePathIsNonModuleName,
22    ensureTrailingDirectorySeparator,
23    every,
24    ExportAssignment,
25    Extension,
26    extensionFromPath,
27    fileExtensionIsOneOf,
28    FileIncludeKind,
29    firstDefined,
30    flatMap,
31    flatten,
32    forEach,
33    forEachAncestorDirectory,
34    GetCanonicalFileName,
35    getDirectoryPath,
36    getEmitModuleResolutionKind,
37    getImpliedNodeFormatForFile,
38    getModeForResolutionAtIndex,
39    getModuleNameStringLiteralAt,
40    getNodeModulePathParts,
41    getNormalizedAbsolutePath,
42    getOwnKeys,
43    getPackageJsonByPMType,
44    getPackageJsonTypesVersionsPaths,
45    getPackageNameFromTypesPackageName,
46    getPathsBasePath,
47    getRelativePathFromDirectory,
48    getRelativePathToDirectoryOrUrl,
49    getSourceFileOfModule,
50    getSupportedExtensions,
51    getTextOfIdentifierOrLiteral,
52    hasJSFileExtension,
53    hasTSFileExtension,
54    hostGetCanonicalFileName,
55    Identifier,
56    isAmbientModule,
57    isApplicableVersionedTypesKey,
58    isExternalModuleAugmentation,
59    isExternalModuleNameRelative,
60    isModuleBlock,
61    isModuleDeclaration,
62    isNonGlobalAmbientModule,
63    isOhpm,
64    isRootedDiskPath,
65    isSourceFile,
66    isString,
67    JsxEmit,
68    map,
69    Map,
70    mapDefined,
71    MapLike,
72    matchPatternOrExact,
73    min,
74    ModuleDeclaration,
75    ModuleKind,
76    ModulePath,
77    ModuleResolutionHost,
78    ModuleResolutionKind,
79    ModuleSpecifierCache,
80    ModuleSpecifierOptions,
81    ModuleSpecifierResolutionHost,
82    NodeFlags,
83    NodeModulePathParts,
84    nodeModulesPathPart,
85    normalizePath,
86    ohModulesPathPart,
87    Path,
88    pathContainsNodeModules,
89    pathContainsOHModules,
90    pathIsBareSpecifier,
91    pathIsRelative,
92    PropertyAccessExpression,
93    removeFileExtension,
94    removeSuffix,
95    resolvePath,
96    ScriptKind,
97    some,
98    SourceFile,
99    startsWith,
100    startsWithDirectory,
101    stringContains,
102    StringLiteral,
103    Symbol,
104    SymbolFlags,
105    toPath,
106    tryGetExtensionFromPath,
107    tryParsePatterns,
108    TypeChecker,
109    UserPreferences,
110} from "./_namespaces/ts";
111
112// Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers.
113
114const enum RelativePreference { Relative, NonRelative, Shortest, ExternalNonRelative }
115// See UserPreferences#importPathEnding
116const enum Ending { Minimal, Index, JsExtension }
117
118// Processed preferences
119interface Preferences {
120    readonly relativePreference: RelativePreference;
121    readonly ending: Ending;
122}
123
124function getPreferences(host: ModuleSpecifierResolutionHost, { importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences {
125    return {
126        relativePreference:
127            importModuleSpecifierPreference === "relative" ? RelativePreference.Relative :
128            importModuleSpecifierPreference === "non-relative" ? RelativePreference.NonRelative :
129            importModuleSpecifierPreference === "project-relative" ? RelativePreference.ExternalNonRelative :
130            RelativePreference.Shortest,
131        ending: getEnding(),
132    };
133    function getEnding(): Ending {
134        switch (importModuleSpecifierEnding) {
135            case "minimal": return Ending.Minimal;
136            case "index": return Ending.Index;
137            case "js": return Ending.JsExtension;
138            default: return usesJsExtensionOnImports(importingSourceFile) || isFormatRequiringExtensions(compilerOptions, importingSourceFile.path, host) ? Ending.JsExtension
139                : getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal;
140        }
141    }
142}
143
144function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Preferences {
145    return {
146        relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative,
147        ending: hasJSFileExtension(oldImportSpecifier) || isFormatRequiringExtensions(compilerOptions, importingSourceFileName, host) ?
148            Ending.JsExtension :
149            getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal,
150    };
151}
152
153function isFormatRequiringExtensions(compilerOptions: CompilerOptions, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost) {
154    if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16
155    && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) {
156        return false;
157    }
158    return getImpliedNodeFormatForFile(importingSourceFileName, host.getPackageJsonInfoCache?.(), getModuleResolutionHost(host), compilerOptions) !== ModuleKind.CommonJS;
159}
160
161function getModuleResolutionHost(host: ModuleSpecifierResolutionHost): ModuleResolutionHost {
162    return {
163        fileExists: host.fileExists,
164        readFile: Debug.checkDefined(host.readFile),
165        directoryExists: host.directoryExists,
166        getCurrentDirectory: host.getCurrentDirectory,
167        realpath: host.realpath,
168        useCaseSensitiveFileNames: host.useCaseSensitiveFileNames?.(),
169    };
170}
171
172// `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`?
173// Because when this is called by the file renamer, `importingSourceFile` is the file being renamed,
174// while `importingSourceFileName` its *new* name. We need a source file just to get its
175// `impliedNodeFormat` and to detect certain preferences from existing import module specifiers.
176/** @internal */
177export function updateModuleSpecifier(
178    compilerOptions: CompilerOptions,
179    importingSourceFile: SourceFile,
180    importingSourceFileName: Path,
181    toFileName: string,
182    host: ModuleSpecifierResolutionHost,
183    oldImportSpecifier: string,
184    options: ModuleSpecifierOptions = {},
185): string | undefined {
186    const res = getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier, importingSourceFileName, host), {}, options);
187    if (res === oldImportSpecifier) return undefined;
188    return res;
189}
190
191// `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`?
192// Because when this is called by the declaration emitter, `importingSourceFile` is the implementation
193// file, but `importingSourceFileName` and `toFileName` refer to declaration files (the former to the
194// one currently being produced; the latter to the one being imported). We need an implementation file
195// just to get its `impliedNodeFormat` and to detect certain preferences from existing import module
196// specifiers.
197/** @internal */
198export function getModuleSpecifier(
199    compilerOptions: CompilerOptions,
200    importingSourceFile: SourceFile,
201    importingSourceFileName: Path,
202    toFileName: string,
203    host: ModuleSpecifierResolutionHost,
204    options: ModuleSpecifierOptions = {},
205): string {
206    return getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferences(host, {}, compilerOptions, importingSourceFile), {}, options);
207}
208
209/** @internal */
210export function getNodeModulesPackageName(
211    compilerOptions: CompilerOptions,
212    importingSourceFile: SourceFile,
213    nodeModulesFileName: string,
214    host: ModuleSpecifierResolutionHost,
215    preferences: UserPreferences,
216    options: ModuleSpecifierOptions = {},
217): string | undefined {
218    const info = getInfo(importingSourceFile.path, host);
219    const modulePaths = getAllModulePaths(importingSourceFile.path, nodeModulesFileName, host, preferences, options, compilerOptions.packageManagerType);
220    return firstDefined(modulePaths,
221        modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, preferences, /*packageNameOnly*/ true, options.overrideImportMode));
222}
223
224function getModuleSpecifierWorker(
225    compilerOptions: CompilerOptions,
226    importingSourceFile: SourceFile,
227    importingSourceFileName: Path,
228    toFileName: string,
229    host: ModuleSpecifierResolutionHost,
230    preferences: Preferences,
231    userPreferences: UserPreferences,
232    options: ModuleSpecifierOptions = {}
233): string {
234    const info = getInfo(importingSourceFileName, host);
235    const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences, options, compilerOptions.packageManagerType);
236    return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode)) ||
237        getLocalModuleSpecifier(toFileName, info, compilerOptions, host, options.overrideImportMode || importingSourceFile.impliedNodeFormat, preferences);
238}
239
240/** @internal */
241export function tryGetModuleSpecifiersFromCache(
242    moduleSymbol: Symbol,
243    importingSourceFile: SourceFile,
244    host: ModuleSpecifierResolutionHost,
245    userPreferences: UserPreferences,
246    options: ModuleSpecifierOptions = {},
247): readonly string[] | undefined {
248    return tryGetModuleSpecifiersFromCacheWorker(
249        moduleSymbol,
250        importingSourceFile,
251        host,
252        userPreferences,
253        options)[0];
254}
255
256function tryGetModuleSpecifiersFromCacheWorker(
257    moduleSymbol: Symbol,
258    importingSourceFile: SourceFile,
259    host: ModuleSpecifierResolutionHost,
260    userPreferences: UserPreferences,
261    options: ModuleSpecifierOptions = {},
262): readonly [specifiers?: readonly string[], moduleFile?: SourceFile, modulePaths?: readonly ModulePath[], cache?: ModuleSpecifierCache] {
263    const moduleSourceFile = getSourceFileOfModule(moduleSymbol);
264    if (!moduleSourceFile) {
265        return emptyArray as [];
266    }
267
268    const cache = host.getModuleSpecifierCache?.();
269    const cached = cache?.get(importingSourceFile.path, moduleSourceFile.path, userPreferences, options);
270    return [cached?.moduleSpecifiers, moduleSourceFile, cached?.modulePaths, cache];
271}
272
273/**
274 * Returns an import for each symlink and for the realpath.
275 *
276 * @internal
277 */
278export function getModuleSpecifiers(
279    moduleSymbol: Symbol,
280    checker: TypeChecker,
281    compilerOptions: CompilerOptions,
282    importingSourceFile: SourceFile,
283    host: ModuleSpecifierResolutionHost,
284    userPreferences: UserPreferences,
285    options: ModuleSpecifierOptions = {},
286): readonly string[] {
287    return getModuleSpecifiersWithCacheInfo(
288        moduleSymbol,
289        checker,
290        compilerOptions,
291        importingSourceFile,
292        host,
293        userPreferences,
294        options
295    ).moduleSpecifiers;
296}
297
298/** @internal */
299export function getModuleSpecifiersWithCacheInfo(
300    moduleSymbol: Symbol,
301    checker: TypeChecker,
302    compilerOptions: CompilerOptions,
303    importingSourceFile: SourceFile,
304    host: ModuleSpecifierResolutionHost,
305    userPreferences: UserPreferences,
306    options: ModuleSpecifierOptions = {},
307): { moduleSpecifiers: readonly string[], computedWithoutCache: boolean } {
308    let computedWithoutCache = false;
309    const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol, checker);
310    if (ambient) return { moduleSpecifiers: [ambient], computedWithoutCache };
311
312    // eslint-disable-next-line prefer-const
313    let [specifiers, moduleSourceFile, modulePaths, cache] = tryGetModuleSpecifiersFromCacheWorker(
314        moduleSymbol,
315        importingSourceFile,
316        host,
317        userPreferences,
318        options
319    );
320    if (specifiers) return { moduleSpecifiers: specifiers, computedWithoutCache };
321    if (!moduleSourceFile) return { moduleSpecifiers: emptyArray, computedWithoutCache };
322
323    computedWithoutCache = true;
324    modulePaths ||= getAllModulePathsWorker(importingSourceFile.path, moduleSourceFile.originalFileName, host, compilerOptions.packageManagerType);
325    const result = computeModuleSpecifiers(modulePaths, compilerOptions, importingSourceFile, host, userPreferences, options);
326    cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, options, modulePaths, result);
327    return { moduleSpecifiers: result, computedWithoutCache };
328}
329
330function computeModuleSpecifiers(
331    modulePaths: readonly ModulePath[],
332    compilerOptions: CompilerOptions,
333    importingSourceFile: SourceFile,
334    host: ModuleSpecifierResolutionHost,
335    userPreferences: UserPreferences,
336    options: ModuleSpecifierOptions = {},
337): readonly string[] {
338    const info = getInfo(importingSourceFile.path, host);
339    const preferences = getPreferences(host, userPreferences, compilerOptions, importingSourceFile);
340    const existingSpecifier = forEach(modulePaths, modulePath => forEach(
341        host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)),
342        reason => {
343            if (reason.kind !== FileIncludeKind.Import || reason.file !== importingSourceFile.path) return undefined;
344            // If the candidate import mode doesn't match the mode we're generating for, don't consider it
345            // TODO: maybe useful to keep around as an alternative option for certain contexts where the mode is overridable
346            if (importingSourceFile.impliedNodeFormat && importingSourceFile.impliedNodeFormat !== getModeForResolutionAtIndex(importingSourceFile, reason.index)) return undefined;
347            const specifier = getModuleNameStringLiteralAt(importingSourceFile, reason.index).text;
348            // If the preference is for non relative and the module specifier is relative, ignore it
349            return preferences.relativePreference !== RelativePreference.NonRelative || !pathIsRelative(specifier) ?
350                specifier :
351                undefined;
352        }
353    ));
354    if (existingSpecifier) {
355        const moduleSpecifiers = [existingSpecifier];
356        return moduleSpecifiers;
357    }
358
359    const importedFileIsInNodeModules = some(modulePaths, p => p.isInNodeModules);
360
361    // Module specifier priority:
362    //   1. "Bare package specifiers" (e.g. "@foo/bar") resulting from a path through node_modules or oh_modules to a package.json's or oh-package.json5's "types" entry
363    //   2. Specifiers generated using "paths" from tsconfig
364    //   3. Non-relative specfiers resulting from a path through node_modules or oh_modules(e.g. "@foo/bar/path/to/file")
365    //   4. Relative paths
366    let modulesSpecifiers: string[] | undefined;
367    let pathsSpecifiers: string[] | undefined;
368    let relativeSpecifiers: string[] | undefined;
369    for (const modulePath of modulePaths) {
370        const specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode);
371        modulesSpecifiers = append(modulesSpecifiers, specifier);
372        if (specifier && modulePath.isRedirect) {
373            // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar",
374            // not "@foo/bar/path/to/file"). No other specifier will be this good, so stop looking.
375            return modulesSpecifiers!;
376        }
377
378        if (!specifier && !modulePath.isRedirect) {
379            const local = getLocalModuleSpecifier(modulePath.path, info, compilerOptions, host, options.overrideImportMode || importingSourceFile.impliedNodeFormat, preferences);
380            if (pathIsBareSpecifier(local)) {
381                pathsSpecifiers = append(pathsSpecifiers, local);
382            }
383            else if (!importedFileIsInNodeModules || modulePath.isInNodeModules) {
384                // Why this extra conditional, not just an `else`? If some path to the file contained
385                // 'node_modules' or 'oh_modules', but we can't create a non-relative specifier (e.g. "@foo/bar/path/to/file"),
386                // that means we had to go through a *sibling's* node_modules or oh_modules, not one we can access directly.
387                // If some path to the file was in node_modules or oh_modules but another was not, this likely indicates that
388                // we have a monorepo structure with symlinks. In this case, the non-node_modules or oh_modules path is
389                // probably the realpath, e.g. "../bar/path/to/file", but a relative path to another package
390                // in a monorepo is probably not portable. So, the module specifier we actually go with will be
391                // the relative path through node_modules or oh_modules, so that the declaration emitter can produce a
392                // portability error. (See declarationEmitReexportedSymlinkReference3)
393                relativeSpecifiers = append(relativeSpecifiers, local);
394            }
395        }
396    }
397
398    return pathsSpecifiers?.length ? pathsSpecifiers :
399        modulesSpecifiers?.length ? modulesSpecifiers :
400        Debug.checkDefined(relativeSpecifiers);
401}
402
403interface Info {
404    readonly getCanonicalFileName: GetCanonicalFileName;
405    readonly importingSourceFileName: Path
406    readonly sourceDirectory: Path;
407}
408// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path
409function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info {
410    const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true);
411    const sourceDirectory = getDirectoryPath(importingSourceFileName);
412    return { getCanonicalFileName, importingSourceFileName, sourceDirectory };
413}
414
415function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: SourceFile["impliedNodeFormat"], { ending, relativePreference }: Preferences): string {
416    const { baseUrl, paths, rootDirs } = compilerOptions;
417    const { sourceDirectory, getCanonicalFileName } = info;
418    const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, ending, compilerOptions) ||
419        removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions);
420    if (!baseUrl && !paths || relativePreference === RelativePreference.Relative) {
421        return relativePath;
422    }
423
424    const baseDirectory = getNormalizedAbsolutePath(getPathsBasePath(compilerOptions, host) || baseUrl!, host.getCurrentDirectory());
425    const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseDirectory, getCanonicalFileName);
426    if (!relativeToBaseUrl) {
427        return relativePath;
428    }
429
430    const fromPaths = paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, getAllowedEndings(ending, compilerOptions, importMode), host, compilerOptions);
431    const nonRelative = fromPaths === undefined && baseUrl !== undefined ? removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions) : fromPaths;
432    if (!nonRelative) {
433        return relativePath;
434    }
435
436    if (relativePreference === RelativePreference.NonRelative) {
437        return nonRelative;
438    }
439
440    if (relativePreference === RelativePreference.ExternalNonRelative) {
441        const projectDirectory = compilerOptions.configFilePath ?
442            toPath(getDirectoryPath(compilerOptions.configFilePath), host.getCurrentDirectory(), info.getCanonicalFileName) :
443            info.getCanonicalFileName(host.getCurrentDirectory());
444        const modulePath = toPath(moduleFileName, projectDirectory, getCanonicalFileName);
445        const sourceIsInternal = startsWith(sourceDirectory, projectDirectory);
446        const targetIsInternal = startsWith(modulePath, projectDirectory);
447        if (sourceIsInternal && !targetIsInternal || !sourceIsInternal && targetIsInternal) {
448            // 1. The import path crosses the boundary of the tsconfig.json-containing directory.
449            //
450            //      src/
451            //        tsconfig.json
452            //        index.ts -------
453            //      lib/              | (path crosses tsconfig.json)
454            //        imported.ts <---
455            //
456            return nonRelative;
457        }
458        const packageManagerType = compilerOptions.packageManagerType;
459        const nearestTargetPackageJson = getNearestAncestorDirectoryWithPackageJson(host, getDirectoryPath(modulePath), packageManagerType);
460        const nearestSourcePackageJson = getNearestAncestorDirectoryWithPackageJson(host, sourceDirectory, packageManagerType);
461        if (nearestSourcePackageJson !== nearestTargetPackageJson) {
462            // 2. The importing and imported files are part of different packages.
463            //
464            //      packages/a/
465            //        package.json
466            //        index.ts --------
467            //      packages/b/        | (path crosses package.json)
468            //        package.json     |
469            //        component.ts <---
470            //
471            return nonRelative;
472        }
473
474        return relativePath;
475    }
476
477    if (relativePreference !== RelativePreference.Shortest) Debug.assertNever(relativePreference);
478
479    // Prefer a relative import over a baseUrl import if it has fewer components.
480    return isPathRelativeToParent(nonRelative) || countPathComponents(relativePath) < countPathComponents(nonRelative) ? relativePath : nonRelative;
481}
482
483/** @internal */
484export function countPathComponents(path: string): number {
485    let count = 0;
486    for (let i = startsWith(path, "./") ? 2 : 0; i < path.length; i++) {
487        if (path.charCodeAt(i) === CharacterCodes.slash) count++;
488    }
489    return count;
490}
491
492function usesJsExtensionOnImports({ imports }: SourceFile): boolean {
493    return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text) : undefined) || false;
494}
495
496function comparePathsByRedirectAndNumberOfDirectorySeparators(a: ModulePath, b: ModulePath) {
497    return compareBooleans(b.isRedirect, a.isRedirect) || compareNumberOfDirectorySeparators(a.path, b.path);
498}
499
500function getNearestAncestorDirectoryWithPackageJson(host: ModuleSpecifierResolutionHost, fileName: string, packageManagerType?: string) {
501    if (host.getNearestAncestorDirectoryWithPackageJson) {
502        return host.getNearestAncestorDirectoryWithPackageJson(fileName);
503    }
504    return !!forEachAncestorDirectory(fileName, directory => {
505        return host.fileExists(combinePaths(directory, getPackageJsonByPMType(packageManagerType))) ? true : undefined;
506    });
507}
508
509/** @internal */
510export function forEachFileNameOfModule<T>(
511    importingFileName: string,
512    importedFileName: string,
513    host: ModuleSpecifierResolutionHost,
514    preferSymlinks: boolean,
515    cb: (fileName: string, isRedirect: boolean) => T | undefined
516): T | undefined {
517    const getCanonicalFileName = hostGetCanonicalFileName(host);
518    const cwd = host.getCurrentDirectory();
519    const referenceRedirect = host.isSourceOfProjectReferenceRedirect(importedFileName) ? host.getProjectReferenceRedirect(importedFileName) : undefined;
520    const importedPath = toPath(importedFileName, cwd, getCanonicalFileName);
521    const redirects = host.redirectTargetsMap.get(importedPath) || emptyArray;
522    const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects];
523    const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd));
524    let shouldFilterIgnoredPaths = !every(targets, containsIgnoredPath);
525
526    if (!preferSymlinks) {
527        // Symlinks inside ignored paths are already filtered out of the symlink cache,
528        // so we only need to remove them from the realpath filenames.
529        const result = forEach(targets, p => !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) && cb(p, referenceRedirect === p));
530        if (result) return result;
531    }
532
533    const symlinkedDirectories = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath();
534    const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd);
535    const result = symlinkedDirectories && forEachAncestorDirectory(getDirectoryPath(fullImportedFileName), realPathDirectory => {
536        const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName)));
537        if (!symlinkDirectories) return undefined; // Continue to ancestor directory
538
539        // Don't want to a package to globally import from itself (importNameCodeFix_symlink_own_package.ts)
540        if (startsWithDirectory(importingFileName, realPathDirectory, getCanonicalFileName)) {
541            return false; // Stop search, each ancestor directory will also hit this condition
542        }
543
544        return forEach(targets, target => {
545            if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) {
546                return;
547            }
548
549            const relative = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName);
550            for (const symlinkDirectory of symlinkDirectories) {
551                const option = resolvePath(symlinkDirectory, relative);
552                const result = cb(option, target === referenceRedirect);
553                shouldFilterIgnoredPaths = true; // We found a non-ignored path in symlinks, so we can reject ignored-path realpaths
554                if (result) return result;
555            }
556        });
557    });
558    return result || (preferSymlinks
559        ? forEach(targets, p => shouldFilterIgnoredPaths && containsIgnoredPath(p) ? undefined : cb(p, p === referenceRedirect))
560        : undefined);
561}
562
563/**
564 * Looks for existing imports that use symlinks to this module.
565 * Symlinks will be returned first so they are preferred over the real path.
566 */
567function getAllModulePaths(
568    importingFilePath: Path,
569    importedFileName: string,
570    host: ModuleSpecifierResolutionHost,
571    preferences: UserPreferences,
572    options: ModuleSpecifierOptions = {},
573    packageManagerType?: string
574) {
575    const importedFilePath = toPath(importedFileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host));
576    const cache = host.getModuleSpecifierCache?.();
577    if (cache) {
578        const cached = cache.get(importingFilePath, importedFilePath, preferences, options);
579        if (cached?.modulePaths) return cached.modulePaths;
580    }
581    const modulePaths = getAllModulePathsWorker(importingFilePath, importedFileName, host, packageManagerType);
582    if (cache) {
583        cache.setModulePaths(importingFilePath, importedFilePath, preferences, options, modulePaths);
584    }
585    return modulePaths;
586}
587
588function getAllModulePathsWorker(importingFileName: Path, importedFileName: string, host: ModuleSpecifierResolutionHost, packageManagerType?: string): readonly ModulePath[] {
589    const getCanonicalFileName = hostGetCanonicalFileName(host);
590    const allFileNames = new Map<string, { path: string, isRedirect: boolean, isInNodeModules: boolean }>();
591    let importedFileFromNodeModules = false;
592    forEachFileNameOfModule(
593        importingFileName,
594        importedFileName,
595        host,
596        /*preferSymlinks*/ true,
597        (path, isRedirect) => {
598            const isInNodeModules = isOhpm(packageManagerType) ? pathContainsOHModules(path) : pathContainsNodeModules(path);
599            allFileNames.set(path, { path: getCanonicalFileName(path), isRedirect, isInNodeModules });
600            importedFileFromNodeModules = importedFileFromNodeModules || isInNodeModules;
601            // don't return value, so we collect everything
602        }
603    );
604
605    // Sort by paths closest to importing file Name directory
606    const sortedPaths: ModulePath[] = [];
607    for (
608        let directory = getDirectoryPath(importingFileName);
609        allFileNames.size !== 0;
610    ) {
611        const directoryStart = ensureTrailingDirectorySeparator(directory);
612        let pathsInDirectory: ModulePath[] | undefined;
613        allFileNames.forEach(({ path, isRedirect, isInNodeModules }, fileName) => {
614            if (startsWith(path, directoryStart)) {
615                (pathsInDirectory ||= []).push({ path: fileName, isRedirect, isInNodeModules });
616                allFileNames.delete(fileName);
617            }
618        });
619        if (pathsInDirectory) {
620            if (pathsInDirectory.length > 1) {
621                pathsInDirectory.sort(comparePathsByRedirectAndNumberOfDirectorySeparators);
622            }
623            sortedPaths.push(...pathsInDirectory);
624        }
625        const newDirectory = getDirectoryPath(directory);
626        if (newDirectory === directory) break;
627        directory = newDirectory;
628    }
629    if (allFileNames.size) {
630        const remainingPaths = arrayFrom(allFileNames.values());
631        if (remainingPaths.length > 1) remainingPaths.sort(comparePathsByRedirectAndNumberOfDirectorySeparators);
632        sortedPaths.push(...remainingPaths);
633    }
634
635    return sortedPaths;
636}
637
638function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol, checker: TypeChecker): string | undefined {
639    const decl = moduleSymbol.declarations?.find(
640        d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name)))
641    ) as (ModuleDeclaration & { name: StringLiteral }) | undefined;
642    if (decl) {
643        return decl.name.text;
644    }
645
646    // the module could be a namespace, which is export through "export=" from an ambient module.
647    /**
648     * declare module "m" {
649     *     namespace ns {
650     *         class c {}
651     *     }
652     *     export = ns;
653     * }
654     */
655    // `import {c} from "m";` is valid, in which case, `moduleSymbol` is "ns", but the module name should be "m"
656    const ambientModuleDeclareCandidates = mapDefined(moduleSymbol.declarations,
657        d => {
658            if (!isModuleDeclaration(d)) return;
659            const topNamespace = getTopNamespace(d);
660            if (!(topNamespace?.parent?.parent
661                && isModuleBlock(topNamespace.parent) && isAmbientModule(topNamespace.parent.parent) && isSourceFile(topNamespace.parent.parent.parent))) return;
662            const exportAssignment = ((topNamespace.parent.parent.symbol.exports?.get("export=" as __String)?.valueDeclaration as ExportAssignment)?.expression as PropertyAccessExpression | Identifier);
663            if (!exportAssignment) return;
664            const exportSymbol = checker.getSymbolAtLocation(exportAssignment);
665            if (!exportSymbol) return;
666            const originalExportSymbol = exportSymbol?.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(exportSymbol) : exportSymbol;
667            if (originalExportSymbol === d.symbol) return topNamespace.parent.parent;
668
669            function getTopNamespace(namespaceDeclaration: ModuleDeclaration) {
670                while (namespaceDeclaration.flags & NodeFlags.NestedNamespace) {
671                    namespaceDeclaration = namespaceDeclaration.parent as ModuleDeclaration;
672                }
673                return namespaceDeclaration;
674            }
675        }
676    );
677    const ambientModuleDeclare = ambientModuleDeclareCandidates[0] as (AmbientModuleDeclaration & { name: StringLiteral }) | undefined;
678    if (ambientModuleDeclare) {
679        return ambientModuleDeclare.name.text;
680    }
681}
682
683function getAllowedEndings(preferredEnding: Ending, compilerOptions: CompilerOptions, importMode: SourceFile["impliedNodeFormat"]) {
684    if (getEmitModuleResolutionKind(compilerOptions) >= ModuleResolutionKind.Node16 && importMode === ModuleKind.ESNext) {
685        return [Ending.JsExtension];
686    }
687    switch (preferredEnding) {
688        case Ending.JsExtension: return [Ending.JsExtension, Ending.Minimal, Ending.Index];
689        case Ending.Index: return [Ending.Index, Ending.Minimal, Ending.JsExtension];
690        case Ending.Minimal: return [Ending.Minimal, Ending.Index, Ending.JsExtension];
691        default: Debug.assertNever(preferredEnding);
692    }
693}
694
695function tryGetModuleNameFromPaths(relativeToBaseUrl: string, paths: MapLike<readonly string[]>, allowedEndings: Ending[], host: ModuleSpecifierResolutionHost, compilerOptions: CompilerOptions): string | undefined {
696    for (const key in paths) {
697        for (const patternText of paths[key]) {
698            const pattern = normalizePath(patternText);
699            const indexOfStar = pattern.indexOf("*");
700            // In module resolution, if `pattern` itself has an extension, a file with that extension is looked up directly,
701            // meaning a '.ts' or '.d.ts' extension is allowed to resolve. This is distinct from the case where a '*' substitution
702            // causes a module specifier to have an extension, i.e. the extension comes from the module specifier in a JS/TS file
703            // and matches the '*'. For example:
704            //
705            // Module Specifier      | Path Mapping (key: [pattern]) | Interpolation       | Resolution Action
706            // ---------------------->------------------------------->--------------------->---------------------------------------------------------------
707            // import "@app/foo"    -> "@app/*": ["./src/app/*.ts"] -> "./src/app/foo.ts" -> tryFile("./src/app/foo.ts") || [continue resolution algorithm]
708            // import "@app/foo.ts" -> "@app/*": ["./src/app/*"]    -> "./src/app/foo.ts" -> [continue resolution algorithm]
709            //
710            // (https://github.com/microsoft/TypeScript/blob/ad4ded80e1d58f0bf36ac16bea71bc10d9f09895/src/compiler/moduleNameResolver.ts#L2509-L2516)
711            //
712            // The interpolation produced by both scenarios is identical, but only in the former, where the extension is encoded in
713            // the path mapping rather than in the module specifier, will we prioritize a file lookup on the interpolation result.
714            // (In fact, currently, the latter scenario will necessarily fail since no resolution mode recognizes '.ts' as a valid
715            // extension for a module specifier.)
716            //
717            // Here, this means we need to be careful about whether we generate a match from the target filename (typically with a
718            // .ts extension) or the possible relative module specifiers representing that file:
719            //
720            // Filename            | Relative Module Specifier Candidates         | Path Mapping                 | Filename Result    | Module Specifier Results
721            // --------------------<----------------------------------------------<------------------------------<-------------------||----------------------------
722            // dist/haha.d.ts      <- dist/haha, dist/haha.js                     <- "@app/*": ["./dist/*.d.ts"] <- @app/haha        || (none)
723            // dist/haha.d.ts      <- dist/haha, dist/haha.js                     <- "@app/*": ["./dist/*"]      <- (none)           || @app/haha, @app/haha.js
724            // dist/foo/index.d.ts <- dist/foo, dist/foo/index, dist/foo/index.js <- "@app/*": ["./dist/*.d.ts"] <- @app/foo/index   || (none)
725            // dist/foo/index.d.ts <- dist/foo, dist/foo/index, dist/foo/index.js <- "@app/*": ["./dist/*"]      <- (none)           || @app/foo, @app/foo/index, @app/foo/index.js
726            // dist/wow.js.js      <- dist/wow.js, dist/wow.js.js                 <- "@app/*": ["./dist/*.js"]   <- @app/wow.js      || @app/wow, @app/wow.js
727            //
728            // The "Filename Result" can be generated only if `pattern` has an extension. Care must be taken that the list of
729            // relative module specifiers to run the interpolation (a) is actually valid for the module resolution mode, (b) takes
730            // into account the existence of other files (e.g. 'dist/wow.js' cannot refer to 'dist/wow.js.js' if 'dist/wow.js'
731            // exists) and (c) that they are ordered by preference. The last row shows that the filename result and module
732            // specifier results are not mutually exclusive. Note that the filename result is a higher priority in module
733            // resolution, but as long criteria (b) above is met, I don't think its result needs to be the highest priority result
734            // in module specifier generation. I have included it last, as it's difficult to tell exactly where it should be
735            // sorted among the others for a particular value of `importModuleSpecifierEnding`.
736            const candidates: { ending: Ending | undefined, value: string }[] = allowedEndings.map(ending => ({
737                ending,
738                value: removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions)
739            }));
740            if (tryGetExtensionFromPath(pattern)) {
741                candidates.push({ ending: undefined, value: relativeToBaseUrl });
742            }
743
744            if (indexOfStar !== -1) {
745                const prefix = pattern.substring(0, indexOfStar);
746                const suffix = pattern.substring(indexOfStar + 1);
747                for (const { ending, value } of candidates) {
748                    if (value.length >= prefix.length + suffix.length &&
749                        startsWith(value, prefix) &&
750                        endsWith(value, suffix) &&
751                        validateEnding({ ending, value })
752                    ) {
753                        const matchedStar = value.substring(prefix.length, value.length - suffix.length);
754                        return key.replace("*", matchedStar);
755                    }
756                }
757            }
758            else if (
759                some(candidates, c => c.ending !== Ending.Minimal && pattern === c.value) ||
760                some(candidates, c => c.ending === Ending.Minimal && pattern === c.value && validateEnding(c))
761            ) {
762                return key;
763            }
764        }
765    }
766
767    function validateEnding({ ending, value }: { ending: Ending | undefined, value: string }) {
768        // Optimization: `removeExtensionAndIndexPostFix` can query the file system (a good bit) if `ending` is `Minimal`, the basename
769        // is 'index', and a `host` is provided. To avoid that until it's unavoidable, we ran the function with no `host` above. Only
770        // here, after we've checked that the minimal ending is indeed a match (via the length and prefix/suffix checks / `some` calls),
771        // do we check that the host-validated result is consistent with the answer we got before. If it's not, it falls back to the
772        // `Ending.Index` result, which should already be in the list of candidates if `Minimal` was. (Note: the assumption here is
773        // that every module resolution mode that supports dropping extensions also supports dropping `/index`. Like literally
774        // everything else in this file, this logic needs to be updated if that's not true in some future module resolution mode.)
775        return ending !== Ending.Minimal || value === removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions, host);
776    }
777}
778
779const enum MatchingMode {
780    Exact,
781    Directory,
782    Pattern
783}
784
785function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode = MatchingMode.Exact): { moduleFileToTry: string } | undefined {
786    if (typeof exports === "string") {
787        const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined);
788        const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined;
789        switch (mode) {
790            case MatchingMode.Exact:
791                if (comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || (extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo)) {
792                    return { moduleFileToTry: packageName };
793                }
794                break;
795            case MatchingMode.Directory:
796                if (containsPath(pathOrPattern, targetFilePath)) {
797                    const fragment = getRelativePathFromDirectory(pathOrPattern, targetFilePath, /*ignoreCase*/ false);
798                    return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) };
799                }
800                break;
801            case MatchingMode.Pattern:
802                const starPos = pathOrPattern.indexOf("*");
803                const leadingSlice = pathOrPattern.slice(0, starPos);
804                const trailingSlice = pathOrPattern.slice(starPos + 1);
805                if (startsWith(targetFilePath, leadingSlice) && endsWith(targetFilePath, trailingSlice)) {
806                    const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length);
807                    return { moduleFileToTry: packageName.replace("*", starReplacement) };
808                }
809                if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) {
810                    const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length);
811                    return { moduleFileToTry: packageName.replace("*", starReplacement) };
812                }
813                break;
814        }
815    }
816    else if (Array.isArray(exports)) {
817        return forEach(exports, e => tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, e, conditions));
818    }
819    else if (typeof exports === "object" && exports !== null) { // eslint-disable-line no-null/no-null
820        if (allKeysStartWithDot(exports as MapLike<unknown>)) {
821            // sub-mappings
822            // 3 cases:
823            // * directory mappings (legacyish, key ends with / (technically allows index/extension resolution under cjs mode))
824            // * pattern mappings (contains a *)
825            // * exact mappings (no *, does not end with /)
826            return forEach(getOwnKeys(exports as MapLike<unknown>), k => {
827                const subPackageName = getNormalizedAbsolutePath(combinePaths(packageName, k), /*currentDirectory*/ undefined);
828                const mode = endsWith(k, "/") ? MatchingMode.Directory
829                    : stringContains(k, "*") ? MatchingMode.Pattern
830                    : MatchingMode.Exact;
831                return tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, subPackageName, (exports as MapLike<unknown>)[k], conditions, mode);
832            });
833        }
834        else {
835            // conditional mapping
836            for (const key of getOwnKeys(exports as MapLike<unknown>)) {
837                if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) {
838                    const subTarget = (exports as MapLike<unknown>)[key];
839                    const result = tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, subTarget, conditions);
840                    if (result) {
841                        return result;
842                    }
843                }
844            }
845        }
846    }
847    return undefined;
848}
849
850function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined {
851    const normalizedTargetPaths = getPathsRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName);
852    if (normalizedTargetPaths === undefined) {
853        return undefined;
854    }
855
856    const normalizedSourcePaths = getPathsRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName);
857    const relativePaths = flatMap(normalizedSourcePaths, sourcePath => {
858        return map(normalizedTargetPaths, targetPath => ensurePathIsNonModuleName(getRelativePathFromDirectory(sourcePath, targetPath, getCanonicalFileName)));
859    });
860    const shortest = min(relativePaths, compareNumberOfDirectorySeparators);
861    if (!shortest) {
862        return undefined;
863    }
864
865    return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs
866        ? removeExtensionAndIndexPostFix(shortest, ending, compilerOptions)
867        : removeFileExtension(shortest);
868}
869
870function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile , host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ModuleKind.ESNext | ModuleKind.CommonJS): string | undefined {
871    if (!host.fileExists || !host.readFile) {
872        return undefined;
873    }
874    const parts: NodeModulePathParts = getNodeModulePathParts(path, isOhpm(options.packageManagerType) ? ohModulesPathPart : nodeModulesPathPart)!;
875    if (!parts) {
876        return undefined;
877    }
878
879    // Simplify the full file path to something that can be resolved by Node.
880
881    const preferences = getPreferences(host, userPreferences, options, importingSourceFile);
882    let moduleSpecifier = path;
883    let isPackageRootPath = false;
884    if (!packageNameOnly) {
885        let packageRootIndex = parts.packageRootIndex;
886        let moduleFileName: string | undefined;
887        while (true) {
888            // If the module could be imported by a directory name, use that directory's name
889            const { moduleFileToTry, packageRootPath, blockedByExports, verbatimFromExports } = tryDirectoryWithPackageJson(packageRootIndex);
890            if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Classic) {
891                if (blockedByExports) {
892                    return undefined; // File is under this package.json, but is not publicly exported - there's no way to name it via `node_modules` resolution
893                }
894                if (verbatimFromExports) {
895                    return moduleFileToTry;
896                }
897            }
898            if (packageRootPath) {
899                moduleSpecifier = packageRootPath;
900                isPackageRootPath = true;
901                break;
902            }
903            if (!moduleFileName) moduleFileName = moduleFileToTry;
904
905            // try with next level of directory
906            packageRootIndex = path.indexOf(directorySeparator, packageRootIndex + 1);
907            if (packageRootIndex === -1) {
908                moduleSpecifier = removeExtensionAndIndexPostFix(moduleFileName, preferences.ending, options, host);
909                break;
910            }
911        }
912    }
913
914    if (isRedirect && !isPackageRootPath) {
915        return undefined;
916    }
917
918    const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation();
919    // Get a path that's relative to node_modules, oh_modules or the importing file's path
920    // if node_modules or oh_modules folder is in this folder or any of its parent folders, no need to keep it.
921    const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex));
922    if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) {
923        return undefined;
924    }
925
926    // If the module was found in @types, get the actual Node package name
927    const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1);
928    const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName);
929    // For classic resolution, only allow importing from node_modules/@types, not other node_modules
930    return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Classic && packageName === nodeModulesDirectoryName ? undefined : packageName;
931
932    function tryDirectoryWithPackageJson(packageRootIndex: number): { moduleFileToTry: string, packageRootPath?: string, blockedByExports?: true, verbatimFromExports?: true } {
933        const packageRootPath = path.substring(0, packageRootIndex);
934        const packageJsonPath = combinePaths(packageRootPath, getPackageJsonByPMType(options.packageManagerType));
935        let moduleFileToTry = path;
936        let maybeBlockedByTypesVersions = false;
937        const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath);
938        if (typeof cachedPackageJson === "object" || cachedPackageJson === undefined && host.fileExists(packageJsonPath)) {
939            const packageJsonContent = cachedPackageJson?.contents.packageJsonContent ||
940                isOhpm(options.packageManagerType) ? require("json5").parse(host.readFile!(packageJsonPath)!) : JSON.parse(host.readFile!(packageJsonPath)!);
941            const importMode = overrideMode || importingSourceFile.impliedNodeFormat;
942            if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) {
943                const conditions = ["node", importMode === ModuleKind.ESNext ? "import" : "require", "types"];
944                const fromExports = packageJsonContent.exports && typeof packageJsonContent.name === "string"
945                    ? tryGetModuleNameFromExports(options, path, packageRootPath, getPackageNameFromTypesPackageName(packageJsonContent.name), packageJsonContent.exports, conditions)
946                    : undefined;
947                if (fromExports) {
948                    const withJsExtension = !hasTSFileExtension(fromExports.moduleFileToTry)
949                        ? fromExports
950                        : { moduleFileToTry: removeFileExtension(fromExports.moduleFileToTry) + tryGetJSExtensionForFile(fromExports.moduleFileToTry, options) };
951                    return { ...withJsExtension, verbatimFromExports: true };
952                }
953                if (packageJsonContent.exports) {
954                    return { moduleFileToTry: path, blockedByExports: true };
955                }
956            }
957            const versionPaths = packageJsonContent.typesVersions
958                ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions)
959                : undefined;
960            if (versionPaths) {
961                const subModuleName = path.slice(packageRootPath.length + 1);
962                const fromPaths = tryGetModuleNameFromPaths(
963                    subModuleName,
964                    versionPaths.paths,
965                    getAllowedEndings(preferences.ending, options, importMode),
966                    host,
967                    options
968                );
969                if (fromPaths === undefined) {
970                    maybeBlockedByTypesVersions = true;
971                }
972                else {
973                    moduleFileToTry = combinePaths(packageRootPath, fromPaths);
974                }
975            }
976            // If the file is the main module, it can be imported by the package name
977            const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main || "index.js";
978            if (isString(mainFileRelative) && !(maybeBlockedByTypesVersions && matchPatternOrExact(tryParsePatterns(versionPaths!.paths), mainFileRelative))) {
979                // The 'main' file is also subject to mapping through typesVersions, and we couldn't come up with a path
980                // explicitly through typesVersions, so if it matches a key in typesVersions now, it's not reachable.
981                // (The only way this can happen is if some file in a package that's not resolvable from outside the
982                // package got pulled into the program anyway, e.g. transitively through a file that *is* reachable. It
983                // happens very easily in fourslash tests though, since every test file listed gets included. See
984                // importNameCodeFix_typesVersions.ts for an example.)
985                const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
986                if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(moduleFileToTry))) {
987                    // ^ An arbitrary removal of file extension for this comparison is almost certainly wrong
988                    return { packageRootPath, moduleFileToTry };
989                }
990            }
991        }
992        else {
993            // No package.json exists; an index.js will still resolve as the package name
994            const fileName = getCanonicalFileName(moduleFileToTry.substring(parts.packageRootIndex + 1));
995            if (fileName === "index.d.ts" || fileName === "index.js" || fileName === "index.ts" || fileName === "index.tsx") {
996                return { moduleFileToTry, packageRootPath };
997            }
998        }
999        return { moduleFileToTry };
1000    }
1001}
1002
1003function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) {
1004    if (!host.fileExists) return;
1005    // We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory
1006    const extensions = flatten(getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }]));
1007    for (const e of extensions) {
1008        const fullPath = path + e;
1009        if (host.fileExists(fullPath)) {
1010            return fullPath;
1011        }
1012    }
1013}
1014
1015function getPathsRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string[] | undefined {
1016    return mapDefined(rootDirs, rootDir => {
1017        const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName);
1018        return relativePath !== undefined && isPathRelativeToParent(relativePath) ? undefined : relativePath;
1019    });
1020}
1021
1022function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions, host?: ModuleSpecifierResolutionHost): string {
1023    if (fileExtensionIsOneOf(fileName, [Extension.Json, Extension.Mjs, Extension.Cjs])) return fileName;
1024    const noExtension = removeFileExtension(fileName);
1025    if (fileName === noExtension) return fileName;
1026    if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) return noExtension + getJSExtensionForFile(fileName, options);
1027    switch (ending) {
1028        case Ending.Minimal:
1029            const withoutIndex = removeSuffix(noExtension, "/index");
1030            if (host && withoutIndex !== noExtension && tryGetAnyFileFromPath(host, withoutIndex)) {
1031                // Can't remove index if there's a file by the same name as the directory.
1032                // Probably more callers should pass `host` so we can determine this?
1033                return noExtension;
1034            }
1035            return withoutIndex;
1036        case Ending.Index:
1037            return noExtension;
1038        case Ending.JsExtension:
1039            return noExtension + getJSExtensionForFile(fileName, options);
1040        default:
1041            return Debug.assertNever(ending);
1042    }
1043}
1044
1045function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension {
1046    return tryGetJSExtensionForFile(fileName, options) ?? Debug.fail(`Extension ${extensionFromPath(fileName)} is unsupported:: FileName:: ${fileName}`);
1047}
1048
1049/** @internal */
1050export function tryGetJSExtensionForFile(fileName: string, options: CompilerOptions): Extension | undefined {
1051    const ext = tryGetExtensionFromPath(fileName);
1052    switch (ext) {
1053        case Extension.Ts:
1054        case Extension.Dts:
1055        case Extension.Ets:
1056        case Extension.Dets:
1057            return Extension.Js;
1058        case Extension.Tsx:
1059            return options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js;
1060        case Extension.Js:
1061        case Extension.Jsx:
1062        case Extension.Json:
1063            return ext;
1064        case Extension.Dmts:
1065        case Extension.Mts:
1066        case Extension.Mjs:
1067            return Extension.Mjs;
1068        case Extension.Dcts:
1069        case Extension.Cts:
1070        case Extension.Cjs:
1071            return Extension.Cjs;
1072        default:
1073            return undefined;
1074    }
1075}
1076
1077function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined {
1078    const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
1079    return isRootedDiskPath(relativePath) ? undefined : relativePath;
1080}
1081
1082function isPathRelativeToParent(path: string): boolean {
1083    return startsWith(path, "..");
1084}
1085