Support a 'recommended' completion entry (#20020)

* Support a 'recommended' completion entry

* Code review

* Restore duplicate comments
This commit is contained in:
Andy
2017-12-01 13:00:01 -08:00
committed by GitHub
parent 973cb767c7
commit fd4d8ab96e
17 changed files with 235 additions and 62 deletions

View File

@@ -44,7 +44,7 @@ namespace ts.Completions {
return undefined;
}
const { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, request, keywordFilters, symbolToOriginInfoMap } = completionData;
const { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, request, keywordFilters, symbolToOriginInfoMap, recommendedCompletion } = completionData;
if (sourceFile.languageVariant === LanguageVariant.JSX &&
location && location.parent && location.parent.kind === SyntaxKind.JsxClosingElement) {
@@ -76,7 +76,7 @@ namespace ts.Completions {
const entries: CompletionEntry[] = [];
if (isSourceFileJavaScript(sourceFile)) {
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral, symbolToOriginInfoMap);
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral, recommendedCompletion, symbolToOriginInfoMap);
getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames, compilerOptions.target, entries);
}
else {
@@ -84,7 +84,7 @@ namespace ts.Completions {
return undefined;
}
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral, symbolToOriginInfoMap);
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral, recommendedCompletion, symbolToOriginInfoMap);
}
// TODO add filter for keyword based on type/value/namespace and also location
@@ -137,6 +137,7 @@ namespace ts.Completions {
target: ScriptTarget,
allowStringLiteral: boolean,
origin: SymbolOriginInfo | undefined,
recommendedCompletion: Symbol | 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
@@ -160,10 +161,20 @@ namespace ts.Completions {
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
sortText: "0",
source: getSourceFromOrigin(origin),
hasAction: origin === undefined ? undefined : true,
hasAction: trueOrUndefined(origin !== undefined),
isRecommended: trueOrUndefined(isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker)),
};
}
function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol, checker: TypeChecker): boolean {
return localSymbol === recommendedCompletion ||
!!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion;
}
function trueOrUndefined(b: boolean): true | undefined {
return b ? true : undefined;
}
function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined {
return origin && stripQuotes(origin.moduleSymbol.name);
}
@@ -177,6 +188,7 @@ namespace ts.Completions {
target: ScriptTarget,
log: Log,
allowStringLiteral: boolean,
recommendedCompletion?: Symbol,
symbolToOriginInfoMap?: SymbolOriginInfoMap,
): Map<true> {
const start = timestamp();
@@ -188,7 +200,7 @@ namespace ts.Completions {
if (symbols) {
for (const symbol of symbols) {
const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap[getSymbolId(symbol)] : undefined;
const entry = createCompletionEntry(symbol, location, performCharacterChecks, typeChecker, target, allowStringLiteral, origin);
const entry = createCompletionEntry(symbol, location, performCharacterChecks, typeChecker, target, allowStringLiteral, origin, recommendedCompletion);
if (!entry) {
continue;
}
@@ -540,9 +552,29 @@ namespace ts.Completions {
request?: Request;
keywordFilters: KeywordCompletionFilters;
symbolToOriginInfoMap: SymbolOriginInfoMap;
recommendedCompletion: Symbol | undefined;
}
type Request = { kind: "JsDocTagName" } | { kind: "JsDocTag" } | { kind: "JsDocParameterName", tag: JSDocParameterTag };
function getRecommendedCompletion(currentToken: Node, checker: TypeChecker/*, symbolToOriginInfoMap: SymbolOriginInfoMap*/): Symbol | undefined {
const ty = checker.getContextualType(currentToken as Expression);
const symbol = ty && ty.symbol;
// Don't include make a recommended completion for an abstract class
return symbol && (symbol.flags & SymbolFlags.Enum || symbol.flags & SymbolFlags.Class && !isAbstractConstructorSymbol(symbol))
? getFirstSymbolInChain(symbol, currentToken, checker)
: undefined;
}
function getFirstSymbolInChain(symbol: Symbol, enclosingDeclaration: Node, checker: TypeChecker): Symbol | undefined {
const chain = checker.getAccessibleSymbolChain(symbol, enclosingDeclaration, /*meaning*/ SymbolFlags.All, /*useOnlyExternalAliasing*/ false);
if (chain) return first(chain);
return isModuleSymbol(symbol.parent) ? symbol : symbol.parent && getFirstSymbolInChain(symbol.parent, enclosingDeclaration, checker);
}
function isModuleSymbol(symbol: Symbol): boolean {
return symbol.declarations.some(d => d.kind === SyntaxKind.SourceFile);
}
function getCompletionData(
typeChecker: TypeChecker,
log: (message: string) => void,
@@ -632,6 +664,7 @@ namespace ts.Completions {
request,
keywordFilters: KeywordCompletionFilters.None,
symbolToOriginInfoMap: undefined,
recommendedCompletion: undefined,
};
}
@@ -771,7 +804,8 @@ namespace ts.Completions {
log("getCompletionData: Semantic work: " + (timestamp() - semanticStart));
return { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), request, keywordFilters, symbolToOriginInfoMap };
const recommendedCompletion = getRecommendedCompletion(previousToken, typeChecker);
return { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), request, keywordFilters, symbolToOriginInfoMap, recommendedCompletion };
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;

View File

@@ -714,19 +714,21 @@ namespace ts {
entries: CompletionEntry[];
}
// see comments in protocol.ts
export interface CompletionEntry {
name: string;
kind: ScriptElementKind;
kindModifiers: string; // see ScriptElementKindModifier, comma separated
kindModifiers: string; // see ScriptElementKindModifier, comma separated
sortText: string;
/**
* An optional span that indicates the text to be replaced by this completion item. It will be
* set if the required span differs from the one generated by the default replacement behavior and should
* be used in that case
* An optional span that indicates the text to be replaced by this completion item.
* If present, this span should be used instead of the default one.
* It will be set if the required span differs from the one generated by the default replacement behavior.
*/
replacementSpan?: TextSpan;
hasAction?: true;
source?: string;
isRecommended?: true;
}
export interface CompletionEntryDetails {