Skip to content

Commit 1948e92

Browse files
authored
[DO NOT MERGE UNTIL 5.6] Fix re-exported defaults in ExportInfoMap (microsoft#58837)
1 parent 46fe067 commit 1948e92

File tree

5 files changed

+96
-30
lines changed

5 files changed

+96
-30
lines changed

src/services/codefixes/importFixes.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1187,7 +1187,7 @@ function getNewImportFixes(
11871187
const exportEquals = checker.resolveExternalModuleSymbol(exportInfo.moduleSymbol);
11881188
let namespacePrefix;
11891189
if (exportEquals !== exportInfo.moduleSymbol) {
1190-
namespacePrefix = forEachNameOfDefaultExport(exportEquals, checker, compilerOptions, /*preferCapitalizedNames*/ false, identity)!;
1190+
namespacePrefix = forEachNameOfDefaultExport(exportEquals, checker, getEmitScriptTarget(compilerOptions), identity)!;
11911191
}
11921192
namespacePrefix ||= moduleSymbolToValidIdentifier(
11931193
exportInfo.moduleSymbol,
@@ -1544,7 +1544,7 @@ function getExportInfos(
15441544
if (
15451545
defaultInfo
15461546
&& symbolFlagsHaveMeaning(checker.getSymbolFlags(defaultInfo.symbol), currentTokenMeaning)
1547-
&& forEachNameOfDefaultExport(defaultInfo.symbol, checker, compilerOptions, isJsxTagName, name => name === symbolName)
1547+
&& forEachNameOfDefaultExport(defaultInfo.symbol, checker, getEmitScriptTarget(compilerOptions), (name, capitalizedName) => (isJsxTagName ? capitalizedName ?? name : name) === symbolName)
15481548
) {
15491549
addSymbol(moduleSymbol, sourceFile, defaultInfo.symbol, defaultInfo.exportKind, program, isFromPackageJson);
15501550
}

src/services/exportInfoMap.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
append,
55
arrayIsEqualTo,
66
CancellationToken,
7-
CompilerOptions,
87
consumesNodeCoreModules,
98
createMultiMap,
109
Debug,
@@ -18,9 +17,7 @@ import {
1817
GetCanonicalFileName,
1918
getDefaultLikeExportNameFromDeclaration,
2019
getDirectoryPath,
21-
getEmitScriptTarget,
2220
getLocalSymbolForExportDefault,
23-
getNamesForExportedSymbol,
2421
getNodeModulePathParts,
2522
getPackageNameFromTypesPackageName,
2623
getRegexFromPattern,
@@ -46,6 +43,7 @@ import {
4643
Path,
4744
pathContainsNodeModules,
4845
Program,
46+
ScriptTarget,
4947
skipAlias,
5048
SourceFile,
5149
startsWith,
@@ -198,7 +196,7 @@ export function createCacheableExportInfoMap(host: CacheableExportInfoMapHost):
198196
// get a better name.
199197
const names = exportKind === ExportKind.Named || isExternalModuleSymbol(namedSymbol)
200198
? unescapeLeadingUnderscores(symbolTableKey)
201-
: getNamesForExportedSymbol(namedSymbol, /*scriptTarget*/ undefined);
199+
: getNamesForExportedSymbol(namedSymbol, checker, /*scriptTarget*/ undefined);
202200

203201
const symbolName = typeof names === "string" ? names : names[0];
204202
const capitalizedSymbolName = typeof names === "string" ? undefined : names[1];
@@ -558,12 +556,21 @@ function isImportableSymbol(symbol: Symbol, checker: TypeChecker) {
558556
return !checker.isUndefinedSymbol(symbol) && !checker.isUnknownSymbol(symbol) && !isKnownSymbol(symbol) && !isPrivateIdentifierSymbol(symbol);
559557
}
560558

559+
function getNamesForExportedSymbol(defaultExport: Symbol, checker: TypeChecker, scriptTarget: ScriptTarget | undefined) {
560+
let names: string | string[] | undefined;
561+
forEachNameOfDefaultExport(defaultExport, checker, scriptTarget, (name, capitalizedName) => {
562+
names = capitalizedName ? [name, capitalizedName] : name;
563+
return true;
564+
});
565+
return Debug.checkDefined(names);
566+
}
567+
561568
/**
562569
* @internal
563570
* May call `cb` multiple times with the same name.
564571
* Terminates when `cb` returns a truthy value.
565572
*/
566-
export function forEachNameOfDefaultExport<T>(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions, preferCapitalizedNames: boolean, cb: (name: string) => T | undefined): T | undefined {
573+
export function forEachNameOfDefaultExport<T>(defaultExport: Symbol, checker: TypeChecker, scriptTarget: ScriptTarget | undefined, cb: (name: string, capitalizedName?: string) => T | undefined): T | undefined {
567574
let chain: Symbol[] | undefined;
568575
let current: Symbol | undefined = defaultExport;
569576

@@ -588,7 +595,10 @@ export function forEachNameOfDefaultExport<T>(defaultExport: Symbol, checker: Ty
588595

589596
for (const symbol of chain ?? emptyArray) {
590597
if (symbol.parent && isExternalModuleSymbol(symbol.parent)) {
591-
const final = cb(moduleSymbolToValidIdentifier(symbol.parent, getEmitScriptTarget(compilerOptions), preferCapitalizedNames));
598+
const final = cb(
599+
moduleSymbolToValidIdentifier(symbol.parent, scriptTarget, /*forceCapitalize*/ false),
600+
moduleSymbolToValidIdentifier(symbol.parent, scriptTarget, /*forceCapitalize*/ true),
601+
);
592602
if (final) return final;
593603
}
594604
}

src/services/utilities.ts

+11-20
Original file line numberDiff line numberDiff line change
@@ -4024,22 +4024,13 @@ export function firstOrOnly<T>(valueOrArray: T | readonly T[]): T {
40244024
return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray;
40254025
}
40264026

4027-
/** @internal */
4028-
export function getNamesForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined): string | [lowercase: string, capitalized: string] {
4029-
if (needsNameFromDeclaration(symbol)) {
4030-
const fromDeclaration = getDefaultLikeExportNameFromDeclaration(symbol);
4031-
if (fromDeclaration) return fromDeclaration;
4032-
const fileNameCase = moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*forceCapitalize*/ false);
4033-
const capitalized = moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*forceCapitalize*/ true);
4034-
if (fileNameCase === capitalized) return fileNameCase;
4035-
return [fileNameCase, capitalized];
4036-
}
4037-
return symbol.name;
4038-
}
4039-
4040-
/** @internal */
4027+
/**
4028+
* If a type checker and multiple files are available, consider using `forEachNameOfDefaultExport`
4029+
* instead, which searches for names of re-exported defaults/namespaces in target files.
4030+
* @internal
4031+
*/
40414032
export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined, preferCapitalized?: boolean) {
4042-
if (needsNameFromDeclaration(symbol)) {
4033+
if (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default) {
40434034
// Names for default exports:
40444035
// - export default foo => foo
40454036
// - export { foo as default } => foo
@@ -4050,11 +4041,11 @@ export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTar
40504041
return symbol.name;
40514042
}
40524043

4053-
function needsNameFromDeclaration(symbol: Symbol) {
4054-
return !(symbol.flags & SymbolFlags.Transient) && (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default);
4055-
}
4056-
4057-
/** @internal */
4044+
/**
4045+
* If a type checker and multiple files are available, consider using `forEachNameOfDefaultExport`
4046+
* instead, which searches for names of re-exported defaults/namespaces in target files.
4047+
* @internal
4048+
*/
40584049
export function getDefaultLikeExportNameFromDeclaration(symbol: Symbol): string | undefined {
40594050
return firstDefined(symbol.declarations, d => {
40604051
// "export default" in this case. See `ExportAssignment`for more details.

tests/baselines/reference/tsserver/fourslashServer/completionsImport_jsModuleExportsAssignment.js

+28-2
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ Info seq [hh:mm:ss:mss] getCompletionData: Is inside comment: *
380380
Info seq [hh:mm:ss:mss] getCompletionData: Get previous token: *
381381
Info seq [hh:mm:ss:mss] getExportInfoMap: cache miss or empty; calculating new results
382382
Info seq [hh:mm:ss:mss] getExportInfoMap: done in * ms
383-
Info seq [hh:mm:ss:mss] collectAutoImports: resolved 0 module specifiers, plus 0 ambient and 3 from cache
383+
Info seq [hh:mm:ss:mss] collectAutoImports: resolved 0 module specifiers, plus 0 ambient and 4 from cache
384384
Info seq [hh:mm:ss:mss] collectAutoImports: response is incomplete
385385
Info seq [hh:mm:ss:mss] collectAutoImports: *
386386
Info seq [hh:mm:ss:mss] getCompletionData: Semantic work: *
@@ -1053,6 +1053,19 @@ Info seq [hh:mm:ss:mss] response:
10531053
"fileName": "/third_party/marked/src/defaults.js"
10541054
}
10551055
},
1056+
{
1057+
"name": "defaults",
1058+
"kind": "property",
1059+
"kindModifiers": "",
1060+
"sortText": "16",
1061+
"hasAction": true,
1062+
"source": "/third_party/marked/src/defaults",
1063+
"data": {
1064+
"exportName": "export=",
1065+
"exportMapKey": "8 * defaults ",
1066+
"fileName": "/third_party/marked/src/defaults.js"
1067+
}
1068+
},
10561069
{
10571070
"name": "defaults",
10581071
"kind": "alias",
@@ -1257,7 +1270,7 @@ Info seq [hh:mm:ss:mss] getCompletionData: Get current token: *
12571270
Info seq [hh:mm:ss:mss] getCompletionData: Is inside comment: *
12581271
Info seq [hh:mm:ss:mss] getCompletionData: Get previous token: *
12591272
Info seq [hh:mm:ss:mss] getExportInfoMap: cache hit
1260-
Info seq [hh:mm:ss:mss] collectAutoImports: resolved 0 module specifiers, plus 0 ambient and 3 from cache
1273+
Info seq [hh:mm:ss:mss] collectAutoImports: resolved 0 module specifiers, plus 0 ambient and 4 from cache
12611274
Info seq [hh:mm:ss:mss] collectAutoImports: response is incomplete
12621275
Info seq [hh:mm:ss:mss] collectAutoImports: *
12631276
Info seq [hh:mm:ss:mss] getCompletionData: Semantic work: *
@@ -1943,6 +1956,19 @@ Info seq [hh:mm:ss:mss] response:
19431956
"fileName": "/third_party/marked/src/defaults.js"
19441957
}
19451958
},
1959+
{
1960+
"name": "defaults",
1961+
"kind": "property",
1962+
"kindModifiers": "",
1963+
"sortText": "16",
1964+
"hasAction": true,
1965+
"source": "/third_party/marked/src/defaults",
1966+
"data": {
1967+
"exportName": "export=",
1968+
"exportMapKey": "8 * defaults ",
1969+
"fileName": "/third_party/marked/src/defaults.js"
1970+
}
1971+
},
19461972
{
19471973
"name": "defaults",
19481974
"kind": "alias",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @module: preserve
4+
// @checkJs: true
5+
6+
// @Filename: /node_modules/example/package.json
7+
//// { "name": "example", "version": "1.0.0", "main": "dist/index.js" }
8+
9+
// @Filename: /node_modules/example/dist/nested/module.d.ts
10+
//// declare const defaultExport: () => void;
11+
//// declare const namedExport: () => void;
12+
////
13+
//// export default defaultExport;
14+
//// export { namedExport };
15+
16+
// @Filename: /node_modules/example/dist/index.d.ts
17+
//// export { default, namedExport } from "./nested/module";
18+
19+
// @Filename: /index.mjs
20+
//// import { namedExport } from "example";
21+
//// defaultExp/**/
22+
23+
verify.completions({
24+
marker: "",
25+
exact: completion.globalsInJsPlus([
26+
"namedExport",
27+
{
28+
name: "defaultExport",
29+
source: "example",
30+
sourceDisplay: "example",
31+
hasAction: true,
32+
sortText: completion.SortText.AutoImportSuggestions
33+
},
34+
]),
35+
preferences: {
36+
includeCompletionsForModuleExports: true,
37+
allowIncompleteCompletions: true,
38+
},
39+
});

0 commit comments

Comments
 (0)