Skip to content

Commit 0f005e7

Browse files
committed
Baseline workspace symbols
1 parent 2be521c commit 0f005e7

File tree

4 files changed

+304
-1
lines changed

4 files changed

+304
-1
lines changed

internal/lsp/lspservertests/declarationmaps_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,37 @@ func getDeclarationMapTestCasesForMaps() []*lspServerTest {
224224
server.openFile("/home/src/projects/project/dummy/dummy.ts", lsproto.LanguageKindTypeScript)
225225
},
226226
},
227+
{
228+
subScenario: "workspace symbols",
229+
files: func() map[string]any {
230+
allFiles := files()
231+
allFiles["/home/src/projects/project/user/user.ts"] = stringtestutil.Dedent(`
232+
import * as a from "../a/a";
233+
import * as b from "../b/b";
234+
export function fnUser() {
235+
a.fnA();
236+
b.fnB();
237+
a.instanceA;
238+
}`)
239+
allFiles["/home/src/projects/project/user/tsconfig.json"] = stringtestutil.Dedent(`
240+
{
241+
"references": [{ "path": "../a" }, { "path": "../b" }]
242+
}`)
243+
allFiles["/home/src/projects/project/b/b.ts"] = stringtestutil.Dedent(`
244+
export function fnB() {}`)
245+
allFiles["/home/src/projects/project/b/c.ts"] = stringtestutil.Dedent(`
246+
export function fnC() {}`)
247+
return allFiles
248+
},
249+
test: func(server *testServer) {
250+
userTs := "/home/src/projects/project/user/user.ts"
251+
server.openFile(userTs, lsproto.LanguageKindTypeScript)
252+
server.baselineWorkspaceSymbol("fn")
253+
// Open temp file and verify all projects alive
254+
server.closeFile(userTs)
255+
server.openFile("/home/src/projects/project/dummy/dummy.ts", lsproto.LanguageKindTypeScript)
256+
},
257+
},
227258
}
228259
}
229260

internal/lsp/lspservertests/testserver.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,16 @@ func (s *testServer) baselineRename(fileName string, position lsproto.Position)
228228
}) + "\n")
229229
}
230230

