@@ -242,8 +242,8 @@ func (f *FourslashTest) handleServerRequest(_ context.Context, req *lsproto.Requ
242242 // Return current user preferences for each requested section.
243243 // The server requests multiple sections (js/ts, typescript, javascript, editor);
244244 // we return user preferences for "js/ts" and nil for others.
245- params , ok := req . Params .( * lsproto.ConfigurationParams )
246- if ! ok || params == nil || params .Items == nil {
245+ params , err := lsproto. UnmarshalParams [ * lsproto.ConfigurationParams ]( req )
246+ if err != nil || params == nil || params .Items == nil {
247247 return & lsproto.ResponseMessage {
248248 ID : req .ID ,
249249 JSONRPC : req .JSONRPC ,
@@ -1098,7 +1098,10 @@ func (f *FourslashTest) VerifyCompletions(t *testing.T, markerInput MarkerInput,
10981098 if item == nil {
10991099 t .Fatalf ("Code action '%s' from source '%s' not found in completions." , expectedAction .Name , expectedAction .Source )
11001100 }
1101- assert .Check (t , strings .Contains (* item .Detail , expectedAction .Description ), "Completion item detail does not contain expected description." )
1101+ // Detail and AdditionalTextEdits for auto-import items are populated by
1102+ // completionItem/resolve, not in the initial completion list.
1103+ item = f .resolveCompletionItem (t , item )
1104+ assert .Check (t , item .Detail != nil && strings .Contains (* item .Detail , expectedAction .Description ), "Completion item detail does not contain expected description." )
11021105 f .applyTextEdits (t , * item .AdditionalTextEdits )
11031106 assert .Equal (t , f .getScriptInfo (f .activeFilename ).content , expectedAction .NewFileContent , fmt .Sprintf ("File content after applying code action '%s' did not match expected content." , expectedAction .Name ))
11041107 },
@@ -2532,7 +2535,7 @@ func (f *FourslashTest) verifyBaselineDefinitions(
25322535 } else if result .DefinitionLinks != nil {
25332536 var originRange * lsproto.Range
25342537 resultAsSpans = core .Map (* result .DefinitionLinks , func (link * lsproto.LocationLink ) documentSpan {
2535- if originRange != nil && originRange != link .OriginSelectionRange {
2538+ if originRange != nil && ( link . OriginSelectionRange == nil || * originRange != * link .OriginSelectionRange ) {
25362539 panic ("multiple different origin ranges in definition links" )
25372540 }
25382541 originRange = link .OriginSelectionRange
@@ -5487,18 +5490,22 @@ func (f *FourslashTest) VerifyBaselineDocumentSymbol(t *testing.T) {
54875490 }
54885491 result := sendRequest (t , f , lsproto .TextDocumentDocumentSymbolInfo , params )
54895492 uri := lsconv .FileNameToDocumentURI (f .activeFilename )
5490- spansToSymbol := make (map [documentSpan ]* lsproto.DocumentSymbol )
5493+ symbolBySpan := make (map [documentSpanKey ]* lsproto.DocumentSymbol )
54915494 if result .DocumentSymbols != nil {
54925495 for _ , symbol := range * result .DocumentSymbols {
5493- collectDocumentSymbolSpans (uri , symbol , spansToSymbol )
5496+ collectDocumentSymbolSpans (uri , symbol , symbolBySpan )
54945497 }
54955498 }
5499+ spans := make ([]documentSpan , 0 , len (symbolBySpan ))
5500+ for key , symbol := range symbolBySpan {
5501+ spans = append (spans , documentSpan {uri : key .uri , textSpan : key .textSpan , contextSpan : & symbol .Range })
5502+ }
54965503 f .addResultToBaseline (
54975504 t ,
54985505 documentSymbolsCmd ,
5499- f .getBaselineForSpansWithFileContents (slices . Collect ( maps . Keys ( spansToSymbol )) , baselineFourslashLocationsOptions {
5506+ f .getBaselineForSpansWithFileContents (spans , baselineFourslashLocationsOptions {
55005507 getLocationData : func (span documentSpan ) string {
5501- symbol := spansToSymbol [ span ]
5508+ symbol := symbolBySpan [ documentSpanKey { uri : span . uri , textSpan : span . textSpan , contextSpan : * span . contextSpan } ]
55025509 return fmt .Sprintf ("{| name: %s, kind: %s |}" , symbol .Name , symbol .Kind .String ())
55035510 },
55045511 }),
@@ -5523,21 +5530,32 @@ func writeDocumentSymbolDetails(symbols []*lsproto.DocumentSymbol, indent int, b
55235530func collectDocumentSymbolSpans (
55245531 uri lsproto.DocumentUri ,
55255532 symbol * lsproto.DocumentSymbol ,
5526- spansToSymbol map [documentSpan ]* lsproto.DocumentSymbol ,
5533+ symbolBySpan map [documentSpanKey ]* lsproto.DocumentSymbol ,
55275534) {
5528- span := documentSpan {
5529- uri : uri ,
5530- textSpan : symbol .SelectionRange ,
5531- contextSpan : & symbol .Range ,
5535+ // Deduplicate by value rather than by the documentSpan key, which holds a pointer to
5536+ // the symbol's Range. The same logical symbol can be reached more than once
5537+ // (e.g. a merged declaration), and depending on transport those occurrences may
5538+ // be the same object (shared *Range) or independent copies (distinct *Range
5539+ // after a JSON round-trip). A value-based key collapses them consistently.
5540+ key := documentSpanKey {uri : uri , textSpan : symbol .SelectionRange , contextSpan : symbol .Range }
5541+ if _ , ok := symbolBySpan [key ]; ! ok {
5542+ symbolBySpan [key ] = symbol
55325543 }
5533- spansToSymbol [span ] = symbol
55345544 if symbol .Children != nil {
55355545 for _ , child := range * symbol .Children {
5536- collectDocumentSymbolSpans (uri , child , spansToSymbol )
5546+ collectDocumentSymbolSpans (uri , child , symbolBySpan )
55375547 }
55385548 }
55395549}
55405550
5551+ // documentSpanKey is a value-comparable variant of documentSpan used to deduplicate
5552+ // document symbols regardless of pointer identity.
5553+ type documentSpanKey struct {
5554+ uri lsproto.DocumentUri
5555+ textSpan lsproto.Range
5556+ contextSpan lsproto.Range
5557+ }
5558+
55415559// VerifyNumberOfErrorsInCurrentFile verifies that the current file has the expected number of errors.
55425560func (f * FourslashTest ) VerifyNumberOfErrorsInCurrentFile (t * testing.T , expectedCount int ) {
55435561 diagnostics := f .getDiagnostics (t , f .activeFilename )
0 commit comments