Path: blob/main/extensions/copilot/src/extension/linkify/test/vscode-node/findSymbol.test.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import * as vscode from 'vscode';7import { findBestSymbolByPath } from '../../vscode-node/findSymbol';89suite('Find symbol', () => {10function docSymbol(name: string, ...children: vscode.DocumentSymbol[]): vscode.DocumentSymbol {11return {12name,13children,14detail: '',15range: new vscode.Range(0, 0, 0, 0),16selectionRange: new vscode.Range(0, 0, 0, 0),17kind: vscode.SymbolKind.Variable,18};19}2021function symbolInfo(name: string): vscode.SymbolInformation {22return {23name,24containerName: '',25kind: vscode.SymbolKind.Variable,26location: {27uri: vscode.Uri.file('fake'),28range: new vscode.Range(0, 0, 0, 0),29}30};31}3233test('Should find exact match', () => {34assert.strictEqual(findBestSymbolByPath([docSymbol('a')], 'a')?.name, 'a');35assert.strictEqual(findBestSymbolByPath([symbolInfo('a')], 'a')?.name, 'a');36});3738test('Should find nested', () => {39assert.strictEqual(findBestSymbolByPath([docSymbol('x', docSymbol('a'))], 'a')?.name, 'a');40});4142test('Should find child match', () => {43assert.strictEqual(findBestSymbolByPath([docSymbol('a', docSymbol('b'))], 'a.b')?.name, 'b');44});4546test('Should find child match skipping level', () => {47assert.strictEqual(findBestSymbolByPath([docSymbol('a', docSymbol('x', docSymbol('b')))], 'a.b')?.name, 'b');48});4950test(`Should find match even when children don't match`, () => {51assert.strictEqual(findBestSymbolByPath([docSymbol('a')], 'a.b')?.name, 'a');52});5354test(`Should find longest match`, () => {55assert.strictEqual(findBestSymbolByPath([56docSymbol('a',57docSymbol('x')),58docSymbol('x',59docSymbol('a',60docSymbol('b',61docSymbol('z'))))62], 'a.b')?.name, 'b');63});6465test('Should ignore function call notation', () => {66assert.strictEqual(findBestSymbolByPath([docSymbol('a')], 'a()')?.name, 'a');67assert.strictEqual(findBestSymbolByPath([docSymbol('a')], 'a(1, 2, 3)')?.name, 'a');68assert.strictEqual(findBestSymbolByPath([docSymbol('a')], 'a(b, c)')?.name, 'a');69assert.strictEqual(findBestSymbolByPath([docSymbol('a')], 'a(b: string)')?.name, 'a');70});7172test('Should ignore generic notation', () => {73assert.strictEqual(findBestSymbolByPath([docSymbol('a')], 'a<T>')?.name, 'a');74assert.strictEqual(findBestSymbolByPath([docSymbol('a')], 'a<T>.b')?.name, 'a');75});7677test('Should match on symbols with $', () => {78assert.strictEqual(findBestSymbolByPath([docSymbol('$a')], '$a')?.name, '$a');79});8081test('Should match on symbols with _', () => {82assert.strictEqual(findBestSymbolByPath([docSymbol('_a_')], '_a_')?.name, '_a_');83});8485test('Should prefer rightmost symbol in flat symbols', () => {86// When symbols are flat (SymbolInformation), prefer the rightmost match87// This handles cases like `TextModel.undo()` where we want `undo`, not `TextModel`88assert.strictEqual(89findBestSymbolByPath([90symbolInfo('TextModel'),91symbolInfo('undo')92], 'TextModel.undo()')?.name,93'undo'94);95});9697test('Should fall back to leftmost symbol if rightmost not found in flat symbols', () => {98// If the rightmost part isn't found, fall back to leftmost matches99assert.strictEqual(100findBestSymbolByPath([101symbolInfo('TextModel'),102symbolInfo('someOtherMethod')103], 'TextModel.undo()')?.name,104'TextModel'105);106});107108test('Should prefer hierarchical match over flat last part match', () => {109// When both hierarchical and flat symbols exist, prefer the hierarchical match110assert.strictEqual(111findBestSymbolByPath([112docSymbol('TextModel', docSymbol('undo')),113symbolInfo('undo') // This is a different undo from a different class114], 'TextModel.undo()')?.name,115'undo'116);117});118119test('Should handle deeply qualified names', () => {120// Test multiple levels of qualification121assert.strictEqual(122findBestSymbolByPath([123docSymbol('namespace', docSymbol('TextModel', docSymbol('undo')))124], 'namespace.TextModel.undo()')?.name,125'undo'126);127128// With flat symbols, prefer the rightmost part129assert.strictEqual(130findBestSymbolByPath([131symbolInfo('namespace'),132symbolInfo('TextModel'),133symbolInfo('undo')134], 'namespace.TextModel.undo()')?.name,135'undo'136);137138// Middle part should be preferred over leftmost139assert.strictEqual(140findBestSymbolByPath([141symbolInfo('namespace'),142symbolInfo('TextModel')143], 'namespace.TextModel.undo()')?.name,144'TextModel'145);146});147148test('Should handle mixed flat and hierarchical symbols', () => {149// Some symbols are flat, some are nested150assert.strictEqual(151findBestSymbolByPath([152symbolInfo('Model'),153docSymbol('TextModel', docSymbol('undo')),154symbolInfo('OtherClass')155], 'TextModel.undo()')?.name,156'undo'157);158});159160test('Should handle Python-style naming conventions', () => {161// Python uses underscores instead of camelCase162assert.strictEqual(163findBestSymbolByPath([164docSymbol('MyClass', docSymbol('my_method'))165], 'MyClass.my_method()')?.name,166'my_method'167);168169// Python dunder methods170assert.strictEqual(171findBestSymbolByPath([172docSymbol('MyClass', docSymbol('__init__'))173], 'MyClass.__init__()')?.name,174'__init__'175);176177// Python private methods178assert.strictEqual(179findBestSymbolByPath([180docSymbol('MyClass', docSymbol('_private_method'))181], 'MyClass._private_method()')?.name,182'_private_method'183);184});185186test('Should handle Python module qualified names', () => {187// Python: module.Class.method188assert.strictEqual(189findBestSymbolByPath([190docSymbol('my_module', docSymbol('MyClass', docSymbol('my_method')))191], 'my_module.MyClass.my_method()')?.name,192'my_method'193);194});195196test('Should prefer rightmost match in flat symbols using position-based priority', () => {197// When both class and method exist as flat symbols, prefer rightmost198assert.strictEqual(199findBestSymbolByPath([200symbolInfo('TextModel'), // matchCount=1 (index 0)201symbolInfo('undo') // matchCount=2 (index 1)202], 'TextModel.undo()')?.name,203'undo'204);205206// Reverse order - should still prefer undo due to higher matchCount207assert.strictEqual(208findBestSymbolByPath([209symbolInfo('undo'), // matchCount=2 (index 1)210symbolInfo('TextModel') // matchCount=1 (index 0)211], 'TextModel.undo()')?.name,212'undo'213);214215// Works for longer qualified names too216// For 'a.b.c.d' => ['a', 'b', 'c', 'd']:217// 'd' (index 3, matchCount=4) > 'c' (index 2, matchCount=3) > 'b' (index 1, matchCount=2) > 'a' (index 0, matchCount=1)218assert.strictEqual(219findBestSymbolByPath([220symbolInfo('a'), // matchCount=1221symbolInfo('b'), // matchCount=2222symbolInfo('c'), // matchCount=3223], 'a.b.c.d')?.name,224'c' // Highest matchCount among available symbols225);226});227});228229230