• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    __String, addToSeen, arrayIsEqualTo, CancellationToken, CompilerOptions, consumesNodeCoreModules, createMultiMap,
3    Debug, emptyArray, findIndex, firstDefined, forEachAncestorDirectory, forEachEntry, getBaseFileName,
4    GetCanonicalFileName, getDirectoryPath, getLocalSymbolForExportDefault, getModuleByPMType, getNameForExportedSymbol,
5    getNamesForExportedSymbol, getNodeModulePathParts, getPackageNameFromTypesPackageName, getPatternFromSpec,
6    getRegexFromPattern, getSymbolId, hostGetCanonicalFileName, hostUsesCaseSensitiveFileNames, InternalSymbolName,
7    isExportAssignment, isExportSpecifier, isExternalModuleNameRelative, isExternalModuleSymbol,
8    isExternalOrCommonJsModule, isIdentifier, isKnownSymbol, isNonGlobalAmbientModule, isOhpm,
9    isPrivateIdentifierSymbol, LanguageServiceHost, Map, mapDefined, ModuleSpecifierCache,
10    ModuleSpecifierResolutionHost, moduleSpecifiers, nodeModulesPathPart, ohModulesPathPart, PackageJsonImportFilter,
11    Path, Program, skipAlias, skipOuterExpressions, SourceFile, startsWith, Statement, stringContains, stripQuotes,
12    Symbol, SymbolFlags, timestamp, tryCast, TypeChecker, unescapeLeadingUnderscores, unmangleScopedPackageName,
13    UserPreferences,
14} from "./_namespaces/ts";
15
16/** @internal */
17export const enum ImportKind {
18    Named,
19    Default,
20    Namespace,
21    CommonJS,
22}
23
24/** @internal */
25export const enum ExportKind {
26    Named,
27    Default,
28    ExportEquals,
29    UMD,
30}
31
32/** @internal */
33export interface SymbolExportInfo {
34    readonly symbol: Symbol;
35    readonly moduleSymbol: Symbol;
36    /** Set if `moduleSymbol` is an external module, not an ambient module */
37    moduleFileName: string | undefined;
38    exportKind: ExportKind;
39    targetFlags: SymbolFlags;
40    /** True if export was only found via the package.json AutoImportProvider (for telemetry). */
41    isFromPackageJson: boolean;
42}
43
44interface CachedSymbolExportInfo {
45    // Used to rehydrate `symbol` and `moduleSymbol` when transient
46    id: number;
47    symbolName: string;
48    capitalizedSymbolName: string | undefined;
49    symbolTableKey: __String;
50    moduleName: string;
51    moduleFile: SourceFile | undefined;
52    packageName: string | undefined;
53
54    // SymbolExportInfo, but optional symbols
55    readonly symbol: Symbol | undefined;
56    readonly moduleSymbol: Symbol | undefined;
57    moduleFileName: string | undefined;
58    exportKind: ExportKind;
59    targetFlags: SymbolFlags;
60    isFromPackageJson: boolean;
61}
62
63/** @internal */
64export interface ExportInfoMap {
65    isUsableByFile(importingFile: Path): boolean;
66    clear(): void;
67    add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, checker: TypeChecker): void;
68    get(importingFile: Path, key: string): readonly SymbolExportInfo[] | undefined;
69    search<T>(importingFile: Path, preferCapitalized: boolean, matches: (name: string, targetFlags: SymbolFlags) => boolean, action: (info: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean, key: string) => T | undefined): T | undefined;
70    releaseSymbols(): void;
71    isEmpty(): boolean;
72    /** @returns Whether the change resulted in the cache being cleared */
73    onFileChanged(oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean): boolean;
74}
75
76/** @internal */
77export interface CacheableExportInfoMapHost {
78    getCurrentProgram(): Program | undefined;
79    getPackageJsonAutoImportProvider(): Program | undefined;
80    getGlobalTypingsCacheLocation(): string | undefined;
81}
82
83/** @internal */
84export function createCacheableExportInfoMap(host: CacheableExportInfoMapHost): ExportInfoMap {
85    let exportInfoId = 1;
86    const exportInfo = createMultiMap<string, CachedSymbolExportInfo>();
87    const symbols = new Map<number, [symbol: Symbol, moduleSymbol: Symbol]>();
88    /**
89     * Key: node_modules package name (no @types).
90     * Value: path to deepest node_modules folder seen that is
91     * both visible to `usableByFileName` and contains the package.
92     *
93     * Later, we can see if a given SymbolExportInfo is shadowed by
94     * a another installation of the same package in a deeper
95     * node_modules folder by seeing if its path starts with the
96     * value stored here.
97     */
98    const packages = new Map<string, string>();
99    let usableByFileName: Path | undefined;
100    const cache: ExportInfoMap = {
101        isUsableByFile: importingFile => importingFile === usableByFileName,
102        isEmpty: () => !exportInfo.size,
103        clear: () => {
104            exportInfo.clear();
105            symbols.clear();
106            usableByFileName = undefined;
107        },
108        add: (importingFile, symbol, symbolTableKey, moduleSymbol, moduleFile, exportKind, isFromPackageJson, checker) => {
109            if (importingFile !== usableByFileName) {
110                cache.clear();
111                usableByFileName = importingFile;
112            }
113
114            let packageName;
115            if (moduleFile) {
116                const nodeModulesPathParts = getNodeModulePathParts(moduleFile.fileName, isOhpm(host.getCurrentProgram()?.getCompilerOptions().packageManagerType) ? ohModulesPathPart : nodeModulesPathPart);
117                if (nodeModulesPathParts) {
118                    const { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex } = nodeModulesPathParts;
119                    packageName = unmangleScopedPackageName(getPackageNameFromTypesPackageName(moduleFile.fileName.substring(topLevelPackageNameIndex + 1, packageRootIndex)));
120                    if (startsWith(importingFile, moduleFile.path.substring(0, topLevelNodeModulesIndex))) {
121                        const prevDeepestNodeModulesPath = packages.get(packageName);
122                        const nodeModulesPath = moduleFile.fileName.substring(0, topLevelPackageNameIndex + 1);
123                        if (prevDeepestNodeModulesPath) {
124                            const prevDeepestNodeModulesIndex = prevDeepestNodeModulesPath.indexOf(nodeModulesPathPart);
125                            if (topLevelNodeModulesIndex > prevDeepestNodeModulesIndex) {
126                                packages.set(packageName, nodeModulesPath);
127                            }
128                        }
129                        else {
130                            packages.set(packageName, nodeModulesPath);
131                        }
132                    }
133                }
134            }
135
136            const isDefault = exportKind === ExportKind.Default;
137            const namedSymbol = isDefault && getLocalSymbolForExportDefault(symbol) || symbol;
138            // 1. A named export must be imported by its key in `moduleSymbol.exports` or `moduleSymbol.members`.
139            // 2. A re-export merged with an export from a module augmentation can result in `symbol`
140            //    being an external module symbol; the name it is re-exported by will be `symbolTableKey`
141            //    (which comes from the keys of `moduleSymbol.exports`.)
142            // 3. Otherwise, we have a default/namespace import that can be imported by any name, and
143            //    `symbolTableKey` will be something undesirable like `export=` or `default`, so we try to
144            //    get a better name.
145            const names = exportKind === ExportKind.Named || isExternalModuleSymbol(namedSymbol)
146                ? unescapeLeadingUnderscores(symbolTableKey)
147                : getNamesForExportedSymbol(namedSymbol, /*scriptTarget*/ undefined);
148
149            const symbolName = typeof names === "string" ? names : names[0];
150            const capitalizedSymbolName = typeof names === "string" ? undefined : names[1];
151
152            const moduleName = stripQuotes(moduleSymbol.name);
153            const id = exportInfoId++;
154            const target = skipAlias(symbol, checker);
155            const storedSymbol = symbol.flags & SymbolFlags.Transient ? undefined : symbol;
156            const storedModuleSymbol = moduleSymbol.flags & SymbolFlags.Transient ? undefined : moduleSymbol;
157            if (!storedSymbol || !storedModuleSymbol) symbols.set(id, [symbol, moduleSymbol]);
158
159            exportInfo.add(key(symbolName, symbol, isExternalModuleNameRelative(moduleName) ? undefined : moduleName, checker), {
160                id,
161                symbolTableKey,
162                symbolName,
163                capitalizedSymbolName,
164                moduleName,
165                moduleFile,
166                moduleFileName: moduleFile?.fileName,
167                packageName,
168                exportKind,
169                targetFlags: target.flags,
170                isFromPackageJson,
171                symbol: storedSymbol,
172                moduleSymbol: storedModuleSymbol,
173            });
174        },
175        get: (importingFile, key) => {
176            if (importingFile !== usableByFileName) return;
177            const result = exportInfo.get(key);
178            return result?.map(rehydrateCachedInfo);
179        },
180        search: (importingFile, preferCapitalized, matches, action) => {
181            if (importingFile !== usableByFileName) return;
182            return forEachEntry(exportInfo, (info, key) => {
183                const { symbolName, ambientModuleName } = parseKey(key);
184                const name = preferCapitalized && info[0].capitalizedSymbolName || symbolName;
185                if (matches(name, info[0].targetFlags)) {
186                    const rehydrated = info.map(rehydrateCachedInfo);
187                    const filtered = rehydrated.filter((r, i) => isNotShadowedByDeeperNodeModulesPackage(r, info[i].packageName));
188                    if (filtered.length) {
189                        const res = action(filtered, name, !!ambientModuleName, key);
190                        if (res !== undefined) return res;
191                    }
192                }
193            });
194        },
195        releaseSymbols: () => {
196            symbols.clear();
197        },
198        onFileChanged: (oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean) => {
199            if (fileIsGlobalOnly(oldSourceFile) && fileIsGlobalOnly(newSourceFile)) {
200                // File is purely global; doesn't affect export map
201                return false;
202            }
203            if (
204                usableByFileName && usableByFileName !== newSourceFile.path ||
205                // If ATA is enabled, auto-imports uses existing imports to guess whether you want auto-imports from node.
206                // Adding or removing imports from node could change the outcome of that guess, so could change the suggestions list.
207                typeAcquisitionEnabled && consumesNodeCoreModules(oldSourceFile) !== consumesNodeCoreModules(newSourceFile) ||
208                // Module agumentation and ambient module changes can add or remove exports available to be auto-imported.
209                // Changes elsewhere in the file can change the *type* of an export in a module augmentation,
210                // but type info is gathered in getCompletionEntryDetails, which doesn’t use the cache.
211                !arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations) ||
212                !ambientModuleDeclarationsAreEqual(oldSourceFile, newSourceFile)
213            ) {
214                cache.clear();
215                return true;
216            }
217            usableByFileName = newSourceFile.path;
218            return false;
219        },
220    };
221    if (Debug.isDebugging) {
222        Object.defineProperty(cache, "__cache", { get: () => exportInfo });
223    }
224    return cache;
225
226    function rehydrateCachedInfo(info: CachedSymbolExportInfo): SymbolExportInfo {
227        if (info.symbol && info.moduleSymbol) return info as SymbolExportInfo;
228        const { id, exportKind, targetFlags, isFromPackageJson, moduleFileName } = info;
229        const [cachedSymbol, cachedModuleSymbol] = symbols.get(id) || emptyArray;
230        if (cachedSymbol && cachedModuleSymbol) {
231            return {
232                symbol: cachedSymbol,
233                moduleSymbol: cachedModuleSymbol,
234                moduleFileName,
235                exportKind,
236                targetFlags,
237                isFromPackageJson,
238            };
239        }
240        const checker = (isFromPackageJson
241            ? host.getPackageJsonAutoImportProvider()!
242            : host.getCurrentProgram()!).getTypeChecker();
243        const moduleSymbol = info.moduleSymbol || cachedModuleSymbol || Debug.checkDefined(info.moduleFile
244            ? checker.getMergedSymbol(info.moduleFile.symbol)
245            : checker.tryFindAmbientModule(info.moduleName));
246        const symbol = info.symbol || cachedSymbol || Debug.checkDefined(exportKind === ExportKind.ExportEquals
247            ? checker.resolveExternalModuleSymbol(moduleSymbol)
248            : checker.tryGetMemberInModuleExportsAndProperties(unescapeLeadingUnderscores(info.symbolTableKey), moduleSymbol),
249            `Could not find symbol '${info.symbolName}' by key '${info.symbolTableKey}' in module ${moduleSymbol.name}`);
250        symbols.set(id, [symbol, moduleSymbol]);
251        return {
252            symbol,
253            moduleSymbol,
254            moduleFileName,
255            exportKind,
256            targetFlags,
257            isFromPackageJson,
258        };
259    }
260
261    function key(importedName: string, symbol: Symbol, ambientModuleName: string | undefined, checker: TypeChecker): string {
262        const moduleKey = ambientModuleName || "";
263        return `${importedName}|${getSymbolId(skipAlias(symbol, checker))}|${moduleKey}`;
264    }
265
266    function parseKey(key: string) {
267        const symbolName = key.substring(0, key.indexOf("|"));
268        const moduleKey = key.substring(key.lastIndexOf("|") + 1);
269        const ambientModuleName = moduleKey === "" ? undefined : moduleKey;
270        return { symbolName, ambientModuleName };
271    }
272
273    function fileIsGlobalOnly(file: SourceFile) {
274        return !file.commonJsModuleIndicator && !file.externalModuleIndicator && !file.moduleAugmentations && !file.ambientModuleNames;
275    }
276
277    function ambientModuleDeclarationsAreEqual(oldSourceFile: SourceFile, newSourceFile: SourceFile) {
278        if (!arrayIsEqualTo(oldSourceFile.ambientModuleNames, newSourceFile.ambientModuleNames)) {
279            return false;
280        }
281        let oldFileStatementIndex = -1;
282        let newFileStatementIndex = -1;
283        for (const ambientModuleName of newSourceFile.ambientModuleNames) {
284            const isMatchingModuleDeclaration = (node: Statement) => isNonGlobalAmbientModule(node) && node.name.text === ambientModuleName;
285            oldFileStatementIndex = findIndex(oldSourceFile.statements, isMatchingModuleDeclaration, oldFileStatementIndex + 1);
286            newFileStatementIndex = findIndex(newSourceFile.statements, isMatchingModuleDeclaration, newFileStatementIndex + 1);
287            if (oldSourceFile.statements[oldFileStatementIndex] !== newSourceFile.statements[newFileStatementIndex]) {
288                return false;
289            }
290        }
291        return true;
292    }
293
294    function isNotShadowedByDeeperNodeModulesPackage(info: SymbolExportInfo, packageName: string | undefined) {
295        if (!packageName || !info.moduleFileName) return true;
296        const typingsCacheLocation = host.getGlobalTypingsCacheLocation();
297        if (typingsCacheLocation && startsWith(info.moduleFileName, typingsCacheLocation)) return true;
298        const packageDeepestNodeModulesPath = packages.get(packageName);
299        return !packageDeepestNodeModulesPath || startsWith(info.moduleFileName, packageDeepestNodeModulesPath);
300    }
301}
302
303/** @internal */
304export function isImportableFile(
305    program: Program,
306    from: SourceFile,
307    to: SourceFile,
308    preferences: UserPreferences,
309    packageJsonFilter: PackageJsonImportFilter | undefined,
310    moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost,
311    moduleSpecifierCache: ModuleSpecifierCache | undefined,
312): boolean {
313    if (from === to) return false;
314    const cachedResult = moduleSpecifierCache?.get(from.path, to.path, preferences, {});
315    if (cachedResult?.isBlockedByPackageJsonDependencies !== undefined) {
316        return !cachedResult.isBlockedByPackageJsonDependencies;
317    }
318
319    const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost);
320    const globalTypingsCache = moduleSpecifierResolutionHost.getGlobalTypingsCacheLocation?.();
321    const hasImportablePath = !!moduleSpecifiers.forEachFileNameOfModule(
322        from.fileName,
323        to.fileName,
324        moduleSpecifierResolutionHost,
325        /*preferSymlinks*/ false,
326        toPath => {
327            const toFile = program.getSourceFile(toPath);
328            // Determine to import using toPath only if toPath is what we were looking at
329            // or there doesnt exist the file in the program by the symlink
330            return (toFile === to || !toFile) &&
331                isImportablePath(from.fileName, toPath, getCanonicalFileName, globalTypingsCache, program.getCompilerOptions().packageManagerType);
332        }
333    );
334
335    if (packageJsonFilter) {
336        const isAutoImportable = hasImportablePath && packageJsonFilter.allowsImportingSourceFile(to, moduleSpecifierResolutionHost);
337        moduleSpecifierCache?.setBlockedByPackageJsonDependencies(from.path, to.path, preferences, {}, !isAutoImportable);
338        return isAutoImportable;
339    }
340
341    return hasImportablePath;
342}
343
344/**
345 * Don't include something from a `node_modules` that isn't actually reachable by a global import.
346 * A relative import to node_modules is usually a bad idea.
347 */
348function isImportablePath(fromPath: string, toPath: string, getCanonicalFileName: GetCanonicalFileName, globalCachePath?: string, packageManagerType?: string): boolean {
349    // If it's in a `node_modules` but is not reachable from here via a global import, don't bother.
350    const toNodeModules = forEachAncestorDirectory(toPath, ancestor => (getBaseFileName(ancestor) === getModuleByPMType(packageManagerType)) ? ancestor : undefined);
351    const toNodeModulesParent = toNodeModules && getDirectoryPath(getCanonicalFileName(toNodeModules));
352    return toNodeModulesParent === undefined
353        || startsWith(getCanonicalFileName(fromPath), toNodeModulesParent)
354        || (!!globalCachePath && startsWith(getCanonicalFileName(globalCachePath), toNodeModulesParent));
355}
356
357/** @internal */
358export function forEachExternalModuleToImportFrom(
359    program: Program,
360    host: LanguageServiceHost,
361    preferences: UserPreferences,
362    useAutoImportProvider: boolean,
363    cb: (module: Symbol, moduleFile: SourceFile | undefined, program: Program, isFromPackageJson: boolean) => void,
364) {
365    const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
366    const excludePatterns = preferences.autoImportFileExcludePatterns && mapDefined(preferences.autoImportFileExcludePatterns, spec => {
367        // The client is expected to send rooted path specs since we don't know
368        // what directory a relative path is relative to.
369        const pattern = getPatternFromSpec(spec, "", "exclude");
370        return pattern ? getRegexFromPattern(pattern, useCaseSensitiveFileNames) : undefined;
371    });
372
373    forEachExternalModule(program.getTypeChecker(), program.getSourceFiles(), excludePatterns, (module, file) => cb(module, file, program, /*isFromPackageJson*/ false));
374    const autoImportProvider = useAutoImportProvider && host.getPackageJsonAutoImportProvider?.();
375    if (autoImportProvider) {
376        const start = timestamp();
377        forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), excludePatterns, (module, file) => cb(module, file, autoImportProvider, /*isFromPackageJson*/ true));
378        host.log?.(`forEachExternalModuleToImportFrom autoImportProvider: ${timestamp() - start}`);
379    }
380}
381
382function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly SourceFile[], excludePatterns: readonly RegExp[] | undefined, cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) {
383    const isExcluded = (fileName: string) => excludePatterns?.some(p => p.test(fileName));
384    for (const ambient of checker.getAmbientModules()) {
385        if (!stringContains(ambient.name, "*") && !(excludePatterns && ambient.declarations?.every(d => isExcluded(d.getSourceFile().fileName)))) {
386            cb(ambient, /*sourceFile*/ undefined);
387        }
388    }
389    for (const sourceFile of allSourceFiles) {
390        if (isExternalOrCommonJsModule(sourceFile) && !isExcluded(sourceFile.fileName)) {
391            cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile);
392        }
393    }
394}
395
396/** @internal */
397export function getExportInfoMap(importingFile: SourceFile, host: LanguageServiceHost, program: Program, preferences: UserPreferences, cancellationToken: CancellationToken | undefined): ExportInfoMap {
398    const start = timestamp();
399    // Pulling the AutoImportProvider project will trigger its updateGraph if pending,
400    // which will invalidate the export map cache if things change, so pull it before
401    // checking the cache.
402    host.getPackageJsonAutoImportProvider?.();
403    const cache = host.getCachedExportInfoMap?.() || createCacheableExportInfoMap({
404        getCurrentProgram: () => program,
405        getPackageJsonAutoImportProvider: () => host.getPackageJsonAutoImportProvider?.(),
406        getGlobalTypingsCacheLocation: () => host.getGlobalTypingsCacheLocation?.(),
407    });
408
409    if (cache.isUsableByFile(importingFile.path)) {
410        host.log?.("getExportInfoMap: cache hit");
411        return cache;
412    }
413
414    host.log?.("getExportInfoMap: cache miss or empty; calculating new results");
415    const compilerOptions = program.getCompilerOptions();
416    let moduleCount = 0;
417    try {
418        forEachExternalModuleToImportFrom(program, host, preferences, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => {
419            if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested();
420            const seenExports = new Map<__String, true>();
421            const checker = program.getTypeChecker();
422            const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
423            // Note: I think we shouldn't actually see resolved module symbols here, but weird merges
424            // can cause it to happen: see 'completionsImport_mergedReExport.ts'
425            if (defaultInfo && isImportableSymbol(defaultInfo.symbol, checker)) {
426                cache.add(
427                    importingFile.path,
428                    defaultInfo.symbol,
429                    defaultInfo.exportKind === ExportKind.Default ? InternalSymbolName.Default : InternalSymbolName.ExportEquals,
430                    moduleSymbol,
431                    moduleFile,
432                    defaultInfo.exportKind,
433                    isFromPackageJson,
434                    checker);
435            }
436            checker.forEachExportAndPropertyOfModule(moduleSymbol, (exported, key) => {
437                if (exported !== defaultInfo?.symbol && isImportableSymbol(exported, checker) && addToSeen(seenExports, key)) {
438                    cache.add(
439                        importingFile.path,
440                        exported,
441                        key,
442                        moduleSymbol,
443                        moduleFile,
444                        ExportKind.Named,
445                        isFromPackageJson,
446                        checker);
447                }
448            });
449        });
450    }
451    catch (err) {
452        // Ensure cache is reset if operation is cancelled
453        cache.clear();
454        throw err;
455    }
456
457    host.log?.(`getExportInfoMap: done in ${timestamp() - start} ms`);
458    return cache;
459}
460
461/** @internal */
462export function getDefaultLikeExportInfo(moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions) {
463    const exported = getDefaultLikeExportWorker(moduleSymbol, checker);
464    if (!exported) return undefined;
465    const { symbol, exportKind } = exported;
466    const info = getDefaultExportInfoWorker(symbol, checker, compilerOptions);
467    return info && { symbol, exportKind, ...info };
468}
469
470function isImportableSymbol(symbol: Symbol, checker: TypeChecker) {
471    return !checker.isUndefinedSymbol(symbol) && !checker.isUnknownSymbol(symbol) && !isKnownSymbol(symbol) && !isPrivateIdentifierSymbol(symbol);
472}
473
474function getDefaultLikeExportWorker(moduleSymbol: Symbol, checker: TypeChecker): { readonly symbol: Symbol, readonly exportKind: ExportKind } | undefined {
475    const exportEquals = checker.resolveExternalModuleSymbol(moduleSymbol);
476    if (exportEquals !== moduleSymbol) return { symbol: exportEquals, exportKind: ExportKind.ExportEquals };
477    const defaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol);
478    if (defaultExport) return { symbol: defaultExport, exportKind: ExportKind.Default };
479}
480
481function getDefaultExportInfoWorker(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly symbolForMeaning: Symbol, readonly name: string } | undefined {
482    const localSymbol = getLocalSymbolForExportDefault(defaultExport);
483    if (localSymbol) return { symbolForMeaning: localSymbol, name: localSymbol.name };
484
485    const name = getNameForExportDefault(defaultExport);
486    if (name !== undefined) return { symbolForMeaning: defaultExport, name };
487
488    if (defaultExport.flags & SymbolFlags.Alias) {
489        const aliased = checker.getImmediateAliasedSymbol(defaultExport);
490        if (aliased && aliased.parent) {
491            // - `aliased` will be undefined if the module is exporting an unresolvable name,
492            //    but we can still offer completions for it.
493            // - `aliased.parent` will be undefined if the module is exporting `globalThis.something`,
494            //    or another expression that resolves to a global.
495            return getDefaultExportInfoWorker(aliased, checker, compilerOptions);
496        }
497    }
498
499    if (defaultExport.escapedName !== InternalSymbolName.Default &&
500        defaultExport.escapedName !== InternalSymbolName.ExportEquals) {
501        return { symbolForMeaning: defaultExport, name: defaultExport.getName() };
502    }
503    return { symbolForMeaning: defaultExport, name: getNameForExportedSymbol(defaultExport, compilerOptions.target) };
504}
505
506function getNameForExportDefault(symbol: Symbol): string | undefined {
507    return symbol.declarations && firstDefined(symbol.declarations, declaration => {
508        if (isExportAssignment(declaration)) {
509            return tryCast(skipOuterExpressions(declaration.expression), isIdentifier)?.text;
510        }
511        else if (isExportSpecifier(declaration)) {
512            Debug.assert(declaration.name.text === InternalSymbolName.Default, "Expected the specifier to be a default export");
513            return declaration.propertyName && declaration.propertyName.text;
514        }
515    });
516}
517