mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Support signature help for type parameters of a type (#26702)
This commit is contained in:
@@ -370,7 +370,9 @@ namespace ts {
|
||||
finally {
|
||||
cancellationToken = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getLocalTypeParametersOfClassOrInterfaceOrTypeAlias,
|
||||
};
|
||||
|
||||
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, isForSignatureHelp: boolean): Signature | undefined {
|
||||
|
||||
@@ -3118,6 +3118,8 @@ namespace ts {
|
||||
* and the operation is cancelled, then it should be discarded, otherwise it is safe to keep.
|
||||
*/
|
||||
runWithCancellationToken<T>(token: CancellationToken, cb: (checker: TypeChecker) => T): T;
|
||||
|
||||
/* @internal */ getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): ReadonlyArray<TypeParameter> | undefined;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace ts.SignatureHelp {
|
||||
const enum InvocationKind { Call, TypeArgs, Contextual }
|
||||
interface CallInvocation { readonly kind: InvocationKind.Call; readonly node: CallLikeExpression; }
|
||||
interface TypeArgsInvocation { readonly kind: InvocationKind.TypeArgs; readonly called: Expression; }
|
||||
interface TypeArgsInvocation { readonly kind: InvocationKind.TypeArgs; readonly called: Identifier; }
|
||||
interface ContextualInvocation {
|
||||
readonly kind: InvocationKind.Contextual;
|
||||
readonly signature: Signature;
|
||||
@@ -44,7 +44,7 @@ namespace ts.SignatureHelp {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
|
||||
// Extra syntactic and semantic filtering of signature help
|
||||
const candidateInfo = getCandidateInfo(argumentInfo, typeChecker, sourceFile, startingToken, onlyUseSyntacticOwners);
|
||||
const candidateInfo = getCandidateOrTypeInfo(argumentInfo, typeChecker, sourceFile, startingToken, onlyUseSyntacticOwners);
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
|
||||
if (!candidateInfo) {
|
||||
@@ -53,11 +53,24 @@ namespace ts.SignatureHelp {
|
||||
return isSourceFileJavaScript(sourceFile) ? createJavaScriptSignatureHelpItems(argumentInfo, program, cancellationToken) : undefined;
|
||||
}
|
||||
|
||||
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker));
|
||||
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker =>
|
||||
candidateInfo.kind === CandidateOrTypeKind.Candidate
|
||||
? createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker)
|
||||
: createTypeHelpItems(candidateInfo.symbol, argumentInfo, sourceFile, typeChecker));
|
||||
}
|
||||
|
||||
interface CandidateInfo { readonly candidates: ReadonlyArray<Signature>; readonly resolvedSignature: Signature; }
|
||||
function getCandidateInfo({ invocation, argumentCount }: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): CandidateInfo | undefined {
|
||||
const enum CandidateOrTypeKind { Candidate, Type }
|
||||
interface CandidateInfo {
|
||||
readonly kind: CandidateOrTypeKind.Candidate;
|
||||
readonly candidates: ReadonlyArray<Signature>;
|
||||
readonly resolvedSignature: Signature;
|
||||
}
|
||||
interface TypeInfo {
|
||||
readonly kind: CandidateOrTypeKind.Type;
|
||||
readonly symbol: Symbol;
|
||||
}
|
||||
|
||||
function getCandidateOrTypeInfo({ invocation, argumentCount }: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): CandidateInfo | TypeInfo | undefined {
|
||||
switch (invocation.kind) {
|
||||
case InvocationKind.Call: {
|
||||
if (onlyUseSyntacticOwners && !isSyntacticOwner(startingToken, invocation.node, sourceFile)) {
|
||||
@@ -65,17 +78,21 @@ namespace ts.SignatureHelp {
|
||||
}
|
||||
const candidates: Signature[] = [];
|
||||
const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentCount)!; // TODO: GH#18217
|
||||
return candidates.length === 0 ? undefined : { candidates, resolvedSignature };
|
||||
return candidates.length === 0 ? undefined : { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature };
|
||||
}
|
||||
case InvocationKind.TypeArgs: {
|
||||
if (onlyUseSyntacticOwners && !lessThanFollowsCalledExpression(startingToken, sourceFile, invocation.called)) {
|
||||
const { called } = invocation;
|
||||
if (onlyUseSyntacticOwners && !containsPrecedingToken(startingToken, sourceFile, isIdentifier(called) ? called.parent : called)) {
|
||||
return undefined;
|
||||
}
|
||||
const candidates = getPossibleGenericSignatures(invocation.called, argumentCount, checker);
|
||||
return candidates.length === 0 ? undefined : { candidates, resolvedSignature: first(candidates) };
|
||||
const candidates = getPossibleGenericSignatures(called, argumentCount, checker);
|
||||
if (candidates.length !== 0) return { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature: first(candidates) };
|
||||
|
||||
const symbol = checker.getSymbolAtLocation(called);
|
||||
return symbol && { kind: CandidateOrTypeKind.Type, symbol };
|
||||
}
|
||||
case InvocationKind.Contextual:
|
||||
return { candidates: [invocation.signature], resolvedSignature: invocation.signature };
|
||||
return { kind: CandidateOrTypeKind.Candidate, candidates: [invocation.signature], resolvedSignature: invocation.signature };
|
||||
default:
|
||||
return Debug.assertNever(invocation);
|
||||
}
|
||||
@@ -92,7 +109,7 @@ namespace ts.SignatureHelp {
|
||||
return !!containingList && contains(invocationChildren, containingList);
|
||||
}
|
||||
case SyntaxKind.LessThanToken:
|
||||
return lessThanFollowsCalledExpression(startingToken, sourceFile, node.expression);
|
||||
return containsPrecedingToken(startingToken, sourceFile, node.expression);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -114,12 +131,12 @@ namespace ts.SignatureHelp {
|
||||
}));
|
||||
}
|
||||
|
||||
function lessThanFollowsCalledExpression(startingToken: Node, sourceFile: SourceFile, calledExpression: Expression) {
|
||||
function containsPrecedingToken(startingToken: Node, sourceFile: SourceFile, container: Node) {
|
||||
const precedingToken = Debug.assertDefined(
|
||||
findPrecedingToken(startingToken.getFullStart(), sourceFile, startingToken.parent, /*excludeJsdoc*/ true)
|
||||
);
|
||||
|
||||
return rangeContainsRange(calledExpression, precedingToken);
|
||||
return rangeContainsRange(container, precedingToken);
|
||||
}
|
||||
|
||||
export interface ArgumentInfoForCompletions {
|
||||
@@ -457,6 +474,10 @@ namespace ts.SignatureHelp {
|
||||
return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called;
|
||||
}
|
||||
|
||||
function getEnclosingDeclarationFromInvocation(invocation: Invocation): Node {
|
||||
return invocation.kind === InvocationKind.Call ? invocation.node : invocation.kind === InvocationKind.TypeArgs ? invocation.called : invocation.node;
|
||||
}
|
||||
|
||||
const signatureHelpNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope;
|
||||
function createSignatureHelpItems(
|
||||
candidates: ReadonlyArray<Signature>,
|
||||
@@ -465,7 +486,7 @@ namespace ts.SignatureHelp {
|
||||
sourceFile: SourceFile,
|
||||
typeChecker: TypeChecker,
|
||||
): SignatureHelpItems {
|
||||
const enclosingDeclaration = invocation.kind === InvocationKind.Call ? invocation.node : invocation.kind === InvocationKind.TypeArgs ? invocation.called : invocation.node;
|
||||
const enclosingDeclaration = getEnclosingDeclarationFromInvocation(invocation);
|
||||
const callTargetSymbol = invocation.kind === InvocationKind.Contextual ? invocation.symbol : typeChecker.getSymbolAtLocation(getExpressionFromInvocation(invocation));
|
||||
const callTargetDisplayParts = callTargetSymbol ? symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined) : emptyArray;
|
||||
const items = candidates.map(candidateSignature => getSignatureHelpItem(candidateSignature, callTargetDisplayParts, isTypeParameterList, typeChecker, enclosingDeclaration, sourceFile));
|
||||
@@ -480,11 +501,36 @@ namespace ts.SignatureHelp {
|
||||
return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount };
|
||||
}
|
||||
|
||||
function createTypeHelpItems(
|
||||
symbol: Symbol,
|
||||
{ argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo,
|
||||
sourceFile: SourceFile,
|
||||
checker: TypeChecker
|
||||
): SignatureHelpItems | undefined {
|
||||
const typeParameters = checker.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
|
||||
if (!typeParameters) return undefined;
|
||||
const items = [getTypeHelpItem(symbol, typeParameters, checker, getEnclosingDeclarationFromInvocation(invocation), sourceFile)];
|
||||
return { items, applicableSpan, selectedItemIndex: 0, argumentIndex, argumentCount };
|
||||
}
|
||||
|
||||
function getTypeHelpItem(symbol: Symbol, typeParameters: ReadonlyArray<TypeParameter>, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem {
|
||||
const typeSymbolDisplay = symbolToDisplayParts(checker, symbol);
|
||||
|
||||
const printer = createPrinter({ removeComments: true });
|
||||
const parameters = typeParameters.map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer));
|
||||
|
||||
const documentation = symbol.getDocumentationComment(checker);
|
||||
const tags = symbol.getJsDocTags();
|
||||
const prefixDisplayParts = [...typeSymbolDisplay, punctuationPart(SyntaxKind.LessThanToken)];
|
||||
return { isVariadic: false, prefixDisplayParts, suffixDisplayParts: [punctuationPart(SyntaxKind.GreaterThanToken)], separatorDisplayParts, parameters, documentation, tags };
|
||||
}
|
||||
|
||||
const separatorDisplayParts: SymbolDisplayPart[] = [punctuationPart(SyntaxKind.CommaToken), spacePart()];
|
||||
|
||||
function getSignatureHelpItem(candidateSignature: Signature, callTargetDisplayParts: ReadonlyArray<SymbolDisplayPart>, isTypeParameterList: boolean, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem {
|
||||
const { isVariadic, parameters, prefix, suffix } = (isTypeParameterList ? itemInfoForTypeParameters : itemInfoForParameters)(candidateSignature, checker, enclosingDeclaration, sourceFile);
|
||||
const prefixDisplayParts = [...callTargetDisplayParts, ...prefix];
|
||||
const suffixDisplayParts = [...suffix, ...returnTypeToDisplayParts(candidateSignature, enclosingDeclaration, checker)];
|
||||
const separatorDisplayParts = [punctuationPart(SyntaxKind.CommaToken), spacePart()];
|
||||
const documentation = candidateSignature.getDocumentationComment(checker);
|
||||
const tags = candidateSignature.getJsDocTags();
|
||||
return { isVariadic, prefixDisplayParts, suffixDisplayParts, separatorDisplayParts, parameters, documentation, tags };
|
||||
|
||||
@@ -11,21 +11,37 @@
|
||||
////var x : testClass</*type2*/
|
||||
////class Bar<T> extends testClass</*type3*/
|
||||
////var x : testClass<,, /*type4*/any>;
|
||||
////
|
||||
////interface I<T> {}
|
||||
////let i: I</*interface*/>;
|
||||
////
|
||||
////type Ty<T> = T;
|
||||
////let t: Ty</*typeAlias*/>;
|
||||
|
||||
// TODO: GH#26699
|
||||
|
||||
if (false) {
|
||||
verify.signatureHelp(
|
||||
{
|
||||
marker: ["type1", "type2", "type3"],
|
||||
text: "testClass<T extends IFoo, U, M extends IFoo>",
|
||||
parameterName: "T",
|
||||
parameterSpan: "T extends IFoo",
|
||||
},
|
||||
{
|
||||
marker: "type4",
|
||||
parameterName: "M",
|
||||
parameterSpan: "M extends IFoo",
|
||||
}
|
||||
);
|
||||
}
|
||||
verify.signatureHelp(
|
||||
{
|
||||
marker: ["type1", "type2", "type3"],
|
||||
text: "testClass<T extends IFoo, U, M extends IFoo>",
|
||||
parameterName: "T",
|
||||
parameterSpan: "T extends IFoo",
|
||||
triggerReason: { kind: "characterTyped", triggerCharacter: "<" },
|
||||
},
|
||||
{
|
||||
marker: "type4",
|
||||
parameterName: "M",
|
||||
parameterSpan: "M extends IFoo",
|
||||
triggerReason: { kind: "characterTyped", triggerCharacter: "," },
|
||||
},
|
||||
{
|
||||
marker: "interface",
|
||||
text: "I<T>",
|
||||
parameterName: "T",
|
||||
parameterSpan: "T",
|
||||
},
|
||||
{
|
||||
marker: "typeAlias",
|
||||
text: "Ty<T>",
|
||||
parameterName: "T",
|
||||
parameterSpan: "T",
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user