@@ -1516,7 +1516,7 @@ func (f *FourslashTest) VerifyBaselineSignatureHelp(t *testing.T) {
15161516
15171517 params := & lsproto.SignatureHelpParams {
15181518 TextDocument : lsproto.TextDocumentIdentifier {
1519- Uri : lsconv .FileNameToDocumentURI (f . activeFilename ),
1519+ Uri : lsconv .FileNameToDocumentURI (marker . FileName () ),
15201520 },
15211521 Position : marker .LSPosition ,
15221522 }
@@ -1547,9 +1547,18 @@ func (f *FourslashTest) VerifyBaselineSignatureHelp(t *testing.T) {
15471547 signatureLine := sig .Label
15481548 activeParamLine := ""
15491549
1550+ // Determine active parameter: per-signature takes precedence over top-level per LSP spec
1551+ // "If provided (or `null`), this is used in place of `SignatureHelp.activeParameter`."
1552+ var activeParamPtr * lsproto.UintegerOrNull
1553+ if sig .ActiveParameter != nil {
1554+ activeParamPtr = sig .ActiveParameter
1555+ } else {
1556+ activeParamPtr = item .ActiveParameter
1557+ }
1558+
15501559 // Show active parameter if specified, and the signature text.
1551- if item . ActiveParameter != nil && sig .Parameters != nil {
1552- activeParamIndex := int (* item . ActiveParameter .Uinteger )
1560+ if activeParamPtr != nil && activeParamPtr . Uinteger != nil && sig .Parameters != nil {
1561+ activeParamIndex := int (* activeParamPtr .Uinteger )
15531562 if activeParamIndex >= 0 && activeParamIndex < len (* sig .Parameters ) {
15541563 activeParam := (* sig .Parameters )[activeParamIndex ]
15551564
@@ -2083,13 +2092,277 @@ func (f *FourslashTest) VerifyQuickInfoIs(t *testing.T, expectedText string, exp
20832092 f .verifyHoverContent (t , hover .Contents , expectedText , expectedDocumentation , f .getCurrentPositionPrefix ())
20842093}
20852094
2095+ // VerifySignatureHelpOptions contains options for verifying signature help.
2096+ // All fields are optional - only specified fields will be verified.
2097+ type VerifySignatureHelpOptions struct {
2098+ // Text is the full signature text (e.g., "fn(x: string, y: number): void")
2099+ Text string
2100+ // DocComment is the documentation comment for the signature
2101+ DocComment string
2102+ // ParameterCount is the expected number of parameters
2103+ ParameterCount int
2104+ // ParameterName is the expected name of the active parameter
2105+ ParameterName string
2106+ // ParameterSpan is the expected label of the active parameter (e.g., "x: string")
2107+ ParameterSpan string
2108+ // ParameterDocComment is the documentation for the active parameter
2109+ ParameterDocComment string
2110+ // OverloadsCount is the expected number of overloads (signatures)
2111+ OverloadsCount int
2112+ // OverrideSelectedItemIndex overrides which signature to check (default: ActiveSignature)
2113+ OverrideSelectedItemIndex int
2114+ // IsVariadic indicates if the signature has a rest parameter
2115+ IsVariadic bool
2116+ // IsVariadicSet is true when IsVariadic was explicitly set (to distinguish from default false)
2117+ IsVariadicSet bool
2118+ }
2119+
2120+ // VerifySignatureHelp verifies signature help at the current position matches the expected options.
2121+ func (f * FourslashTest ) VerifySignatureHelp (t * testing.T , expected VerifySignatureHelpOptions ) {
2122+ t .Helper ()
2123+ prefix := f .getCurrentPositionPrefix ()
2124+ params := & lsproto.SignatureHelpParams {
2125+ TextDocument : lsproto.TextDocumentIdentifier {
2126+ Uri : lsconv .FileNameToDocumentURI (f .activeFilename ),
2127+ },
2128+ Position : f .currentCaretPosition ,
2129+ }
2130+ result := sendRequest (t , f , lsproto .TextDocumentSignatureHelpInfo , params )
2131+ help := result .SignatureHelp
2132+ if help == nil {
2133+ t .Fatalf ("%sCould not get signature help" , prefix )
2134+ }
2135+
2136+ // Determine which signature to check
2137+ selectedIndex := 0
2138+ if expected .OverrideSelectedItemIndex > 0 {
2139+ selectedIndex = expected .OverrideSelectedItemIndex
2140+ } else if help .ActiveSignature != nil {
2141+ selectedIndex = int (* help .ActiveSignature )
2142+ }
2143+
2144+ if selectedIndex >= len (help .Signatures ) {
2145+ t .Fatalf ("%sSelected signature index %d out of range (have %d signatures)" , prefix , selectedIndex , len (help .Signatures ))
2146+ }
2147+
2148+ selectedSig := help .Signatures [selectedIndex ]
2149+
2150+ // Verify overloads count
2151+ if expected .OverloadsCount > 0 {
2152+ if len (help .Signatures ) != expected .OverloadsCount {
2153+ t .Errorf ("%sExpected %d overloads, got %d" , prefix , expected .OverloadsCount , len (help .Signatures ))
2154+ }
2155+ }
2156+
2157+ // Verify signature text
2158+ if expected .Text != "" {
2159+ if selectedSig .Label != expected .Text {
2160+ t .Errorf ("%sExpected signature text %q, got %q" , prefix , expected .Text , selectedSig .Label )
2161+ }
2162+ }
2163+
2164+ // Verify doc comment
2165+ if expected .DocComment != "" {
2166+ actualDoc := ""
2167+ if selectedSig .Documentation != nil {
2168+ if selectedSig .Documentation .MarkupContent != nil {
2169+ actualDoc = selectedSig .Documentation .MarkupContent .Value
2170+ } else if selectedSig .Documentation .String != nil {
2171+ actualDoc = * selectedSig .Documentation .String
2172+ }
2173+ }
2174+ if actualDoc != expected .DocComment {
2175+ t .Errorf ("%sExpected doc comment %q, got %q" , prefix , expected .DocComment , actualDoc )
2176+ }
2177+ }
2178+
2179+ // Verify parameter count
2180+ if expected .ParameterCount > 0 {
2181+ paramCount := 0
2182+ if selectedSig .Parameters != nil {
2183+ paramCount = len (* selectedSig .Parameters )
2184+ }
2185+ if paramCount != expected .ParameterCount {
2186+ t .Errorf ("%sExpected %d parameters, got %d" , prefix , expected .ParameterCount , paramCount )
2187+ }
2188+ }
2189+
2190+ // Get active parameter
2191+ var activeParamIndex int
2192+ if selectedSig .ActiveParameter != nil && selectedSig .ActiveParameter .Uinteger != nil {
2193+ activeParamIndex = int (* selectedSig .ActiveParameter .Uinteger )
2194+ } else if help .ActiveParameter != nil && help .ActiveParameter .Uinteger != nil {
2195+ activeParamIndex = int (* help .ActiveParameter .Uinteger )
2196+ }
2197+
2198+ var activeParam * lsproto.ParameterInformation
2199+ if selectedSig .Parameters != nil && activeParamIndex < len (* selectedSig .Parameters ) {
2200+ activeParam = (* selectedSig .Parameters )[activeParamIndex ]
2201+ }
2202+
2203+ // Verify parameter name
2204+ if expected .ParameterName != "" {
2205+ if activeParam == nil {
2206+ t .Errorf ("%sExpected parameter name %q, but no active parameter" , prefix , expected .ParameterName )
2207+ } else {
2208+ // Parameter name is extracted from the label
2209+ actualName := ""
2210+ if activeParam .Label .String != nil {
2211+ // Extract name from label like "x: string" -> "x" or "T extends Foo" -> "T" or "...x: any[]" -> "x"
2212+ label := * activeParam .Label .String
2213+ // Strip leading "..." for rest parameters
2214+ label = strings .TrimPrefix (label , "..." )
2215+ if name , _ , found := strings .Cut (label , ":" ); found {
2216+ actualName = strings .TrimSpace (name )
2217+ } else if name , _ , found := strings .Cut (label , " extends " ); found {
2218+ actualName = strings .TrimSpace (name )
2219+ } else {
2220+ actualName = label
2221+ }
2222+ }
2223+ if actualName != expected .ParameterName {
2224+ t .Errorf ("%sExpected parameter name %q, got %q" , prefix , expected .ParameterName , actualName )
2225+ }
2226+ }
2227+ }
2228+
2229+ // Verify parameter span (label)
2230+ if expected .ParameterSpan != "" {
2231+ if activeParam == nil {
2232+ t .Errorf ("%sExpected parameter span %q, but no active parameter" , prefix , expected .ParameterSpan )
2233+ } else {
2234+ actualSpan := ""
2235+ if activeParam .Label .String != nil {
2236+ actualSpan = * activeParam .Label .String
2237+ }
2238+ if actualSpan != expected .ParameterSpan {
2239+ t .Errorf ("%sExpected parameter span %q, got %q" , prefix , expected .ParameterSpan , actualSpan )
2240+ }
2241+ }
2242+ }
2243+
2244+ // Verify parameter doc comment
2245+ if expected .ParameterDocComment != "" {
2246+ if activeParam == nil {
2247+ t .Errorf ("%sExpected parameter doc comment %q, but no active parameter" , prefix , expected .ParameterDocComment )
2248+ } else {
2249+ actualDoc := ""
2250+ if activeParam .Documentation != nil {
2251+ if activeParam .Documentation .MarkupContent != nil {
2252+ actualDoc = activeParam .Documentation .MarkupContent .Value
2253+ } else if activeParam .Documentation .String != nil {
2254+ actualDoc = * activeParam .Documentation .String
2255+ }
2256+ }
2257+ if actualDoc != expected .ParameterDocComment {
2258+ t .Errorf ("%sExpected parameter doc comment %q, got %q" , prefix , expected .ParameterDocComment , actualDoc )
2259+ }
2260+ }
2261+ }
2262+
2263+ // Verify isVariadic (check if any parameter starts with "...")
2264+ if expected .IsVariadicSet {
2265+ actualIsVariadic := false
2266+ if selectedSig .Parameters != nil {
2267+ for _ , param := range * selectedSig .Parameters {
2268+ if param .Label .String != nil && strings .HasPrefix (* param .Label .String , "..." ) {
2269+ actualIsVariadic = true
2270+ break
2271+ }
2272+ }
2273+ }
2274+ if actualIsVariadic != expected .IsVariadic {
2275+ t .Errorf ("%sExpected isVariadic=%v, got %v" , prefix , expected .IsVariadic , actualIsVariadic )
2276+ }
2277+ }
2278+ }
2279+
2280+ // VerifyNoSignatureHelp verifies that no signature help is available at the current position.
2281+ func (f * FourslashTest ) VerifyNoSignatureHelp (t * testing.T ) {
2282+ t .Helper ()
2283+ prefix := f .getCurrentPositionPrefix ()
2284+ params := & lsproto.SignatureHelpParams {
2285+ TextDocument : lsproto.TextDocumentIdentifier {
2286+ Uri : lsconv .FileNameToDocumentURI (f .activeFilename ),
2287+ },
2288+ Position : f .currentCaretPosition ,
2289+ }
2290+ result := sendRequest (t , f , lsproto .TextDocumentSignatureHelpInfo , params )
2291+ if result .SignatureHelp != nil && len (result .SignatureHelp .Signatures ) > 0 {
2292+ t .Errorf ("%sExpected no signature help, but got %d signatures" , prefix , len (result .SignatureHelp .Signatures ))
2293+ }
2294+ }
2295+
2296+ // VerifyNoSignatureHelpWithContext verifies that no signature help is available at the current position with a given context.
2297+ func (f * FourslashTest ) VerifyNoSignatureHelpWithContext (t * testing.T , context * lsproto.SignatureHelpContext ) {
2298+ t .Helper ()
2299+ prefix := f .getCurrentPositionPrefix ()
2300+ params := & lsproto.SignatureHelpParams {
2301+ TextDocument : lsproto.TextDocumentIdentifier {
2302+ Uri : lsconv .FileNameToDocumentURI (f .activeFilename ),
2303+ },
2304+ Position : f .currentCaretPosition ,
2305+ Context : context ,
2306+ }
2307+ result := sendRequest (t , f , lsproto .TextDocumentSignatureHelpInfo , params )
2308+ if result .SignatureHelp != nil && len (result .SignatureHelp .Signatures ) > 0 {
2309+ t .Errorf ("%sExpected no signature help, but got %d signatures" , prefix , len (result .SignatureHelp .Signatures ))
2310+ }
2311+ }
2312+
2313+ // VerifyNoSignatureHelpForMarkersWithContext verifies that no signature help is available at the given markers with a given context.
2314+ func (f * FourslashTest ) VerifyNoSignatureHelpForMarkersWithContext (t * testing.T , context * lsproto.SignatureHelpContext , markers ... string ) {
2315+ t .Helper ()
2316+ for _ , marker := range markers {
2317+ f .GoToMarker (t , marker )
2318+ f .VerifyNoSignatureHelpWithContext (t , context )
2319+ }
2320+ }
2321+
2322+ // VerifySignatureHelpPresent verifies that signature help is available at the current position with a given context.
2323+ func (f * FourslashTest ) VerifySignatureHelpPresent (t * testing.T , context * lsproto.SignatureHelpContext ) {
2324+ t .Helper ()
2325+ prefix := f .getCurrentPositionPrefix ()
2326+ params := & lsproto.SignatureHelpParams {
2327+ TextDocument : lsproto.TextDocumentIdentifier {
2328+ Uri : lsconv .FileNameToDocumentURI (f .activeFilename ),
2329+ },
2330+ Position : f .currentCaretPosition ,
2331+ Context : context ,
2332+ }
2333+ result := sendRequest (t , f , lsproto .TextDocumentSignatureHelpInfo , params )
2334+ if result .SignatureHelp == nil || len (result .SignatureHelp .Signatures ) == 0 {
2335+ t .Errorf ("%sExpected signature help to be present, but got none" , prefix )
2336+ }
2337+ }
2338+
2339+ // VerifySignatureHelpPresentForMarkers verifies that signature help is available at the given markers with a given context.
2340+ func (f * FourslashTest ) VerifySignatureHelpPresentForMarkers (t * testing.T , context * lsproto.SignatureHelpContext , markers ... string ) {
2341+ t .Helper ()
2342+ for _ , marker := range markers {
2343+ f .GoToMarker (t , marker )
2344+ f .VerifySignatureHelpPresent (t , context )
2345+ }
2346+ }
2347+
2348+ // VerifyNoSignatureHelpForMarkers verifies that no signature help is available at the given markers.
2349+ func (f * FourslashTest ) VerifyNoSignatureHelpForMarkers (t * testing.T , markers ... string ) {
2350+ t .Helper ()
2351+ for _ , marker := range markers {
2352+ f .GoToMarker (t , marker )
2353+ f .VerifyNoSignatureHelp (t )
2354+ }
2355+ }
2356+
20862357type SignatureHelpCase struct {
20872358 Context * lsproto.SignatureHelpContext
20882359 MarkerInput MarkerInput
20892360 Expected * lsproto.SignatureHelp
20902361}
20912362
2092- func (f * FourslashTest ) VerifySignatureHelp (t * testing.T , signatureHelpCases ... * SignatureHelpCase ) {
2363+ // VerifySignatureHelpWithCases verifies signature help using detailed SignatureHelpCase structs.
2364+ // This is useful for more complex tests that need to verify the full signature help response.
2365+ func (f * FourslashTest ) VerifySignatureHelpWithCases (t * testing.T , signatureHelpCases ... * SignatureHelpCase ) {
20932366 for _ , option := range signatureHelpCases {
20942367 switch marker := option .MarkerInput .(type ) {
20952368 case string :
0 commit comments