diff --git a/src/PowerShellEditorServices/Language/AstOperations.cs b/src/PowerShellEditorServices/Language/AstOperations.cs index da45431bd..f146c82d2 100644 --- a/src/PowerShellEditorServices/Language/AstOperations.cs +++ b/src/PowerShellEditorServices/Language/AstOperations.cs @@ -243,41 +243,33 @@ static public IEnumerable FindSymbolsInDocument(Ast scriptAst, // TODO: Restore this when we figure out how to support multiple // PS versions in the new PSES-as-a-module world (issue #276) -// if (powerShellVersion >= new Version(5,0)) -// { -//#if PowerShellv5 -// FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2(); -// scriptAst.Visit(findSymbolsVisitor); -// symbolReferences = findSymbolsVisitor.SymbolReferences; -//#endif -// } -// else - { - if (IsPowerShellDataFileAst(scriptAst)) - { - var findHashtableSymbolsVisitor = new FindHashtableSymbolsVisitor(); - scriptAst.Visit(findHashtableSymbolsVisitor); - symbolReferences = findHashtableSymbolsVisitor.SymbolReferences; - } - else - { - FindSymbolsVisitor findSymbolsVisitor = new FindSymbolsVisitor(); - scriptAst.Visit(findSymbolsVisitor); - symbolReferences = findSymbolsVisitor.SymbolReferences; - } - } - + // if (powerShellVersion >= new Version(5,0)) + // { + //#if PowerShellv5 + // FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2(); + // scriptAst.Visit(findSymbolsVisitor); + // symbolReferences = findSymbolsVisitor.SymbolReferences; + //#endif + // } + // else + + FindSymbolsVisitor findSymbolsVisitor = new FindSymbolsVisitor(); + scriptAst.Visit(findSymbolsVisitor); + symbolReferences = findSymbolsVisitor.SymbolReferences; return symbolReferences; } - static private bool IsPowerShellDataFileAst(Ast ast) + /// + /// Checks if a given ast represents the root node of a *.psd1 file. + /// + /// The abstract syntax tree of the given script + /// true if the AST represts a *.psd1 file, otherwise false + static public bool IsPowerShellDataFileAst(Ast ast) { // sometimes we don't have reliable access to the filename // so we employ heuristics to check if the contents are // part of a psd1 file. - return (ast.Extent.File != null - && ast.Extent.File.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) - || IsPowerShellDataFileAstNode( + return IsPowerShellDataFileAstNode( new { Item = ast, Children = new List() }, new Type[] { typeof(ScriptBlockAst), diff --git a/src/PowerShellEditorServices/Language/DocumentSymbolProvider.cs b/src/PowerShellEditorServices/Language/DocumentSymbolProvider.cs new file mode 100644 index 000000000..a418cae1d --- /dev/null +++ b/src/PowerShellEditorServices/Language/DocumentSymbolProvider.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.PowerShell.EditorServices +{ + internal abstract class DocumentSymbolProvider + { + public IEnumerable GetSymbols(ScriptFile scriptFile, Version psVersion = null) + { + if (CanProvideFor(scriptFile)) + { + return GetSymbolsImpl(scriptFile, psVersion); + } + + return Enumerable.Empty(); + } + + protected abstract IEnumerable GetSymbolsImpl(ScriptFile scriptFile, Version psVersion); + + protected abstract bool CanProvideFor(ScriptFile scriptFile); + } +} diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index f808a2a1d..634edaae7 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -32,6 +32,7 @@ public class LanguageService private string mostRecentRequestFile; private Dictionary> CmdletToAliasDictionary; private Dictionary AliasToCmdletDictionary; + private DocumentSymbolProvider[] documentSymbolProviders; const int DefaultWaitTimeoutMilliseconds = 5000; @@ -54,6 +55,12 @@ public LanguageService(PowerShellContext powerShellContext) this.CmdletToAliasDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); this.AliasToCmdletDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + this.documentSymbolProviders = new DocumentSymbolProvider[] + { + new ScriptDocumentSymbolProvider(), + new PSDDocumentSymbolProvider(), + new PesterDocumentSymbolProvider() + }; } #endregion @@ -226,24 +233,18 @@ await SymbolDetails.Create( public FindOccurrencesResult FindSymbolsInFile(ScriptFile scriptFile) { Validate.IsNotNull("scriptFile", scriptFile); - - IEnumerable symbolReferencesinFile = - AstOperations - .FindSymbolsInDocument(scriptFile.ScriptAst, this.powerShellContext.LocalPowerShellVersion.Version) - .Select( - reference => + return new FindOccurrencesResult + { + FoundOccurrences = documentSymbolProviders + .SelectMany(p => p.GetSymbols(scriptFile, powerShellContext.LocalPowerShellVersion.Version)) + .Select(reference => { reference.SourceLine = scriptFile.GetLine(reference.ScriptRegion.StartLineNumber); reference.FilePath = scriptFile.FilePath; return reference; - }); - - return - new FindOccurrencesResult - { - FoundOccurrences = symbolReferencesinFile - }; + }) + }; } /// diff --git a/src/PowerShellEditorServices/Language/PSDDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Language/PSDDocumentSymbolProvider.cs new file mode 100644 index 000000000..85abd5bd7 --- /dev/null +++ b/src/PowerShellEditorServices/Language/PSDDocumentSymbolProvider.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.PowerShell.EditorServices +{ + internal class PSDDocumentSymbolProvider : DocumentSymbolProvider + { + protected override bool CanProvideFor(ScriptFile scriptFile) + { + return (scriptFile.FilePath != null && + scriptFile.FilePath.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) || + AstOperations.IsPowerShellDataFileAst(scriptFile.ScriptAst); + } + protected override IEnumerable GetSymbolsImpl(ScriptFile scriptFile, Version psVersion) + { + var findHashtableSymbolsVisitor = new FindHashtableSymbolsVisitor(); + scriptFile.ScriptAst.Visit(findHashtableSymbolsVisitor); + return findHashtableSymbolsVisitor.SymbolReferences; + } + } +} diff --git a/src/PowerShellEditorServices/Language/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Language/PesterDocumentSymbolProvider.cs new file mode 100644 index 000000000..4fb061700 --- /dev/null +++ b/src/PowerShellEditorServices/Language/PesterDocumentSymbolProvider.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.EditorServices +{ + internal class PesterDocumentSymbolProvider : DocumentSymbolProvider + { + protected override bool CanProvideFor(ScriptFile scriptFile) + { + return scriptFile.FilePath.EndsWith("tests.ps1", StringComparison.OrdinalIgnoreCase); + } + + protected override IEnumerable GetSymbolsImpl(ScriptFile scriptFile, Version psVersion) + { + var commandAsts = scriptFile.ScriptAst.FindAll(ast => + { + switch ((ast as CommandAst)?.GetCommandName().ToLower()) + { + case "describe": + case "context": + case "it": + return true; + + default: + return false; + } + }, + true); + + return commandAsts.Select(ast => new SymbolReference( + SymbolType.Function, + ast.Extent, + scriptFile.FilePath, + scriptFile.GetLine(ast.Extent.StartLineNumber))); + } + } +} diff --git a/src/PowerShellEditorServices/Language/ScriptDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Language/ScriptDocumentSymbolProvider.cs new file mode 100644 index 000000000..74aefa91d --- /dev/null +++ b/src/PowerShellEditorServices/Language/ScriptDocumentSymbolProvider.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.PowerShell.EditorServices +{ + internal class ScriptDocumentSymbolProvider : DocumentSymbolProvider + { + protected override bool CanProvideFor(ScriptFile scriptFile) + { + return scriptFile != null && + scriptFile.FilePath != null && + (scriptFile.FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) || + scriptFile.FilePath.EndsWith(".psm1", StringComparison.OrdinalIgnoreCase)); + } + + protected override IEnumerable GetSymbolsImpl( + ScriptFile scriptFile, + Version psVersion) + { + return AstOperations.FindSymbolsInDocument( + scriptFile.ScriptAst, + psVersion); + + } + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInPSDFile.cs b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInPSDFile.cs new file mode 100644 index 000000000..8d96f49d6 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInPSDFile.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Symbols +{ + public class FindSymbolsInPSDFile + { + public static readonly ScriptRegion SourceDetails = + new ScriptRegion + { + File = @"Symbols\PowerShellDataFile.psd1" + }; + } +} + diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInPesterFile.cs b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInPesterFile.cs new file mode 100644 index 000000000..cebcae3aa --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInPesterFile.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Symbols +{ + public class FindSymbolsInPesterFile + { + public static readonly ScriptRegion SourceDetails = + new ScriptRegion + { + File = @"Symbols\PesterFile.tests.ps1" + }; + } +} + diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.tests.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.tests.ps1 new file mode 100644 index 000000000..086b071e0 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.tests.ps1 @@ -0,0 +1,15 @@ +Describe "A dummy test" { + Context "When a pester file is given" { + It "Should return it symbols" { + + } + + It "Should return context symbols" { + + } + + It "Should return describe symbols" { + + } + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/PowerShellDataFile.psd1 b/test/PowerShellEditorServices.Test.Shared/Symbols/PowerShellDataFile.psd1 new file mode 100644 index 000000000..f8f4e32c1 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/PowerShellDataFile.psd1 @@ -0,0 +1,5 @@ +@{ + property1 = "value1" + property2 = "value2" + property3 = "value3" +} diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index c9be58fcb..81a184ab5 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -300,6 +300,20 @@ public void LanguageServiceFindsSymbolsInFile() //Assert.Equal(1, firstConfigurationSymbol.ScriptRegion.StartColumnNumber); } + [Fact] + public void LanguageServiceFindsSymbolsInPesterFile() + { + var symbolsResult = this.FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails); + Assert.Equal(5, symbolsResult.FoundOccurrences.Count()); + } + + [Fact] + public void LangServerFindsSymbolsInPSDFile() + { + var symbolsResult = this.FindSymbolsInFile(FindSymbolsInPSDFile.SourceDetails); + Assert.Equal(3, symbolsResult.FoundOccurrences.Count()); + } + [Fact] public void LanguageServiceFindsSymbolsInNoSymbolsFile() { @@ -310,7 +324,6 @@ public void LanguageServiceFindsSymbolsInNoSymbolsFile() Assert.Equal(0, symbolsResult.FoundOccurrences.Count()); } - private ScriptFile GetScriptFile(ScriptRegion scriptRegion) { string resolvedPath =