231+
func (s *testServer) baselineWorkspaceSymbol(query string) {
232+
s.t.Helper()
233+
result := sendRequest(s.t, s, lsproto.WorkspaceSymbolInfo, &lsproto.WorkspaceSymbolParams{
234+
Query: query,
235+
})
236+
s.baseline.WriteString(lsptestutil.GetBaselineForWorkspaceSymbol(s.server.FS, result, lsptestutil.BaselineLocationsOptions{
237+
OpenFiles: s.openFiles,
238+
}) + "\n")
239+
}
240+
231241
type marker struct {
232242
fileName string
233243
position lsproto.Position

internal/testutil/lsptestutil/baseline.go

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type BaselineLocationsOptions struct {
3030

3131
StartMarkerPrefix func(span lsproto.Location) *string
3232
EndMarkerSuffix func(span lsproto.Location) *string
33+
GetLocationData func(span lsproto.Location) string
3334

3435
AdditionalLocation *lsproto.Location
3536

@@ -82,6 +83,94 @@ func GetBaselineForRename(
8283
)
8384
}
8485

86+
func symbolKindToString(kind lsproto.SymbolKind) string {
87+
switch kind {
88+
case lsproto.SymbolKindFile:
89+
return "file"
90+
case lsproto.SymbolKindModule:
91+
return "module"
92+
case lsproto.SymbolKindNamespace:
93+
return "namespace"
94+
case lsproto.SymbolKindPackage:
95+
return "package"
96+
case lsproto.SymbolKindClass:
97+
return "class"
98+
case lsproto.SymbolKindMethod:
99+
return "method"
100+
case lsproto.SymbolKindProperty:
101+
return "property"
102+
case lsproto.SymbolKindField:
103+
return "field"
104+
case lsproto.SymbolKindConstructor:
105+
return "constructor"
106+
case lsproto.SymbolKindEnum:
107+
return "enum"
108+
case lsproto.SymbolKindInterface:
109+
return "interface"
110+
case lsproto.SymbolKindFunction:
111+
return "function"
112+
case lsproto.SymbolKindVariable:
113+
return "variable"
114+
case lsproto.SymbolKindConstant:
115+
return "constant"
116+
case lsproto.SymbolKindString:
117+
return "string"
118+
case lsproto.SymbolKindNumber:
119+
return "number"
120+
case lsproto.SymbolKindBoolean:
121+
return "boolean"
122+
case lsproto.SymbolKindArray:
123+
return "array"
124+
case lsproto.SymbolKindObject:
125+
return "object"
126+
case lsproto.SymbolKindKey:
127+
return "key"
128+
case lsproto.SymbolKindNull:
129+
return "null"
130+
case lsproto.SymbolKindEnumMember:
131+
return "enumMember"
132+
case lsproto.SymbolKindStruct:
133+
return "struct"
134+
case lsproto.SymbolKindEvent:
135+
return "event"
136+
case lsproto.SymbolKindOperator:
137+
return "operator"
138+
case lsproto.SymbolKindTypeParameter:
139+
return "typeParameter"
140+
default:
141+
return "unknown"
142+
}
143+
}
144+
145+
func GetBaselineForWorkspaceSymbol(
146+
fs vfs.FS,
147+
result lsproto.SymbolInformationsOrWorkspaceSymbolsOrNull,
148+
options BaselineLocationsOptions,
149+
) string {
150+
locationToText := map[lsproto.Location]*lsproto.SymbolInformation{}
151+
fileToRange := collections.MultiMap[lsproto.DocumentUri, lsproto.Range]{}
152+
var symbolInformations []*lsproto.SymbolInformation
153+
if result.SymbolInformations != nil {
154+
symbolInformations = *result.SymbolInformations
155+
}
156+
for _, symbol := range symbolInformations {
157+
uri := symbol.Location.Uri
158+
fileToRange.Add(uri, symbol.Location.Range)
159+
locationToText[symbol.Location] = symbol
160+
}
161+
return getBaselineForGroupedLocationsWithFileContents(
162+
fs,
163+
&fileToRange,
164+
BaselineLocationsOptions{
165+
GetLocationData: func(span lsproto.Location) string {
166+
symbol := locationToText[span]
167+
return fmt.Sprintf("{| name: %s, kind: %s |}", symbol.Name, symbolKindToString(symbol.Kind))
168+
},
169+
OpenFiles: options.OpenFiles,
170+
},
171+
)
172+
}
173+
85174
func GetBaselineForLocationsWithFileContents(
86175
f vfs.FS,
87176
spans []lsproto.Location,
@@ -211,8 +300,12 @@ func getBaselineContentForFile(
211300

212301
for _, span := range spansInFile {
213302
textSpanIndex := len(details)
303+
startMarker := "[|"
304+
if options.GetLocationData != nil {
305+
startMarker += options.GetLocationData(lsproto.Location{Uri: uri, Range: span})
306+
}
214307
details = append(details,
215-
&baselineDetail{pos: span.Start, positionMarker: "[|", span: &span, kind: "textStart"},
308+
&baselineDetail{pos: span.Start, positionMarker: startMarker, span: &span, kind: "textStart"},
216309
&baselineDetail{pos: span.End, positionMarker: core.OrElse(options.EndMarker, "|]"), span: &span, kind: "textEnd"},
217310
)
218311

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
UseCaseSensitiveFileNames: false
2+
//// [/home/src/projects/project/a/a.ts] *new*
3+
export function fnA() {}
4+
export interface IfaceA {}
5+
export const instanceA: IfaceA = {};
6+
//// [/home/src/projects/project/a/bin/a.d.ts] *new*
7+
export declare function fnA(): void;
8+
export interface IfaceA {
9+
}
10+
export declare const instanceA: IfaceA;
11+
//# sourceMappingURL=a.d.ts.map
12+
//// [/home/src/projects/project/a/bin/a.d.ts.map] *new*
13+
{
14+
"version": 3,
15+
"file": "a.d.ts",
16+
"sourceRoot": "",
17+
"sources": ["../a.ts"],
18+
"names": [],
19+
"mappings": "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC"
20+
}
21+
//// [/home/src/projects/project/a/tsconfig.json] *new*
22+
{
23+
"compilerOptions": {
24+
"outDir": "bin",
25+
"declarationMap": true,
26+
"composite": true
27+
}
28+
}
29+
//// [/home/src/projects/project/b/b.ts] *new*
30+
export function fnB() {}
31+
//// [/home/src/projects/project/b/bin/b.d.ts] *new*
32+
export declare function fnB(): void;
33+
//# sourceMappingURL=b.d.ts.map
34+
//// [/home/src/projects/project/b/bin/b.d.ts.map] *new*
35+
{
36+
"version": 3,
37+
"file": "b.d.ts",
38+
"sourceRoot": "",
39+
"sources": ["../b.ts"],
40+
"names": [],
41+
"mappings": "AAAA,wBAAgB,GAAG,SAAK"
42+
}
43+
//// [/home/src/projects/project/b/c.ts] *new*
44+
export function fnC() {}
45+
//// [/home/src/projects/project/b/tsconfig.json] *new*
46+
{
47+
"compilerOptions": {
48+
"outDir": "bin",
49+
"declarationMap": true,
50+
"composite": true
51+
}
52+
}
53+
//// [/home/src/projects/project/dummy/dummy.ts] *new*
54+
export const a = 10;
55+
//// [/home/src/projects/project/dummy/tsconfig.json] *new*
56+
{}
57+
//// [/home/src/projects/project/user/tsconfig.json] *new*
58+
{
59+
"references": [{ "path": "../a" }, { "path": "../b" }]
60+
}
61+
//// [/home/src/projects/project/user/user.ts] *new*
62+
import * as a from "../a/a";
63+
import * as b from "../b/b";
64+
export function fnUser() {
65+
a.fnA();
66+
b.fnB();
67+
a.instanceA;
68+
}
69+
70+
{
71+
"method": "textDocument/didOpen",
72+
"params": {
73+
"textDocument": {
74+
"uri": "file:///home/src/projects/project/user/user.ts",
75+
"languageId": "typescript",
76+
"version": 0,
77+
"text": "import * as a from \"../a/a\";\nimport * as b from \"../b/b\";\nexport function fnUser() {\n a.fnA();\n b.fnB();\n a.instanceA;\n}"
78+
}
79+
}
80+
}
81+
Projects::
82+
[/home/src/projects/project/user/tsconfig.json] *new*
83+
/home/src/projects/project/a/a.ts
84+
/home/src/projects/project/b/b.ts
85+
/home/src/projects/project/user/user.ts
86+
Open Files::
87+
[/home/src/projects/project/user/user.ts] *new*
88+
/home/src/projects/project/user/tsconfig.json (default)
89+
Config::
90+
[/home/src/projects/project/a/tsconfig.json] *new*
91+
RetainingProjects:
92+
/home/src/projects/project/user/tsconfig.json
93+
[/home/src/projects/project/b/tsconfig.json] *new*
94+
RetainingProjects:
95+
/home/src/projects/project/user/tsconfig.json
96+
[/home/src/projects/project/user/tsconfig.json] *new*
97+
RetainingProjects:
98+
/home/src/projects/project/user/tsconfig.json
99+
RetainingOpenFiles:
100+
/home/src/projects/project/user/user.ts
101+
Config File Names::
102+
[/home/src/projects/project/user/user.ts] *new*
103+
NearestConfigFileName: /home/src/projects/project/user/tsconfig.json
104+
{
105+
"method": "workspace/symbol",
106+
"params": {
107+
"query": "fn"
108+
}
109+
}
110+
// === /home/src/projects/project/a/a.ts ===
111+
// export function [|{| name: fnA, kind: function |}fnA|]() {}
112+
// export interface IfaceA {}
113+
// export const instanceA: IfaceA = {};
114+
115+
// === /home/src/projects/project/b/b.ts ===
116+
// export function [|{| name: fnB, kind: function |}fnB|]() {}
117+
118+
// === /home/src/projects/project/user/user.ts ===
119+
// import * as a from "../a/a";
120+
// import * as b from "../b/b";
121+
// export function [|{| name: fnUser, kind: function |}fnUser|]() {
122+
// a.fnA();
123+
// b.fnB();
124+
// a.instanceA;
125+
// }
126+
{
127+
"method": "textDocument/didClose",
128+
"params": {
129+
"textDocument": {
130+
"uri": "file:///home/src/projects/project/user/user.ts"
131+
}
132+
}
133+
}
134+
Open Files::
135+
[/home/src/projects/project/user/user.ts] *closed*
136+
{
137+
"method": "textDocument/didOpen",
138+
"params": {
139+
"textDocument": {
140+
"uri": "file:///home/src/projects/project/dummy/dummy.ts",
141+
"languageId": "typescript",
142+
"version": 0,
143+
"text": "export const a = 10;"
144+
}
145+
}
146+
}
147+
Projects::
148+
[/home/src/projects/project/dummy/tsconfig.json] *new*
149+
/home/src/projects/project/dummy/dummy.ts
150+
[/home/src/projects/project/user/tsconfig.json] *deleted*
151+
/home/src/projects/project/a/a.ts
152+
/home/src/projects/project/b/b.ts
153+
/home/src/projects/project/user/user.ts
154+
Open Files::
155+
[/home/src/projects/project/dummy/dummy.ts] *new*
156+
/home/src/projects/project/dummy/tsconfig.json (default)
157+
Config::
158+
[/home/src/projects/project/a/tsconfig.json] *deleted*
159+
[/home/src/projects/project/b/tsconfig.json] *deleted*
160+
[/home/src/projects/project/dummy/tsconfig.json] *new*
161+
RetainingProjects:
162+
/home/src/projects/project/dummy/tsconfig.json
163+
RetainingOpenFiles:
164+
/home/src/projects/project/dummy/dummy.ts
165+
[/home/src/projects/project/user/tsconfig.json] *deleted*
166+
Config File Names::
167+
[/home/src/projects/project/dummy/dummy.ts] *new*
168+
NearestConfigFileName: /home/src/projects/project/dummy/tsconfig.json
169+
[/home/src/projects/project/user/user.ts] *deleted*

0 commit comments

Comments
 (0)