From 9481faab98bae79b897b9f130ae0bf508e280e26 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 3 Jul 2018 17:04:03 -0700 Subject: [PATCH] Only provide signature help contextually on a character trigger. --- src/services/signatureHelp.ts | 64 +++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index a9239592583..491c905785e 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -29,8 +29,11 @@ namespace ts.SignatureHelp { return undefined; } - if (shouldCarefullyCheckContext(triggerReason)) { - // In the middle of a string, don't provide signature help unless the user explicitly requested it. + // Only need to be careful if the user typed a character and signature help wasn't showing. + const shouldCarefullyCheckContext = !!triggerReason && triggerReason.kind === "characterTyped"; + + // Bail out quickly in the middle of a string or comment, don't provide signature help unless the user explicitly requested it. + if (shouldCarefullyCheckContext) { if (isInString(sourceFile, position, startingToken) || isInComment(sourceFile, position)) { return undefined; } @@ -41,8 +44,8 @@ namespace ts.SignatureHelp { cancellationToken.throwIfCancellationRequested(); - // Semantic filtering of signature help - const candidateInfo = getCandidateInfo(argumentInfo, typeChecker); + // Extra syntactic and semantic filtering of signature help + const candidateInfo = getCandidateInfo(argumentInfo, typeChecker, sourceFile, startingToken, shouldCarefullyCheckContext); cancellationToken.throwIfCancellationRequested(); if (!candidateInfo) { @@ -57,24 +60,57 @@ namespace ts.SignatureHelp { return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker)); } - function shouldCarefullyCheckContext(reason: SignatureHelpTriggerReason | undefined) { - // Only need to be careful if the user typed a character and signature help wasn't showing. - return !!reason && reason.kind === "characterTyped"; - } + function getCandidateInfo( + argumentInfo: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): + { readonly candidates: ReadonlyArray, readonly resolvedSignature: Signature } | undefined { - function getCandidateInfo(argumentInfo: ArgumentListInfo, checker: TypeChecker): { readonly candidates: ReadonlyArray, readonly resolvedSignature: Signature } | undefined { const { invocation } = argumentInfo; if (invocation.kind === InvocationKind.Call) { + if (onlyUseSyntacticOwners) { + if (isCallOrNewExpression(invocation.node)) { + const invocationChildren = invocation.node.getChildren(sourceFile); + switch (startingToken.kind) { + case SyntaxKind.OpenParenToken: + if (!contains(invocationChildren, startingToken)) { + return undefined; + } + break; + case SyntaxKind.CommaToken: + const containingList = findContainingList(startingToken); + if (!containingList || !contains(invocationChildren, findContainingList(startingToken))) { + return undefined; + } + break; + case SyntaxKind.LessThanToken: + if (!lessThanFollowsCalledExpression(startingToken, sourceFile, invocation.node.expression)) { + return undefined; + } + break; + default: + return undefined; + } + } + else { + return undefined; + } + } + const candidates: Signature[] = []; const resolvedSignature = checker.getResolvedSignature(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217 return candidates.length === 0 ? undefined : { candidates, resolvedSignature }; } - else { + else if (invocation.kind === InvocationKind.TypeArgs) { + if (onlyUseSyntacticOwners && !lessThanFollowsCalledExpression(startingToken, sourceFile, invocation.called)) { + return undefined; + } const type = checker.getTypeAtLocation(invocation.called)!; // TODO: GH#18217 const signatures = isNewExpression(invocation.called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); const candidates = signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= argumentInfo.argumentCount); return candidates.length === 0 ? undefined : { candidates, resolvedSignature: first(candidates) }; } + else { + Debug.assertNever(invocation); + } } function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined { @@ -107,6 +143,14 @@ namespace ts.SignatureHelp { } } + function lessThanFollowsCalledExpression(startingToken: Node, sourceFile: SourceFile, calledExpression: Expression) { + const precedingToken = Debug.assertDefined( + findPrecedingToken(startingToken.getFullStart(), sourceFile, startingToken.parent, /*excludeJsdoc*/ true) + ); + + return rangeContainsRange(calledExpression, precedingToken); + } + export interface ArgumentInfoForCompletions { readonly invocation: CallLikeExpression; readonly argumentIndex: number;