[DO NOT MERGE UNTIL 5.6] Fix re-exported defaults in ExportInfoMap (#58837)

This commit is contained in:
Andrew Branch 2024-06-12 15:43:20 -07:00 коммит произвёл GitHub
Родитель 46fe067a06
Коммит 1948e92e3a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 96 добавлений и 30 удалений

Просмотреть файл

@ -1187,7 +1187,7 @@ function getNewImportFixes(
const exportEquals = checker.resolveExternalModuleSymbol(exportInfo.moduleSymbol);
let namespacePrefix;
if (exportEquals !== exportInfo.moduleSymbol) {
namespacePrefix = forEachNameOfDefaultExport(exportEquals, checker, compilerOptions, /*preferCapitalizedNames*/ false, identity)!;
namespacePrefix = forEachNameOfDefaultExport(exportEquals, checker, getEmitScriptTarget(compilerOptions), identity)!;
}
namespacePrefix ||= moduleSymbolToValidIdentifier(
exportInfo.moduleSymbol,
@ -1544,7 +1544,7 @@ function getExportInfos(
if (
defaultInfo
&& symbolFlagsHaveMeaning(checker.getSymbolFlags(defaultInfo.symbol), currentTokenMeaning)
&& forEachNameOfDefaultExport(defaultInfo.symbol, checker, compilerOptions, isJsxTagName, name => name === symbolName)
&& forEachNameOfDefaultExport(defaultInfo.symbol, checker, getEmitScriptTarget(compilerOptions), (name, capitalizedName) => (isJsxTagName ? capitalizedName ?? name : name) === symbolName)
) {
addSymbol(moduleSymbol, sourceFile, defaultInfo.symbol, defaultInfo.exportKind, program, isFromPackageJson);
}

Просмотреть файл

@ -4,7 +4,6 @@ import {
append,
arrayIsEqualTo,
CancellationToken,
CompilerOptions,
consumesNodeCoreModules,
createMultiMap,
Debug,
@ -18,9 +17,7 @@ import {
GetCanonicalFileName,
getDefaultLikeExportNameFromDeclaration,
getDirectoryPath,
getEmitScriptTarget,
getLocalSymbolForExportDefault,
getNamesForExportedSymbol,
getNodeModulePathParts,
getPackageNameFromTypesPackageName,
getRegexFromPattern,
@ -46,6 +43,7 @@ import {
Path,
pathContainsNodeModules,
Program,
ScriptTarget,
skipAlias,
SourceFile,
startsWith,
@ -198,7 +196,7 @@ export function createCacheableExportInfoMap(host: CacheableExportInfoMapHost):
// get a better name.
const names = exportKind === ExportKind.Named || isExternalModuleSymbol(namedSymbol)
? unescapeLeadingUnderscores(symbolTableKey)
: getNamesForExportedSymbol(namedSymbol, /*scriptTarget*/ undefined);
: getNamesForExportedSymbol(namedSymbol, checker, /*scriptTarget*/ undefined);
const symbolName = typeof names === "string" ? names : names[0];
const capitalizedSymbolName = typeof names === "string" ? undefined : names[1];
@ -558,12 +556,21 @@ function isImportableSymbol(symbol: Symbol, checker: TypeChecker) {
return !checker.isUndefinedSymbol(symbol) && !checker.isUnknownSymbol(symbol) && !isKnownSymbol(symbol) && !isPrivateIdentifierSymbol(symbol);
}
function getNamesForExportedSymbol(defaultExport: Symbol, checker: TypeChecker, scriptTarget: ScriptTarget | undefined) {
let names: string | string[] | undefined;
forEachNameOfDefaultExport(defaultExport, checker, scriptTarget, (name, capitalizedName) => {
names = capitalizedName ? [name, capitalizedName] : name;
return true;
});
return Debug.checkDefined(names);
}
/**
* @internal
* May call `cb` multiple times with the same name.
* Terminates when `cb` returns a truthy value.
*/
export function forEachNameOfDefaultExport<T>(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions, preferCapitalizedNames: boolean, cb: (name: string) => T | undefined): T | undefined {
export function forEachNameOfDefaultExport<T>(defaultExport: Symbol, checker: TypeChecker, scriptTarget: ScriptTarget | undefined, cb: (name: string, capitalizedName?: string) => T | undefined): T | undefined {
let chain: Symbol[] | undefined;
let current: Symbol | undefined = defaultExport;
@ -588,7 +595,10 @@ export function forEachNameOfDefaultExport<T>(defaultExport: Symbol, checker: Ty
for (const symbol of chain ?? emptyArray) {
if (symbol.parent && isExternalModuleSymbol(symbol.parent)) {
const final = cb(moduleSymbolToValidIdentifier(symbol.parent, getEmitScriptTarget(compilerOptions), preferCapitalizedNames));
const final = cb(
moduleSymbolToValidIdentifier(symbol.parent, scriptTarget, /*forceCapitalize*/ false),
moduleSymbolToValidIdentifier(symbol.parent, scriptTarget, /*forceCapitalize*/ true),
);
if (final) return final;
}
}

Просмотреть файл

@ -4024,22 +4024,13 @@ export function firstOrOnly<T>(valueOrArray: T | readonly T[]): T {
return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray;
}
/** @internal */
export function getNamesForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined): string | [lowercase: string, capitalized: string] {
if (needsNameFromDeclaration(symbol)) {
const fromDeclaration = getDefaultLikeExportNameFromDeclaration(symbol);
if (fromDeclaration) return fromDeclaration;
const fileNameCase = moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*forceCapitalize*/ false);
const capitalized = moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*forceCapitalize*/ true);
if (fileNameCase === capitalized) return fileNameCase;
return [fileNameCase, capitalized];
}
return symbol.name;
}
/** @internal */
/**
* If a type checker and multiple files are available, consider using `forEachNameOfDefaultExport`
* instead, which searches for names of re-exported defaults/namespaces in target files.
* @internal
*/
export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined, preferCapitalized?: boolean) {
if (needsNameFromDeclaration(symbol)) {
if (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default) {
// Names for default exports:
// - export default foo => foo
// - export { foo as default } => foo
@ -4050,11 +4041,11 @@ export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTar
return symbol.name;
}
function needsNameFromDeclaration(symbol: Symbol) {
return !(symbol.flags & SymbolFlags.Transient) && (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default);
}
/** @internal */
/**
* If a type checker and multiple files are available, consider using `forEachNameOfDefaultExport`
* instead, which searches for names of re-exported defaults/namespaces in target files.
* @internal
*/
export function getDefaultLikeExportNameFromDeclaration(symbol: Symbol): string | undefined {
return firstDefined(symbol.declarations, d => {
// "export default" in this case. See `ExportAssignment`for more details.

Просмотреть файл

@ -380,7 +380,7 @@ Info seq [hh:mm:ss:mss] getCompletionData: Is inside comment: *
Info seq [hh:mm:ss:mss] getCompletionData: Get previous token: *
Info seq [hh:mm:ss:mss] getExportInfoMap: cache miss or empty; calculating new results
Info seq [hh:mm:ss:mss] getExportInfoMap: done in * ms
Info seq [hh:mm:ss:mss] collectAutoImports: resolved 0 module specifiers, plus 0 ambient and 3 from cache
Info seq [hh:mm:ss:mss] collectAutoImports: resolved 0 module specifiers, plus 0 ambient and 4 from cache
Info seq [hh:mm:ss:mss] collectAutoImports: response is incomplete
Info seq [hh:mm:ss:mss] collectAutoImports: *
Info seq [hh:mm:ss:mss] getCompletionData: Semantic work: *
@ -1053,6 +1053,19 @@ Info seq [hh:mm:ss:mss] response:
"fileName": "/third_party/marked/src/defaults.js"
}
},
{
"name": "defaults",
"kind": "property",
"kindModifiers": "",
"sortText": "16",
"hasAction": true,
"source": "/third_party/marked/src/defaults",
"data": {
"exportName": "export=",
"exportMapKey": "8 * defaults ",
"fileName": "/third_party/marked/src/defaults.js"
}
},
{
"name": "defaults",
"kind": "alias",
@ -1257,7 +1270,7 @@ Info seq [hh:mm:ss:mss] getCompletionData: Get current token: *
Info seq [hh:mm:ss:mss] getCompletionData: Is inside comment: *
Info seq [hh:mm:ss:mss] getCompletionData: Get previous token: *
Info seq [hh:mm:ss:mss] getExportInfoMap: cache hit
Info seq [hh:mm:ss:mss] collectAutoImports: resolved 0 module specifiers, plus 0 ambient and 3 from cache
Info seq [hh:mm:ss:mss] collectAutoImports: resolved 0 module specifiers, plus 0 ambient and 4 from cache
Info seq [hh:mm:ss:mss] collectAutoImports: response is incomplete
Info seq [hh:mm:ss:mss] collectAutoImports: *
Info seq [hh:mm:ss:mss] getCompletionData: Semantic work: *
@ -1943,6 +1956,19 @@ Info seq [hh:mm:ss:mss] response:
"fileName": "/third_party/marked/src/defaults.js"
}
},
{
"name": "defaults",
"kind": "property",
"kindModifiers": "",
"sortText": "16",
"hasAction": true,
"source": "/third_party/marked/src/defaults",
"data": {
"exportName": "export=",
"exportMapKey": "8 * defaults ",
"fileName": "/third_party/marked/src/defaults.js"
}
},
{
"name": "defaults",
"kind": "alias",

Просмотреть файл

@ -0,0 +1,39 @@
/// <reference path="fourslash.ts" />
// @module: preserve
// @checkJs: true
// @Filename: /node_modules/example/package.json
//// { "name": "example", "version": "1.0.0", "main": "dist/index.js" }
// @Filename: /node_modules/example/dist/nested/module.d.ts
//// declare const defaultExport: () => void;
//// declare const namedExport: () => void;
////
//// export default defaultExport;
//// export { namedExport };
// @Filename: /node_modules/example/dist/index.d.ts
//// export { default, namedExport } from "./nested/module";
// @Filename: /index.mjs
//// import { namedExport } from "example";
//// defaultExp/**/
verify.completions({
marker: "",
exact: completion.globalsInJsPlus([
"namedExport",
{
name: "defaultExport",
source: "example",
sourceDisplay: "example",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions
},
]),
preferences: {
includeCompletionsForModuleExports: true,
allowIncompleteCompletions: true,
},
});