|
1 | 1 | import sinon from 'sinon'; |
2 | 2 | import { |
3 | 3 | CompletionItem, |
| 4 | + DocumentSymbol, |
4 | 5 | Location, |
5 | 6 | LocationLink, |
6 | 7 | Position, |
7 | 8 | Range, |
| 9 | + SymbolInformation, |
| 10 | + SymbolKind, |
8 | 11 | TextDocumentItem |
9 | 12 | } from 'vscode-languageserver-types'; |
10 | 13 | import { DocumentManager, Document } from '../../src/lib/documents'; |
11 | 14 | import { LSPProviderConfig, PluginHost } from '../../src/plugins'; |
12 | | -import { CompletionTriggerKind } from 'vscode-languageserver'; |
| 15 | +import { CompletionTriggerKind, CancellationToken } from 'vscode-languageserver'; |
13 | 16 | import assert from 'assert'; |
14 | 17 |
|
15 | 18 | describe('PluginHost', () => { |
@@ -187,4 +190,146 @@ describe('PluginHost', () => { |
187 | 190 | ]); |
188 | 191 | }); |
189 | 192 | }); |
| 193 | + |
| 194 | + describe('getHierarchicalDocumentSymbols', () => { |
| 195 | + it('converts flat symbols to hierarchical structure', async () => { |
| 196 | + const cancellation_token: CancellationToken = { |
| 197 | + isCancellationRequested: false, |
| 198 | + onCancellationRequested: () => ({ dispose: () => {} }) |
| 199 | + }; |
| 200 | + |
| 201 | + const flat_symbols: SymbolInformation[] = [ |
| 202 | + // Root level class (lines 0-10) |
| 203 | + SymbolInformation.create( |
| 204 | + 'MyClass', |
| 205 | + SymbolKind.Class, |
| 206 | + Range.create(Position.create(0, 0), Position.create(10, 0)), |
| 207 | + 'file:///hello.svelte' |
| 208 | + ), |
| 209 | + // Method inside class (lines 1-5) |
| 210 | + SymbolInformation.create( |
| 211 | + 'myMethod', |
| 212 | + SymbolKind.Method, |
| 213 | + Range.create(Position.create(1, 0), Position.create(5, 0)), |
| 214 | + 'file:///hello.svelte' |
| 215 | + ), |
| 216 | + // Variable inside method (lines 2-3) |
| 217 | + SymbolInformation.create( |
| 218 | + 'localVar', |
| 219 | + SymbolKind.Variable, |
| 220 | + Range.create(Position.create(2, 0), Position.create(3, 0)), |
| 221 | + 'file:///hello.svelte' |
| 222 | + ), |
| 223 | + // Another method in class (lines 6-8) |
| 224 | + SymbolInformation.create( |
| 225 | + 'anotherMethod', |
| 226 | + SymbolKind.Method, |
| 227 | + Range.create(Position.create(6, 0), Position.create(8, 0)), |
| 228 | + 'file:///hello.svelte' |
| 229 | + ), |
| 230 | + // Root level function (lines 12-15) |
| 231 | + SymbolInformation.create( |
| 232 | + 'topLevelFunction', |
| 233 | + SymbolKind.Function, |
| 234 | + Range.create(Position.create(12, 0), Position.create(15, 0)), |
| 235 | + 'file:///hello.svelte' |
| 236 | + ) |
| 237 | + ]; |
| 238 | + |
| 239 | + const { docManager, pluginHost } = setup({ |
| 240 | + getDocumentSymbols: sinon.stub().returns(flat_symbols) |
| 241 | + }); |
| 242 | + docManager.openClientDocument(textDocument); |
| 243 | + |
| 244 | + const result = await pluginHost.getHierarchicalDocumentSymbols( |
| 245 | + textDocument, |
| 246 | + cancellation_token |
| 247 | + ); |
| 248 | + |
| 249 | + // Should have 2 root symbols: MyClass and topLevelFunction |
| 250 | + assert.strictEqual(result.length, 2); |
| 251 | + |
| 252 | + // Check first root symbol (MyClass) |
| 253 | + assert.strictEqual(result[0].name, 'MyClass'); |
| 254 | + assert.strictEqual(result[0].kind, SymbolKind.Class); |
| 255 | + assert.strictEqual(result[0].children?.length, 2); |
| 256 | + |
| 257 | + // Check children of MyClass |
| 258 | + assert.strictEqual(result[0].children![0].name, 'myMethod'); |
| 259 | + assert.strictEqual(result[0].children![0].kind, SymbolKind.Method); |
| 260 | + assert.strictEqual(result[0].children![0].children?.length, 1); |
| 261 | + |
| 262 | + // Check nested child (localVar inside myMethod) |
| 263 | + assert.strictEqual(result[0].children![0].children![0].name, 'localVar'); |
| 264 | + assert.strictEqual(result[0].children![0].children![0].kind, SymbolKind.Variable); |
| 265 | + assert.strictEqual(result[0].children![0].children![0].children?.length, 0); |
| 266 | + |
| 267 | + // Check second child of MyClass |
| 268 | + assert.strictEqual(result[0].children![1].name, 'anotherMethod'); |
| 269 | + assert.strictEqual(result[0].children![1].kind, SymbolKind.Method); |
| 270 | + assert.strictEqual(result[0].children![1].children?.length, 0); |
| 271 | + |
| 272 | + // Check second root symbol (topLevelFunction) |
| 273 | + assert.strictEqual(result[1].name, 'topLevelFunction'); |
| 274 | + assert.strictEqual(result[1].kind, SymbolKind.Function); |
| 275 | + assert.strictEqual(result[1].children?.length, 0); |
| 276 | + }); |
| 277 | + |
| 278 | + it('handles empty symbol list', async () => { |
| 279 | + const cancellation_token: CancellationToken = { |
| 280 | + isCancellationRequested: false, |
| 281 | + onCancellationRequested: () => ({ dispose: () => {} }) |
| 282 | + }; |
| 283 | + |
| 284 | + const { docManager, pluginHost } = setup({ |
| 285 | + getDocumentSymbols: sinon.stub().returns([]) |
| 286 | + }); |
| 287 | + docManager.openClientDocument(textDocument); |
| 288 | + |
| 289 | + const result = await pluginHost.getHierarchicalDocumentSymbols( |
| 290 | + textDocument, |
| 291 | + cancellation_token |
| 292 | + ); |
| 293 | + |
| 294 | + assert.deepStrictEqual(result, []); |
| 295 | + }); |
| 296 | + |
| 297 | + it('handles symbols with same start position', async () => { |
| 298 | + const cancellation_token: CancellationToken = { |
| 299 | + isCancellationRequested: false, |
| 300 | + onCancellationRequested: () => ({ dispose: () => {} }) |
| 301 | + }; |
| 302 | + |
| 303 | + const flat_symbols: SymbolInformation[] = [ |
| 304 | + // Two symbols starting at same position, longer one should be parent |
| 305 | + SymbolInformation.create( |
| 306 | + 'outer', |
| 307 | + SymbolKind.Class, |
| 308 | + Range.create(Position.create(0, 0), Position.create(10, 0)), |
| 309 | + 'file:///hello.svelte' |
| 310 | + ), |
| 311 | + SymbolInformation.create( |
| 312 | + 'inner', |
| 313 | + SymbolKind.Method, |
| 314 | + Range.create(Position.create(0, 0), Position.create(5, 0)), |
| 315 | + 'file:///hello.svelte' |
| 316 | + ) |
| 317 | + ]; |
| 318 | + |
| 319 | + const { docManager, pluginHost } = setup({ |
| 320 | + getDocumentSymbols: sinon.stub().returns(flat_symbols) |
| 321 | + }); |
| 322 | + docManager.openClientDocument(textDocument); |
| 323 | + |
| 324 | + const result = await pluginHost.getHierarchicalDocumentSymbols( |
| 325 | + textDocument, |
| 326 | + cancellation_token |
| 327 | + ); |
| 328 | + |
| 329 | + assert.strictEqual(result.length, 1); |
| 330 | + assert.strictEqual(result[0].name, 'outer'); |
| 331 | + assert.strictEqual(result[0].children?.length, 1); |
| 332 | + assert.strictEqual(result[0].children![0].name, 'inner'); |
| 333 | + }); |
| 334 | + }); |
190 | 335 | }); |
0 commit comments