• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.SignatureHelp {
3    const enum InvocationKind { Call, TypeArgs, Contextual }
4    interface CallInvocation { readonly kind: InvocationKind.Call; readonly node: CallLikeExpression; }
5    interface TypeArgsInvocation { readonly kind: InvocationKind.TypeArgs; readonly called: Identifier; }
6    interface ContextualInvocation {
7        readonly kind: InvocationKind.Contextual;
8        readonly signature: Signature;
9        readonly node: Node; // Just for enclosingDeclaration for printing types
10        readonly symbol: Symbol;
11    }
12    type Invocation = CallInvocation | TypeArgsInvocation | ContextualInvocation;
13
14    interface ArgumentListInfo {
15        readonly isTypeParameterList: boolean;
16        readonly invocation: Invocation;
17        readonly argumentsSpan: TextSpan;
18        readonly argumentIndex: number;
19        /** argumentCount is the *apparent* number of arguments. */
20        readonly argumentCount: number;
21    }
22
23    export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerReason: SignatureHelpTriggerReason | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
24        const typeChecker = program.getTypeChecker();
25
26        // Decide whether to show signature help
27        const startingToken = findTokenOnLeftOfPosition(sourceFile, position);
28        if (!startingToken) {
29            // We are at the beginning of the file
30            return undefined;
31        }
32
33        // Only need to be careful if the user typed a character and signature help wasn't showing.
34        const onlyUseSyntacticOwners = !!triggerReason && triggerReason.kind === "characterTyped";
35
36        // Bail out quickly in the middle of a string or comment, don't provide signature help unless the user explicitly requested it.
37        if (onlyUseSyntacticOwners && (isInString(sourceFile, position, startingToken) || isInComment(sourceFile, position))) {
38            return undefined;
39        }
40
41        const isManuallyInvoked = !!triggerReason && triggerReason.kind === "invoked";
42        const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile, typeChecker, isManuallyInvoked);
43        if (!argumentInfo) return undefined;
44
45        cancellationToken.throwIfCancellationRequested();
46
47        // Extra syntactic and semantic filtering of signature help
48        const candidateInfo = getCandidateOrTypeInfo(argumentInfo, typeChecker, sourceFile, startingToken, onlyUseSyntacticOwners);
49        cancellationToken.throwIfCancellationRequested();
50
51        if (!candidateInfo) {
52            // We didn't have any sig help items produced by the TS compiler.  If this is a JS
53            // file, then see if we can figure out anything better.
54            return isSourceFileJS(sourceFile) ? createJSSignatureHelpItems(argumentInfo, program, cancellationToken) : undefined;
55        }
56
57        return typeChecker.runWithCancellationToken(cancellationToken, typeChecker =>
58            candidateInfo.kind === CandidateOrTypeKind.Candidate
59                ? createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker)
60                : createTypeHelpItems(candidateInfo.symbol, argumentInfo, sourceFile, typeChecker));
61    }
62
63    const enum CandidateOrTypeKind { Candidate, Type }
64    interface CandidateInfo {
65        readonly kind: CandidateOrTypeKind.Candidate;
66        readonly candidates: readonly Signature[];
67        readonly resolvedSignature: Signature;
68    }
69    interface TypeInfo {
70        readonly kind: CandidateOrTypeKind.Type;
71        readonly symbol: Symbol;
72    }
73
74    function getCandidateOrTypeInfo({ invocation, argumentCount }: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): CandidateInfo | TypeInfo | undefined {
75        switch (invocation.kind) {
76            case InvocationKind.Call: {
77                if (onlyUseSyntacticOwners && !isSyntacticOwner(startingToken, invocation.node, sourceFile)) {
78                    return undefined;
79                }
80                const candidates: Signature[] = [];
81                const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentCount)!; // TODO: GH#18217
82                return candidates.length === 0 ? undefined : { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature };
83            }
84            case InvocationKind.TypeArgs: {
85                const { called } = invocation;
86                if (onlyUseSyntacticOwners && !containsPrecedingToken(startingToken, sourceFile, isIdentifier(called) ? called.parent : called)) {
87                    return undefined;
88                }
89                const candidates = getPossibleGenericSignatures(called, argumentCount, checker);
90                if (candidates.length !== 0) return { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature: first(candidates) };
91
92                const symbol = checker.getSymbolAtLocation(called);
93                return symbol && { kind: CandidateOrTypeKind.Type, symbol };
94            }
95            case InvocationKind.Contextual:
96                return { kind: CandidateOrTypeKind.Candidate, candidates: [invocation.signature], resolvedSignature: invocation.signature };
97            default:
98                return Debug.assertNever(invocation);
99        }
100    }
101
102    function isSyntacticOwner(startingToken: Node, node: CallLikeExpression, sourceFile: SourceFile): boolean {
103        if (!isCallOrNewExpression(node)) return false;
104        const invocationChildren = node.getChildren(sourceFile);
105        switch (startingToken.kind) {
106            case SyntaxKind.OpenParenToken:
107                return contains(invocationChildren, startingToken);
108            case SyntaxKind.CommaToken: {
109                const containingList = findContainingList(startingToken);
110                return !!containingList && contains(invocationChildren, containingList);
111            }
112            case SyntaxKind.LessThanToken:
113                return containsPrecedingToken(startingToken, sourceFile, node.expression);
114            default:
115                return false;
116        }
117    }
118
119    function createJSSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
120        if (argumentInfo.invocation.kind === InvocationKind.Contextual) return undefined;
121        // See if we can find some symbol with the call expression name that has call signatures.
122        const expression = getExpressionFromInvocation(argumentInfo.invocation);
123        const name = isPropertyAccessExpression(expression) ? expression.name.text : undefined;
124        const typeChecker = program.getTypeChecker();
125        return name === undefined ? undefined : firstDefined(program.getSourceFiles(), sourceFile =>
126            firstDefined(sourceFile.getNamedDeclarations().get(name), declaration => {
127                const type = declaration.symbol && typeChecker.getTypeOfSymbolAtLocation(declaration.symbol, declaration);
128                const callSignatures = type && type.getCallSignatures();
129                if (callSignatures && callSignatures.length) {
130                    return typeChecker.runWithCancellationToken(
131                        cancellationToken,
132                        typeChecker => createSignatureHelpItems(
133                            callSignatures,
134                            callSignatures[0],
135                            argumentInfo,
136                            sourceFile,
137                            typeChecker,
138                            /*useFullPrefix*/ true));
139                }
140            }));
141    }
142
143    function containsPrecedingToken(startingToken: Node, sourceFile: SourceFile, container: Node) {
144        const pos = startingToken.getFullStart();
145        // There’s a possibility that `startingToken.parent` contains only `startingToken` and
146        // missing nodes, none of which are valid to be returned by `findPrecedingToken`. In that
147        // case, the preceding token we want is actually higher up the tree—almost definitely the
148        // next parent, but theoretically the situation with missing nodes might be happening on
149        // multiple nested levels.
150        let currentParent: Node | undefined = startingToken.parent;
151        while (currentParent) {
152            const precedingToken = findPrecedingToken(pos, sourceFile, currentParent, /*excludeJsdoc*/ true);
153            if (precedingToken) {
154                return rangeContainsRange(container, precedingToken);
155            }
156            currentParent = currentParent.parent;
157        }
158        return Debug.fail("Could not find preceding token");
159    }
160
161    export interface ArgumentInfoForCompletions {
162        readonly invocation: CallLikeExpression;
163        readonly argumentIndex: number;
164        readonly argumentCount: number;
165    }
166    export function getArgumentInfoForCompletions(node: Node, position: number, sourceFile: SourceFile): ArgumentInfoForCompletions | undefined {
167        const info = getImmediatelyContainingArgumentInfo(node, position, sourceFile);
168        return !info || info.isTypeParameterList || info.invocation.kind !== InvocationKind.Call ? undefined
169            : { invocation: info.invocation.node, argumentCount: info.argumentCount, argumentIndex: info.argumentIndex };
170    }
171
172    function getArgumentOrParameterListInfo(node: Node, position: number, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number, readonly argumentCount: number, readonly argumentsSpan: TextSpan } | undefined {
173        const info = getArgumentOrParameterListAndIndex(node, sourceFile);
174        if (!info) return undefined;
175        const { list, argumentIndex } = info;
176
177        const argumentCount = getArgumentCount(list, /*ignoreTrailingComma*/ isInString(sourceFile, position, node));
178        if (argumentIndex !== 0) {
179            Debug.assertLessThan(argumentIndex, argumentCount);
180        }
181        const argumentsSpan = getApplicableSpanForArguments(list, sourceFile);
182        return { list, argumentIndex, argumentCount, argumentsSpan };
183    }
184    function getArgumentOrParameterListAndIndex(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number } | undefined {
185        if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) {
186            // Find the list that starts right *after* the < or ( token.
187            // If the user has just opened a list, consider this item 0.
188            return { list: getChildListThatStartsWithOpenerToken(node.parent, node, sourceFile), argumentIndex: 0 };
189        }
190        else {
191            // findListItemInfo can return undefined if we are not in parent's argument list
192            // or type argument list. This includes cases where the cursor is:
193            //   - To the right of the closing parenthesis, non-substitution template, or template tail.
194            //   - Between the type arguments and the arguments (greater than token)
195            //   - On the target of the call (parent.func)
196            //   - On the 'new' keyword in a 'new' expression
197            const list = findContainingList(node);
198            return list && { list, argumentIndex: getArgumentIndex(list, node) };
199        }
200    }
201
202    /**
203     * Returns relevant information for the argument list and the current argument if we are
204     * in the argument of an invocation; returns undefined otherwise.
205     */
206    function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined {
207        const { parent } = node;
208        if (isCallOrNewExpression(parent)) {
209            const invocation = parent;
210
211            // There are 3 cases to handle:
212            //   1. The token introduces a list, and should begin a signature help session
213            //   2. The token is either not associated with a list, or ends a list, so the session should end
214            //   3. The token is buried inside a list, and should give signature help
215            //
216            // The following are examples of each:
217            //
218            //    Case 1:
219            //          foo<#T, U>(#a, b)    -> The token introduces a list, and should begin a signature help session
220            //    Case 2:
221            //          fo#o<T, U>#(a, b)#   -> The token is either not associated with a list, or ends a list, so the session should end
222            //    Case 3:
223            //          foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give signature help
224            // Find out if 'node' is an argument, a type argument, or neither
225            const info = getArgumentOrParameterListInfo(node, position, sourceFile);
226            if (!info) return undefined;
227            const { list, argumentIndex, argumentCount, argumentsSpan } = info;
228            const isTypeParameterList = !!parent.typeArguments && parent.typeArguments.pos === list.pos;
229            return { isTypeParameterList, invocation: { kind: InvocationKind.Call, node: invocation }, argumentsSpan, argumentIndex, argumentCount };
230        }
231        else if (isNoSubstitutionTemplateLiteral(node) && isTaggedTemplateExpression(parent)) {
232            // Check if we're actually inside the template;
233            // otherwise we'll fall out and return undefined.
234            if (isInsideTemplateLiteral(node, position, sourceFile)) {
235                return getArgumentListInfoForTemplate(parent, /*argumentIndex*/ 0, sourceFile);
236            }
237            return undefined;
238        }
239        else if (isTemplateHead(node) && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
240            const templateExpression = parent as TemplateExpression;
241            const tagExpression = templateExpression.parent as TaggedTemplateExpression;
242            Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
243
244            const argumentIndex = isInsideTemplateLiteral(node, position, sourceFile) ? 0 : 1;
245
246            return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile);
247        }
248        else if (isTemplateSpan(parent) && isTaggedTemplateExpression(parent.parent.parent)) {
249            const templateSpan = parent;
250            const tagExpression = parent.parent.parent;
251
252            // If we're just after a template tail, don't show signature help.
253            if (isTemplateTail(node) && !isInsideTemplateLiteral(node, position, sourceFile)) {
254                return undefined;
255            }
256
257            const spanIndex = templateSpan.parent.templateSpans.indexOf(templateSpan);
258            const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position, sourceFile);
259
260            return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile);
261        }
262        else if (isJsxOpeningLikeElement(parent)) {
263            // Provide a signature help for JSX opening element or JSX self-closing element.
264            // This is not guarantee that JSX tag-name is resolved into stateless function component. (that is done in "getSignatureHelpItems")
265            // i.e
266            //      export function MainButton(props: ButtonProps, context: any): JSX.Element { ... }
267            //      <MainButton /*signatureHelp*/
268            const attributeSpanStart = parent.attributes.pos;
269            const attributeSpanEnd = skipTrivia(sourceFile.text, parent.attributes.end, /*stopAfterLineBreak*/ false);
270            return {
271                isTypeParameterList: false,
272                invocation: { kind: InvocationKind.Call, node: parent },
273                argumentsSpan: createTextSpan(attributeSpanStart, attributeSpanEnd - attributeSpanStart),
274                argumentIndex: 0,
275                argumentCount: 1
276            };
277        }
278        else {
279            const typeArgInfo = getPossibleTypeArgumentsInfo(node, sourceFile);
280            if (typeArgInfo) {
281                const { called, nTypeArguments } = typeArgInfo;
282                const invocation: Invocation = { kind: InvocationKind.TypeArgs, called };
283                const argumentsSpan = createTextSpanFromBounds(called.getStart(sourceFile), node.end);
284                return { isTypeParameterList: true, invocation, argumentsSpan, argumentIndex: nTypeArguments, argumentCount: nTypeArguments + 1 };
285            }
286            return undefined;
287        }
288    }
289
290    function getImmediatelyContainingArgumentOrContextualParameterInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): ArgumentListInfo | undefined {
291        return tryGetParameterInfo(node, position, sourceFile, checker) || getImmediatelyContainingArgumentInfo(node, position, sourceFile);
292    }
293
294    function getHighestBinary(b: BinaryExpression): BinaryExpression {
295        return isBinaryExpression(b.parent) ? getHighestBinary(b.parent) : b;
296    }
297
298    function countBinaryExpressionParameters(b: BinaryExpression): number {
299        return isBinaryExpression(b.left) ? countBinaryExpressionParameters(b.left) + 1 : 2;
300    }
301
302    function tryGetParameterInfo(startingToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): ArgumentListInfo | undefined {
303        const info = getContextualSignatureLocationInfo(startingToken, sourceFile, position, checker);
304        if (!info) return undefined;
305        const { contextualType, argumentIndex, argumentCount, argumentsSpan } = info;
306
307        // for optional function condition.
308        const nonNullableContextualType = contextualType.getNonNullableType();
309
310        const symbol = nonNullableContextualType.symbol;
311        if (symbol === undefined) return undefined;
312
313        const signature = lastOrUndefined(nonNullableContextualType.getCallSignatures());
314        if (signature === undefined) return undefined;
315
316        const invocation: ContextualInvocation = { kind: InvocationKind.Contextual, signature, node: startingToken, symbol: chooseBetterSymbol(symbol) };
317        return { isTypeParameterList: false, invocation, argumentsSpan, argumentIndex, argumentCount };
318    }
319
320    interface ContextualSignatureLocationInfo { readonly contextualType: Type; readonly argumentIndex: number; readonly argumentCount: number; readonly argumentsSpan: TextSpan; }
321    function getContextualSignatureLocationInfo(startingToken: Node, sourceFile: SourceFile, position: number, checker: TypeChecker): ContextualSignatureLocationInfo | undefined {
322        if (startingToken.kind !== SyntaxKind.OpenParenToken && startingToken.kind !== SyntaxKind.CommaToken) return undefined;
323        const { parent } = startingToken;
324        switch (parent.kind) {
325            case SyntaxKind.ParenthesizedExpression:
326            case SyntaxKind.MethodDeclaration:
327            case SyntaxKind.FunctionExpression:
328            case SyntaxKind.ArrowFunction:
329                const info = getArgumentOrParameterListInfo(startingToken, position, sourceFile);
330                if (!info) return undefined;
331                const { argumentIndex, argumentCount, argumentsSpan } = info;
332                const contextualType = isMethodDeclaration(parent) ? checker.getContextualTypeForObjectLiteralElement(parent) : checker.getContextualType(parent as ParenthesizedExpression | FunctionExpression | ArrowFunction);
333                return contextualType && { contextualType, argumentIndex, argumentCount, argumentsSpan };
334            case SyntaxKind.BinaryExpression: {
335                const highestBinary = getHighestBinary(parent as BinaryExpression);
336                const contextualType = checker.getContextualType(highestBinary);
337                const argumentIndex = startingToken.kind === SyntaxKind.OpenParenToken ? 0 : countBinaryExpressionParameters(parent as BinaryExpression) - 1;
338                const argumentCount = countBinaryExpressionParameters(highestBinary);
339                return contextualType && { contextualType, argumentIndex, argumentCount, argumentsSpan: createTextSpanFromNode(parent) };
340            }
341            default:
342                return undefined;
343        }
344    }
345
346    // The type of a function type node has a symbol at that node, but it's better to use the symbol for a parameter or type alias.
347    function chooseBetterSymbol(s: Symbol): Symbol {
348        return s.name === InternalSymbolName.Type
349            ? firstDefined(s.declarations, d => isFunctionTypeNode(d) ? d.parent.symbol : undefined) || s
350            : s;
351    }
352
353    function getArgumentIndex(argumentsList: Node, node: Node) {
354        // The list we got back can include commas.  In the presence of errors it may
355        // also just have nodes without commas.  For example "Foo(a b c)" will have 3
356        // args without commas. We want to find what index we're at.  So we count
357        // forward until we hit ourselves, only incrementing the index if it isn't a
358        // comma.
359        //
360        // Note: the subtlety around trailing commas (in getArgumentCount) does not apply
361        // here.  That's because we're only walking forward until we hit the node we're
362        // on.  In that case, even if we're after the trailing comma, we'll still see
363        // that trailing comma in the list, and we'll have generated the appropriate
364        // arg index.
365        let argumentIndex = 0;
366        for (const child of argumentsList.getChildren()) {
367            if (child === node) {
368                break;
369            }
370            if (child.kind !== SyntaxKind.CommaToken) {
371                argumentIndex++;
372            }
373        }
374
375        return argumentIndex;
376    }
377
378    function getArgumentCount(argumentsList: Node, ignoreTrailingComma: boolean) {
379        // The argument count for a list is normally the number of non-comma children it has.
380        // For example, if you have "Foo(a,b)" then there will be three children of the arg
381        // list 'a' '<comma>' 'b'.  So, in this case the arg count will be 2.  However, there
382        // is a small subtlety.  If you have "Foo(a,)", then the child list will just have
383        // 'a' '<comma>'.  So, in the case where the last child is a comma, we increase the
384        // arg count by one to compensate.
385        //
386        // Note: this subtlety only applies to the last comma.  If you had "Foo(a,," then
387        // we'll have: 'a' '<comma>' '<missing>'
388        // That will give us 2 non-commas.  We then add one for the last comma, giving us an
389        // arg count of 3.
390        const listChildren = argumentsList.getChildren();
391
392        let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken);
393        if (!ignoreTrailingComma && listChildren.length > 0 && last(listChildren).kind === SyntaxKind.CommaToken) {
394            argumentCount++;
395        }
396        return argumentCount;
397    }
398
399    // spanIndex is either the index for a given template span.
400    // This does not give appropriate results for a NoSubstitutionTemplateLiteral
401    function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node, position: number, sourceFile: SourceFile): number {
402        // Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1.
403        // There are three cases we can encounter:
404        //      1. We are precisely in the template literal (argIndex = 0).
405        //      2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1).
406        //      3. We are directly to the right of the template literal, but because we look for the token on the left,
407        //          not enough to put us in the substitution expression; we should consider ourselves part of
408        //          the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1).
409        //
410        /* eslint-disable local/no-double-space */
411        // Example: f  `# abcd $#{#  1 + 1#  }# efghi ${ #"#hello"#  }  #  `
412        //              ^       ^ ^       ^   ^          ^ ^      ^     ^
413        // Case:        1       1 3       2   1          3 2      2     1
414        /* eslint-enable local/no-double-space */
415        Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node.");
416        if (isTemplateLiteralToken(node)) {
417            if (isInsideTemplateLiteral(node, position, sourceFile)) {
418                return 0;
419            }
420            return spanIndex + 2;
421        }
422        return spanIndex + 1;
423    }
424
425    function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number, sourceFile: SourceFile): ArgumentListInfo {
426        // argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument.
427        const argumentCount = isNoSubstitutionTemplateLiteral(tagExpression.template) ? 1 : tagExpression.template.templateSpans.length + 1;
428        if (argumentIndex !== 0) {
429            Debug.assertLessThan(argumentIndex, argumentCount);
430        }
431        return {
432            isTypeParameterList: false,
433            invocation: { kind: InvocationKind.Call, node: tagExpression },
434            argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile),
435            argumentIndex,
436            argumentCount
437        };
438    }
439
440    function getApplicableSpanForArguments(argumentsList: Node, sourceFile: SourceFile): TextSpan {
441        // We use full start and skip trivia on the end because we want to include trivia on
442        // both sides. For example,
443        //
444        //    foo(   /*comment */     a, b, c      /*comment*/     )
445        //        |                                               |
446        //
447        // The applicable span is from the first bar to the second bar (inclusive,
448        // but not including parentheses)
449        const applicableSpanStart = argumentsList.getFullStart();
450        const applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false);
451        return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
452    }
453
454    function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression, sourceFile: SourceFile): TextSpan {
455        const template = taggedTemplate.template;
456        const applicableSpanStart = template.getStart();
457        let applicableSpanEnd = template.getEnd();
458
459        // We need to adjust the end position for the case where the template does not have a tail.
460        // Otherwise, we will not show signature help past the expression.
461        // For example,
462        //
463        //      ` ${ 1 + 1 foo(10)
464        //       |       |
465        // This is because a Missing node has no width. However, what we actually want is to include trivia
466        // leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail.
467        if (template.kind === SyntaxKind.TemplateExpression) {
468            const lastSpan = last(template.templateSpans);
469            if (lastSpan.literal.getFullWidth() === 0) {
470                applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false);
471            }
472        }
473
474        return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
475    }
476
477    function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker, isManuallyInvoked: boolean): ArgumentListInfo | undefined {
478        for (let n = node; !isSourceFile(n) && (isManuallyInvoked || !isBlock(n)); n = n.parent) {
479            // If the node is not a subspan of its parent, this is a big problem.
480            // There have been crashes that might be caused by this violation.
481            Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.formatSyntaxKind(n.kind)}, parent: ${Debug.formatSyntaxKind(n.parent.kind)}`);
482            const argumentInfo = getImmediatelyContainingArgumentOrContextualParameterInfo(n, position, sourceFile, checker);
483            if (argumentInfo) {
484                return argumentInfo;
485            }
486        }
487        return undefined;
488    }
489
490    function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node {
491        const children = parent.getChildren(sourceFile);
492        const indexOfOpenerToken = children.indexOf(openerToken);
493        Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1);
494        return children[indexOfOpenerToken + 1];
495    }
496
497    function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression {
498        return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called;
499    }
500
501    function getEnclosingDeclarationFromInvocation(invocation: Invocation): Node {
502        return invocation.kind === InvocationKind.Call ? invocation.node : invocation.kind === InvocationKind.TypeArgs ? invocation.called : invocation.node;
503    }
504
505    const signatureHelpNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope;
506    function createSignatureHelpItems(
507        candidates: readonly Signature[],
508        resolvedSignature: Signature,
509        { isTypeParameterList, argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo,
510        sourceFile: SourceFile,
511        typeChecker: TypeChecker,
512        useFullPrefix?: boolean,
513    ): SignatureHelpItems {
514        const enclosingDeclaration = getEnclosingDeclarationFromInvocation(invocation);
515        const callTargetSymbol = invocation.kind === InvocationKind.Contextual ? invocation.symbol : (typeChecker.getSymbolAtLocation(getExpressionFromInvocation(invocation)) || useFullPrefix && resolvedSignature.declaration?.symbol);
516        const callTargetDisplayParts = callTargetSymbol ? symbolToDisplayParts(typeChecker, callTargetSymbol, useFullPrefix ? sourceFile : undefined, /*meaning*/ undefined) : emptyArray;
517        const items = map(candidates, candidateSignature => getSignatureHelpItem(candidateSignature, callTargetDisplayParts, isTypeParameterList, typeChecker, enclosingDeclaration, sourceFile));
518
519        if (argumentIndex !== 0) {
520            Debug.assertLessThan(argumentIndex, argumentCount);
521        }
522        let selectedItemIndex = 0;
523        let itemsSeen = 0;
524        for (let i = 0; i < items.length; i++) {
525            const item = items[i];
526            if (candidates[i] === resolvedSignature) {
527                selectedItemIndex = itemsSeen;
528                if (item.length > 1) {
529                    // check to see if any items in the list better match than the first one, as the checker isn't filtering the nested lists
530                    // (those come from tuple parameter expansion)
531                    let count = 0;
532                    for (const i of item) {
533                        if (i.isVariadic || i.parameters.length >= argumentCount) {
534                            selectedItemIndex = itemsSeen + count;
535                            break;
536                        }
537                        count++;
538                    }
539                }
540            }
541            itemsSeen += item.length;
542        }
543
544        Debug.assert(selectedItemIndex !== -1); // If candidates is non-empty it should always include bestSignature. We check for an empty candidates before calling this function.
545        const help = { items: flatMapToMutable(items, identity), applicableSpan, selectedItemIndex, argumentIndex, argumentCount };
546        const selected = help.items[selectedItemIndex];
547        if (selected.isVariadic) {
548            const firstRest = findIndex(selected.parameters, p => !!p.isRest);
549            if (-1 < firstRest && firstRest < selected.parameters.length - 1) {
550                // We don't have any code to get this correct; instead, don't highlight a current parameter AT ALL
551                help.argumentIndex = selected.parameters.length;
552            }
553            else {
554                help.argumentIndex = Math.min(help.argumentIndex, selected.parameters.length - 1);
555            }
556        }
557        return help;
558    }
559
560    function createTypeHelpItems(
561        symbol: Symbol,
562        { argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo,
563        sourceFile: SourceFile,
564        checker: TypeChecker
565    ): SignatureHelpItems | undefined {
566        const typeParameters = checker.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
567        if (!typeParameters) return undefined;
568        const items = [getTypeHelpItem(symbol, typeParameters, checker, getEnclosingDeclarationFromInvocation(invocation), sourceFile)];
569        return { items, applicableSpan, selectedItemIndex: 0, argumentIndex, argumentCount };
570    }
571
572    function getTypeHelpItem(symbol: Symbol, typeParameters: readonly TypeParameter[], checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem {
573        const typeSymbolDisplay = symbolToDisplayParts(checker, symbol);
574
575        const printer = createPrinter({ removeComments: true });
576        const parameters = typeParameters.map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer));
577
578        const documentation = symbol.getDocumentationComment(checker);
579        const tags = symbol.getJsDocTags(checker);
580        const prefixDisplayParts = [...typeSymbolDisplay, punctuationPart(SyntaxKind.LessThanToken)];
581        return { isVariadic: false, prefixDisplayParts, suffixDisplayParts: [punctuationPart(SyntaxKind.GreaterThanToken)], separatorDisplayParts, parameters, documentation, tags };
582    }
583
584    const separatorDisplayParts: SymbolDisplayPart[] = [punctuationPart(SyntaxKind.CommaToken), spacePart()];
585
586    function getSignatureHelpItem(candidateSignature: Signature, callTargetDisplayParts: readonly SymbolDisplayPart[], isTypeParameterList: boolean, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem[] {
587        const infos = (isTypeParameterList ? itemInfoForTypeParameters : itemInfoForParameters)(candidateSignature, checker, enclosingDeclaration, sourceFile);
588        return map(infos, ({ isVariadic, parameters, prefix, suffix }) => {
589            const prefixDisplayParts = [...callTargetDisplayParts, ...prefix];
590            const suffixDisplayParts = [...suffix, ...returnTypeToDisplayParts(candidateSignature, enclosingDeclaration, checker)];
591            const documentation = candidateSignature.getDocumentationComment(checker);
592            const tags = candidateSignature.getJsDocTags();
593            return { isVariadic, prefixDisplayParts, suffixDisplayParts, separatorDisplayParts, parameters, documentation, tags };
594        });
595    }
596
597    function returnTypeToDisplayParts(candidateSignature: Signature, enclosingDeclaration: Node, checker: TypeChecker): readonly SymbolDisplayPart[] {
598        return mapToDisplayParts(writer => {
599            writer.writePunctuation(":");
600            writer.writeSpace(" ");
601            const predicate = checker.getTypePredicateOfSignature(candidateSignature);
602            if (predicate) {
603                checker.writeTypePredicate(predicate, enclosingDeclaration, /*flags*/ undefined, writer);
604            }
605            else {
606                checker.writeType(checker.getReturnTypeOfSignature(candidateSignature), enclosingDeclaration, /*flags*/ undefined, writer);
607            }
608        });
609    }
610
611    interface SignatureHelpItemInfo { readonly isVariadic: boolean; readonly parameters: SignatureHelpParameter[]; readonly prefix: readonly SymbolDisplayPart[]; readonly suffix: readonly SymbolDisplayPart[]; }
612
613    function itemInfoForTypeParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo[] {
614        const typeParameters = (candidateSignature.target || candidateSignature).typeParameters;
615        const printer = createPrinter({ removeComments: true });
616        const parameters = (typeParameters || emptyArray).map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer));
617        const thisParameter = candidateSignature.thisParameter ? [checker.symbolToParameterDeclaration(candidateSignature.thisParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!] : [];
618
619        return checker.getExpandedParameters(candidateSignature).map(paramList => {
620            const params = factory.createNodeArray([...thisParameter, ...map(paramList, param => checker.symbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)]);
621            const parameterParts = mapToDisplayParts(writer => {
622                printer.writeList(ListFormat.CallExpressionArguments, params, sourceFile, writer);
623            });
624            return { isVariadic: false, parameters, prefix: [punctuationPart(SyntaxKind.LessThanToken)], suffix: [punctuationPart(SyntaxKind.GreaterThanToken), ...parameterParts] };
625        });
626    }
627
628    function itemInfoForParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo[] {
629        const printer = createPrinter({ removeComments: true });
630        const typeParameterParts = mapToDisplayParts(writer => {
631            if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) {
632                const args = factory.createNodeArray(candidateSignature.typeParameters.map(p => checker.typeParameterToDeclaration(p, enclosingDeclaration, signatureHelpNodeBuilderFlags)!));
633                printer.writeList(ListFormat.TypeParameters, args, sourceFile, writer);
634            }
635        });
636        const lists = checker.getExpandedParameters(candidateSignature);
637        const isVariadic: (parameterList: readonly Symbol[]) => boolean =
638            !checker.hasEffectiveRestParameter(candidateSignature) ? _ => false
639            : lists.length === 1 ? _ => true
640            : pList => !!(pList.length && (pList[pList.length - 1] as TransientSymbol).checkFlags & CheckFlags.RestParameter);
641        return lists.map(parameterList => ({
642            isVariadic: isVariadic(parameterList),
643            parameters: parameterList.map(p => createSignatureHelpParameterForParameter(p, checker, enclosingDeclaration, sourceFile, printer)),
644            prefix: [...typeParameterParts, punctuationPart(SyntaxKind.OpenParenToken)],
645            suffix: [punctuationPart(SyntaxKind.CloseParenToken)]
646        }));
647    }
648
649    function createSignatureHelpParameterForParameter(parameter: Symbol, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter {
650        const displayParts = mapToDisplayParts(writer => {
651            const param = checker.symbolToParameterDeclaration(parameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!;
652            printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer);
653        });
654        const isOptional = checker.isOptionalParameter(parameter.valueDeclaration as ParameterDeclaration);
655        const isRest = !!((parameter as TransientSymbol).checkFlags & CheckFlags.RestParameter);
656        return { name: parameter.name, documentation: parameter.getDocumentationComment(checker), displayParts, isOptional, isRest };
657    }
658
659    function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter {
660        const displayParts = mapToDisplayParts(writer => {
661            const param = checker.typeParameterToDeclaration(typeParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!;
662            printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer);
663        });
664        return { name: typeParameter.symbol.name, documentation: typeParameter.symbol.getDocumentationComment(checker), displayParts, isOptional: false, isRest: false };
665    }
666}
667