diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index 58d7f581a..941be5800 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -50,6 +50,8 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { + // Treat filter the same as function: both get SymbolType.Function so they use the same + // function-like symbol kind. Document-symbol inclusion and child hierarchy are determined elsewhere. SymbolType symbolType = functionDefinitionAst.IsWorkflow ? SymbolType.Workflow : SymbolType.Function; diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 index 2831ea332..79aacc081 100644 --- a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 @@ -6,7 +6,7 @@ $Script:ScriptVar2 = 2 function script:AFunction {} -filter AFilter {$_} +filter AFilter { $FilterVar = $_ } function AnAdvancedFunction { begin { diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 593fddb04..d971622c8 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -21,6 +21,7 @@ using Microsoft.PowerShell.EditorServices.Test.Shared.References; using Microsoft.PowerShell.EditorServices.Test.Shared.SymbolDetails; using Microsoft.PowerShell.EditorServices.Test.Shared.Symbols; +using Microsoft.PowerShell.EditorServices.Handlers; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Xunit; @@ -140,6 +141,21 @@ private IEnumerable FindSymbolsInFile(ScriptRegion scriptRegion .OrderBy(symbol => symbol.ScriptRegion.ToRange().Start); } + private async Task> GetDocumentSymbols(ScriptRegion scriptRegion) + { + string path = TestUtilities.GetSharedPath(scriptRegion.File); + PsesDocumentSymbolHandler handler = new(NullLoggerFactory.Instance, workspace); + SymbolInformationOrDocumentSymbolContainer result = await handler.Handle( + new DocumentSymbolParams + { + TextDocument = new TextDocumentIdentifier { Uri = DocumentUri.FromFileSystemPath(path) } + }, + CancellationToken.None); + return result + .Where(s => s.IsDocumentSymbol) + .Select(s => s.DocumentSymbol); + } + [Fact] public async Task FindsParameterHintsOnCommand() { @@ -768,14 +784,14 @@ public async Task FindsDetailsForBuiltInCommand() } [Fact] - public void FindsSymbolsInFile() + public async Task FindsSymbolsInFile() { IEnumerable symbols = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails); Assert.Equal(7, symbols.Count(i => i.Type == SymbolType.Function)); - Assert.Equal(8, symbols.Count(i => i.Type == SymbolType.Variable)); + Assert.Equal(9, symbols.Count(i => i.Type == SymbolType.Variable)); Assert.Equal(4, symbols.Count(i => i.Type == SymbolType.Parameter)); - Assert.Equal(12, symbols.Count(i => i.Id.StartsWith("var "))); + Assert.Equal(13, symbols.Count(i => i.Id.StartsWith("var "))); Assert.Equal(2, symbols.Count(i => i.Id.StartsWith("prop "))); SymbolReference symbol = symbols.First(i => i.Type == SymbolType.Function); @@ -788,6 +804,20 @@ public void FindsSymbolsInFile() Assert.Equal("filter AFilter ()", symbol.Name); Assert.True(symbol.IsDeclaration); + // Verify that a variable declared inside a filter is tracked as a declaration, + // and that it is returned as a child of the filter in the LSP outline hierarchy. + symbol = Assert.Single(symbols, i => i.Id == "var FilterVar"); + Assert.Equal("$FilterVar", symbol.Name); + Assert.True(symbol.IsDeclaration); + + DocumentSymbol filterDocumentSymbol = Assert.Single( + await GetDocumentSymbols(FindSymbolsInMultiSymbolFile.SourceDetails), + i => i.Name == "filter AFilter ()"); + DocumentSymbol filterVariableDocumentSymbol = Assert.Single( + filterDocumentSymbol.Children, + i => i.Name == "$FilterVar"); + Assert.Equal(SymbolKind.Variable, filterVariableDocumentSymbol.Kind); + symbol = symbols.Last(i => i.Type == SymbolType.Variable); Assert.Equal("var nestedVar", symbol.Id); Assert.Equal("$nestedVar", symbol.Name);