Skip to content

Commit 9481faa

Browse files
Only provide signature help contextually on a character trigger.
1 parent a3b2237 commit 9481faa

File tree

1 file changed

+54
-10
lines changed

1 file changed

+54
-10
lines changed

src/services/signatureHelp.ts

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ namespace ts.SignatureHelp {
2929
return undefined;
3030
}
3131

32-
if (shouldCarefullyCheckContext(triggerReason)) {
33-
// In the middle of a string, don't provide signature help unless the user explicitly requested it.
32+
// Only need to be careful if the user typed a character and signature help wasn't showing.
33+
const shouldCarefullyCheckContext = !!triggerReason && triggerReason.kind === "characterTyped";
34+
35+
// Bail out quickly in the middle of a string or comment, don't provide signature help unless the user explicitly requested it.
36+
if (shouldCarefullyCheckContext) {
3437
if (isInString(sourceFile, position, startingToken) || isInComment(sourceFile, position)) {
3538
return undefined;
3639
}
@@ -41,8 +44,8 @@ namespace ts.SignatureHelp {
4144

4245
cancellationToken.throwIfCancellationRequested();
4346

44-
// Semantic filtering of signature help
45-
const candidateInfo = getCandidateInfo(argumentInfo, typeChecker);
47+
// Extra syntactic and semantic filtering of signature help
48+
const candidateInfo = getCandidateInfo(argumentInfo, typeChecker, sourceFile, startingToken, shouldCarefullyCheckContext);
4649
cancellationToken.throwIfCancellationRequested();
4750

4851
if (!candidateInfo) {
@@ -57,24 +60,57 @@ namespace ts.SignatureHelp {
5760
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker));
5861
}
5962

60-
function shouldCarefullyCheckContext(reason: SignatureHelpTriggerReason | undefined) {
61-
// Only need to be careful if the user typed a character and signature help wasn't showing.
62-
return !!reason && reason.kind === "characterTyped";
63-
}
63+
function getCandidateInfo(
64+
argumentInfo: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean):
65+
{ readonly candidates: ReadonlyArray<Signature>, readonly resolvedSignature: Signature } | undefined {
6466

65-
function getCandidateInfo(argumentInfo: ArgumentListInfo, checker: TypeChecker): { readonly candidates: ReadonlyArray<Signature>, readonly resolvedSignature: Signature } | undefined {
6667
const { invocation } = argumentInfo;
6768
if (invocation.kind === InvocationKind.Call) {
69+
if (onlyUseSyntacticOwners) {
70+
if (isCallOrNewExpression(invocation.node)) {
71+
const invocationChildren = invocation.node.getChildren(sourceFile);
72+
switch (startingToken.kind) {
73+
case SyntaxKind.OpenParenToken:
74+
if (!contains(invocationChildren, startingToken)) {
75+
return undefined;
76+
}
77+
break;
78+
case SyntaxKind.CommaToken:
79+
const containingList = findContainingList(startingToken);
80+
if (!containingList || !contains(invocationChildren, findContainingList(startingToken))) {
81+
return undefined;
82+
}
83+
break;
84+
case SyntaxKind.LessThanToken:
85+
if (!lessThanFollowsCalledExpression(startingToken, sourceFile, invocation.node.expression)) {
86+
return undefined;
87+
}
88+
break;
89+
default:
90+
return undefined;
91+
}
92+
}
93+
else {
94+
return undefined;
95+
}
96+
}
97+
6898
const candidates: Signature[] = [];
6999
const resolvedSignature = checker.getResolvedSignature(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217
70100
return candidates.length === 0 ? undefined : { candidates, resolvedSignature };
71101
}
72-
else {
102+
else if (invocation.kind === InvocationKind.TypeArgs) {
103+
if (onlyUseSyntacticOwners && !lessThanFollowsCalledExpression(startingToken, sourceFile, invocation.called)) {
104+
return undefined;
105+
}
73106
const type = checker.getTypeAtLocation(invocation.called)!; // TODO: GH#18217
74107
const signatures = isNewExpression(invocation.called.parent) ? type.getConstructSignatures() : type.getCallSignatures();
75108
const candidates = signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= argumentInfo.argumentCount);
76109
return candidates.length === 0 ? undefined : { candidates, resolvedSignature: first(candidates) };
77110
}
111+
else {
112+
Debug.assertNever(invocation);
113+
}
78114
}
79115

80116
function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
@@ -107,6 +143,14 @@ namespace ts.SignatureHelp {
107143
}
108144
}
109145

146+
function lessThanFollowsCalledExpression(startingToken: Node, sourceFile: SourceFile, calledExpression: Expression) {
147+
const precedingToken = Debug.assertDefined(
148+
findPrecedingToken(startingToken.getFullStart(), sourceFile, startingToken.parent, /*excludeJsdoc*/ true)
149+
);
150+
151+
return rangeContainsRange(calledExpression, precedingToken);
152+
}
153+
110154
export interface ArgumentInfoForCompletions {
111155
readonly invocation: CallLikeExpression;
112156
readonly argumentIndex: number;

0 commit comments

Comments
 (0)