Support a 'recommended' completion entry (#20020)
* Support a 'recommended' completion entry * Code review * Restore duplicate comments
This commit is contained in:
Родитель
973cb767c7
Коммит
fd4d8ab96e
|
@ -256,6 +256,7 @@ namespace ts {
|
|||
return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false);
|
||||
},
|
||||
getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()),
|
||||
getAccessibleSymbolChain,
|
||||
};
|
||||
|
||||
const tupleTypes: GenericType[] = [];
|
||||
|
@ -763,10 +764,6 @@ namespace ts {
|
|||
return nodeLinks[nodeId] || (nodeLinks[nodeId] = { flags: 0 });
|
||||
}
|
||||
|
||||
function getObjectFlags(type: Type): ObjectFlags {
|
||||
return type.flags & TypeFlags.Object ? (<ObjectType>type).objectFlags : 0;
|
||||
}
|
||||
|
||||
function isGlobalSourceFile(node: Node) {
|
||||
return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(<SourceFile>node);
|
||||
}
|
||||
|
@ -10452,20 +10449,6 @@ namespace ts {
|
|||
!hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass;
|
||||
}
|
||||
|
||||
// Return true if the given type is the constructor type for an abstract class
|
||||
function isAbstractConstructorType(type: Type) {
|
||||
if (getObjectFlags(type) & ObjectFlags.Anonymous) {
|
||||
const symbol = type.symbol;
|
||||
if (symbol && symbol.flags & SymbolFlags.Class) {
|
||||
const declaration = getClassLikeDeclarationOfSymbol(symbol);
|
||||
if (declaration && hasModifier(declaration, ModifierFlags.Abstract)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons
|
||||
// for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible,
|
||||
// though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely
|
||||
|
@ -13767,7 +13750,7 @@ namespace ts {
|
|||
// the contextual type of an initializer expression is the type annotation of the containing declaration, if present.
|
||||
function getContextualTypeForInitializerExpression(node: Expression): Type {
|
||||
const declaration = <VariableLikeDeclaration>node.parent;
|
||||
if (node === declaration.initializer) {
|
||||
if (node === declaration.initializer || node.kind === SyntaxKind.EqualsToken) {
|
||||
const typeNode = getEffectiveTypeAnnotationNode(declaration);
|
||||
if (typeNode) {
|
||||
return getTypeFromTypeNode(typeNode);
|
||||
|
@ -13899,6 +13882,12 @@ namespace ts {
|
|||
case SyntaxKind.AmpersandAmpersandToken:
|
||||
case SyntaxKind.CommaToken:
|
||||
return node === right ? getContextualType(binaryExpression) : undefined;
|
||||
case SyntaxKind.EqualsEqualsEqualsToken:
|
||||
case SyntaxKind.EqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsToken:
|
||||
// For completions after `x === `
|
||||
return node === operatorToken ? getTypeOfExpression(binaryExpression.left) : undefined;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
@ -14114,9 +14103,13 @@ namespace ts {
|
|||
return getContextualTypeForReturnExpression(node);
|
||||
case SyntaxKind.YieldExpression:
|
||||
return getContextualTypeForYieldOperand(<YieldExpression>parent);
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
return getContextualTypeForArgument(<CallExpression>parent, node);
|
||||
if (node.kind === SyntaxKind.NewKeyword) { // for completions after `new `
|
||||
return getContextualType(parent as NewExpression);
|
||||
}
|
||||
// falls through
|
||||
case SyntaxKind.CallExpression:
|
||||
return getContextualTypeForArgument(<CallExpression | NewExpression>parent, node);
|
||||
case SyntaxKind.TypeAssertionExpression:
|
||||
case SyntaxKind.AsExpression:
|
||||
return getTypeFromTypeNode((<AssertionExpression>parent).type);
|
||||
|
@ -14150,6 +14143,12 @@ namespace ts {
|
|||
case SyntaxKind.JsxOpeningElement:
|
||||
case SyntaxKind.JsxSelfClosingElement:
|
||||
return getAttributesTypeFromJsxOpeningLikeElement(<JsxOpeningLikeElement>parent);
|
||||
case SyntaxKind.CaseClause: {
|
||||
if (node.kind === SyntaxKind.CaseKeyword) { // for completions after `case `
|
||||
const switchStatement = (parent as CaseClause).parent.parent;
|
||||
return getTypeOfExpression(switchStatement.expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@ -22578,10 +22577,6 @@ namespace ts {
|
|||
return getCheckFlags(s) & CheckFlags.Instantiated ? (<TransientSymbol>s).target : s;
|
||||
}
|
||||
|
||||
function getClassLikeDeclarationOfSymbol(symbol: Symbol): Declaration {
|
||||
return forEach(symbol.declarations, d => isClassLike(d) ? d : undefined);
|
||||
}
|
||||
|
||||
function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) {
|
||||
return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration =>
|
||||
d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration);
|
||||
|
|
|
@ -2818,6 +2818,17 @@ namespace ts {
|
|||
/* @internal */ getAllPossiblePropertiesOfTypes(type: ReadonlyArray<Type>): Symbol[];
|
||||
/* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags): Symbol | undefined;
|
||||
/* @internal */ getJsxNamespace(): string;
|
||||
|
||||
/**
|
||||
* Note that this will return undefined in the following case:
|
||||
* // a.ts
|
||||
* export namespace N { export class C { } }
|
||||
* // b.ts
|
||||
* <<enclosingDeclaration>>
|
||||
* Where `C` is the symbol we're looking for.
|
||||
* This should be called in a loop climbing parents of the symbol, so we'll get `N`.
|
||||
*/
|
||||
/* @internal */ getAccessibleSymbolChain(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean): Symbol[] | undefined;
|
||||
}
|
||||
|
||||
export enum NodeBuilderFlags {
|
||||
|
|
|
@ -465,7 +465,6 @@ namespace ts {
|
|||
return isExternalModule(node) || compilerOptions.isolatedModules || ((getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS) && !!node.commonJsModuleIndicator);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function isBlockScope(node: Node, parentNode: Node) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.SourceFile:
|
||||
|
@ -493,7 +492,6 @@ namespace ts {
|
|||
return false;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function isDeclarationWithTypeParameters(node: Node): node is DeclarationWithTypeParameters;
|
||||
export function isDeclarationWithTypeParameters(node: DeclarationWithTypeParameters): node is DeclarationWithTypeParameters {
|
||||
switch (node.kind) {
|
||||
|
@ -523,7 +521,6 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function isAnyImportSyntax(node: Node): node is AnyImportSyntax {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
|
@ -1805,7 +1802,6 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
// See GH#16030
|
||||
export function isAnyDeclarationName(name: Node): boolean {
|
||||
switch (name.kind) {
|
||||
|
@ -3115,7 +3111,6 @@ namespace ts {
|
|||
return flags;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function getModifierFlagsNoCache(node: Node): ModifierFlags {
|
||||
|
||||
let flags = ModifierFlags.None;
|
||||
|
@ -3677,6 +3672,27 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
// Return true if the given type is the constructor type for an abstract class
|
||||
export function isAbstractConstructorType(type: Type): boolean {
|
||||
return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isAbstractConstructorSymbol(type.symbol);
|
||||
}
|
||||
|
||||
export function isAbstractConstructorSymbol(symbol: Symbol): boolean {
|
||||
if (symbol.flags & SymbolFlags.Class) {
|
||||
const declaration = getClassLikeDeclarationOfSymbol(symbol);
|
||||
return !!declaration && hasModifier(declaration, ModifierFlags.Abstract);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getClassLikeDeclarationOfSymbol(symbol: Symbol): Declaration | undefined {
|
||||
return find(symbol.declarations, isClassLike);
|
||||
}
|
||||
|
||||
export function getObjectFlags(type: Type): ObjectFlags {
|
||||
return type.flags & TypeFlags.Object ? (<ObjectType>type).objectFlags : 0;
|
||||
}
|
||||
|
||||
export function typeHasCallOrConstructSignatures(type: Type, checker: TypeChecker) {
|
||||
return checker.getSignaturesOfType(type, SignatureKind.Call).length !== 0 || checker.getSignaturesOfType(type, SignatureKind.Construct).length !== 0;
|
||||
}
|
||||
|
|
|
@ -888,7 +888,7 @@ namespace FourSlash {
|
|||
* @param expectedKind the kind of symbol (see ScriptElementKind)
|
||||
* @param spanIndex the index of the range that the completion item's replacement text span should match
|
||||
*/
|
||||
public verifyCompletionListDoesNotContain(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number, options?: ts.GetCompletionsAtPositionOptions) {
|
||||
public verifyCompletionListDoesNotContain(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number, options?: FourSlashInterface.CompletionsAtOptions) {
|
||||
let replacementSpan: ts.TextSpan;
|
||||
if (spanIndex !== undefined) {
|
||||
replacementSpan = this.getTextSpanForRangeAtIndex(spanIndex);
|
||||
|
@ -1207,7 +1207,7 @@ Actual: ${stringify(fullActual)}`);
|
|||
this.raiseError(`verifyReferencesAtPositionListContains failed - could not find the item: ${stringify(missingItem)} in the returned list: (${stringify(references)})`);
|
||||
}
|
||||
|
||||
private getCompletionListAtCaret(options?: ts.GetCompletionsAtPositionOptions): ts.CompletionInfo {
|
||||
private getCompletionListAtCaret(options?: FourSlashInterface.CompletionsAtOptions): ts.CompletionInfo {
|
||||
return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition, options);
|
||||
}
|
||||
|
||||
|
@ -1721,7 +1721,7 @@ Actual: ${stringify(fullActual)}`);
|
|||
const longestNameLength = max(entries, m => m.name.length);
|
||||
const longestKindLength = max(entries, m => m.kind.length);
|
||||
entries.sort((m, n) => m.sortText > n.sortText ? 1 : m.sortText < n.sortText ? -1 : m.name > n.name ? 1 : m.name < n.name ? -1 : 0);
|
||||
const membersString = entries.map(m => `${pad(m.name, longestNameLength)} ${pad(m.kind, longestKindLength)} ${m.kindModifiers} ${m.source === undefined ? "" : m.source}`).join("\n");
|
||||
const membersString = entries.map(m => `${pad(m.name, longestNameLength)} ${pad(m.kind, longestKindLength)} ${m.kindModifiers} ${m.isRecommended ? "recommended " : ""}${m.source === undefined ? "" : m.source}`).join("\n");
|
||||
Harness.IO.log(membersString);
|
||||
}
|
||||
|
||||
|
@ -3114,6 +3114,7 @@ Actual: ${stringify(fullActual)}`);
|
|||
}
|
||||
|
||||
assert.equal(item.hasAction, hasAction);
|
||||
assert.equal(item.isRecommended, options && options.isRecommended, "isRecommended");
|
||||
}
|
||||
|
||||
private findFile(indexOrName: string | number) {
|
||||
|
@ -4552,12 +4553,13 @@ namespace FourSlashInterface {
|
|||
newContent: string;
|
||||
}
|
||||
|
||||
export interface CompletionsAtOptions {
|
||||
export interface CompletionsAtOptions extends ts.GetCompletionsAtPositionOptions {
|
||||
isNewIdentifierLocation?: boolean;
|
||||
}
|
||||
|
||||
export interface VerifyCompletionListContainsOptions extends ts.GetCompletionsAtPositionOptions {
|
||||
sourceDisplay: string;
|
||||
isRecommended?: true;
|
||||
}
|
||||
|
||||
export interface NewContentOptions {
|
||||
|
|
|
@ -180,14 +180,15 @@ namespace ts.server {
|
|||
isGlobalCompletion: false,
|
||||
isMemberCompletion: false,
|
||||
isNewIdentifierLocation: false,
|
||||
entries: response.body.map(entry => {
|
||||
|
||||
entries: response.body.map<CompletionEntry>(entry => {
|
||||
if (entry.replacementSpan !== undefined) {
|
||||
const { name, kind, kindModifiers, sortText, replacementSpan } = entry;
|
||||
return { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName) };
|
||||
const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source, isRecommended } = entry;
|
||||
// TODO: GH#241
|
||||
const res: CompletionEntry = { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName), hasAction, source, isRecommended };
|
||||
return res;
|
||||
}
|
||||
|
||||
return entry as { name: string, kind: ScriptElementKind, kindModifiers: string, sortText: string };
|
||||
return entry as { name: string, kind: ScriptElementKind, kindModifiers: string, sortText: string }; // TODO: GH#18217
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1701,8 +1701,9 @@ namespace ts.server.protocol {
|
|||
*/
|
||||
sortText: string;
|
||||
/**
|
||||
* 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.
|
||||
* 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;
|
||||
/**
|
||||
|
@ -1714,6 +1715,12 @@ namespace ts.server.protocol {
|
|||
* Identifier (not necessarily human-readable) identifying where this completion came from.
|
||||
*/
|
||||
source?: string;
|
||||
/**
|
||||
* If true, this completion should be highlighted as recommended. There will only be one of these.
|
||||
* This will be set when we know the user should write an expression with a certain type and that type is an enum or constructable class.
|
||||
* Then either that enum/class or a namespace containing it will be the recommended symbol.
|
||||
*/
|
||||
isRecommended?: true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1207,10 +1207,10 @@ namespace ts.server {
|
|||
if (simplifiedResult) {
|
||||
return mapDefined<CompletionEntry, protocol.CompletionEntry>(completions && completions.entries, entry => {
|
||||
if (completions.isMemberCompletion || startsWith(entry.name.toLowerCase(), prefix.toLowerCase())) {
|
||||
const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source } = entry;
|
||||
const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source, isRecommended } = entry;
|
||||
const convertedSpan = replacementSpan ? this.toLocationTextSpan(replacementSpan, scriptInfo) : undefined;
|
||||
// Use `hasAction || undefined` to avoid serializing `false`.
|
||||
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan, hasAction: hasAction || undefined, source };
|
||||
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan, hasAction: hasAction || undefined, source, isRecommended };
|
||||
}
|
||||
}).sort((a, b) => compareStringsCaseSensitiveUI(a.name, b.name));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -4342,13 +4342,14 @@ declare namespace ts {
|
|||
kindModifiers: string;
|
||||
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;
|
||||
}
|
||||
interface CompletionEntryDetails {
|
||||
name: string;
|
||||
|
@ -6155,8 +6156,9 @@ declare namespace ts.server.protocol {
|
|||
*/
|
||||
sortText: string;
|
||||
/**
|
||||
* 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.
|
||||
* 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;
|
||||
/**
|
||||
|
@ -6168,6 +6170,12 @@ declare namespace ts.server.protocol {
|
|||
* Identifier (not necessarily human-readable) identifying where this completion came from.
|
||||
*/
|
||||
source?: string;
|
||||
/**
|
||||
* If true, this completion should be highlighted as recommended. There will only be one of these.
|
||||
* This will be set when we know the user should write an expression with a certain type and that type is an enum or constructable class.
|
||||
* Then either that enum/class or a namespace containing it will be the recommended symbol.
|
||||
*/
|
||||
isRecommended?: true;
|
||||
}
|
||||
/**
|
||||
* Additional completion entry details, available on demand
|
||||
|
|
|
@ -4342,13 +4342,14 @@ declare namespace ts {
|
|||
kindModifiers: string;
|
||||
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;
|
||||
}
|
||||
interface CompletionEntryDetails {
|
||||
name: string;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////enum E {}
|
||||
////declare const e: E;
|
||||
////e === /**/
|
||||
|
||||
goTo.marker();
|
||||
verify.completionListContains("E", "enum E", "", "enum", undefined, undefined, { isRecommended: true });
|
|
@ -0,0 +1,27 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @noLib: true
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export class C {}
|
||||
////export function f(c: C) {}
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import { f } from "./a";
|
||||
// Here we will recommend a new import of 'C'
|
||||
////f(new /*b*/);
|
||||
|
||||
// @Filename: /c.ts
|
||||
////import * as a from "./a";
|
||||
// Here we will recommend 'a' because it contains 'C'.
|
||||
////a.f(new /*c*/);
|
||||
|
||||
goTo.marker("b");
|
||||
verify.completionListContains({ name: "C", source: "/a" }, "class C", "", "class", undefined, /*hasAction*/ true, {
|
||||
includeExternalModuleExports: true,
|
||||
isRecommended: true,
|
||||
sourceDisplay: "./a",
|
||||
});
|
||||
|
||||
goTo.marker("c");
|
||||
verify.completionListContains("a", "import a", "", "alias", undefined, undefined, { isRecommended: true });
|
|
@ -0,0 +1,18 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////enum E {}
|
||||
////class C {}
|
||||
////abstract class A {}
|
||||
////const e: E = /*e*/
|
||||
////const c: C = new /*c*/
|
||||
////const a: A = new /*a*/
|
||||
|
||||
goTo.marker("e");
|
||||
verify.completionListContains("E", "enum E", "", "enum", undefined, undefined, { isRecommended: true });
|
||||
|
||||
goTo.marker("c");
|
||||
verify.completionListContains("C", "class C", "", "class", undefined, undefined, { isRecommended: true });
|
||||
|
||||
goTo.marker("a");
|
||||
// Not recommended, because it's an abstract class
|
||||
verify.completionListContains("A", "class A", "", "class");
|
|
@ -0,0 +1,33 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @noLib: true
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export namespace N {
|
||||
//// export class C {}
|
||||
////}
|
||||
////export function f(c: N.C) {}
|
||||
////f(new /*a*/);
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import { f } from "./a";
|
||||
// Here we will recommend a new import of 'N'
|
||||
////f(new /*b*/);
|
||||
|
||||
// @Filename: /c.ts
|
||||
////import * as a from "./a";
|
||||
// Here we will recommend 'a' because it contains 'N' which contains 'C'.
|
||||
////a.f(new /*c*/);
|
||||
|
||||
goTo.marker("a");
|
||||
verify.completionListContains("N", "namespace N", "", "module", undefined, undefined, { isRecommended: true });
|
||||
|
||||
goTo.marker("b");
|
||||
verify.completionListContains({ name: "N", source: "/a" }, "namespace N", "", "module", undefined, /*hasAction*/ true, {
|
||||
includeExternalModuleExports: true,
|
||||
isRecommended: true,
|
||||
sourceDisplay: "./a",
|
||||
});
|
||||
|
||||
goTo.marker("c");
|
||||
verify.completionListContains("a", "import a", "", "alias", undefined, undefined, { isRecommended: true });
|
|
@ -0,0 +1,10 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////enum E {}
|
||||
////declare const e: E;
|
||||
////switch (e) {
|
||||
//// case /**/
|
||||
////}
|
||||
|
||||
goTo.marker();
|
||||
verify.completionListContains("E", "enum E", "", "enum", undefined, undefined, { isRecommended: true });
|
|
@ -148,7 +148,7 @@ declare namespace FourSlashInterface {
|
|||
kind?: string,
|
||||
spanIndex?: number,
|
||||
hasAction?: boolean,
|
||||
options?: { includeExternalModuleExports?: boolean, sourceDisplay?: string },
|
||||
options?: { includeExternalModuleExports?: boolean, sourceDisplay?: string, isRecommended?: true },
|
||||
): void;
|
||||
completionListItemsCountIsGreaterThan(count: number): void;
|
||||
completionListIsEmpty(): void;
|
||||
|
|
Загрузка…
Ссылка в новой задаче