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