diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index c88f6449e1..8df59ec51d 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1948,7 +1948,10 @@ func IsComputedNonLiteralName(name *Node) bool { } func IsQuestionToken(node *Node) bool { - return node != nil && node.Kind == KindQuestionToken + if node == nil { + return false + } + return node.Kind == KindQuestionToken } func GetTextOfPropertyName(name *Node) string { @@ -3385,3 +3388,75 @@ func IsRightSideOfQualifiedNameOrPropertyAccess(node *Node) bool { } return false } + +func HasQuestionToken(node *Node) bool { + switch node.Kind { + case KindParameter: + return node.AsParameterDeclaration().QuestionToken != nil + case KindMethodDeclaration: + return IsQuestionToken(node.AsMethodDeclaration().PostfixToken) + case KindShorthandPropertyAssignment: + return IsQuestionToken(node.AsShorthandPropertyAssignment().PostfixToken) + case KindMethodSignature: + return IsQuestionToken(node.AsMethodSignatureDeclaration().PostfixToken) + case KindPropertySignature: + return IsQuestionToken(node.AsPropertySignatureDeclaration().PostfixToken) + case KindPropertyAssignment: + return IsQuestionToken(node.AsPropertyAssignment().PostfixToken) + case KindPropertyDeclaration: + return IsQuestionToken(node.AsPropertyDeclaration().PostfixToken) + } + return false +} + +func IsJsxOpeningLikeElement(node *Node) bool { + return IsJsxOpeningElement(node) || IsJsxSelfClosingElement(node) +} + +func GetInvokedExpression(node *Node) *Node { + switch node.Kind { + case KindTaggedTemplateExpression: + return node.AsTaggedTemplateExpression().Tag + case KindJsxOpeningElement, KindJsxSelfClosingElement: + return node.TagName() + case KindBinaryExpression: + return node.AsBinaryExpression().Right + default: + return node.Expression() + } +} + +func IsCallOrNewExpression(node *Node) bool { + return IsCallExpression(node) || IsNewExpression(node) +} + +func CanHaveSymbol(node *Node) bool { + switch node.Kind { + case KindArrowFunction, KindBinaryExpression, KindBindingElement, KindCallExpression, KindCallSignature, + KindClassDeclaration, KindClassExpression, KindClassStaticBlockDeclaration, KindConstructor, KindConstructorType, + KindConstructSignature, KindElementAccessExpression, KindEnumDeclaration, KindEnumMember, KindExportAssignment, KindJSExportAssignment, + KindExportDeclaration, KindExportSpecifier, KindFunctionDeclaration, KindFunctionExpression, KindFunctionType, + KindGetAccessor, KindIdentifier, KindImportClause, KindImportEqualsDeclaration, KindImportSpecifier, + KindIndexSignature, KindInterfaceDeclaration, KindJSDocSignature, KindJSDocTypeLiteral, + KindJsxAttribute, KindJsxAttributes, KindJsxSpreadAttribute, KindMappedType, KindMethodDeclaration, + KindMethodSignature, KindModuleDeclaration, KindNamedTupleMember, KindNamespaceExport, KindNamespaceExportDeclaration, + KindNamespaceImport, KindNewExpression, KindNoSubstitutionTemplateLiteral, KindNumericLiteral, KindObjectLiteralExpression, + KindParameter, KindPropertyAccessExpression, KindPropertyAssignment, KindPropertyDeclaration, KindPropertySignature, + KindSetAccessor, KindShorthandPropertyAssignment, KindSourceFile, KindSpreadAssignment, KindStringLiteral, + KindTypeAliasDeclaration, KindJSTypeAliasDeclaration, KindTypeLiteral, KindTypeParameter, KindVariableDeclaration: + return true + } + return false +} + +func IndexOfNode(nodes []*Node, node *Node) int { + index, ok := slices.BinarySearchFunc(nodes, node, compareNodePositions) + if ok { + return index + } + return -1 +} + +func compareNodePositions(n1, n2 *Node) int { + return n1.Pos() - n2.Pos() +} diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 6f46105e92..b05288e66e 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -7713,7 +7713,7 @@ func (c *Checker) createArrayLiteralType(t *Type) *Type { func isSpreadIntoCallOrNew(node *ast.Node) bool { parent := ast.WalkUpParenthesizedExpressions(node.Parent) - return ast.IsSpreadElement(parent) && isCallOrNewExpression(parent.Parent) + return ast.IsSpreadElement(parent) && ast.IsCallOrNewExpression(parent.Parent) } func (c *Checker) checkQualifiedName(node *ast.Node, checkMode CheckMode) *Type { @@ -7953,7 +7953,7 @@ func (c *Checker) checkDeprecatedSignature(sig *Signature, node *ast.Node) { } if sig.declaration != nil && sig.declaration.Flags&ast.NodeFlagsDeprecated != 0 { suggestionNode := c.getDeprecatedSuggestionNode(node) - name := tryGetPropertyAccessOrIdentifierToString(getInvokedExpression(node)) + name := tryGetPropertyAccessOrIdentifierToString(ast.GetInvokedExpression(node)) c.addDeprecatedSuggestionWithSignature(suggestionNode, sig.declaration, name, c.signatureToString(sig)) } } @@ -8419,7 +8419,7 @@ type CallState struct { func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidatesOutArray *[]*Signature, checkMode CheckMode, callChainFlags SignatureFlags, headMessage *diagnostics.Message) *Signature { isTaggedTemplate := node.Kind == ast.KindTaggedTemplateExpression isDecorator := node.Kind == ast.KindDecorator - isJsxOpeningOrSelfClosingElement := isJsxOpeningLikeElement(node) + isJsxOpeningOrSelfClosingElement := ast.IsJsxOpeningLikeElement(node) isInstanceof := node.Kind == ast.KindBinaryExpression reportErrors := !c.isInferencePartiallyBlocked && candidatesOutArray == nil var s CallState @@ -8725,7 +8725,7 @@ func (c *Checker) hasCorrectArity(node *ast.Node, args []*ast.Node, signature *S argCount = c.getDecoratorArgumentCount(node, signature) case ast.IsBinaryExpression(node): argCount = 1 - case isJsxOpeningLikeElement(node): + case ast.IsJsxOpeningLikeElement(node): callIsIncomplete = node.Attributes().End() == node.End() if callIsIncomplete { return true @@ -8845,7 +8845,7 @@ func (c *Checker) checkTypeArguments(signature *Signature, typeArgumentNodes []* } func (c *Checker) isSignatureApplicable(node *ast.Node, args []*ast.Node, signature *Signature, relation *Relation, checkMode CheckMode, reportErrors bool, inferenceContext *InferenceContext, diagnosticOutput *[]*ast.Diagnostic) bool { - if isJsxOpeningLikeElement(node) { + if ast.IsJsxOpeningLikeElement(node) { return c.checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, diagnosticOutput) } thisType := c.getThisTypeOfSignature(signature) @@ -8986,7 +8986,7 @@ func (c *Checker) getEffectiveCheckNode(argument *ast.Node) *ast.Node { } func (c *Checker) inferTypeArguments(node *ast.Node, signature *Signature, args []*ast.Node, checkMode CheckMode, context *InferenceContext) []*Type { - if isJsxOpeningLikeElement(node) { + if ast.IsJsxOpeningLikeElement(node) { return c.inferJsxTypeArguments(node, signature, checkMode, context) } // If a contextual type is available, infer from that type to the return type of the call expression. For @@ -10835,7 +10835,7 @@ func (c *Checker) isMethodAccessForCall(node *ast.Node) bool { for ast.IsParenthesizedExpression(node.Parent) { node = node.Parent } - return isCallOrNewExpression(node.Parent) && node.Parent.Expression() == node + return ast.IsCallOrNewExpression(node.Parent) && node.Parent.Expression() == node } // Lookup the private identifier lexically. @@ -11051,7 +11051,7 @@ func (c *Checker) isUncalledFunctionReference(node *ast.Node, symbol *ast.Symbol parent = node.Parent } if ast.IsCallLikeExpression(parent) { - return isCallOrNewExpression(parent) && ast.IsIdentifier(node) && c.hasMatchingArgument(parent, node) + return ast.IsCallOrNewExpression(parent) && ast.IsIdentifier(node) && c.hasMatchingArgument(parent, node) } return core.Every(symbol.Declarations, func(d *ast.Node) bool { return !ast.IsFunctionLike(d) || c.IsDeprecatedDeclaration(d) @@ -14179,7 +14179,7 @@ func (c *Checker) getTargetOfAliasLikeExpression(expression *ast.Node, dontResol } func (c *Checker) getTargetOfNamespaceExportDeclaration(node *ast.Node, dontResolveAlias bool) *ast.Symbol { - if canHaveSymbol(node.Parent) { + if ast.CanHaveSymbol(node.Parent) { resolved := c.resolveExternalModuleSymbol(node.Parent.Symbol(), dontResolveAlias) c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil /*immediateTarget*/, resolved, false /*overwriteEmpty*/, nil, "") return resolved @@ -26275,18 +26275,6 @@ func (c *Checker) markPropertyAsReferenced(prop *ast.Symbol, nodeForCheckWriteOn c.symbolReferenceLinks.Get(target).referenceKinds |= ast.SymbolFlagsAll } -func (c *Checker) GetExpandedParameters(signature *Signature /* !!! skipUnionExpanding */) []*ast.Symbol { - if signatureHasRestParameter(signature) { - restIndex := len(signature.parameters) - 1 - restSymbol := signature.parameters[restIndex] - restType := c.getTypeOfSymbol(restSymbol) - if isTupleType(restType) { - return c.expandSignatureParametersWithTupleMembers(signature, restType.AsTypeReference(), restIndex, restSymbol) - } - } - return signature.parameters -} - func (c *Checker) expandSignatureParametersWithTupleMembers(signature *Signature, restType *TypeReference, restIndex int, restSymbol *ast.Symbol) []*ast.Symbol { elementTypes := c.getTypeArguments(restType.AsType()) elementInfos := restType.TargetTupleType().elementInfos @@ -26765,7 +26753,7 @@ func (c *Checker) markLinkedReferences(location *ast.Node, hint ReferenceHint, p c.markExportAssignmentAliasReferenced(location) return } - if isJsxOpeningLikeElement(location) || ast.IsJsxOpeningFragment(location) { + if ast.IsJsxOpeningLikeElement(location) || ast.IsJsxOpeningFragment(location) { c.markJsxAliasReferenced(location) return } @@ -26930,7 +26918,7 @@ func (c *Checker) markJsxAliasReferenced(node *ast.Node /*JsxOpeningLikeElement jsxFactoryRefErr := core.IfElse(c.compilerOptions.Jsx == core.JsxEmitReact, diagnostics.This_JSX_tag_requires_0_to_be_in_scope_but_it_could_not_be_found, nil) jsxFactoryNamespace := c.getJsxNamespace(node) jsxFactoryLocation := node - if isJsxOpeningLikeElement(node) { + if ast.IsJsxOpeningLikeElement(node) { jsxFactoryLocation = node.TagName() } // allow null as jsxFragmentFactory @@ -27572,7 +27560,7 @@ func (c *Checker) getContextualType(node *ast.Node, contextFlags ContextFlags) * return c.getContextualType(parent.Parent, contextFlags) case ast.KindArrayLiteralExpression: t := c.getApparentTypeOfContextualType(parent, contextFlags) - elementIndex := indexOfNode(parent.AsArrayLiteralExpression().Elements.Nodes, node) + elementIndex := ast.IndexOfNode(parent.AsArrayLiteralExpression().Elements.Nodes, node) firstSpreadIndex, lastSpreadIndex := c.getSpreadIndices(parent) return c.getContextualTypeForElementExpression(t, elementIndex, len(parent.AsArrayLiteralExpression().Elements.Nodes), firstSpreadIndex, lastSpreadIndex) case ast.KindConditionalExpression: @@ -27975,7 +27963,7 @@ func (c *Checker) getContextualTypeForArgumentAtIndex(callTarget *ast.Node, argI } else { signature = c.getResolvedSignature(callTarget, nil, CheckModeNormal) } - if isJsxOpeningLikeElement(callTarget) && argIndex == 0 { + if ast.IsJsxOpeningLikeElement(callTarget) && argIndex == 0 { return c.getEffectiveFirstArgumentForJsxSignature(signature, callTarget) } restIndex := len(signature.parameters) - 1 @@ -28229,7 +28217,7 @@ func (c *Checker) getEffectiveCallArguments(node *ast.Node) []*ast.Node { case ast.IsBinaryExpression(node): // Handles instanceof operator return []*ast.Node{node.AsBinaryExpression().Left} - case isJsxOpeningLikeElement(node): + case ast.IsJsxOpeningLikeElement(node): if len(node.Attributes().AsJsxAttributes().Properties.Nodes) != 0 || (ast.IsJsxOpeningElement(node) && len(node.Parent.Children().Nodes) != 0) { return []*ast.Node{node.Attributes()} } diff --git a/internal/checker/exports.go b/internal/checker/exports.go index c9062f149e..48a4453eb9 100644 --- a/internal/checker/exports.go +++ b/internal/checker/exports.go @@ -85,3 +85,35 @@ func (c *Checker) GetEffectiveDeclarationFlags(n *ast.Node, flagsToCheck ast.Mod func (c *Checker) GetBaseConstraintOfType(t *Type) *Type { return c.getBaseConstraintOfType(t) } + +func (c *Checker) GetTypePredicateOfSignature(sig *Signature) *TypePredicate { + return c.getTypePredicateOfSignature(sig) +} + +func IsTupleType(t *Type) bool { + return isTupleType(t) +} + +func (c *Checker) GetReturnTypeOfSignature(sig *Signature) *Type { + return c.getReturnTypeOfSignature(sig) +} + +func (c *Checker) HasEffectiveRestParameter(signature *Signature) bool { + return c.hasEffectiveRestParameter(signature) +} + +func (c *Checker) GetLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol *ast.Symbol) []*Type { + return c.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) +} + +func (c *Checker) GetContextualTypeForObjectLiteralElement(element *ast.Node, contextFlags ContextFlags) *Type { + return c.getContextualTypeForObjectLiteralElement(element, contextFlags) +} + +func (c *Checker) TypePredicateToString(t *TypePredicate) string { + return c.typePredicateToString(t) +} + +func (c *Checker) GetExpandedParameters(signature *Signature, skipUnionExpanding bool) [][]*ast.Symbol { + return c.getExpandedParameters(signature, skipUnionExpanding) +} diff --git a/internal/checker/jsx.go b/internal/checker/jsx.go index 10a172eec6..507e5063e4 100644 --- a/internal/checker/jsx.go +++ b/internal/checker/jsx.go @@ -119,7 +119,7 @@ func (c *Checker) checkJsxAttributes(node *ast.Node, checkMode CheckMode) *Type } func (c *Checker) checkJsxOpeningLikeElementOrOpeningFragment(node *ast.Node) { - isNodeOpeningLikeElement := isJsxOpeningLikeElement(node) + isNodeOpeningLikeElement := ast.IsJsxOpeningLikeElement(node) if isNodeOpeningLikeElement { c.checkGrammarJsxElement(node) } diff --git a/internal/checker/relater.go b/internal/checker/relater.go index 56dfb8cdd8..8da7906566 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -2693,7 +2693,7 @@ func (r *Relater) hasExcessProperties(source *Type, target *Type, reportErrors b if r.errorNode == nil { panic("No errorNode in hasExcessProperties") } - if ast.IsJsxAttributes(r.errorNode) || isJsxOpeningLikeElement(r.errorNode) || isJsxOpeningLikeElement(r.errorNode.Parent) { + if ast.IsJsxAttributes(r.errorNode) || ast.IsJsxOpeningLikeElement(r.errorNode) || ast.IsJsxOpeningLikeElement(r.errorNode.Parent) { // JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal. // However, using an object-literal error message will be very confusing to the users so we give different a message. if prop.ValueDeclaration != nil && ast.IsJsxAttribute(prop.ValueDeclaration) && ast.GetSourceFileOfNode(r.errorNode) == ast.GetSourceFileOfNode(prop.ValueDeclaration.Name()) { diff --git a/internal/checker/services.go b/internal/checker/services.go index 9405803c42..6eb89e044a 100644 --- a/internal/checker/services.go +++ b/internal/checker/services.go @@ -6,6 +6,7 @@ import ( "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/printer" ) func (c *Checker) GetSymbolsInScope(location *ast.Node, meaning ast.SymbolFlags) []*ast.Symbol { @@ -299,10 +300,20 @@ func runWithInferenceBlockedFromSourceNode[T any](c *Checker, node *ast.Node, fn return result } -func runWithoutResolvedSignatureCaching[T any](c *Checker, node *ast.Node, fn func() T) T { - ancestorNode := ast.FindAncestor(node, func(n *ast.Node) bool { - return ast.IsCallLikeOrFunctionLikeExpression(n) +func GetResolvedSignatureForSignatureHelp(node *ast.Node, argumentCount int, c *Checker) (*Signature, []*Signature) { + type result struct { + signature *Signature + candidates []*Signature + } + res := runWithoutResolvedSignatureCaching(c, node, func() result { + signature, candidates := c.getResolvedSignatureWorker(node, CheckModeIsForSignatureHelp, argumentCount) + return result{signature, candidates} }) + return res.signature, res.candidates +} + +func runWithoutResolvedSignatureCaching[T any](c *Checker, node *ast.Node, fn func() T) T { + ancestorNode := ast.FindAncestor(node, ast.IsCallLikeOrFunctionLikeExpression) if ancestorNode != nil { cachedResolvedSignatures := make(map[*SignatureLinks]*Signature) cachedTypes := make(map[*ValueSymbolLinks]*Type) @@ -506,3 +517,15 @@ func (c *Checker) GetConstantValue(node *ast.Node) any { return nil } + +func (c *Checker) getResolvedSignatureWorker(node *ast.Node, checkMode CheckMode, argumentCount int) (*Signature, []*Signature) { + parsedNode := printer.NewEmitContext().ParseNode(node) + c.apparentArgumentCount = &argumentCount + candidatesOutArray := &[]*Signature{} + var res *Signature + if parsedNode != nil { + res = c.getResolvedSignature(parsedNode, candidatesOutArray, checkMode) + } + c.apparentArgumentCount = nil + return res, *candidatesOutArray +} diff --git a/internal/checker/types.go b/internal/checker/types.go index 6a2ace4023..95e19ea247 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -588,6 +588,10 @@ func (t *Type) Flags() TypeFlags { return t.flags } +func (t *Type) ObjectFlags() ObjectFlags { + return t.objectFlags +} + // Casts for concrete struct types func (t *Type) AsIntrinsicType() *IntrinsicType { return t.data.(*IntrinsicType) } @@ -940,6 +944,8 @@ type TupleElementInfo struct { labeledDeclaration *ast.Node // NamedTupleMember | ParameterDeclaration | nil } +func (t *TupleElementInfo) TupleElementFlags() ElementFlags { return t.flags } + type TupleType struct { InterfaceType elementInfos []TupleElementInfo @@ -949,6 +955,15 @@ type TupleType struct { readonly bool } +func (t *TupleType) FixedLength() int { return t.fixedLength } +func (t *TupleType) ElementFlags() []ElementFlags { + elementFlags := make([]ElementFlags, len(t.elementInfos)) + for i, info := range t.elementInfos { + elementFlags[i] = info.flags + } + return elementFlags +} + // SingleSignatureType type SingleSignatureType struct { @@ -1148,6 +1163,22 @@ type Signature struct { composite *CompositeSignature } +func (s *Signature) TypeParameters() []*Type { + return s.typeParameters +} + +func (s *Signature) Declaration() *ast.Node { + return s.declaration +} + +func (s *Signature) Target() *Signature { + return s.target +} + +func (s *Signature) ThisParameter() *ast.Symbol { + return s.thisParameter +} + type CompositeSignature struct { isUnion bool // True for union, false for intersection signatures []*Signature // Individual signatures diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index 083e48c4c4..ea755d7718 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -256,25 +256,6 @@ func nodeCanBeDecorated(useLegacyDecorators bool, node *ast.Node, parent *ast.No return false } -func canHaveSymbol(node *ast.Node) bool { - switch node.Kind { - case ast.KindArrowFunction, ast.KindBinaryExpression, ast.KindBindingElement, ast.KindCallExpression, ast.KindCallSignature, - ast.KindClassDeclaration, ast.KindClassExpression, ast.KindClassStaticBlockDeclaration, ast.KindConstructor, ast.KindConstructorType, - ast.KindConstructSignature, ast.KindElementAccessExpression, ast.KindEnumDeclaration, ast.KindEnumMember, ast.KindExportAssignment, ast.KindJSExportAssignment, - ast.KindExportDeclaration, ast.KindExportSpecifier, ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindFunctionType, - ast.KindGetAccessor, ast.KindIdentifier, ast.KindImportClause, ast.KindImportEqualsDeclaration, ast.KindImportSpecifier, - ast.KindIndexSignature, ast.KindInterfaceDeclaration, ast.KindJSDocSignature, ast.KindJSDocTypeLiteral, - ast.KindJsxAttribute, ast.KindJsxAttributes, ast.KindJsxSpreadAttribute, ast.KindMappedType, ast.KindMethodDeclaration, - ast.KindMethodSignature, ast.KindModuleDeclaration, ast.KindNamedTupleMember, ast.KindNamespaceExport, ast.KindNamespaceExportDeclaration, - ast.KindNamespaceImport, ast.KindNewExpression, ast.KindNoSubstitutionTemplateLiteral, ast.KindNumericLiteral, ast.KindObjectLiteralExpression, - ast.KindParameter, ast.KindPropertyAccessExpression, ast.KindPropertyAssignment, ast.KindPropertyDeclaration, ast.KindPropertySignature, - ast.KindSetAccessor, ast.KindShorthandPropertyAssignment, ast.KindSourceFile, ast.KindSpreadAssignment, ast.KindStringLiteral, - ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration, ast.KindTypeLiteral, ast.KindTypeParameter, ast.KindVariableDeclaration: - return true - } - return false -} - func canHaveLocals(node *ast.Node) bool { switch node.Kind { case ast.KindArrowFunction, ast.KindBlock, ast.KindCallSignature, ast.KindCaseBlock, ast.KindCatchClause, @@ -1094,10 +1075,6 @@ func isThisTypeParameter(t *Type) bool { return t.flags&TypeFlagsTypeParameter != 0 && t.AsTypeParameter().isThisType } -func isCallOrNewExpression(node *ast.Node) bool { - return ast.IsCallExpression(node) || ast.IsNewExpression(node) -} - func isClassInstanceProperty(node *ast.Node) bool { return node.Parent != nil && ast.IsClassLike(node.Parent) && ast.IsPropertyDeclaration(node) && !ast.HasAccessorModifier(node) } @@ -1197,10 +1174,6 @@ func reverseAccessKind(a AccessKind) AccessKind { panic("Unhandled case in reverseAccessKind") } -func isJsxOpeningLikeElement(node *ast.Node) bool { - return ast.IsJsxOpeningElement(node) || ast.IsJsxSelfClosingElement(node) -} - // Deprecated in favor of `ast.IsObjectLiteralElement` func isObjectLiteralElementLike(node *ast.Node) bool { return ast.IsObjectLiteralElement(node) @@ -1278,18 +1251,6 @@ func getBindingElementPropertyName(node *ast.Node) *ast.Node { return node.Name() } -func indexOfNode(nodes []*ast.Node, node *ast.Node) int { - index, ok := slices.BinarySearchFunc(nodes, node, compareNodePositions) - if ok { - return index - } - return -1 -} - -func compareNodePositions(n1, n2 *ast.Node) int { - return n1.Pos() - n2.Pos() -} - func hasContextSensitiveParameters(node *ast.Node) bool { // Functions with type parameters are not context sensitive. if node.TypeParameters() == nil { @@ -1314,7 +1275,7 @@ func isCallChain(node *ast.Node) bool { } func (c *Checker) callLikeExpressionMayHaveTypeArguments(node *ast.Node) bool { - return isCallOrNewExpression(node) || ast.IsTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node) + return ast.IsCallOrNewExpression(node) || ast.IsTaggedTemplateExpression(node) || ast.IsJsxOpeningLikeElement(node) } func isSuperCall(n *ast.Node) bool { @@ -1851,19 +1812,6 @@ func tryGetPropertyAccessOrIdentifierToString(expr *ast.Node) string { return "" } -func getInvokedExpression(node *ast.Node) *ast.Node { - switch node.Kind { - case ast.KindTaggedTemplateExpression: - return node.AsTaggedTemplateExpression().Tag - case ast.KindJsxOpeningElement, ast.KindJsxSelfClosingElement: - return node.TagName() - case ast.KindBinaryExpression: - return node.AsBinaryExpression().Right - default: - return node.Expression() - } -} - func getFirstJSDocTag(node *ast.Node, f func(*ast.Node) bool) *ast.Node { for _, jsdoc := range node.JSDoc(nil) { tags := jsdoc.AsJSDoc().Tags diff --git a/internal/ls/signaturehelp.go b/internal/ls/signaturehelp.go new file mode 100644 index 0000000000..dcca18f73f --- /dev/null +++ b/internal/ls/signaturehelp.go @@ -0,0 +1,1149 @@ +package ls + +import ( + "context" + "strings" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/astnav" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/compiler" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/nodebuilder" + "github.com/microsoft/typescript-go/internal/printer" + "github.com/microsoft/typescript-go/internal/scanner" +) + +type callInvocation struct { + node *ast.Node +} + +type typeArgsInvocation struct { + called *ast.Identifier +} + +type contextualInvocation struct { + signature *checker.Signature + node *ast.Node // Just for enclosingDeclaration for printing types + symbol *ast.Symbol +} + +type invocation struct { + callInvocation *callInvocation + typeArgsInvocation *typeArgsInvocation + contextualInvocation *contextualInvocation +} + +func (l *LanguageService) ProvideSignatureHelp( + ctx context.Context, + documentURI lsproto.DocumentUri, + position lsproto.Position, + context *lsproto.SignatureHelpContext, + clientOptions *lsproto.SignatureHelpClientCapabilities, + preferences *UserPreferences, +) *lsproto.SignatureHelp { + program, sourceFile := l.getProgramAndFile(documentURI) + return l.GetSignatureHelpItems( + ctx, + int(l.converters.LineAndCharacterToPosition(sourceFile, position)), + program, + sourceFile, + context, + clientOptions, + preferences) +} + +func (l *LanguageService) GetSignatureHelpItems( + ctx context.Context, + position int, + program *compiler.Program, + sourceFile *ast.SourceFile, + context *lsproto.SignatureHelpContext, + clientOptions *lsproto.SignatureHelpClientCapabilities, + preferences *UserPreferences, +) *lsproto.SignatureHelp { + typeChecker, done := program.GetTypeCheckerForFile(ctx, sourceFile) + defer done() + + // Decide whether to show signature help + startingToken := astnav.FindPrecedingToken(sourceFile, position) + if startingToken == nil { + // We are at the beginning of the file + return nil + } + + // Only need to be careful if the user typed a character and signature help wasn't showing. + onlyUseSyntacticOwners := context.TriggerKind == lsproto.SignatureHelpTriggerKindTriggerCharacter + + // 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) needs formatting implemented + return nil + } + + isManuallyInvoked := context.TriggerKind == 1 + argumentInfo := getContainingArgumentInfo(startingToken, sourceFile, typeChecker, isManuallyInvoked, position) + if argumentInfo == nil { + return nil + } + + // cancellationToken.throwIfCancellationRequested(); + + // Extra syntactic and semantic filtering of signature help + 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 => + if candidateInfo.candidateInfo != nil { + return createSignatureHelpItems(candidateInfo.candidateInfo.candidates, candidateInfo.candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker, onlyUseSyntacticOwners, clientOptions) + } + return createTypeHelpItems(candidateInfo.typeInfo, argumentInfo, sourceFile, clientOptions, typeChecker) +} + +func createTypeHelpItems(symbol *ast.Symbol, argumentInfo *argumentListInfo, sourceFile *ast.SourceFile, clientOptions *lsproto.SignatureHelpClientCapabilities, c *checker.Checker) *lsproto.SignatureHelp { + typeParameters := c.GetLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) + if typeParameters == nil { + return nil + } + item := getTypeHelpItem(symbol, typeParameters, getEnclosingDeclarationFromInvocation(argumentInfo.invocation), sourceFile, c) + + // Converting signatureHelpParameter to *lsproto.ParameterInformation + parameters := make([]*lsproto.ParameterInformation, len(item.Parameters)) + for i, param := range item.Parameters { + parameters[i] = param.parameterInfo + } + signatureInformation := []*lsproto.SignatureInformation{ + { + Label: item.Label, + Documentation: nil, + Parameters: ¶meters, + }, + } + + var activeParameter *lsproto.Nullable[uint32] + if argumentInfo.argumentIndex == nil { + if clientOptions.SignatureInformation.NoActiveParameterSupport != nil && *clientOptions.SignatureInformation.NoActiveParameterSupport { + activeParameter = nil + } else { + activeParameter = ptrTo(lsproto.ToNullable(uint32(0))) + } + } else { + activeParameter = ptrTo(lsproto.ToNullable(uint32(*argumentInfo.argumentIndex))) + } + return &lsproto.SignatureHelp{ + Signatures: signatureInformation, + ActiveSignature: ptrTo(uint32(0)), + ActiveParameter: activeParameter, + } +} + +func getTypeHelpItem(symbol *ast.Symbol, typeParameter []*checker.Type, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, c *checker.Checker) signatureInformation { + printer := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, nil) + + parameters := make([]signatureHelpParameter, len(typeParameter)) + for i, typeParam := range typeParameter { + parameters[i] = createSignatureHelpParameterForTypeParameter(typeParam, sourceFile, enclosingDeclaration, c, printer) + } + + // Creating display label + var displayParts strings.Builder + displayParts.WriteString(c.SymbolToString(symbol)) + if len(parameters) != 0 { + displayParts.WriteString(scanner.TokenToString(ast.KindLessThanToken)) + for i, typeParameter := range parameters { + if i > 0 { + displayParts.WriteString(", ") + } + displayParts.WriteString(*typeParameter.parameterInfo.Label.String) + } + displayParts.WriteString(scanner.TokenToString(ast.KindGreaterThanToken)) + } + + return signatureInformation{ + Label: displayParts.String(), + Documentation: nil, + Parameters: parameters, + IsVariadic: false, + } +} + +func createSignatureHelpItems(candidates []*checker.Signature, resolvedSignature *checker.Signature, argumentInfo *argumentListInfo, sourceFile *ast.SourceFile, c *checker.Checker, useFullPrefix bool, clientOptions *lsproto.SignatureHelpClientCapabilities) *lsproto.SignatureHelp { + enclosingDeclaration := getEnclosingDeclarationFromInvocation(argumentInfo.invocation) + if enclosingDeclaration == nil { + return nil + } + var callTargetSymbol *ast.Symbol + if argumentInfo.invocation.contextualInvocation != nil { + callTargetSymbol = argumentInfo.invocation.contextualInvocation.symbol + } else { + callTargetSymbol = c.GetSymbolAtLocation(getExpressionFromInvocation(argumentInfo)) + if callTargetSymbol == nil && useFullPrefix && resolvedSignature.Declaration() != nil { + callTargetSymbol = resolvedSignature.Declaration().Symbol() + } + } + + var callTargetDisplayParts strings.Builder + if callTargetSymbol != nil { + callTargetDisplayParts.WriteString(c.SymbolToString(callTargetSymbol)) + } + items := make([][]signatureInformation, len(candidates)) + for i, candidateSignature := range candidates { + items[i] = getSignatureHelpItem(candidateSignature, argumentInfo.isTypeParameterList, callTargetDisplayParts.String(), enclosingDeclaration, sourceFile, c) + } + + selectedItemIndex := 0 + itemSeen := 0 + for i := range items { + item := items[i] + if (candidates)[i] == resolvedSignature { + selectedItemIndex = itemSeen + if len(item) > 1 { + count := 0 + for _, j := range item { + if j.IsVariadic || len(j.Parameters) >= argumentInfo.argumentCount { + selectedItemIndex = itemSeen + count + break + } + count++ + } + } + } + itemSeen = itemSeen + len(item) + } + + // Debug.assert(selectedItemIndex !== -1) + flattenedSignatures := []signatureInformation{} + for _, item := range items { + flattenedSignatures = append(flattenedSignatures, item...) + } + if len(flattenedSignatures) == 0 { + return nil + } + + // Converting []signatureInformation to []*lsproto.SignatureInformation + signatureInformation := make([]*lsproto.SignatureInformation, len(flattenedSignatures)) + for i, item := range flattenedSignatures { + parameters := make([]*lsproto.ParameterInformation, len(item.Parameters)) + for j, param := range item.Parameters { + parameters[j] = param.parameterInfo + } + signatureInformation[i] = &lsproto.SignatureInformation{ + Label: item.Label, + Documentation: nil, + Parameters: ¶meters, + } + } + + var activeParameter *lsproto.Nullable[uint32] + if argumentInfo.argumentIndex == nil { + if clientOptions.SignatureInformation.NoActiveParameterSupport != nil && *clientOptions.SignatureInformation.NoActiveParameterSupport { + activeParameter = nil + } + } else { + activeParameter = ptrTo(lsproto.ToNullable(uint32(*argumentInfo.argumentIndex))) + } + help := &lsproto.SignatureHelp{ + Signatures: signatureInformation, + ActiveSignature: ptrTo(uint32(selectedItemIndex)), + ActiveParameter: activeParameter, + } + + activeSignature := flattenedSignatures[selectedItemIndex] + if activeSignature.IsVariadic { + firstRest := core.FindIndex(activeSignature.Parameters, func(p signatureHelpParameter) bool { + return p.isRest + }) + if -1 < firstRest && firstRest < len(activeSignature.Parameters)-1 { + // We don't have any code to get this correct; instead, don't highlight a current parameter AT ALL + help.ActiveParameter = ptrTo(lsproto.ToNullable(uint32(len(activeSignature.Parameters)))) + } + if help.ActiveParameter != nil && *&help.ActiveParameter.Value > uint32(len(activeSignature.Parameters)-1) { + help.ActiveParameter = ptrTo(lsproto.ToNullable(uint32(len(activeSignature.Parameters) - 1))) + } + } + return help +} + +func getSignatureHelpItem(candidate *checker.Signature, isTypeParameterList bool, callTargetSymbol string, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, c *checker.Checker) []signatureInformation { + var infos []*signatureHelpItemInfo + if isTypeParameterList { + infos = itemInfoForTypeParameters(candidate, c, enclosingDeclaration, sourceFile) + } else { + infos = itemInfoForParameters(candidate, c, enclosingDeclaration, sourceFile) + } + + suffixDisplayParts := returnTypeToDisplayParts(candidate, c) + + result := make([]signatureInformation, len(infos)) + for i, info := range infos { + var display strings.Builder + display.WriteString(callTargetSymbol) + display.WriteString(info.displayParts) + display.WriteString(suffixDisplayParts) + result[i] = signatureInformation{ + Label: display.String(), + Documentation: nil, + Parameters: info.parameters, + IsVariadic: info.isVariadic, + } + } + return result +} + +func returnTypeToDisplayParts(candidateSignature *checker.Signature, c *checker.Checker) string { + var returnType strings.Builder + returnType.WriteString(": ") + predicate := c.GetTypePredicateOfSignature(candidateSignature) + if predicate != nil { + returnType.WriteString(c.TypePredicateToString(predicate)) + } else { + returnType.WriteString(c.TypeToString(c.GetReturnTypeOfSignature(candidateSignature))) + } + return returnType.String() +} + +func itemInfoForTypeParameters(candidateSignature *checker.Signature, c *checker.Checker, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile) []*signatureHelpItemInfo { + printer := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, nil) + + var typeParameters []*checker.Type + if candidateSignature.Target() != nil { + typeParameters = candidateSignature.Target().TypeParameters() + } else { + typeParameters = candidateSignature.TypeParameters() + } + signatureHelpTypeParameters := make([]signatureHelpParameter, len(typeParameters)) + for i, typeParameter := range typeParameters { + signatureHelpTypeParameters[i] = createSignatureHelpParameterForTypeParameter(typeParameter, sourceFile, enclosingDeclaration, c, printer) + } + + thisParameter := []signatureHelpParameter{} + if candidateSignature.ThisParameter() != nil { + thisParameter = []signatureHelpParameter{createSignatureHelpParameterForParameter(candidateSignature.ThisParameter(), enclosingDeclaration, printer, sourceFile, c)} + } + + // Creating type parameter display label + var displayParts strings.Builder + displayParts.WriteString(scanner.TokenToString(ast.KindLessThanToken)) + for i, typeParameter := range signatureHelpTypeParameters { + if i > 0 { + displayParts.WriteString(", ") + } + displayParts.WriteString(*typeParameter.parameterInfo.Label.String) + } + displayParts.WriteString(scanner.TokenToString(ast.KindGreaterThanToken)) + + // Creating display label for parameters like, (a: string, b: number) + lists := c.GetExpandedParameters(candidateSignature, false) + if len(lists) != 0 { + displayParts.WriteString(scanner.TokenToString(ast.KindOpenParenToken)) + } + + result := make([]*signatureHelpItemInfo, len(lists)) + for i, parameterList := range lists { + var displayParameters strings.Builder + displayParameters.WriteString(displayParts.String()) + parameters := thisParameter + for j, param := range parameterList { + parameter := createSignatureHelpParameterForParameter(param, enclosingDeclaration, printer, sourceFile, c) + parameters = append(parameters, parameter) + if j > 0 { + displayParameters.WriteString(", ") + } + displayParameters.WriteString(*parameter.parameterInfo.Label.String) + } + displayParameters.WriteString(scanner.TokenToString(ast.KindCloseParenToken)) + + result[i] = &signatureHelpItemInfo{ + isVariadic: false, + parameters: signatureHelpTypeParameters, + displayParts: displayParameters.String(), + } + } + return result +} + +func itemInfoForParameters(candidateSignature *checker.Signature, c *checker.Checker, enclosingDeclaratipn *ast.Node, sourceFile *ast.SourceFile) []*signatureHelpItemInfo { + printer := printer.NewPrinter(printer.PrinterOptions{NewLine: core.NewLineKindLF}, printer.PrintHandlers{}, nil) + + signatureHelpTypeParameters := make([]signatureHelpParameter, len(candidateSignature.TypeParameters())) + if len(candidateSignature.TypeParameters()) != 0 { + for i, typeParameter := range candidateSignature.TypeParameters() { + signatureHelpTypeParameters[i] = createSignatureHelpParameterForTypeParameter(typeParameter, sourceFile, enclosingDeclaratipn, c, printer) + } + } + + // Creating display label for type parameters like, + var displayParts strings.Builder + if len(signatureHelpTypeParameters) != 0 { + displayParts.WriteString(scanner.TokenToString(ast.KindLessThanToken)) + for _, typeParameter := range signatureHelpTypeParameters { + displayParts.WriteString(*typeParameter.parameterInfo.Label.String) + } + displayParts.WriteString(scanner.TokenToString(ast.KindGreaterThanToken)) + } + + // Creating display parts for parameters. For example, (a: string, b: number) + lists := c.GetExpandedParameters(candidateSignature, false) + if len(lists) != 0 { + displayParts.WriteString(scanner.TokenToString(ast.KindOpenParenToken)) + } + + isVariadic := func(parameterList []*ast.Symbol) bool { + if !c.HasEffectiveRestParameter(candidateSignature) { + return false + } + if len(lists) == 1 { + return true + } + return len(parameterList) != 0 && parameterList[len(parameterList)-1] != nil && (parameterList[len(parameterList)-1].CheckFlags&ast.CheckFlagsRestParameter != 0) + } + + result := make([]*signatureHelpItemInfo, len(lists)) + for i, parameterList := range lists { + parameters := make([]signatureHelpParameter, len(parameterList)) + var displayParameters strings.Builder + displayParameters.WriteString(displayParts.String()) + for j, param := range parameterList { + parameter := createSignatureHelpParameterForParameter(param, enclosingDeclaratipn, printer, sourceFile, c) + parameters[j] = parameter + if j > 0 { + displayParameters.WriteString(", ") + } + displayParameters.WriteString(*parameter.parameterInfo.Label.String) + } + displayParameters.WriteString(scanner.TokenToString(ast.KindCloseParenToken)) + + result[i] = &signatureHelpItemInfo{ + isVariadic: isVariadic(parameterList), + parameters: parameters, + displayParts: displayParameters.String(), + } + + } + return result +} + +const signatureHelpNodeBuilderFlags = nodebuilder.FlagsOmitParameterModifiers | nodebuilder.FlagsIgnoreErrors | nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope + +func createSignatureHelpParameterForParameter(parameter *ast.Symbol, enclosingDeclaratipn *ast.Node, p *printer.Printer, sourceFile *ast.SourceFile, c *checker.Checker) signatureHelpParameter { + display := p.Emit(checker.NewNodeBuilder(c, printer.NewEmitContext()).SymbolToParameterDeclaration(parameter, enclosingDeclaratipn, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil), sourceFile) + isOptional := parameter.CheckFlags&ast.CheckFlagsOptionalParameter != 0 + isRest := parameter.CheckFlags&ast.CheckFlagsRestParameter != 0 + return signatureHelpParameter{ + parameterInfo: &lsproto.ParameterInformation{ + Label: lsproto.StringOrTuple{String: &display}, + Documentation: nil, + }, + isRest: isRest, + isOptional: isOptional, + } +} + +func createSignatureHelpParameterForTypeParameter(t *checker.Type, sourceFile *ast.SourceFile, enclosingDeclaration *ast.Node, c *checker.Checker, p *printer.Printer) signatureHelpParameter { + display := p.Emit(checker.NewNodeBuilder(c, printer.NewEmitContext()).TypeParameterToDeclaration(t, enclosingDeclaration, signatureHelpNodeBuilderFlags, nodebuilder.InternalFlagsNone, nil), sourceFile) + return signatureHelpParameter{ + parameterInfo: &lsproto.ParameterInformation{ + Label: lsproto.StringOrTuple{String: &display}, + }, + isRest: false, + isOptional: false, + } +} + +// Represents the signature of something callable. A signature +// can have a label, like a function-name, a doc-comment, and +// a set of parameters. +type signatureInformation struct { + // The Label of this signature. Will be shown in + // the UI. + Label string + // The human-readable doc-comment of this signature. Will be shown + // in the UI but can be omitted. + Documentation *string + // The Parameters of this signature. + Parameters []signatureHelpParameter + // Needed only here, not in lsp + IsVariadic bool +} + +type signatureHelpItemInfo struct { + isVariadic bool + parameters []signatureHelpParameter + displayParts string +} + +type signatureHelpParameter struct { + parameterInfo *lsproto.ParameterInformation + isRest bool + isOptional bool +} + +func getEnclosingDeclarationFromInvocation(invocation *invocation) *ast.Node { + if invocation.callInvocation != nil { + return invocation.callInvocation.node + } else if invocation.typeArgsInvocation != nil { + return invocation.typeArgsInvocation.called.AsNode() + } else { + return invocation.contextualInvocation.node + } +} + +func getExpressionFromInvocation(argumentInfo *argumentListInfo) *ast.Node { + if argumentInfo.invocation.callInvocation != nil { + return ast.GetInvokedExpression(argumentInfo.invocation.callInvocation.node) + } + return argumentInfo.invocation.typeArgsInvocation.called.AsNode() +} + +type candidateInfo struct { + candidates []*checker.Signature + resolvedSignature *checker.Signature +} + +type CandidateOrTypeInfo struct { + candidateInfo *candidateInfo + typeInfo *ast.Symbol +} + +func getCandidateOrTypeInfo(info *argumentListInfo, c *checker.Checker, sourceFile *ast.SourceFile, startingToken *ast.Node, onlyUseSyntacticOwners bool) *CandidateOrTypeInfo { + if info.invocation.callInvocation != nil { + if onlyUseSyntacticOwners && !isSyntacticOwner(startingToken, info.invocation.callInvocation.node, sourceFile) { + return nil + } + resolvedSignature, candidates := checker.GetResolvedSignatureForSignatureHelp(info.invocation.callInvocation.node, info.argumentCount, c) + return &CandidateOrTypeInfo{ + candidateInfo: &candidateInfo{ + candidates: candidates, + resolvedSignature: resolvedSignature, + }, + } + } + if info.invocation.typeArgsInvocation != nil { + called := info.invocation.typeArgsInvocation.called.AsNode() + container := called + if ast.IsIdentifier(called) { + container = called.Parent + } + if onlyUseSyntacticOwners && !containsPrecedingToken(startingToken, sourceFile, container) { + return nil + } + candidates := getPossibleGenericSignatures(called, info.argumentCount, c) + if len(candidates) != 0 { + return &CandidateOrTypeInfo{ + candidateInfo: &candidateInfo{ + candidates: candidates, + resolvedSignature: candidates[0], + }, + } + } + symbol := c.GetSymbolAtLocation(called) + return &CandidateOrTypeInfo{ + typeInfo: symbol, + } + } + if info.invocation.contextualInvocation != nil { + return &CandidateOrTypeInfo{ + candidateInfo: &candidateInfo{ + candidates: []*checker.Signature{info.invocation.contextualInvocation.signature}, + resolvedSignature: info.invocation.contextualInvocation.signature, + }, + } + } + return nil // return Debug.assertNever(invocation); +} + +func isSyntacticOwner(startingToken *ast.Node, node *ast.Node, sourceFile *ast.SourceFile) bool { // !!! not tested + if !ast.IsCallOrNewExpression(node) { + return false + } + invocationChildren := getTokensFromNode(node, sourceFile) + switch startingToken.Kind { + case ast.KindOpenParenToken: + return containsNode(invocationChildren, startingToken) + case ast.KindCommaToken: + return containsNode(invocationChildren, startingToken) + // !!! + // const containingList = findContainingList(startingToken); + // return !!containingList && contains(invocationChildren, containingList); + case ast.KindLessThanToken: + return containsPrecedingToken(startingToken, sourceFile, node.AsCallExpression().Expression) + default: + return false + } +} + +func containsPrecedingToken(startingToken *ast.Node, sourceFile *ast.SourceFile, container *ast.Node) bool { + pos := startingToken.Pos() + // 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. + currentParent := startingToken.Parent + for currentParent != nil { + precedingToken := astnav.FindPrecedingToken(sourceFile, pos) + if precedingToken != nil { + return RangeContainsRange(container.Loc, precedingToken.Loc) + } + currentParent = currentParent.Parent + } + // return Debug.fail("Could not find preceding token"); + return false +} + +func getContainingArgumentInfo(node *ast.Node, sourceFile *ast.SourceFile, checker *checker.Checker, isManuallyInvoked bool, position int) *argumentListInfo { + for n := node; !ast.IsSourceFile(n) && (isManuallyInvoked || !ast.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)}`); + argumentInfo := getImmediatelyContainingArgumentOrContextualParameterInfo(n, position, sourceFile, checker) + if argumentInfo != nil { + return argumentInfo + } + } + return nil +} + +func getImmediatelyContainingArgumentOrContextualParameterInfo(node *ast.Node, position int, sourceFile *ast.SourceFile, checker *checker.Checker) *argumentListInfo { + result := tryGetParameterInfo(node, sourceFile, checker) + if result == nil { + return getImmediatelyContainingArgumentInfo(node, position, sourceFile, checker) + } + return result +} + +type argumentListInfo struct { + isTypeParameterList bool + invocation *invocation + argumentsRange core.TextRange + argumentIndex *int + /** argumentCount is the *apparent* number of arguments. */ + argumentCount int +} + +// Returns relevant information for the argument list and the current argument if we are +// in the argument of an invocation; returns undefined otherwise. +func getImmediatelyContainingArgumentInfo(node *ast.Node, position int, sourceFile *ast.SourceFile, c *checker.Checker) *argumentListInfo { + parent := node.Parent + if ast.IsCallOrNewExpression(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, position, sourceFile, checker); + list, argumentIndex, argumentCount, argumentSpan := getArgumentOrParameterListInfo(node, sourceFile, c) + isTypeParameterList := false + parentTypeArgumentList := parent.TypeArgumentList() + if parentTypeArgumentList != nil { + if parentTypeArgumentList.Pos() == list.Pos() { + isTypeParameterList = true + } + } + return &argumentListInfo{ + isTypeParameterList: isTypeParameterList, + invocation: &invocation{callInvocation: &callInvocation{node: parent}}, + argumentsRange: argumentSpan, + argumentIndex: argumentIndex, + argumentCount: 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.AsTaggedTemplateExpression(), ptrTo(0), sourceFile) + } + return nil + } else if isTemplateHead(node) && parent.Parent.Kind == ast.KindTaggedTemplateExpression { + templateExpression := parent.AsTemplateExpression() + tagExpression := templateExpression.Parent.AsTaggedTemplateExpression() + + argumentIndex := ptrTo(1) + if isInsideTemplateLiteral(node, position, sourceFile) { + argumentIndex = ptrTo(0) + } + return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile) + } else if ast.IsTemplateSpan(parent) && isTaggedTemplateExpression(parent.Parent.Parent) { + templateSpan := parent + 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 nil + } + + spanIndex := ast.IndexOfNode(templateSpan.Parent.AsTemplateExpression().TemplateSpans.Nodes, templateSpan) + argumentIndex := getArgumentIndexForTemplatePiece(spanIndex, templateSpan, position, sourceFile) + + return getArgumentListInfoForTemplate(tagExpression.AsTaggedTemplateExpression(), argumentIndex, sourceFile) + } else if ast.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 { ... } + // = node.getStart(), "Assumed 'position' could not occur before node."); + if ast.IsTemplateLiteralToken(node) { + if isInsideTemplateLiteral(node, position, sourceFile) { + return ptrTo(0) + } + return ptrTo(spanIndex + 2) + } + return ptrTo(spanIndex + 1) +} + +func getAdjustedNode(node *ast.Node) *ast.Node { + switch node.Kind { + case ast.KindOpenParenToken, ast.KindCommaToken: + return node + default: + return ast.FindAncestor(node.Parent, func(n *ast.Node) bool { + if ast.IsParameter(n) { + return true + } else if ast.IsBindingElement(n) || ast.IsObjectBindingPattern(n) || ast.IsArrayBindingPattern(n) { + return false + } + return false + }) + } +} + +type contextualSignatureLocationInfo struct { + contextualType *checker.Type + argumentIndex *int + argumentCount int + argumentsSpan core.TextRange +} + +func getSpreadElementCount(node *ast.SpreadElement, c *checker.Checker) int { + spreadType := c.GetTypeAtLocation(node.Expression) + if checker.IsTupleType(spreadType) { + tupleType := spreadType.Target().AsTupleType() + if tupleType == nil { + return 0 + } + elementFlags := tupleType.ElementFlags() + fixedLength := tupleType.FixedLength() + if fixedLength == 0 { + return 0 + } + + firstOptionalIndex := core.FindIndex(elementFlags, func(f checker.ElementFlags) bool { + return (f&checker.ElementFlagsRequired == 0) + }) + if firstOptionalIndex < 0 { + return fixedLength + } + return firstOptionalIndex + } + return 0 +} + +func getArgumentIndex(node *ast.Node, arguments *ast.NodeList, sourceFile *ast.SourceFile, c *checker.Checker) *int { + return getArgumentIndexOrCount(getTokenFromNodeList(arguments, node.Parent, sourceFile), node, c) +} + +func getArgumentCount(node *ast.Node, arguments *ast.NodeList, sourceFile *ast.SourceFile, c *checker.Checker) int { + argumentCount := getArgumentIndexOrCount(getTokenFromNodeList(arguments, node.Parent, sourceFile), nil, c) + if argumentCount == nil { + return 0 + } + return *argumentCount +} + +func getArgumentIndexOrCount(arguments []*ast.Node, node *ast.Node, c *checker.Checker) *int { + var argumentIndex *int = nil + skipComma := false + for _, arg := range arguments { + if node != nil && arg == node { + if argumentIndex == nil { + argumentIndex = ptrTo(0) + } + if !skipComma && arg.Kind == ast.KindCommaToken { + *argumentIndex++ + } + return argumentIndex + } + if ast.IsSpreadElement(arg) { + if argumentIndex == nil { + argumentIndex = ptrTo(getSpreadElementCount(arg.AsSpreadElement(), c)) + } else { + argumentIndex = ptrTo(*argumentIndex + getSpreadElementCount(arg.AsSpreadElement(), c)) + } + skipComma = true + continue + } + if arg.Kind != ast.KindCommaToken { + if argumentIndex == nil { + argumentIndex = ptrTo(0) + } + *argumentIndex++ + skipComma = true + continue + } + if skipComma { + skipComma = false + continue + } + if argumentIndex == nil { + argumentIndex = ptrTo(0) + } + *argumentIndex++ + } + if node != nil { + return argumentIndex + } + // 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. + argumentCount := argumentIndex + if len(arguments) > 0 && arguments[len(arguments)-1].Kind == ast.KindCommaToken { + if argumentIndex == nil { + argumentIndex = ptrTo(0) + } + argumentCount = ptrTo(*argumentIndex + 1) + } + return argumentCount +} + +func getArgumentOrParameterListInfo(node *ast.Node, sourceFile *ast.SourceFile, c *checker.Checker) (*ast.NodeList, *int, int, core.TextRange) { + arguments, argumentIndex := getArgumentOrParameterListAndIndex(node, sourceFile, c) + argumentCount := getArgumentCount(node, arguments, sourceFile, c) + argumentSpan := getApplicableSpanForArguments(arguments, node, sourceFile) + return arguments, argumentIndex, argumentCount, argumentSpan +} + +func getApplicableSpanForArguments(argumentList *ast.NodeList, node *ast.Node, sourceFile *ast.SourceFile) core.TextRange { + // 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) + if argumentList == nil && node != nil { + // If the user has just opened a list, and there are no arguments. + // For example, foo( ) + // | | + return core.NewTextRange(node.End(), scanner.SkipTrivia(sourceFile.Text(), node.End())) + } + applicableSpanStart := argumentList.Pos() + applicableSpanEnd := scanner.SkipTrivia(sourceFile.Text(), argumentList.End()) + return core.NewTextRange(applicableSpanStart, applicableSpanEnd) +} + +func getArgumentOrParameterListAndIndex(node *ast.Node, sourceFile *ast.SourceFile, c *checker.Checker) (*ast.NodeList, *int) { + if node.Kind == ast.KindLessThanToken || node.Kind == ast.KindOpenParenToken { + // Find the list that starts right *after* the < or ( token. + // If the user has just opened a list, consider this item 0. + list := getChildListThatStartsWithOpenerToken(node.Parent, node) + return list, ptrTo(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 + var arguments *ast.NodeList + switch node.Parent.Kind { + case ast.KindCallExpression: + arguments = node.Parent.AsCallExpression().Arguments + case ast.KindNewExpression: + arguments = node.Parent.AsNewExpression().Arguments + case ast.KindParenthesizedExpression: + arguments = node.Parent.AsParenthesizedExpression().ExpressionBase.NodeBase.Node.ArgumentList() // !!! + case ast.KindMethodDeclaration: + arguments = node.Parent.AsMethodDeclaration().FunctionLikeWithBodyBase.Parameters + case ast.KindFunctionExpression: + arguments = node.Parent.AsFunctionExpression().FunctionLikeWithBodyBase.Parameters + case ast.KindArrowFunction: + arguments = node.Parent.AsArrowFunction().FunctionLikeWithBodyBase.Parameters + } + // Find the index of the argument that contains the node. + argumentIndex := getArgumentIndex(node, arguments, sourceFile, c) + return arguments, argumentIndex + } +} + +func getChildListThatStartsWithOpenerToken(parent *ast.Node, openerToken *ast.Node) *ast.NodeList { //!!! + if ast.IsCallExpression(parent) { + parentCallExpression := parent.AsCallExpression() + if openerToken.Kind == ast.KindLessThanToken { + return parentCallExpression.TypeArgumentList() + } + return parentCallExpression.Arguments + } else if ast.IsNewExpression(parent) { + parentNewExpression := parent.AsNewExpression() + if openerToken.Kind == ast.KindLessThanToken { + return parentNewExpression.TypeArgumentList() + } + return parentNewExpression.Arguments + } + return nil +} + +func tryGetParameterInfo(startingToken *ast.Node, sourceFile *ast.SourceFile, c *checker.Checker) *argumentListInfo { + node := getAdjustedNode(startingToken) + if node == nil { + return nil + } + info := getContextualSignatureLocationInfo(node, sourceFile, c) + if info == nil { + return nil + } + + // for optional function condition + nonNullableContextualType := c.GetNonNullableType(info.contextualType) + if nonNullableContextualType == nil { + return nil + } + + symbol := nonNullableContextualType.Symbol() + if symbol == nil { + return nil + } + + signatures := c.GetSignaturesOfType(nonNullableContextualType, checker.SignatureKindCall) + if signatures == nil || signatures[len(signatures)-1] == nil { + return nil + } + signature := signatures[len(signatures)-1] + + contextualInvocation := &contextualInvocation{ + signature: signature, + node: startingToken, + symbol: chooseBetterSymbol(symbol), + } + return &argumentListInfo{ + isTypeParameterList: false, + invocation: &invocation{contextualInvocation: contextualInvocation}, + argumentsRange: info.argumentsSpan, + argumentIndex: info.argumentIndex, + argumentCount: info.argumentCount, + } +} + +func chooseBetterSymbol(s *ast.Symbol) *ast.Symbol { + if s.Name == ast.InternalSymbolNameType { + for _, d := range s.Declarations { + if ast.IsFunctionTypeNode(d) && ast.CanHaveSymbol(d.Parent) { + return d.Parent.Symbol() + } + } + } + return s +} + +func getContextualSignatureLocationInfo(node *ast.Node, sourceFile *ast.SourceFile, c *checker.Checker) *contextualSignatureLocationInfo { + parent := node.Parent + switch parent.Kind { + case ast.KindParenthesizedExpression, ast.KindMethodDeclaration, ast.KindFunctionExpression, ast.KindArrowFunction: + _, argumentIndex, argumentCount, argumentSpan := getArgumentOrParameterListInfo(node, sourceFile, c) + + var contextualType *checker.Type + if ast.IsMethodDeclaration(parent) { + contextualType = c.GetContextualTypeForObjectLiteralElement(parent, checker.ContextFlagsNone) + } else { + contextualType = c.GetContextualType(parent, checker.ContextFlagsNone) + } + if contextualType != nil { + return &contextualSignatureLocationInfo{ + contextualType: contextualType, + argumentIndex: argumentIndex, + argumentCount: argumentCount, + argumentsSpan: argumentSpan, + } + } + return nil + case ast.KindBinaryExpression: + highestBinary := getHighestBinary(parent.AsBinaryExpression()) + contextualType := c.GetContextualType(highestBinary.AsNode(), checker.ContextFlagsNone) + argumentIndex := ptrTo(0) + if node.Kind != ast.KindOpenParenToken { + argumentIndex = ptrTo(countBinaryExpressionParameters(parent.AsBinaryExpression()) - 1) + argumentCount := countBinaryExpressionParameters(highestBinary) + if contextualType != nil { + return &contextualSignatureLocationInfo{ + contextualType: contextualType, + argumentIndex: argumentIndex, + argumentCount: argumentCount, + argumentsSpan: core.NewTextRange(parent.Pos(), parent.End()), + } + } + return nil + } + } + return nil +} + +func getHighestBinary(b *ast.BinaryExpression) *ast.BinaryExpression { + if ast.IsBinaryExpression(b.Parent) { + return getHighestBinary(b.Parent.AsBinaryExpression()) + } + return b +} + +func countBinaryExpressionParameters(b *ast.BinaryExpression) int { + if ast.IsBinaryExpression(b.Left) { + return countBinaryExpressionParameters(b.Left.AsBinaryExpression()) + 1 + } + return 2 +} + +func getTokensFromNode(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node { + if node == nil { + return nil + } + var children []*ast.Node + current := node + left := node.Pos() + scanner := scanner.GetScannerForSourceFile(sourceFile, left) + for left < current.End() { + token := scanner.Token() + tokenFullStart := scanner.TokenFullStart() + tokenEnd := scanner.TokenEnd() + children = append(children, sourceFile.GetOrCreateToken(token, tokenFullStart, tokenEnd, current)) + left = tokenEnd + scanner.Scan() + } + return children +} + +func getTokenFromNodeList(nodeList *ast.NodeList, nodeListParent *ast.Node, sourceFile *ast.SourceFile) []*ast.Node { + if nodeList == nil || nodeListParent == nil { + return nil + } + left := nodeList.Pos() + nodeListIndex := 0 + var tokens []*ast.Node + for left < nodeList.End() { + if len(nodeList.Nodes) > nodeListIndex && left == nodeList.Nodes[nodeListIndex].Pos() { + tokens = append(tokens, nodeList.Nodes[nodeListIndex]) + left = nodeList.Nodes[nodeListIndex].End() + nodeListIndex++ + } else { + scanner := scanner.GetScannerForSourceFile(sourceFile, left) + token := scanner.Token() + tokenFullStart := scanner.TokenFullStart() + tokenEnd := scanner.TokenEnd() + tokens = append(tokens, sourceFile.GetOrCreateToken(token, tokenFullStart, tokenEnd, nodeListParent)) + left = tokenEnd + } + } + return tokens +} + +func containsNode(nodes []*ast.Node, node *ast.Node) bool { + for i := range nodes { + if nodes[i] == node { + return true + } + } + return false +} + +func getArgumentListInfoForTemplate(tagExpression *ast.TaggedTemplateExpression, argumentIndex *int, sourceFile *ast.SourceFile) *argumentListInfo { + // argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument. + argumentCount := 1 + if !isNoSubstitutionTemplateLiteral(tagExpression.Template) { + argumentCount = len(tagExpression.Template.AsTemplateExpression().TemplateSpans.Nodes) + 1 + } + // if (argumentIndex !== 0) { + // Debug.assertLessThan(argumentIndex, argumentCount); + // } + return &argumentListInfo{ + isTypeParameterList: false, + invocation: &invocation{callInvocation: &callInvocation{node: tagExpression.AsNode()}}, + argumentIndex: argumentIndex, + argumentCount: argumentCount, + argumentsRange: getApplicableRangeForTaggedTemplate(tagExpression, sourceFile), + } +} + +func getApplicableRangeForTaggedTemplate(taggedTemplate *ast.TaggedTemplateExpression, sourceFile *ast.SourceFile) core.TextRange { + template := taggedTemplate.Template + applicableSpanStart := scanner.GetTokenPosOfNode(template, sourceFile, false) + applicableSpanEnd := template.End() + + // 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 == ast.KindTemplateExpression { + templateSpans := template.AsTemplateExpression().TemplateSpans + lastSpan := templateSpans.Nodes[len(templateSpans.Nodes)-1] + if lastSpan.AsTemplateSpan().Literal.End()-lastSpan.AsTemplateSpan().Literal.Pos() == 0 { + applicableSpanEnd = scanner.SkipTrivia(sourceFile.Text(), applicableSpanEnd) + } + } + + return core.NewTextRange(applicableSpanStart, applicableSpanEnd-applicableSpanStart) +} diff --git a/internal/ls/signaturehelp_test.go b/internal/ls/signaturehelp_test.go new file mode 100644 index 0000000000..72f4323d01 --- /dev/null +++ b/internal/ls/signaturehelp_test.go @@ -0,0 +1,1057 @@ +package ls_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/bundled" + "github.com/microsoft/typescript-go/internal/ls" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil/lstestutil" + "github.com/microsoft/typescript-go/internal/testutil/projecttestutil" + "gotest.tools/v3/assert" +) + +type verifySignatureHelpOptions struct { + docComment string + text string + parameterSpan string + parameterCount int + activeParameter *lsproto.Nullable[uint32] + // triggerReason ls.SignatureHelpTriggerReason + // tags?: ReadonlyArray; +} + +func TestSignatureHelp(t *testing.T) { + t.Parallel() + if !bundled.Embedded { + // Without embedding, we'd need to read all of the lib files out from disk into the MapFS. + // Just skip this for now. + t.Skip("bundled files are not embedded") + } + + testCases := []struct { + title string + input string + expected map[string]verifySignatureHelpOptions + }{ + { + title: "SignatureHelpCallExpressions", + input: `function fnTest(str: string, num: number) { } +fnTest(/*1*/'', /*2*/5);`, + expected: map[string]verifySignatureHelpOptions{ + "1": { + text: `fnTest(str: string, num: number): void`, + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "str: string", + }, + "2": { + text: `fnTest(str: string, num: number): void`, + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 1}, + parameterSpan: "num: number", + }, + }, + }, + { + title: "SignatureHelp_contextual", + input: `interface I { + m(n: number, s: string): void; + m2: () => void; +} +declare function takesObj(i: I): void; +takesObj({ m: (/*takesObj0*/) }); +takesObj({ m(/*takesObj1*/) }); +takesObj({ m: function(/*takesObj2*/) }); +takesObj({ m2: (/*takesObj3*/) }) +declare function takesCb(cb: (n: number, s: string, b: boolean) => void): void; +takesCb((/*contextualParameter1*/)); +takesCb((/*contextualParameter1b*/) => {}); +takesCb((n, /*contextualParameter2*/)); +takesCb((n, s, /*contextualParameter3*/)); +takesCb((n,/*contextualParameter3_2*/ s, b)); +takesCb((n, s, b, /*contextualParameter4*/)) +type Cb = () => void; +const cb: Cb = (/*contextualTypeAlias*/ +const cb2: () => void = (/*contextualFunctionType*/)`, + expected: map[string]verifySignatureHelpOptions{ + "takesObj0": { + text: "m(n: number, s: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "n: number", + }, + "takesObj1": { + text: "m(n: number, s: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "n: number", + }, + "takesObj2": { + text: "m(n: number, s: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "n: number", + }, + "takesObj3": { + text: "m2(): void", + parameterCount: 0, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "", + }, + "contextualParameter1": { + text: "cb(n: number, s: string, b: boolean): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "n: number", + }, + "contextualParameter1b": { + text: "cb(n: number, s: string, b: boolean): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "n: number", + }, + "contextualParameter2": { + text: "cb(n: number, s: string, b: boolean): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 1}, + parameterSpan: "s: string", + }, + "contextualParameter3": { + text: "cb(n: number, s: string, b: boolean): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 2}, + parameterSpan: "b: boolean", + }, + "contextualParameter3_2": { + text: "cb(n: number, s: string, b: boolean): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 1}, + parameterSpan: "s: string", + }, + "contextualParameter4": { + text: "cb(n: number, s: string, b: boolean): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 3}, + parameterSpan: "", + }, + "contextualTypeAlias": { + text: "Cb(): void", + parameterCount: 0, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "", + }, + "contextualFunctionType": { + text: "cb2(): void", + parameterCount: 0, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "", + }, + }, + }, + { + title: "signatureHelpAnonymousFunction", + input: `var anonymousFunctionTest = function(n: number, s: string): (a: number, b: string) => string { + return null; +} +anonymousFunctionTest(5, "")(/*anonymousFunction1*/1, /*anonymousFunction2*/"");`, + expected: map[string]verifySignatureHelpOptions{ + "anonymousFunction1": { + text: `(a: number, b: string): string`, + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "a: number", + }, + "anonymousFunction2": { + text: `(a: number, b: string): string`, + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 1}, + parameterSpan: "b: string", + }, + }, + }, + { + title: "signatureHelpAtEOFs", + input: `function Foo(arg1: string, arg2: string) { +} + +Foo(/**/`, + expected: map[string]verifySignatureHelpOptions{ + "": { + text: "Foo(arg1: string, arg2: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "arg1: string", + }, + }, + }, + { + title: "signatureHelpBeforeSemicolon1", + input: `function Foo(arg1: string, arg2: string) { +} + +Foo(/**/;`, + expected: map[string]verifySignatureHelpOptions{ + "": { + text: "Foo(arg1: string, arg2: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "arg1: string", + }, + }, + }, + { + title: "signatureHelpCallExpression", + input: `function fnTest(str: string, num: number) { } +fnTest(/*1*/'', /*2*/5);`, + expected: map[string]verifySignatureHelpOptions{ + "1": { + text: `fnTest(str: string, num: number): void`, + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "str: string", + }, + "2": { + text: `fnTest(str: string, num: number): void`, + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 1}, + parameterSpan: "num: number", + }, + }, + }, + { + title: "signatureHelpConstructExpression", + input: `class sampleCls { constructor(str: string, num: number) { } } +var x = new sampleCls(/*1*/"", /*2*/5);`, + expected: map[string]verifySignatureHelpOptions{ + "1": { + text: "sampleCls(str: string, num: number): sampleCls", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "str: string", + }, + "2": { + text: "sampleCls(str: string, num: number): sampleCls", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 1}, + parameterSpan: "num: number", + }, + }, + }, + { + title: "signatureHelpConstructorInheritance", + input: `class base { +constructor(s: string); +constructor(n: number); +constructor(a: any) { } +} +class B1 extends base { } +class B2 extends B1 { } +class B3 extends B2 { + constructor() { + super(/*indirectSuperCall*/3); + } +}`, + expected: map[string]verifySignatureHelpOptions{ + "indirectSuperCall": { + text: "B2(n: number): B2", + parameterCount: 1, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "n: number", + }, + }, + }, + { + title: "signatureHelpConstructorOverload", + input: `class clsOverload { constructor(); constructor(test: string); constructor(test?: string) { } } +var x = new clsOverload(/*1*/); +var y = new clsOverload(/*2*/'');`, + expected: map[string]verifySignatureHelpOptions{ + "1": { + text: "clsOverload(): clsOverload", + parameterCount: 0, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + }, + "2": { + text: "clsOverload(test: string): clsOverload", + parameterCount: 1, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "test: string", + }, + }, + }, + { + title: "signatureHelpEmptyLists", + input: `function Foo(arg1: string, arg2: string) { + } + + Foo(/*1*/); + function Bar(arg1: string, arg2: string) { } + Bar();`, + expected: map[string]verifySignatureHelpOptions{ + "1": { + text: "Foo(arg1: string, arg2: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "arg1: string", + }, + "2": { + text: "Bar(arg1: string, arg2: string): void", + parameterCount: 1, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "T", + }, + }, + }, + { + title: "signatureHelpExpandedRestTuples", + input: `export function complex(item: string, another: string, ...rest: [] | [settings: object, errorHandler: (err: Error) => void] | [errorHandler: (err: Error) => void, ...mixins: object[]]) { + +} + +complex(/*1*/); +complex("ok", "ok", /*2*/); +complex("ok", "ok", e => void e, {}, /*3*/);`, + + expected: map[string]verifySignatureHelpOptions{ + "1": { + text: "complex(item: string, another: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "item: string", + }, + "2": { + text: "complex(item: string, another: string, settings: object, errorHandler: (err: Error) => void): void", + parameterCount: 4, + activeParameter: &lsproto.Nullable[uint32]{Value: 2}, + parameterSpan: "settings: object", + }, + "3": { + text: "complex(item: string, another: string, errorHandler: (err: Error) => void, ...mixins: object[]): void", + parameterCount: 4, + activeParameter: &lsproto.Nullable[uint32]{Value: 3}, + parameterSpan: "...mixins: object[]", + }, + }, + }, + { + title: "signatureHelpExpandedRestUnlabeledTuples", + input: `export function complex(item: string, another: string, ...rest: [] | [object, (err: Error) => void] | [(err: Error) => void, ...object[]]) { + +} + +complex(/*1*/); +complex("ok", "ok", /*2*/); +complex("ok", "ok", e => void e, {}, /*3*/);`, + expected: map[string]verifySignatureHelpOptions{ + "1": { + text: "complex(item: string, another: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "item: string", + }, + "2": { + text: "complex(item: string, another: string, rest_0: object, rest_1: (err: Error) => void): void", + parameterCount: 4, + activeParameter: &lsproto.Nullable[uint32]{Value: 2}, + parameterSpan: "rest_0: object", + }, + "3": { + text: "complex(item: string, another: string, rest_0: (err: Error) => void, ...rest: object[]): void", + parameterCount: 4, + activeParameter: &lsproto.Nullable[uint32]{Value: 3}, + parameterSpan: "...rest: object[]", + }, + }, + }, + { + title: "signatureHelpExpandedTuplesArgumentIndex", + input: `function foo(...args: [string, string] | [number, string, string] +) { + +} +foo(123/*1*/,) +foo(""/*2*/, ""/*3*/) +foo(123/*4*/, ""/*5*/, ) +foo(123/*6*/, ""/*7*/, ""/*8*/)`, + expected: map[string]verifySignatureHelpOptions{ + "1": { + text: "foo(args_0: string, args_1: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "args_0: string", + }, + "2": { + text: "foo(args_0: string, args_1: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "args_0: string", + }, + "3": { + text: "foo(args_0: string, args_1: string): void", + parameterCount: 2, + activeParameter: &lsproto.Nullable[uint32]{Value: 1}, + parameterSpan: "args_1: string", + }, + "4": { + text: "foo(args_0: number, args_1: string, args_2: string): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "args_0: number", + }, + "5": { + text: "foo(args_0: number, args_1: string, args_2: string): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 1}, + parameterSpan: "args_1: string", + }, + "6": { + text: "foo(args_0: number, args_1: string, args_2: string): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 0}, + parameterSpan: "args_0: number", + }, + "7": { + text: "foo(args_0: number, args_1: string, args_2: string): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 1}, + parameterSpan: "args_1: string", + }, + "8": { + text: "foo(args_0: number, args_1: string, args_2: string): void", + parameterCount: 3, + activeParameter: &lsproto.Nullable[uint32]{Value: 2}, + parameterSpan: "args_2: string", + }, + }, + }, + { + title: "signatureHelpExplicitTypeArguments", + input: `declare function f(x: T, y: U): T; +f(/*1*/); +f(/*2*/); +f(/*3*/); +f(/*4*/); + +interface A { a: number } +interface B extends A { b: string } +declare function g(x: T, y: U, z: V): T; +declare function h(x: T, y: U, z: V): T; +declare function j(x: T, y: U, z: V): T; +g(/*5*/); +h(/*6*/); +j(/*7*/); +g(/*8*/); +h(/*9*/); +j(/*10*/);`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "f(x: number, y: string): number", parameterCount: 2, activeParameter: &lsproto.Nullable[uint32]{Value: 0}, parameterSpan: "x: number"}, + "2": {text: "f(x: boolean, y: string): boolean", parameterCount: 2, parameterSpan: "x: boolean", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + // too few -- fill in rest with default + "3": {text: "f(x: number, y: string): number", parameterCount: 2, parameterSpan: "x: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + // too many -- ignore extra type arguments + "4": {text: "f(x: number, y: string): number", parameterCount: 2, parameterSpan: "x: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + + // not matched signature and no type arguments + "5": {text: "g(x: unknown, y: unknown, z: B): unknown", parameterCount: 3, parameterSpan: "x: unknown", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "6": {text: "h(x: unknown, y: unknown, z: A): unknown", parameterCount: 3, parameterSpan: "x: unknown", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "7": {text: "j(x: unknown, y: unknown, z: B): unknown", parameterCount: 3, parameterSpan: "x: unknown", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + // not matched signature and too few type arguments + "8": {text: "g(x: number, y: unknown, z: B): number", parameterCount: 3, parameterSpan: "x: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "9": {text: "h(x: number, y: unknown, z: A): number", parameterCount: 3, parameterSpan: "x: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "10": {text: "j(x: number, y: unknown, z: B): number", parameterCount: 3, parameterSpan: "x: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpForOptionalMethods", + input: `interface Obj { + optionalMethod?: (current: any) => any; +}; + +const o: Obj = { + optionalMethod(/*1*/) { + return {}; + } +};`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "optionalMethod(current: any): any", parameterCount: 1, parameterSpan: "current: any", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpForSuperCalls", + input: `class A { } +class B extends A { } +class C extends B { + constructor() { + super(/*1*/ // sig help here? + } +} +class A2 { } +class B2 extends A2 { + constructor(x:number) {} +} +class C2 extends B2 { + constructor() { + super(/*2*/ // sig help here? + } +}`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "B(): B", parameterCount: 0, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "2": {text: "B2(x: number): B2", parameterCount: 1, parameterSpan: "x: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpFunctionOverload", + input: `function functionOverload(); +function functionOverload(test: string); +function functionOverload(test?: string) { } +functionOverload(/*functionOverload1*/); +functionOverload(""/*functionOverload2*/);`, + expected: map[string]verifySignatureHelpOptions{ + "functionOverload1": {text: "functionOverload(): any", parameterCount: 0, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "functionOverload2": {text: "functionOverload(test: string): any", parameterCount: 1, parameterSpan: "test: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpFunctionParameter", + input: `function parameterFunction(callback: (a: number, b: string) => void) { + callback(/*parameterFunction1*/5, /*parameterFunction2*/""); +}`, + expected: map[string]verifySignatureHelpOptions{ + "parameterFunction1": {text: "callback(a: number, b: string): void", parameterCount: 2, parameterSpan: "a: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "parameterFunction2": {text: "callback(a: number, b: string): void", parameterCount: 2, parameterSpan: "b: string", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpImplicitConstructor", + input: `class ImplicitConstructor { +} +var implicitConstructor = new ImplicitConstructor(/*1*/);`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "ImplicitConstructor(): ImplicitConstructor", parameterCount: 0, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpInCallback", + input: `declare function forEach(f: () => void); +forEach(/*1*/() => { +});`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "forEach(f: () => void): any", parameterCount: 1, parameterSpan: "f: () => void", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpIncompleteCalls", + input: `module IncompleteCalls { + class Foo { + public f1() { } + public f2(n: number): number { return 0; } + public f3(n: number, s: string) : string { return ""; } + } + var x = new Foo(); + x.f1(); + x.f2(5); + x.f3(5, ""); + x.f1(/*incompleteCalls1*/ + x.f2(5,/*incompleteCalls2*/ + x.f3(5,/*incompleteCalls3*/ +}`, + expected: map[string]verifySignatureHelpOptions{ + "incompleteCalls1": {text: "f1(): void", parameterCount: 0, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "incompleteCalls2": {text: "f2(n: number): number", parameterCount: 1, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "incompleteCalls3": {text: "f3(n: number, s: string): string", parameterCount: 2, parameterSpan: "s: string", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpCompleteGenericsCall", + input: `function foo(x: number, callback: (x: T) => number) { +} +foo(/*1*/`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "foo(x: number, callback: (x: unknown) => number): void", parameterCount: 2, parameterSpan: "x: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpInference", + input: `declare function f(a: T, b: T, c: T): void; +f("x", /**/);`, + expected: map[string]verifySignatureHelpOptions{ + "": {text: `f(a: "x", b: "x", c: "x"): void`, parameterCount: 3, parameterSpan: `b: "x"`, activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpInParenthetical", + input: `class base { constructor (public n: number, public y: string) { } } +(new base(/*1*/ +(new base(0, /*2*/`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "base(n: number, y: string): base", parameterCount: 2, parameterSpan: "n: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "2": {text: "base(n: number, y: string): base", parameterCount: 2, parameterSpan: "y: string", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpLeadingRestTuple", + input: `export function leading(...args: [...names: string[], allCaps: boolean]): void { +} + +leading(/*1*/); +leading("ok", /*2*/); +leading("ok", "ok", /*3*/);`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "leading(...names: string[], allCaps: boolean): void", parameterCount: 2, parameterSpan: "allCaps: boolean", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "2": {text: "leading(...names: string[], allCaps: boolean): void", parameterCount: 2, parameterSpan: "allCaps: boolean", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "3": {text: "leading(...names: string[], allCaps: boolean): void", parameterCount: 2, parameterSpan: "allCaps: boolean", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpNoArguments", + input: `function foo(n: number): string { +} +foo(/**/`, + expected: map[string]verifySignatureHelpOptions{ + "": {text: "foo(n: number): string", parameterCount: 1, parameterSpan: "n: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpObjectLiteral", + input: `var objectLiteral = { n: 5, s: "", f: (a: number, b: string) => "" }; +objectLiteral.f(/*objectLiteral1*/4, /*objectLiteral2*/"");`, + expected: map[string]verifySignatureHelpOptions{ + "objectLiteral1": {text: "f(a: number, b: string): string", parameterCount: 2, parameterSpan: "a: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "objectLiteral2": {text: "f(a: number, b: string): string", parameterCount: 2, parameterSpan: "b: string", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpOnNestedOverloads", + input: `declare function fn(x: string); +declare function fn(x: string, y: number); +declare function fn2(x: string); +declare function fn2(x: string, y: number); +fn('', fn2(/*1*/ +fn2('', fn2('',/*2*/`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "fn2(x: string): any", parameterCount: 1, parameterSpan: "x: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "2": {text: "fn2(x: string, y: number): any", parameterCount: 2, parameterSpan: "y: number", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpOnOverloadOnConst", + input: `function x1(x: 'hi'); +function x1(y: 'bye'); +function x1(z: string); +function x1(a: any) { +} + +x1(''/*1*/); +x1('hi'/*2*/); +x1('bye'/*3*/);`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: `x1(z: string): any`, parameterCount: 1, parameterSpan: "z: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "2": {text: `x1(x: "hi"): any`, parameterCount: 1, parameterSpan: `x: "hi"`, activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "3": {text: `x1(y: "bye"): any`, parameterCount: 1, parameterSpan: `y: "bye"`, activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpOnOverloads", + input: `declare function fn(x: string); +declare function fn(x: string, y: number); +fn(/*1*/ +fn('',/*2*/)`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "fn(x: string): any", parameterCount: 1, parameterSpan: "x: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "2": {text: "fn(x: string, y: number): any", parameterCount: 2, parameterSpan: "y: number", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpOnOverloadsDifferentArity1", + input: `declare function f(s: string); +declare function f(n: number); +declare function f(s: string, b: boolean); +declare function f(n: number, b: boolean) +f(1/*1*/`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "f(n: number): any", parameterCount: 1, parameterSpan: "n: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpOnOverloadsDifferentArity1_1", + input: `declare function f(s: string); +declare function f(n: number); +declare function f(s: string, b: boolean); +declare function f(n: number, b: boolean) +f(1, /*1*/`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "f(n: number, b: boolean): any", parameterCount: 2, parameterSpan: "b: boolean", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpOnOverloadsDifferentArity2", + input: `declare function f(s: string); +declare function f(n: number); +declare function f(s: string, b: boolean); +declare function f(n: number, b: boolean); + +f(1/*1*/ var`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "f(n: number): any", parameterCount: 1, parameterSpan: "n: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpOnOverloadsDifferentArity2_2", + input: `declare function f(s: string); +declare function f(n: number); +declare function f(s: string, b: boolean); +declare function f(n: number, b: boolean); + +f(1, /*1*/var`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "f(n: number, b: boolean): any", parameterCount: 2, parameterSpan: "b: boolean", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpOnOverloadsDifferentArity3_1", + input: `declare function f(); +declare function f(s: string); +declare function f(s: string, b: boolean); +declare function f(n: number, b: boolean); + +f(/*1*/`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "f(): any", parameterCount: 0, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpOnOverloadsDifferentArity3_2", + input: `declare function f(); +declare function f(s: string); +declare function f(s: string, b: boolean); +declare function f(n: number, b: boolean); + +f(x, /*1*/`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "f(s: string, b: boolean): any", parameterCount: 2, parameterSpan: "b: boolean", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpOnSuperWhenMembersAreNotResolved", + input: `class A { } +class B extends A { constructor(public x: string) { } } +class C extends B { + constructor() { + super(/*1*/ + } +}`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "B(x: string): B", parameterCount: 1, parameterSpan: "x: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpOnTypePredicates", + input: `function f1(a: any): a is number {} +function f2(a: any): a is T {} +function f3(a: any, ...b): a is number {} +f1(/*1*/) +f2(/*2*/) +f3(/*3*/)`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "f1(a: any): a is number", parameterCount: 1, parameterSpan: "a: any", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "2": {text: "f2(a: any): a is unknown", parameterCount: 1, parameterSpan: "a: any", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "3": {text: "f3(a: any, ...b: any[]): a is number", parameterCount: 2, parameterSpan: "a: any", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpOptionalCall", + input: `function fnTest(str: string, num: number) { } +fnTest?.(/*1*/);`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "fnTest(str: string, num: number): void", parameterCount: 2, parameterSpan: "str: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHepSimpleConstructorCall", + input: `class ConstructorCall { + constructor(str: string, num: number) { + } +} +var x = new ConstructorCall(/*constructorCall1*/1,/*constructorCall2*/2);`, + expected: map[string]verifySignatureHelpOptions{ + "constructorCall1": {text: "ConstructorCall(str: string, num: number): ConstructorCall", parameterCount: 2, parameterSpan: "str: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "constructorCall2": {text: "ConstructorCall(str: string, num: number): ConstructorCall", parameterCount: 2, parameterSpan: "num: number", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpSimpleFunctionCall", + input: `function functionCall(str: string, num: number) { +} +functionCall(/*functionCall1*/); +functionCall("", /*functionCall2*/1);`, + expected: map[string]verifySignatureHelpOptions{ + "functionCall1": {text: "functionCall(str: string, num: number): void", parameterCount: 2, parameterSpan: "str: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "functionCall2": {text: "functionCall(str: string, num: number): void", parameterCount: 2, parameterSpan: "num: number", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpSimpleSuperCall", + input: `class SuperCallBase { + constructor(b: boolean) { + } +} +class SuperCall extends SuperCallBase { + constructor() { + super(/*superCall*/); + } +}`, + expected: map[string]verifySignatureHelpOptions{ + "superCall": {text: "SuperCallBase(b: boolean): SuperCallBase", parameterCount: 1, parameterSpan: "b: boolean", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpSuperConstructorOverload", + input: `class SuperOverloadBase { + constructor(); + constructor(test: string); + constructor(test?: string) { + } +} +class SuperOverLoad1 extends SuperOverloadBase { + constructor() { + super(/*superOverload1*/); + } +} +class SuperOverLoad2 extends SuperOverloadBase { + constructor() { + super(""/*superOverload2*/); + } +}`, + expected: map[string]verifySignatureHelpOptions{ + "superOverload1": {text: "SuperOverloadBase(): SuperOverloadBase", parameterCount: 0, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "superOverload2": {text: "SuperOverloadBase(test: string): SuperOverloadBase", parameterCount: 1, parameterSpan: "test: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpTrailingRestTuple", + input: `export function leading(allCaps: boolean, ...names: string[]): void { +} + +leading(/*1*/); +leading(false, /*2*/); +leading(false, "ok", /*3*/);`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "leading(allCaps: boolean, ...names: string[]): void", parameterCount: 2, parameterSpan: "allCaps: boolean", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "2": {text: "leading(allCaps: boolean, ...names: string[]): void", parameterCount: 2, parameterSpan: "...names: string[]", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "3": {text: "leading(allCaps: boolean, ...names: string[]): void", parameterCount: 2, parameterSpan: "...names: string[]", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + }, + }, + { + title: "signatureHelpWithInvalidArgumentList1", + input: `function foo(a) { } +foo(hello my name /**/is`, + expected: map[string]verifySignatureHelpOptions{ + "": {text: "foo(a: any): void", parameterCount: 1, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 2}}, + }, + }, + { + title: "signatureHelpAfterParameter", + input: `type Type = (a, b, c) => void +const a: Type = (a/*1*/, b/*2*/) => {} +const b: Type = function (a/*3*/, b/*4*/) {} +const c: Type = ({ /*5*/a: { b/*6*/ }}/*7*/ = { }/*8*/, [b/*9*/]/*10*/, .../*11*/c/*12*/) => {}`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "a: any", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "2": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "b: any", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "3": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "a: any", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "4": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "b: any", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "5": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "a: any", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "6": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "a: any", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "7": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "a: any", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "8": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "a: any", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "9": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "b: any", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "10": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "b: any", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "11": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "c: any", activeParameter: &lsproto.Nullable[uint32]{Value: 2}}, + "12": {text: "Type(a: any, b: any, c: any): void", parameterCount: 3, parameterSpan: "c: any", activeParameter: &lsproto.Nullable[uint32]{Value: 2}}, + }, + }, + { + title: "signaturehelpCallExpressionTuples", + input: `function fnTest(str: string, num: number) { } +declare function wrap(fn: (...a: A) => R) : (...a: A) => R; +var fnWrapped = wrap(fnTest); +fnWrapped(/*1*/'', /*2*/5); +function fnTestVariadic (str: string, ...num: number[]) { } +var fnVariadicWrapped = wrap(fnTestVariadic); +fnVariadicWrapped(/*3*/'', /*4*/5); +function fnNoParams () { } +var fnNoParamsWrapped = wrap(fnNoParams); +fnNoParamsWrapped(/*5*/);`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "fnWrapped(str: string, num: number): void", parameterCount: 2, parameterSpan: "str: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "2": {text: "fnWrapped(str: string, num: number): void", parameterCount: 2, parameterSpan: "num: number", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "3": {text: "fnVariadicWrapped(str: string, ...num: number[]): void", parameterCount: 2, parameterSpan: "str: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "4": {text: "fnVariadicWrapped(str: string, ...num: number[]): void", parameterCount: 2, parameterSpan: "...num: number[]", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "5": {text: "fnNoParamsWrapped(): void", parameterCount: 0, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpConstructorCallParamProperties", + input: `class Circle { + constructor(private radius: number) { + } +} +var a = new Circle(/**/`, + expected: map[string]verifySignatureHelpOptions{ + "": {text: "Circle(radius: number): Circle", parameterCount: 1, parameterSpan: "radius: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpInRecursiveType", + input: `type Tail = + ((...args: T) => any) extends ((head: any, ...tail: infer R) => any) ? R : never; + +type Reverse = _Reverse; + +type _Reverse = { + 1: Result, + 0: _Reverse, 0>, +}[Source extends [] ? 1 : 0]; + +type Foo = Reverse<[0,/**/]>;`, + expected: map[string]verifySignatureHelpOptions{ + "": {text: "Reverse", parameterCount: 1, parameterSpan: "List extends any[]", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpRestArgs1", + input: `function fn(a: number, b: number, c: number) {} +const a = [1, 2] as const; +const b = [1] as const; + +fn(...a, /*1*/); +fn(/*2*/, ...a); + +fn(...b, /*3*/); +fn(/*4*/, ...b, /*5*/);`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "fn(a: number, b: number, c: number): void", parameterCount: 3, parameterSpan: "c: number", activeParameter: &lsproto.Nullable[uint32]{Value: 2}}, + "2": {text: "fn(a: number, b: number, c: number): void", parameterCount: 3, parameterSpan: "a: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "3": {text: "fn(a: number, b: number, c: number): void", parameterCount: 3, parameterSpan: "b: number", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "4": {text: "fn(a: number, b: number, c: number): void", parameterCount: 3, parameterSpan: "a: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "5": {text: "fn(a: number, b: number, c: number): void", parameterCount: 3, parameterSpan: "c: number", activeParameter: &lsproto.Nullable[uint32]{Value: 2}}, + }, + }, + { + title: "signatureHelpSkippedArgs1", + input: `function fn(a: number, b: number, c: number) {} +fn(/*1*/, /*2*/, /*3*/, /*4*/, /*5*/);`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "fn(a: number, b: number, c: number): void", parameterCount: 3, parameterSpan: "a: number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "2": {text: "fn(a: number, b: number, c: number): void", parameterCount: 3, parameterSpan: "b: number", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "3": {text: "fn(a: number, b: number, c: number): void", parameterCount: 3, parameterSpan: "c: number", activeParameter: &lsproto.Nullable[uint32]{Value: 2}}, + "4": {text: "fn(a: number, b: number, c: number): void", parameterCount: 3, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 3}}, + "5": {text: "fn(a: number, b: number, c: number): void", parameterCount: 3, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 4}}, + }, + }, + { + title: "signatureHelpTypeArguments", + input: `declare function f(a: number, b: string, c: boolean): void; // ignored, not generic +declare function f(): void; +declare function f(): void; +declare function f(): void; +f(): void; + new(): void; + new(): void; +}; +new C(): void", parameterCount: 1, parameterSpan: "T extends number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "f1": {text: "f(): void", parameterCount: 2, parameterSpan: "U", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "f2": {text: "f(): void", parameterCount: 3, parameterSpan: "V extends string", activeParameter: &lsproto.Nullable[uint32]{Value: 2}}, + "C0": {text: "C(): void", parameterCount: 1, parameterSpan: "T extends number", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "C1": {text: "C(): void", parameterCount: 2, parameterSpan: "U", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "C2": {text: "C(): void", parameterCount: 3, parameterSpan: "V extends string", activeParameter: &lsproto.Nullable[uint32]{Value: 2}}, + }, + }, + { + title: "signatureHelpTypeArguments2", + input: `function f(a: number, b: string, c: boolean): void { } +f(a: number, b: string, c: boolean): void", parameterCount: 4, parameterSpan: "T", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + "f1": {text: "f(a: number, b: string, c: boolean): void", parameterCount: 4, parameterSpan: "U", activeParameter: &lsproto.Nullable[uint32]{Value: 1}}, + "f2": {text: "f(a: number, b: string, c: boolean): void", parameterCount: 4, parameterSpan: "V", activeParameter: &lsproto.Nullable[uint32]{Value: 2}}, + "f3": {text: "f(a: number, b: string, c: boolean): void", parameterCount: 4, parameterSpan: "W", activeParameter: &lsproto.Nullable[uint32]{Value: 3}}, + }, + }, + { + title: "signatureHelpTypeParametersNotVariadic", + input: `declare function f(a: any, ...b: any[]): any; +f(1, 2);`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "f<>(a: any, ...b: any[]): any", parameterCount: 0, parameterSpan: "", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + { + title: "signatureHelpWithUnknown", + input: `eval(\/*1*/`, + expected: map[string]verifySignatureHelpOptions{ + "1": {text: "eval(x: string): any", parameterCount: 1, parameterSpan: "x: string", activeParameter: &lsproto.Nullable[uint32]{Value: 0}}, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.title, func(t *testing.T) { + t.Parallel() + runSignatureHelpTest(t, testCase.input, testCase.expected) + }) + } +} + +func runSignatureHelpTest(t *testing.T, input string, expected map[string]verifySignatureHelpOptions) { + testData := lstestutil.ParseTestData("/mainFile.ts", input, "/mainFile.ts") + file := testData.Files[0].Filename + markerPositions := testData.MarkerPositions + ctx := projecttestutil.WithRequestID(t.Context()) + languageService, done := createLanguageService(ctx, file, map[string]any{ + file: testData.Files[0].Content, + }) + defer done() + context := &lsproto.SignatureHelpContext{ + TriggerKind: lsproto.SignatureHelpTriggerKindInvoked, + TriggerCharacter: nil, + } + ptrTrue := ptrTo(true) + capabilities := &lsproto.SignatureHelpClientCapabilities{ + SignatureInformation: &lsproto.ClientSignatureInformationOptions{ + ActiveParameterSupport: ptrTrue, + NoActiveParameterSupport: ptrTrue, + ParameterInformation: &lsproto.ClientSignatureParameterInformationOptions{ + LabelOffsetSupport: ptrTrue, + }, + }, + } + preferences := &ls.UserPreferences{} + for markerName, expectedResult := range expected { + marker, ok := markerPositions[markerName] + if !ok { + t.Fatalf("No marker found for '%s'", markerName) + } + result := languageService.ProvideSignatureHelp(ctx, ls.FileNameToDocumentURI(file), marker.LSPosition, context, capabilities, preferences) + assert.Equal(t, expectedResult.text, result.Signatures[*result.ActiveSignature].Label) + assert.Equal(t, expectedResult.parameterCount, len(*result.Signatures[*result.ActiveSignature].Parameters)) + assert.DeepEqual(t, expectedResult.activeParameter, result.ActiveParameter) + // Checking the parameter span that will be highlighted in the editor + if expectedResult.activeParameter != nil && int(expectedResult.activeParameter.Value) < expectedResult.parameterCount { + assert.Equal(t, expectedResult.parameterSpan, *(*result.Signatures[*result.ActiveSignature].Parameters)[int(result.ActiveParameter.Value)].Label.String) + } + } +} diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index aa5d535fd2..53fc7e6a7a 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -263,13 +263,96 @@ type PossibleTypeArgumentInfo struct { nTypeArguments int } -// !!! signature help +// Get info for an expression like `f <` that may be the start of type arguments. func getPossibleTypeArgumentsInfo(tokenIn *ast.Node, sourceFile *ast.SourceFile) *PossibleTypeArgumentInfo { - return nil -} + // This is a rare case, but one that saves on a _lot_ of work if true - if the source file has _no_ `<` character, + // then there obviously can't be any type arguments - no expensive brace-matching backwards scanning required + if strings.LastIndexByte(sourceFile.Text(), '<') == -1 { + return nil + } -// !!! signature help -func getPossibleGenericSignatures(called *ast.Expression, typeArgumentCount int, checker *checker.Checker) []*checker.Signature { + token := tokenIn + // This function determines if the node could be a type argument position + // When editing, it is common to have an incomplete type argument list (e.g. missing ">"), + // so the tree can have any shape depending on the tokens before the current node. + // Instead, scanning for an identifier followed by a "<" before current node + // will typically give us better results than inspecting the tree. + // Note that we also balance out the already provided type arguments, arrays, object literals while doing so. + remainingLessThanTokens := 0 + nTypeArguments := 0 + for token != nil { + switch token.Kind { + case ast.KindLessThanToken: + // Found the beginning of the generic argument expression + token = astnav.FindPrecedingToken(sourceFile, token.Pos()) + if token != nil && token.Kind == ast.KindQuestionDotToken { + token = astnav.FindPrecedingToken(sourceFile, token.Pos()) + } + if token == nil || !ast.IsIdentifier(token) { + return nil + } + if remainingLessThanTokens == 0 { + if ast.IsDeclarationName(token) { + return nil + } + return &PossibleTypeArgumentInfo{ + called: token, + nTypeArguments: nTypeArguments, + } + } + remainingLessThanTokens-- + break + case ast.KindGreaterThanGreaterThanGreaterThanToken: + remainingLessThanTokens = +3 + break + case ast.KindGreaterThanGreaterThanToken: + remainingLessThanTokens = +2 + break + case ast.KindGreaterThanToken: + remainingLessThanTokens++ + break + case ast.KindCloseBraceToken: + // This can be object type, skip until we find the matching open brace token + // Skip until the matching open brace token + token = findPrecedingMatchingToken(token, ast.KindOpenBraceToken, sourceFile) + if token == nil { + return nil + } + break + case ast.KindCloseParenToken: + // This can be object type, skip until we find the matching open brace token + // Skip until the matching open brace token + token = findPrecedingMatchingToken(token, ast.KindOpenParenToken, sourceFile) + if token == nil { + return nil + } + break + case ast.KindCloseBracketToken: + // This can be object type, skip until we find the matching open brace token + // Skip until the matching open brace token + token = findPrecedingMatchingToken(token, ast.KindOpenBracketToken, sourceFile) + if token == nil { + return nil + } + break + + // Valid tokens in a type name. Skip. + case ast.KindCommaToken: + nTypeArguments++ + break + case ast.KindEqualsGreaterThanToken, ast.KindIdentifier, ast.KindStringLiteral, ast.KindNumericLiteral, + ast.KindBigIntLiteral, ast.KindTrueKeyword, ast.KindFalseKeyword, ast.KindTypeOfKeyword, ast.KindExtendsKeyword, + ast.KindKeyOfKeyword, ast.KindDotToken, ast.KindBarToken, ast.KindQuestionToken, ast.KindColonToken: + break + default: + if ast.IsTypeNode(token) { + break + } + // Invalid token in type + return nil + } + token = astnav.FindPrecedingToken(sourceFile, token.Pos()) + } return nil } @@ -829,3 +912,93 @@ func newCaseClauseTracker(typeChecker *checker.Checker, clauses []*ast.CaseOrDef } return c } + +func RangeContainsRange(r1 core.TextRange, r2 core.TextRange) bool { + return startEndContainsRange(r1.Pos(), r1.End(), r2) +} + +func startEndContainsRange(start int, end int, textRange core.TextRange) bool { + return start <= textRange.Pos() && end >= textRange.End() +} + +func getPossibleGenericSignatures(called *ast.Expression, typeArgumentCount int, c *checker.Checker) []*checker.Signature { + typeAtLocation := c.GetTypeAtLocation(called) + if ast.IsOptionalChain(called.Parent) { + typeAtLocation = removeOptionality(typeAtLocation, ast.IsOptionalChainRoot(called.Parent), true /*isOptionalChain*/, c) + } + var signatures []*checker.Signature + if ast.IsNewExpression(called.Parent) { + signatures = c.GetSignaturesOfType(typeAtLocation, checker.SignatureKindConstruct) + } else { + signatures = c.GetSignaturesOfType(typeAtLocation, checker.SignatureKindCall) + } + return core.Filter(signatures, func(s *checker.Signature) bool { + return s.TypeParameters() != nil && len(s.TypeParameters()) >= typeArgumentCount + }) +} + +func removeOptionality(t *checker.Type, isOptionalExpression bool, isOptionalChain bool, c *checker.Checker) *checker.Type { + if isOptionalExpression { + return c.GetNonNullableType(t) + } else if isOptionalChain { + return c.GetNonOptionalType(t) + } + return t +} + +func isNoSubstitutionTemplateLiteral(node *ast.Node) bool { + return node.Kind == ast.KindNoSubstitutionTemplateLiteral +} + +func isTaggedTemplateExpression(node *ast.Node) bool { + return node.Kind == ast.KindTaggedTemplateExpression +} + +func isInsideTemplateLiteral(node *ast.Node, position int, sourceFile *ast.SourceFile) bool { + return ast.IsTemplateLiteralKind(node.Kind) && (scanner.GetTokenPosOfNode(node, sourceFile, false) < position && position < node.End() || (ast.IsUnterminatedLiteral(node) && position == node.End())) +} + +// Pseudo-literals +func isTemplateHead(node *ast.Node) bool { + return node.Kind == ast.KindTemplateHead +} + +func isTemplateTail(node *ast.Node) bool { + return node.Kind == ast.KindTemplateTail +} + +func findPrecedingMatchingToken(token *ast.Node, matchingTokenKind ast.Kind, sourceFile *ast.SourceFile) *ast.Node { + closeTokenText := scanner.TokenToString(token.Kind) + matchingTokenText := scanner.TokenToString(matchingTokenKind) + tokenFullStart := token.Loc.Pos() + // Text-scan based fast path - can be bamboozled by comments and other trivia, but often provides + // a good, fast approximation without too much extra work in the cases where it fails. + bestGuessIndex := strings.LastIndex(sourceFile.Text(), matchingTokenText) + if bestGuessIndex == -1 { + return nil // if the token text doesn't appear in the file, there can't be a match - super fast bail + } + // we can only use the textual result directly if we didn't have to count any close tokens within the range + if strings.LastIndex(sourceFile.Text(), closeTokenText) < bestGuessIndex { + nodeAtGuess := astnav.FindPrecedingToken(sourceFile, bestGuessIndex+1) + if nodeAtGuess != nil && nodeAtGuess.Kind == matchingTokenKind { + return nodeAtGuess + } + } + tokenKind := token.Kind + remainingMatchingTokens := 0 + for { + preceding := astnav.FindPrecedingToken(sourceFile, tokenFullStart) + if preceding == nil { + return nil + } + token = preceding + if token.Kind == matchingTokenKind { + if remainingMatchingTokens == 0 { + return token + } + remainingMatchingTokens-- + } else if token.Kind == tokenKind { + remainingMatchingTokens++ + } + } +} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index a16a09abc8..25c42458f8 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -410,6 +410,8 @@ func (s *Server) handleRequestOrNotification(ctx context.Context, req *lsproto.R return s.handleDefinition(ctx, req) case *lsproto.CompletionParams: return s.handleCompletion(ctx, req) + case *lsproto.SignatureHelpParams: + return s.handleSignatureHelp(ctx, req) default: switch req.Method { case lsproto.MethodShutdown: @@ -471,6 +473,9 @@ func (s *Server) handleInitialize(req *lsproto.RequestMessage) { TriggerCharacters: &ls.TriggerCharacters, // !!! other options }, + SignatureHelpProvider: &lsproto.SignatureHelpOptions{ + TriggerCharacters: &[]string{"(", ","}, + }, }, }) } @@ -548,6 +553,23 @@ func (s *Server) handleHover(ctx context.Context, req *lsproto.RequestMessage) e return nil } +func (s *Server) handleSignatureHelp(ctx context.Context, req *lsproto.RequestMessage) error { + params := req.Params.(*lsproto.SignatureHelpParams) + project := s.projectService.EnsureDefaultProjectForURI(params.TextDocument.Uri) + languageService, done := project.GetLanguageServiceForRequest(ctx) + defer done() + signatureHelp := languageService.ProvideSignatureHelp( + ctx, + params.TextDocument.Uri, + params.Position, + params.Context, + s.initializeParams.Capabilities.TextDocument.SignatureHelp, + &ls.UserPreferences{}, + ) + s.sendResult(req.ID, signatureHelp) + return nil +} + func (s *Server) handleDefinition(ctx context.Context, req *lsproto.RequestMessage) error { params := req.Params.(*lsproto.DefinitionParams) project := s.projectService.EnsureDefaultProjectForURI(params.TextDocument.Uri)