mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-05 16:54:54 -05:00
For import completion of default import, convert module name to identifier (#19875)
* For import completion of default import, convert module name to identifier * Suggestions from code review
This commit is contained in:
@@ -699,9 +699,10 @@ namespace ts.codefix {
|
||||
const defaultExport = checker.tryGetMemberInModuleExports("default", moduleSymbol);
|
||||
if (defaultExport) {
|
||||
const localSymbol = getLocalSymbolForExportDefault(defaultExport);
|
||||
if (localSymbol && localSymbol.escapedName === symbolName && checkSymbolHasMeaning(localSymbol, currentTokenMeaning)) {
|
||||
if ((localSymbol && localSymbol.escapedName === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, context.compilerOptions.target) === symbolName)
|
||||
&& checkSymbolHasMeaning(localSymbol || defaultExport, currentTokenMeaning)) {
|
||||
// check if this symbol is already used
|
||||
const symbolId = getUniqueSymbolId(localSymbol, checker);
|
||||
const symbolId = getUniqueSymbolId(localSymbol || defaultExport, checker);
|
||||
symbolIdActionMap.addActions(symbolId, getCodeActionForImport(moduleSymbol, { ...context, kind: ImportKind.Default }));
|
||||
}
|
||||
}
|
||||
@@ -731,4 +732,35 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function moduleSymbolToValidIdentifier(moduleSymbol: Symbol, target: ScriptTarget): string {
|
||||
return moduleSpecifierToValidIdentifier(removeFileExtension(getBaseFileName(moduleSymbol.name)), target);
|
||||
}
|
||||
|
||||
function moduleSpecifierToValidIdentifier(moduleSpecifier: string, target: ScriptTarget): string {
|
||||
let res = "";
|
||||
let lastCharWasValid = true;
|
||||
const firstCharCode = moduleSpecifier.charCodeAt(0);
|
||||
if (isIdentifierStart(firstCharCode, target)) {
|
||||
res += String.fromCharCode(firstCharCode);
|
||||
}
|
||||
else {
|
||||
lastCharWasValid = false;
|
||||
}
|
||||
for (let i = 1; i < moduleSpecifier.length; i++) {
|
||||
const ch = moduleSpecifier.charCodeAt(i);
|
||||
const isValid = isIdentifierPart(ch, target);
|
||||
if (isValid) {
|
||||
let char = String.fromCharCode(ch);
|
||||
if (!lastCharWasValid) {
|
||||
char = char.toUpperCase();
|
||||
}
|
||||
res += char;
|
||||
}
|
||||
lastCharWasValid = isValid;
|
||||
}
|
||||
// Need `|| "_"` to ensure result isn't empty.
|
||||
const token = stringToToken(res);
|
||||
return token === undefined || !isNonContextualKeyword(token) ? res || "_" : `_${res}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace ts.Completions {
|
||||
return getStringLiteralCompletionEntries(sourceFile, position, typeChecker, compilerOptions, host, log);
|
||||
}
|
||||
|
||||
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, options);
|
||||
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, options, compilerOptions.target);
|
||||
if (!completionData) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -136,12 +136,12 @@ namespace ts.Completions {
|
||||
typeChecker: TypeChecker,
|
||||
target: ScriptTarget,
|
||||
allowStringLiteral: boolean,
|
||||
origin: SymbolOriginInfo,
|
||||
origin: SymbolOriginInfo | undefined,
|
||||
): CompletionEntry | undefined {
|
||||
// Try to get a valid display name for this symbol, if we could not find one, then ignore it.
|
||||
// We would like to only show things that can be added after a dot, so for instance numeric properties can
|
||||
// not be accessed with a dot (a.1 <- invalid)
|
||||
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, target, performCharacterChecks, allowStringLiteral);
|
||||
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, target, performCharacterChecks, allowStringLiteral, origin);
|
||||
if (!displayName) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -381,7 +381,7 @@ namespace ts.Completions {
|
||||
{ name, source }: CompletionEntryIdentifier,
|
||||
allSourceFiles: ReadonlyArray<SourceFile>,
|
||||
): { type: "symbol", symbol: Symbol, location: Node, symbolToOriginInfoMap: SymbolOriginInfoMap } | { type: "request", request: Request } | { type: "none" } {
|
||||
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, { includeExternalModuleExports: true });
|
||||
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, { includeExternalModuleExports: true }, compilerOptions.target);
|
||||
if (!completionData) {
|
||||
return { type: "none" };
|
||||
}
|
||||
@@ -395,12 +395,18 @@ namespace ts.Completions {
|
||||
// We don't need to perform character checks here because we're only comparing the
|
||||
// name against 'entryName' (which is known to be good), not building a new
|
||||
// completion entry.
|
||||
const symbol = find(symbols, s =>
|
||||
getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral) === name
|
||||
&& getSourceFromOrigin(symbolToOriginInfoMap[getSymbolId(s)]) === source);
|
||||
const symbol = find(symbols, s => {
|
||||
const origin = symbolToOriginInfoMap[getSymbolId(s)];
|
||||
return getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral, origin) === name
|
||||
&& getSourceFromOrigin(origin) === source;
|
||||
});
|
||||
return symbol ? { type: "symbol", symbol, location, symbolToOriginInfoMap } : { type: "none" };
|
||||
}
|
||||
|
||||
function getSymbolName(symbol: Symbol, origin: SymbolOriginInfo | undefined, target: ScriptTarget): string {
|
||||
return origin && origin.isDefaultExport && symbol.name === "default" ? codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target) : symbol.name;
|
||||
}
|
||||
|
||||
export interface CompletionEntryIdentifier {
|
||||
name: string;
|
||||
source?: string;
|
||||
@@ -482,7 +488,7 @@ namespace ts.Completions {
|
||||
compilerOptions,
|
||||
sourceFile,
|
||||
formatContext,
|
||||
symbolName: symbol.name,
|
||||
symbolName: getSymbolName(symbol, symbolOriginInfo, compilerOptions.target),
|
||||
getCanonicalFileName: createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : false),
|
||||
symbolToken: undefined,
|
||||
kind: isDefaultExport ? codefix.ImportKind.Default : codefix.ImportKind.Named,
|
||||
@@ -523,6 +529,7 @@ namespace ts.Completions {
|
||||
position: number,
|
||||
allSourceFiles: ReadonlyArray<SourceFile>,
|
||||
options: GetCompletionsAtPositionOptions,
|
||||
target: ScriptTarget,
|
||||
): CompletionData | undefined {
|
||||
const isJavaScriptFile = isSourceFileJavaScript(sourceFile);
|
||||
|
||||
@@ -921,7 +928,7 @@ namespace ts.Completions {
|
||||
|
||||
symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings);
|
||||
if (options.includeExternalModuleExports) {
|
||||
getSymbolsFromOtherSourceFileExports(symbols, previousToken && isIdentifier(previousToken) ? previousToken.text : "");
|
||||
getSymbolsFromOtherSourceFileExports(symbols, previousToken && isIdentifier(previousToken) ? previousToken.text : "", target);
|
||||
}
|
||||
filterGlobalCompletion(symbols);
|
||||
|
||||
@@ -1003,7 +1010,7 @@ namespace ts.Completions {
|
||||
}
|
||||
}
|
||||
|
||||
function getSymbolsFromOtherSourceFileExports(symbols: Symbol[], tokenText: string): void {
|
||||
function getSymbolsFromOtherSourceFileExports(symbols: Symbol[], tokenText: string, target: ScriptTarget): void {
|
||||
const tokenTextLowerCase = tokenText.toLowerCase();
|
||||
|
||||
codefix.forEachExternalModule(typeChecker, allSourceFiles, moduleSymbol => {
|
||||
@@ -1020,6 +1027,9 @@ namespace ts.Completions {
|
||||
symbol = localSymbol;
|
||||
name = localSymbol.name;
|
||||
}
|
||||
else {
|
||||
name = codefix.moduleSymbolToValidIdentifier(moduleSymbol, target);
|
||||
}
|
||||
}
|
||||
|
||||
if (symbol.declarations && symbol.declarations.some(d => isExportSpecifier(d) && !!d.parent.parent.moduleSpecifier)) {
|
||||
@@ -1847,8 +1857,8 @@ namespace ts.Completions {
|
||||
*
|
||||
* @return undefined if the name is of external module
|
||||
*/
|
||||
function getCompletionEntryDisplayNameForSymbol(symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, allowStringLiteral: boolean): string | undefined {
|
||||
const name = symbol.name;
|
||||
function getCompletionEntryDisplayNameForSymbol(symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, allowStringLiteral: boolean, origin: SymbolOriginInfo | undefined): string | undefined {
|
||||
const name = getSymbolName(symbol, origin, target);
|
||||
if (!name) return undefined;
|
||||
|
||||
// First check of the displayName is not external module; if it is an external module, it is not valid entry
|
||||
|
||||
Reference in New Issue
Block a user