/* @internal */ 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: Identifier; } interface ContextualInvocation { readonly kind: InvocationKind.Contextual; readonly signature: Signature; readonly node: Node; // Just for enclosingDeclaration for printing types readonly symbol: Symbol; } type Invocation = CallInvocation | TypeArgsInvocation | ContextualInvocation; interface ArgumentListInfo { readonly isTypeParameterList: boolean; readonly invocation: Invocation; readonly argumentsSpan: TextSpan; readonly argumentIndex: number; /** argumentCount is the *apparent* number of arguments. */ readonly argumentCount: number; } export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerReason: SignatureHelpTriggerReason | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined { const typeChecker = program.getTypeChecker(); // Decide whether to show signature help const startingToken = findTokenOnLeftOfPosition(sourceFile, position); if (!startingToken) { // We are at the beginning of the file return undefined; } // Only need to be careful if the user typed a character and signature help wasn't showing. const onlyUseSyntacticOwners = !!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 (onlyUseSyntacticOwners && (isInString(sourceFile, position, startingToken) || isInComment(sourceFile, position))) { return undefined; } const isManuallyInvoked = !!triggerReason && triggerReason.kind === "invoked"; const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile, typeChecker, isManuallyInvoked); if (!argumentInfo) return undefined; cancellationToken.throwIfCancellationRequested(); // Extra syntactic and semantic filtering of signature help const candidateInfo = getCandidateOrTypeInfo(argumentInfo, typeChecker, sourceFile, startingToken, onlyUseSyntacticOwners); cancellationToken.throwIfCancellationRequested(); if (!candidateInfo) { // We didn't have any sig help items produced by the TS compiler. If this is a JS // file, then see if we can figure out anything better. return isSourceFileJS(sourceFile) ? createJSSignatureHelpItems(argumentInfo, program, cancellationToken) : undefined; } return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => candidateInfo.kind === CandidateOrTypeKind.Candidate ? createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker) : createTypeHelpItems(candidateInfo.symbol, argumentInfo, sourceFile, typeChecker)); } const enum CandidateOrTypeKind { Candidate, Type } interface CandidateInfo { readonly kind: CandidateOrTypeKind.Candidate; readonly candidates: readonly 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)) { return undefined; } const candidates: Signature[] = []; const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentCount)!; // TODO: GH#18217 return candidates.length === 0 ? undefined : { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature }; } case InvocationKind.TypeArgs: { const { called } = invocation; if (onlyUseSyntacticOwners && !containsPrecedingToken(startingToken, sourceFile, isIdentifier(called) ? called.parent : called)) { return undefined; } 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 { kind: CandidateOrTypeKind.Candidate, candidates: [invocation.signature], resolvedSignature: invocation.signature }; default: return Debug.assertNever(invocation); } } function isSyntacticOwner(startingToken: Node, node: CallLikeExpression, sourceFile: SourceFile): boolean { if (!isCallOrNewExpression(node)) return false; const invocationChildren = node.getChildren(sourceFile); switch (startingToken.kind) { case SyntaxKind.OpenParenToken: return contains(invocationChildren, startingToken); case SyntaxKind.CommaToken: { const containingList = findContainingList(startingToken); return !!containingList && contains(invocationChildren, containingList); } case SyntaxKind.LessThanToken: return containsPrecedingToken(startingToken, sourceFile, node.expression); default: return false; } } function createJSSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined { if (argumentInfo.invocation.kind === InvocationKind.Contextual) return undefined; // See if we can find some symbol with the call expression name that has call signatures. const expression = getExpressionFromInvocation(argumentInfo.invocation); const name = isIdentifier(expression) ? expression.text : isPropertyAccessExpression(expression) ? expression.name.text : undefined; const typeChecker = program.getTypeChecker(); return name === undefined ? undefined : firstDefined(program.getSourceFiles(), sourceFile => firstDefined(sourceFile.getNamedDeclarations().get(name), declaration => { const type = declaration.symbol && typeChecker.getTypeOfSymbolAtLocation(declaration.symbol, declaration); const callSignatures = type && type.getCallSignatures(); if (callSignatures && callSignatures.length) { return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, sourceFile, typeChecker)); } })); } function containsPrecedingToken(startingToken: Node, sourceFile: SourceFile, container: Node) { const pos = startingToken.getFullStart(); // There’s a possibility that `startingToken.parent` contains only `startingToken` and // missing nodes, none of which are valid to be returned by `findPrecedingToken`. In that // case, the preceding token we want is actually higher up the tree—almost definitely the // next parent, but theoretically the situation with missing nodes might be happening on // multiple nested levels. let currentParent: Node | undefined = startingToken.parent; while (currentParent) { const precedingToken = findPrecedingToken(pos, sourceFile, currentParent, /*excludeJsdoc*/ true); if (precedingToken) { return rangeContainsRange(container, precedingToken); } currentParent = currentParent.parent; } return Debug.fail("Could not find preceding token"); } export interface ArgumentInfoForCompletions { readonly invocation: CallLikeExpression; readonly argumentIndex: number; readonly argumentCount: number; } export function getArgumentInfoForCompletions(node: Node, position: number, sourceFile: SourceFile): ArgumentInfoForCompletions | undefined { const info = getImmediatelyContainingArgumentInfo(node, position, sourceFile); return !info || info.isTypeParameterList || info.invocation.kind !== InvocationKind.Call ? undefined : { invocation: info.invocation.node, argumentCount: info.argumentCount, argumentIndex: info.argumentIndex }; } function getArgumentOrParameterListInfo(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number, readonly argumentCount: number, readonly argumentsSpan: TextSpan } | undefined { const info = getArgumentOrParameterListAndIndex(node, sourceFile); if (!info) return undefined; const { list, argumentIndex } = info; const argumentCount = getArgumentCount(list); if (argumentIndex !== 0) { Debug.assertLessThan(argumentIndex, argumentCount); } const argumentsSpan = getApplicableSpanForArguments(list, sourceFile); return { list, argumentIndex, argumentCount, argumentsSpan }; } function getArgumentOrParameterListAndIndex(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number } | undefined { if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) { // Find the list that starts right *after* the < or ( token. // If the user has just opened a list, consider this item 0. return { list: getChildListThatStartsWithOpenerToken(node.parent, node, sourceFile), argumentIndex: 0 }; } else { // findListItemInfo can return undefined if we are not in parent's argument list // or type argument list. This includes cases where the cursor is: // - To the right of the closing parenthesis, non-substitution template, or template tail. // - Between the type arguments and the arguments (greater than token) // - On the target of the call (parent.func) // - On the 'new' keyword in a 'new' expression const list = findContainingList(node); return list && { list, argumentIndex: getArgumentIndex(list, node) }; } } /** * Returns relevant information for the argument list and the current argument if we are * in the argument of an invocation; returns undefined otherwise. */ function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined { const { parent } = node; if (isCallOrNewExpression(parent)) { const invocation = parent; // There are 3 cases to handle: // 1. The token introduces a list, and should begin a signature help session // 2. The token is either not associated with a list, or ends a list, so the session should end // 3. The token is buried inside a list, and should give signature help // // The following are examples of each: // // Case 1: // foo<#T, U>(#a, b) -> The token introduces a list, and should begin a signature help session // Case 2: // fo#o#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end // Case 3: // foo(a#, #b#) -> The token is buried inside a list, and should give signature help // Find out if 'node' is an argument, a type argument, or neither const info = getArgumentOrParameterListInfo(node, sourceFile); if (!info) return undefined; const { list, argumentIndex, argumentCount, argumentsSpan } = info; const isTypeParameterList = !!parent.typeArguments && parent.typeArguments.pos === list.pos; return { isTypeParameterList, invocation: { kind: InvocationKind.Call, node: invocation }, argumentsSpan, argumentIndex, argumentCount }; } else if (isNoSubstitutionTemplateLiteral(node) && isTaggedTemplateExpression(parent)) { // Check if we're actually inside the template; // otherwise we'll fall out and return undefined. if (isInsideTemplateLiteral(node, position, sourceFile)) { return getArgumentListInfoForTemplate(parent, /*argumentIndex*/ 0, sourceFile); } return undefined; } else if (isTemplateHead(node) && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { const templateExpression = parent; const tagExpression = templateExpression.parent; Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); const argumentIndex = isInsideTemplateLiteral(node, position, sourceFile) ? 0 : 1; return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); } else if (isTemplateSpan(parent) && isTaggedTemplateExpression(parent.parent.parent)) { const templateSpan = parent; const tagExpression = parent.parent.parent; // If we're just after a template tail, don't show signature help. if (isTemplateTail(node) && !isInsideTemplateLiteral(node, position, sourceFile)) { return undefined; } const spanIndex = templateSpan.parent.templateSpans.indexOf(templateSpan); const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position, sourceFile); return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); } else if (isJsxOpeningLikeElement(parent)) { // Provide a signature help for JSX opening element or JSX self-closing element. // This is not guarantee that JSX tag-name is resolved into stateless function component. (that is done in "getSignatureHelpItems") // i.e // export function MainButton(props: ButtonProps, context: any): JSX.Element { ... } // isFunctionTypeNode(d) ? d.parent.symbol : undefined) || s : s; } function getArgumentIndex(argumentsList: Node, node: Node) { // The list we got back can include commas. In the presence of errors it may // also just have nodes without commas. For example "Foo(a b c)" will have 3 // args without commas. We want to find what index we're at. So we count // forward until we hit ourselves, only incrementing the index if it isn't a // comma. // // Note: the subtlety around trailing commas (in getArgumentCount) does not apply // here. That's because we're only walking forward until we hit the node we're // on. In that case, even if we're after the trailing comma, we'll still see // that trailing comma in the list, and we'll have generated the appropriate // arg index. let argumentIndex = 0; for (const child of argumentsList.getChildren()) { if (child === node) { break; } if (child.kind !== SyntaxKind.CommaToken) { argumentIndex++; } } return argumentIndex; } function getArgumentCount(argumentsList: Node) { // The argument count for a list is normally the number of non-comma children it has. // For example, if you have "Foo(a,b)" then there will be three children of the arg // list 'a' '' 'b'. So, in this case the arg count will be 2. However, there // is a small subtlety. If you have "Foo(a,)", then the child list will just have // 'a' ''. So, in the case where the last child is a comma, we increase the // arg count by one to compensate. // // Note: this subtlety only applies to the last comma. If you had "Foo(a,," then // we'll have: 'a' '' '' // That will give us 2 non-commas. We then add one for the last comma, giving us an // arg count of 3. const listChildren = argumentsList.getChildren(); let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken); if (listChildren.length > 0 && last(listChildren).kind === SyntaxKind.CommaToken) { argumentCount++; } return argumentCount; } // spanIndex is either the index for a given template span. // This does not give appropriate results for a NoSubstitutionTemplateLiteral function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node, position: number, sourceFile: SourceFile): number { // Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1. // There are three cases we can encounter: // 1. We are precisely in the template literal (argIndex = 0). // 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1). // 3. We are directly to the right of the template literal, but because we look for the token on the left, // not enough to put us in the substitution expression; we should consider ourselves part of // the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1). // /* eslint-disable no-double-space */ // Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # ` // ^ ^ ^ ^ ^ ^ ^ ^ ^ // Case: 1 1 3 2 1 3 2 2 1 /* eslint-enable no-double-space */ Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node."); if (isTemplateLiteralToken(node)) { if (isInsideTemplateLiteral(node, position, sourceFile)) { return 0; } return spanIndex + 2; } return spanIndex + 1; } function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number, sourceFile: SourceFile): ArgumentListInfo { // argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument. const argumentCount = isNoSubstitutionTemplateLiteral(tagExpression.template) ? 1 : tagExpression.template.templateSpans.length + 1; if (argumentIndex !== 0) { Debug.assertLessThan(argumentIndex, argumentCount); } return { isTypeParameterList: false, invocation: { kind: InvocationKind.Call, node: tagExpression }, argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile), argumentIndex, argumentCount }; } function getApplicableSpanForArguments(argumentsList: Node, sourceFile: SourceFile): TextSpan { // We use full start and skip trivia on the end because we want to include trivia on // both sides. For example, // // foo( /*comment */ a, b, c /*comment*/ ) // | | // // The applicable span is from the first bar to the second bar (inclusive, // but not including parentheses) const applicableSpanStart = argumentsList.getFullStart(); const applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false); return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); } function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression, sourceFile: SourceFile): TextSpan { const template = taggedTemplate.template; const applicableSpanStart = template.getStart(); let applicableSpanEnd = template.getEnd(); // We need to adjust the end position for the case where the template does not have a tail. // Otherwise, we will not show signature help past the expression. // For example, // // ` ${ 1 + 1 foo(10) // | | // This is because a Missing node has no width. However, what we actually want is to include trivia // leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail. if (template.kind === SyntaxKind.TemplateExpression) { const lastSpan = last(template.templateSpans); if (lastSpan.literal.getFullWidth() === 0) { applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false); } } return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); } function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker, isManuallyInvoked: boolean): ArgumentListInfo | undefined { for (let n = node; !isSourceFile(n) && (isManuallyInvoked || !isBlock(n)); n = n.parent) { // If the node is not a subspan of its parent, this is a big problem. // There have been crashes that might be caused by this violation. Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.formatSyntaxKind(n.kind)}, parent: ${Debug.formatSyntaxKind(n.parent.kind)}`); const argumentInfo = getImmediatelyContainingArgumentOrContextualParameterInfo(n, position, sourceFile, checker); if (argumentInfo) { return argumentInfo; } } return undefined; } function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node { const children = parent.getChildren(sourceFile); const indexOfOpenerToken = children.indexOf(openerToken); Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1); return children[indexOfOpenerToken + 1]; } function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression { 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: readonly Signature[], resolvedSignature: Signature, { isTypeParameterList, argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo, sourceFile: SourceFile, typeChecker: TypeChecker, ): SignatureHelpItems { 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)); if (argumentIndex !== 0) { Debug.assertLessThan(argumentIndex, argumentCount); } const selectedItemIndex = candidates.indexOf(resolvedSignature); Debug.assert(selectedItemIndex !== -1); // If candidates is non-empty it should always include bestSignature. We check for an empty candidates before calling this function. 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: readonly 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: readonly 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 documentation = candidateSignature.getDocumentationComment(checker); const tags = candidateSignature.getJsDocTags(); return { isVariadic, prefixDisplayParts, suffixDisplayParts, separatorDisplayParts, parameters, documentation, tags }; } function returnTypeToDisplayParts(candidateSignature: Signature, enclosingDeclaration: Node, checker: TypeChecker): readonly SymbolDisplayPart[] { return mapToDisplayParts(writer => { writer.writePunctuation(":"); writer.writeSpace(" "); const predicate = checker.getTypePredicateOfSignature(candidateSignature); if (predicate) { checker.writeTypePredicate(predicate, enclosingDeclaration, /*flags*/ undefined, writer); } else { checker.writeType(checker.getReturnTypeOfSignature(candidateSignature), enclosingDeclaration, /*flags*/ undefined, writer); } }); } interface SignatureHelpItemInfo { readonly isVariadic: boolean; readonly parameters: SignatureHelpParameter[]; readonly prefix: readonly SymbolDisplayPart[]; readonly suffix: readonly SymbolDisplayPart[]; } function itemInfoForTypeParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo { const typeParameters = (candidateSignature.target || candidateSignature).typeParameters; const printer = createPrinter({ removeComments: true }); const parameters = (typeParameters || emptyArray).map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer)); const parameterParts = mapToDisplayParts(writer => { const thisParameter = candidateSignature.thisParameter ? [checker.symbolToParameterDeclaration(candidateSignature.thisParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!] : []; const params = createNodeArray([...thisParameter, ...checker.getExpandedParameters(candidateSignature).map(param => checker.symbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)]); printer.writeList(ListFormat.CallExpressionArguments, params, sourceFile, writer); }); return { isVariadic: false, parameters, prefix: [punctuationPart(SyntaxKind.LessThanToken)], suffix: [punctuationPart(SyntaxKind.GreaterThanToken), ...parameterParts] }; } function itemInfoForParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo { const isVariadic = checker.hasEffectiveRestParameter(candidateSignature); const printer = createPrinter({ removeComments: true }); const typeParameterParts = mapToDisplayParts(writer => { if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) { const args = createNodeArray(candidateSignature.typeParameters.map(p => checker.typeParameterToDeclaration(p, enclosingDeclaration)!)); printer.writeList(ListFormat.TypeParameters, args, sourceFile, writer); } }); const parameters = checker.getExpandedParameters(candidateSignature).map(p => createSignatureHelpParameterForParameter(p, checker, enclosingDeclaration, sourceFile, printer)); return { isVariadic, parameters, prefix: [...typeParameterParts, punctuationPart(SyntaxKind.OpenParenToken)], suffix: [punctuationPart(SyntaxKind.CloseParenToken)] }; } function createSignatureHelpParameterForParameter(parameter: Symbol, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter { const displayParts = mapToDisplayParts(writer => { const param = checker.symbolToParameterDeclaration(parameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!; printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); }); const isOptional = checker.isOptionalParameter(parameter.valueDeclaration); return { name: parameter.name, documentation: parameter.getDocumentationComment(checker), displayParts, isOptional }; } function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter { const displayParts = mapToDisplayParts(writer => { const param = checker.typeParameterToDeclaration(typeParameter, enclosingDeclaration)!; printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); }); return { name: typeParameter.symbol.name, documentation: typeParameter.symbol.getDocumentationComment(checker), displayParts, isOptional: false }; } }