From b71db62ee2a171b7d0fabc68e83fe49b336596c4 Mon Sep 17 00:00:00 2001 From: Kayla Davis Date: Thu, 23 Jul 2015 15:27:56 -0700 Subject: [PATCH 01/18] Finds all dot sourced files in a ScriptFile --- .../Language/AstOperations.cs | 14 ++++++ .../Language/FindDotSourcedVisitor.cs | 45 +++++++++++++++++++ .../PowerShellEditorServices.csproj | 1 + .../Session/ScriptFile.cs | 20 +++++++++ ...{FileChangeTests.cs => ScriptFileTests.cs} | 19 ++++++++ 5 files changed, 99 insertions(+) create mode 100644 src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs rename test/PowerShellEditorServices.Test/Session/{FileChangeTests.cs => ScriptFileTests.cs} (84%) diff --git a/src/PowerShellEditorServices/Language/AstOperations.cs b/src/PowerShellEditorServices/Language/AstOperations.cs index d44100759..93c3945c1 100644 --- a/src/PowerShellEditorServices/Language/AstOperations.cs +++ b/src/PowerShellEditorServices/Language/AstOperations.cs @@ -4,6 +4,7 @@ // using System.Collections.Generic; +using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; @@ -135,5 +136,18 @@ static public SymbolReference FindDefinitionOfSymbol(Ast scriptAst, SymbolRefere return declarationVisitor.FoundDeclartion; } + + /// + /// Finds all files dot sourced in a script + /// + /// The abstract syntax tree of the given script + /// + static public string[] FindDotSourcedIncludes(Ast scriptAst) + { + FindDotSourcedVisitor dotSourcedVisitor = new FindDotSourcedVisitor(); + scriptAst.Visit(dotSourcedVisitor); + + return dotSourcedVisitor.DotSourcedFiles.ToArray(); + } } } diff --git a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs b/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs new file mode 100644 index 000000000..39ac35974 --- /dev/null +++ b/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.PowerShell.EditorServices.Session; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.EditorServices.Language +{ + /// + /// The vistor used to find the dont sourced files in an AST + /// + internal class FindDotSourcedVisitor : AstVisitor + { + /// + /// A hash set of the dot sourced files (because we don't want duplicates) + /// + public HashSet DotSourcedFiles { get; private set; } + + public FindDotSourcedVisitor() + { + this.DotSourcedFiles = new HashSet(); + } + + /// + /// Checks to see if the command invocation is a dot + /// in order to find a dot sourced file + /// + /// A CommandAst object in the script's AST + /// A descion to stop searching if the right commandAst was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitCommand(CommandAst commandAst) + { + if (commandAst.InvocationOperator.Equals(TokenKind.Dot)) + { + string fileName = commandAst.CommandElements[0].Extent.Text; + DotSourcedFiles.Add(fileName); + } + return base.VisitCommand(commandAst); + } + } +} diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 0e376e5a7..818c52d10 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -72,6 +72,7 @@ + diff --git a/src/PowerShellEditorServices/Session/ScriptFile.cs b/src/PowerShellEditorServices/Session/ScriptFile.cs index 584619c18..f73e7b4e4 100644 --- a/src/PowerShellEditorServices/Session/ScriptFile.cs +++ b/src/PowerShellEditorServices/Session/ScriptFile.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.PowerShell.EditorServices.Language; using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; @@ -78,6 +79,15 @@ public Token[] ScriptTokens get { return this.scriptTokens; } } + /// + /// Gets the array of filepaths dot sourced in this ScriptFile + /// + public string[] ReferencedFiles + { + get; + private set; + } + #endregion #region Constructors @@ -224,6 +234,16 @@ private void ReadFile(TextReader textReader) // Parse the contents to get syntax tree and errors this.ParseFileContents(); + this.FindDotSourcedFiles(); + } + + /// + /// Finds the dot sourced files in this ScriptFile + /// + private void FindDotSourcedFiles() + { + ReferencedFiles = + AstOperations.FindDotSourcedIncludes(this.ScriptAst); } /// diff --git a/test/PowerShellEditorServices.Test/Session/FileChangeTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs similarity index 84% rename from test/PowerShellEditorServices.Test/Session/FileChangeTests.cs rename to test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index ffef79453..826190604 100644 --- a/test/PowerShellEditorServices.Test/Session/FileChangeTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -123,6 +123,25 @@ public void CanApplyMultiLineDelete() }); } + [Fact] + public void FindsDotSourcedFiles() + { + string exampleScriptContents = + @". .\athing.ps1"+"\r\n"+ + @". .\somefile.ps1"+"\r\n" + + @". .\somefile.ps1"+"\r\n" + + @"Do-Stuff $uri"+"\r\n" + + @". simpleps.ps1"; + + using (StringReader stringReader = new StringReader(exampleScriptContents)) + { + ScriptFile scriptFile = new ScriptFile("DotSourceTestFile.ps1", stringReader); + Assert.Equal(3, scriptFile.ReferencedFiles.Length); + System.Console.Write("a" + scriptFile.ReferencedFiles[0]); + Assert.Equal(@".\athing.ps1", scriptFile.ReferencedFiles[0]); + } + } + private void AssertFileChange( string initialString, string expectedString, From 1e1f1894c1aba0a4c6f55afc1bbc38b89e0749ea Mon Sep 17 00:00:00 2001 From: Kayla Davis Date: Fri, 24 Jul 2015 14:18:01 -0700 Subject: [PATCH 02/18] Adding multiple file support on references request --- .../Request/ReferencesRequest.cs | 10 ++- .../Language/LanguageService.cs | 37 ++++++++--- .../Session/EditorSession.cs | 46 +++++++++++++- .../Session/ScriptFile.cs | 18 +++--- .../TestFiles/FindReferences.ps1 | 2 +- .../Language/LanguageServiceTests.cs | 62 +++++++++---------- .../PowerShellEditorServices.Test.csproj | 2 +- 7 files changed, 124 insertions(+), 53 deletions(-) diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs index 0f175db7d..544fe6453 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs @@ -18,13 +18,17 @@ public override void ProcessMessage( MessageWriter messageWriter) { ScriptFile scriptFile = this.GetScriptFile(editorSession); - - FindReferencesResult referencesResult = - editorSession.LanguageService.FindReferencesInFile( + SymbolReference foundSymbol = + editorSession.LanguageService.FindSymbolAtLocation( scriptFile, this.Arguments.Line, this.Arguments.Offset); + FindReferencesResult referencesResult = + editorSession.LanguageService.FindReferencesOfSymbol( + foundSymbol, + editorSession.ExpandReferences(scriptFile)); + ReferencesResponse referencesResponse = ReferencesResponse.Create(referencesResult, this.Arguments.File); diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index f53ea1d6f..8077de6bf 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -122,26 +122,47 @@ public CompletionDetails GetCompletionDetailsInFile( } /// - /// Finds all the references of a symbol in the script given a file location + /// Finds the symbol in the script given a file location /// /// The details and contents of a open script file /// The line number of the cursor for the given script /// The coulumn number of the cursor for the given script - /// FindReferencesResult - public FindReferencesResult FindReferencesInFile( + /// A SymbolReference of the symbol found at the given location + /// or null if there is no symbol at that location + /// + public SymbolReference FindSymbolAtLocation( ScriptFile file, int lineNumber, int columnNumber) { - SymbolReference foundSymbol = + return AstOperations.FindSymbolAtPosition( file.ScriptAst, lineNumber, columnNumber); + } + /// + /// Finds all the references of a symbol + /// + /// The details and contents of a open script file + /// The line number of the cursor for the given script + /// The coulumn number of the cursor for the given script + /// FindReferencesResult + public FindReferencesResult FindReferencesOfSymbol( + SymbolReference foundSymbol, + ScriptFile[] referencedFiles) + { if (foundSymbol != null) { - IEnumerable symbolReferences = + int symbolOffset = referencedFiles[0].GetOffsetAtPosition( + foundSymbol.ScriptRegion.StartLineNumber, + foundSymbol.ScriptRegion.StartColumnNumber); + List symbolReferences = new List(); + + foreach (ScriptFile file in referencedFiles) + { + IEnumerable symbolReferencesinFile = AstOperations .FindReferencesOfSymbol( file.ScriptAst, @@ -149,15 +170,17 @@ public FindReferencesResult FindReferencesInFile( .Select( reference => { - reference.SourceLine = + reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber); return reference; }); + symbolReferences.AddRange(symbolReferencesinFile); + } return new FindReferencesResult { - SymbolFileOffset = file.GetOffsetAtPosition(lineNumber, columnNumber), + SymbolFileOffset = symbolOffset, SymbolName = foundSymbol.SymbolName, FoundReferences = symbolReferences }; diff --git a/src/PowerShellEditorServices/Session/EditorSession.cs b/src/PowerShellEditorServices/Session/EditorSession.cs index 21161ad40..2dfcbdaa3 100644 --- a/src/PowerShellEditorServices/Session/EditorSession.cs +++ b/src/PowerShellEditorServices/Session/EditorSession.cs @@ -27,6 +27,7 @@ public class EditorSession private Runspace languageRunspace; private Dictionary workspaceFiles = new Dictionary(); + private Dictionary referencedScriptFiles = new Dictionary(); #endregion @@ -46,7 +47,7 @@ public class EditorSession /// Gets the ConsoleService instance for this session. /// public ConsoleService ConsoleService { get; private set; } - + #endregion #region Public Methods @@ -145,6 +146,49 @@ public IEnumerable GetOpenFiles() return this.workspaceFiles.Values; } + /// + /// Gets all file references by recursively searching + /// through referenced files in a scriptfile + /// + /// Contains the details and contents of an open script file + /// A scriptfile array where the first file + /// in the array is the "root file" of the search + public ScriptFile[] ExpandReferences(ScriptFile scriptFile) + { + RecursivelyFindReferences(scriptFile); + ScriptFile[] expandedReferences = { scriptFile }; + if (referencedScriptFiles.Count != 0) + { + referencedScriptFiles.Values.CopyTo(expandedReferences, 1); + } + return expandedReferences; + } + + #endregion + + #region Private Methods + /// + /// Recusrively searches through referencedFiles in scriptFiles + /// and builds a Dictonary of the file references + /// + /// Contains the details and contents of an open script file + public void RecursivelyFindReferences(ScriptFile scriptFile) + { + ScriptFile newFile; + foreach (string filename in scriptFile.ReferencedFiles) + { + string filePath = Path.GetFullPath(filename); + if (referencedScriptFiles.ContainsKey(filePath)) + { + using (StreamReader streamReader = new StreamReader(filePath, Encoding.UTF8)) + { + newFile = new ScriptFile(filePath, streamReader); + referencedScriptFiles.Add(filePath, newFile); + } + RecursivelyFindReferences(newFile); + } + } + } #endregion #region IDisposable Implementation diff --git a/src/PowerShellEditorServices/Session/ScriptFile.cs b/src/PowerShellEditorServices/Session/ScriptFile.cs index f73e7b4e4..e40e21643 100644 --- a/src/PowerShellEditorServices/Session/ScriptFile.cs +++ b/src/PowerShellEditorServices/Session/ScriptFile.cs @@ -208,6 +208,15 @@ public int GetOffsetAtPosition(int lineNumber, int columnNumber) return offset; } + /// + /// Finds the dot sourced files in this ScriptFile + /// + public void FindDotSourcedFiles() + { + ReferencedFiles = + AstOperations.FindDotSourcedIncludes(this.ScriptAst); + } + #endregion #region Private Methods @@ -237,15 +246,6 @@ private void ReadFile(TextReader textReader) this.FindDotSourcedFiles(); } - /// - /// Finds the dot sourced files in this ScriptFile - /// - private void FindDotSourcedFiles() - { - ReferencedFiles = - AstOperations.FindDotSourcedIncludes(this.ScriptAst); - } - /// /// Parses the current file contents to get the AST, tokens, /// and parse errors. diff --git a/test/PowerShellEditorServices.Test.Host/TestFiles/FindReferences.ps1 b/test/PowerShellEditorServices.Test.Host/TestFiles/FindReferences.ps1 index 9373c10f4..76d3c9101 100644 --- a/test/PowerShellEditorServices.Test.Host/TestFiles/FindReferences.ps1 +++ b/test/PowerShellEditorServices.Test.Host/TestFiles/FindReferences.ps1 @@ -6,7 +6,7 @@ function My-Function ($myInput) $things = 4 $things - +. simpleps.ps1 My-Function $things Write-Output "Hi"; diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index f05bda85e..d3dde411d 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -134,29 +134,29 @@ public void LanguageServiceFindsVariableDefinition() Assert.Equal("$things", definition.SymbolName); } - [Fact] - public void LanguageServiceFindsFunctionReferences() - { - FindReferencesResult referencesResult = - this.GetReferences( - FindsReferencesOnFunction.SourceDetails); - - Assert.Equal(3, referencesResult.FoundReferences.Count()); - Assert.Equal(1, referencesResult.FoundReferences.First().ScriptRegion.StartLineNumber); - Assert.Equal(10, referencesResult.FoundReferences.First().ScriptRegion.StartColumnNumber); - } - - [Fact] - public void LanguageServiceFindsVariableReferences() - { - FindReferencesResult referencesResult = - this.GetReferences( - FindsReferencesOnVariable.SourceDetails); - - Assert.Equal(3, referencesResult.FoundReferences.Count()); - Assert.Equal(10, referencesResult.FoundReferences.Last().ScriptRegion.StartLineNumber); - Assert.Equal(13, referencesResult.FoundReferences.Last().ScriptRegion.StartColumnNumber); - } + //[Fact] + //public void LanguageServiceFindsFunctionReferences() + //{ + // FindReferencesResult referencesResult = + // this.GetReferences( + // FindsReferencesOnFunction.SourceDetails); + + // Assert.Equal(3, referencesResult.FoundReferences.Count()); + // Assert.Equal(1, referencesResult.FoundReferences.First().ScriptRegion.StartLineNumber); + // Assert.Equal(10, referencesResult.FoundReferences.First().ScriptRegion.StartColumnNumber); + //} + + //[Fact] + //public void LanguageServiceFindsVariableReferences() + //{ + // FindReferencesResult referencesResult = + // this.GetReferences( + // FindsReferencesOnVariable.SourceDetails); + + // Assert.Equal(3, referencesResult.FoundReferences.Count()); + // Assert.Equal(10, referencesResult.FoundReferences.Last().ScriptRegion.StartLineNumber); + // Assert.Equal(13, referencesResult.FoundReferences.Last().ScriptRegion.StartColumnNumber); + //} [Fact] public void LanguageServiceFindsOccurrencesOnFunction() @@ -215,14 +215,14 @@ private GetDefinitionResult GetDefinition(ScriptRegion scriptRegion) scriptRegion.StartColumnNumber); } - private FindReferencesResult GetReferences(ScriptRegion scriptRegion) - { - return - this.languageService.FindReferencesInFile( - GetScriptFile(scriptRegion), - scriptRegion.StartLineNumber, - scriptRegion.StartColumnNumber); - } + //private FindReferencesResult GetReferences(ScriptRegion scriptRegion) + //{ + // SymbolReference foundSymbol = + // this.languageService.FindSymbolAtLocation( + // GetScriptFile(scriptRegion), + // scriptRegion.StartLineNumber, + // scriptRegion.StartColumnNumber); + //} private FindOccurrencesResult GetOccurrences(ScriptRegion scriptRegion) { diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 1bf55103e..bc4a81fc8 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -63,7 +63,7 @@ - + From a3655325f3854dffb534ae53d2afe711bad50562 Mon Sep 17 00:00:00 2001 From: Kayla Davis Date: Sun, 26 Jul 2015 15:18:19 -0700 Subject: [PATCH 03/18] Adding multi file support on definitions --- .../Request/DeclarationRequest.cs | 10 ++- .../Request/ReferencesRequest.cs | 2 +- .../Language/LanguageService.cs | 40 ++++++------ .../Session/EditorSession.cs | 30 +++++---- .../Session/ScriptFile.cs | 14 ++-- .../Language/LanguageServiceTests.cs | 64 +++++++++---------- 6 files changed, 80 insertions(+), 80 deletions(-) diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs index 900de6419..08553389f 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs @@ -18,13 +18,17 @@ public override void ProcessMessage( MessageWriter messageWriter) { ScriptFile scriptFile = this.GetScriptFile(editorSession); - - GetDefinitionResult definition = - editorSession.LanguageService.GetDefinitionInFile( + SymbolReference foundSymbol = + editorSession.LanguageService.FindSymbolAtLocation( scriptFile, this.Arguments.Line, this.Arguments.Offset); + GetDefinitionResult definition = + editorSession.LanguageService.GetDefinitionOfSymbol( + foundSymbol, + editorSession.ExpandScriptReferences(scriptFile)); + if (definition != null) { DefinitionResponse defResponse = diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs index 544fe6453..8bad162e4 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs @@ -27,7 +27,7 @@ public override void ProcessMessage( FindReferencesResult referencesResult = editorSession.LanguageService.FindReferencesOfSymbol( foundSymbol, - editorSession.ExpandReferences(scriptFile)); + editorSession.ExpandScriptReferences(scriptFile)); ReferencesResponse referencesResponse = ReferencesResponse.Create(referencesResult, this.Arguments.File); diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index 8077de6bf..41c817ea4 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -145,9 +145,8 @@ public SymbolReference FindSymbolAtLocation( /// /// Finds all the references of a symbol /// - /// The details and contents of a open script file - /// The line number of the cursor for the given script - /// The coulumn number of the cursor for the given script + /// The symbol to find all references for + /// An array of scriptFiles too search for references in /// FindReferencesResult public FindReferencesResult FindReferencesOfSymbol( SymbolReference foundSymbol, @@ -189,30 +188,27 @@ public FindReferencesResult FindReferencesOfSymbol( } /// - /// Finds the definition of a symbol in the script given a file location + /// Finds the definition of a symbol in the script /// - /// The details and contents of a open script file - /// The line number of the cursor for the given script - /// The coulumn number of the cursor for the given script + /// The symbol to find a definition for + /// An array of scriptFiles too search for the definition in /// GetDefinitionResult - public GetDefinitionResult GetDefinitionInFile( - ScriptFile file, - int lineNumber, - int columnNumber) + public GetDefinitionResult GetDefinitionOfSymbol( + SymbolReference foundSymbol, + ScriptFile[] referencedFiles) { - SymbolReference foundSymbol = - AstOperations.FindSymbolAtPosition( - file.ScriptAst, - lineNumber, - columnNumber); - if (foundSymbol != null) { - SymbolReference foundDefinition = - AstOperations.FindDefinitionOfSymbol( - file.ScriptAst, - foundSymbol); - + SymbolReference foundDefinition = null; + int index = 0; + while (foundDefinition == null && index < referencedFiles.Length) + { + foundDefinition = + AstOperations.FindDefinitionOfSymbol( + referencedFiles[index].ScriptAst, + foundSymbol); + index++; + } return new GetDefinitionResult(foundDefinition); } else { return null; } diff --git a/src/PowerShellEditorServices/Session/EditorSession.cs b/src/PowerShellEditorServices/Session/EditorSession.cs index 2dfcbdaa3..bc5e3a33c 100644 --- a/src/PowerShellEditorServices/Session/EditorSession.cs +++ b/src/PowerShellEditorServices/Session/EditorSession.cs @@ -27,7 +27,6 @@ public class EditorSession private Runspace languageRunspace; private Dictionary workspaceFiles = new Dictionary(); - private Dictionary referencedScriptFiles = new Dictionary(); #endregion @@ -90,7 +89,7 @@ public void StartSession(IConsoleHost consoleHost) /// /// contains a null or empty string. /// - public void OpenFile(string filePath) + public ScriptFile OpenFile(string filePath) { Validate.IsNotNullOrEmptyString("filePath", filePath); @@ -104,6 +103,7 @@ public void OpenFile(string filePath) { ScriptFile newFile = new ScriptFile(filePath, streamReader); this.workspaceFiles.Add(filePath, newFile); + return newFile; } } else @@ -153,15 +153,18 @@ public IEnumerable GetOpenFiles() /// Contains the details and contents of an open script file /// A scriptfile array where the first file /// in the array is the "root file" of the search - public ScriptFile[] ExpandReferences(ScriptFile scriptFile) + public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile) { - RecursivelyFindReferences(scriptFile); - ScriptFile[] expandedReferences = { scriptFile }; + Dictionary referencedScriptFiles = new Dictionary(); + List expandedReferences = new List(); + + RecursivelyFindReferences(scriptFile, referencedScriptFiles); + expandedReferences.Add(scriptFile); // add original file first if (referencedScriptFiles.Count != 0) { - referencedScriptFiles.Values.CopyTo(expandedReferences, 1); + expandedReferences.AddRange(referencedScriptFiles.Values); } - return expandedReferences; + return expandedReferences.ToArray(); } #endregion @@ -171,8 +174,11 @@ public ScriptFile[] ExpandReferences(ScriptFile scriptFile) /// Recusrively searches through referencedFiles in scriptFiles /// and builds a Dictonary of the file references /// - /// Contains the details and contents of an open script file - public void RecursivelyFindReferences(ScriptFile scriptFile) + /// Details an contents of "root" script file + /// A Dictionary of referenced script files + private void RecursivelyFindReferences( + ScriptFile scriptFile, + Dictionary referencedScriptFiles) { ScriptFile newFile; foreach (string filename in scriptFile.ReferencedFiles) @@ -180,12 +186,12 @@ public void RecursivelyFindReferences(ScriptFile scriptFile) string filePath = Path.GetFullPath(filename); if (referencedScriptFiles.ContainsKey(filePath)) { - using (StreamReader streamReader = new StreamReader(filePath, Encoding.UTF8)) + if (TryGetFile(filePath, out newFile)) { - newFile = new ScriptFile(filePath, streamReader); + newFile = OpenFile(filePath); referencedScriptFiles.Add(filePath, newFile); } - RecursivelyFindReferences(newFile); + RecursivelyFindReferences(newFile, referencedScriptFiles); } } } diff --git a/src/PowerShellEditorServices/Session/ScriptFile.cs b/src/PowerShellEditorServices/Session/ScriptFile.cs index e40e21643..c0f444d28 100644 --- a/src/PowerShellEditorServices/Session/ScriptFile.cs +++ b/src/PowerShellEditorServices/Session/ScriptFile.cs @@ -208,15 +208,6 @@ public int GetOffsetAtPosition(int lineNumber, int columnNumber) return offset; } - /// - /// Finds the dot sourced files in this ScriptFile - /// - public void FindDotSourcedFiles() - { - ReferencedFiles = - AstOperations.FindDotSourcedIncludes(this.ScriptAst); - } - #endregion #region Private Methods @@ -243,7 +234,6 @@ private void ReadFile(TextReader textReader) // Parse the contents to get syntax tree and errors this.ParseFileContents(); - this.FindDotSourcedFiles(); } /// @@ -280,6 +270,10 @@ private void ParseFileContents() parseErrors .Select(ScriptFileMarker.FromParseError) .ToArray(); + + //Get all dot sourced referenced files and store them + this.ReferencedFiles = + AstOperations.FindDotSourcedIncludes(this.ScriptAst); } #endregion diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index d3dde411d..0f5b17d86 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -108,31 +108,31 @@ public void LanguageServiceFindsCommandForParamHintsWithSpaces() Assert.Equal(1, paramSignatures.Signatures.Count()); } - [Fact] - public void LanguageServiceFindsFunctionDefinition() - { - GetDefinitionResult definitionResult = - this.GetDefinition( - FindsFunctionDefinition.SourceDetails); - - SymbolReference definition = definitionResult.FoundDefinition; - Assert.Equal(1, definition.ScriptRegion.StartLineNumber); - Assert.Equal(10, definition.ScriptRegion.StartColumnNumber); - Assert.Equal("My-Function", definition.SymbolName); - } + //[Fact] + //public void LanguageServiceFindsFunctionDefinition() + //{ + // GetDefinitionResult definitionResult = + // this.GetDefinition( + // FindsFunctionDefinition.SourceDetails); + + // SymbolReference definition = definitionResult.FoundDefinition; + // Assert.Equal(1, definition.ScriptRegion.StartLineNumber); + // Assert.Equal(10, definition.ScriptRegion.StartColumnNumber); + // Assert.Equal("My-Function", definition.SymbolName); + //} - [Fact] - public void LanguageServiceFindsVariableDefinition() - { - GetDefinitionResult definitionResult = - this.GetDefinition( - FindsVariableDefinition.SourceDetails); - - SymbolReference definition = definitionResult.FoundDefinition; - Assert.Equal(6, definition.ScriptRegion.StartLineNumber); - Assert.Equal(1, definition.ScriptRegion.StartColumnNumber); - Assert.Equal("$things", definition.SymbolName); - } + //[Fact] + //public void LanguageServiceFindsVariableDefinition() + //{ + // GetDefinitionResult definitionResult = + // this.GetDefinition( + // FindsVariableDefinition.SourceDetails); + + // SymbolReference definition = definitionResult.FoundDefinition; + // Assert.Equal(6, definition.ScriptRegion.StartLineNumber); + // Assert.Equal(1, definition.ScriptRegion.StartColumnNumber); + // Assert.Equal("$things", definition.SymbolName); + //} //[Fact] //public void LanguageServiceFindsFunctionReferences() @@ -206,14 +206,14 @@ private ParameterSetSignatures GetParamSetSignatures(ScriptRegion scriptRegion) scriptRegion.StartColumnNumber); } - private GetDefinitionResult GetDefinition(ScriptRegion scriptRegion) - { - return - this.languageService.GetDefinitionInFile( - GetScriptFile(scriptRegion), - scriptRegion.StartLineNumber, - scriptRegion.StartColumnNumber); - } + //private GetDefinitionResult GetDefinition(ScriptRegion scriptRegion) + //{ + // return + // this.languageService.GetDefinitionOfSymbol( + // GetScriptFile(scriptRegion), + // scriptRegion.StartLineNumber, + // scriptRegion.StartColumnNumber); + //} //private FindReferencesResult GetReferences(ScriptRegion scriptRegion) //{ From bede0e9b47d60460a2e6c1c8a5fcbd74908cfd56 Mon Sep 17 00:00:00 2001 From: Kayla Davis Date: Tue, 28 Jul 2015 12:28:58 -0700 Subject: [PATCH 04/18] Add definition support for builtin commands --- .../Request/DeclarationRequest.cs | 11 ++- .../Response/DefinitionResponse.cs | 11 ++- .../Language/FindCommandVisitor.cs | 3 +- .../Language/FindDeclartionVisitor.cs | 7 +- .../Language/FindReferencesVisitor.cs | 12 +-- .../Language/FindSymbolVisitor.cs | 12 +-- .../Language/LanguageService.cs | 91 ++++++++++++++++++- .../Language/SymbolReference.cs | 9 +- 8 files changed, 127 insertions(+), 29 deletions(-) diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs index 08553389f..134a3a863 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs @@ -31,8 +31,15 @@ public override void ProcessMessage( if (definition != null) { - DefinitionResponse defResponse = - DefinitionResponse.Create(definition.FoundDefinition, this.Arguments.File); + DefinitionResponse defResponse; + if (definition.FoundDefinition != null) + { + defResponse = DefinitionResponse.Create(definition.FoundDefinition); + } + else + { + defResponse = DefinitionResponse.Create(); + } messageWriter.WriteMessage( this.PrepareResponse(defResponse)); diff --git a/src/PowerShellEditorServices.Transport.Stdio/Response/DefinitionResponse.cs b/src/PowerShellEditorServices.Transport.Stdio/Response/DefinitionResponse.cs index a3b315e2f..a50871914 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Response/DefinitionResponse.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Response/DefinitionResponse.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerShell.EditorServices.Transport.Stdio.Response [MessageTypeName("definition")] public class DefinitionResponse : ResponseBase { - public static DefinitionResponse Create(SymbolReference result, string thisFile) + public static DefinitionResponse Create(SymbolReference result) { if (result != null) { @@ -31,7 +31,7 @@ public static DefinitionResponse Create(SymbolReference result, string thisFile) Line = result.ScriptRegion.EndLineNumber, Offset = result.ScriptRegion.EndColumnNumber }, - File = thisFile, + File = result.FilePath }); return new DefinitionResponse { @@ -46,5 +46,12 @@ public static DefinitionResponse Create(SymbolReference result, string thisFile) }; } } + public static DefinitionResponse Create() + { + return new DefinitionResponse + { + Body = null + }; + } } } diff --git a/src/PowerShellEditorServices/Language/FindCommandVisitor.cs b/src/PowerShellEditorServices/Language/FindCommandVisitor.cs index 349081f15..57e409e96 100644 --- a/src/PowerShellEditorServices/Language/FindCommandVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindCommandVisitor.cs @@ -46,8 +46,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) this.FoundCommandReference = new SymbolReference( SymbolType.Function, - commandNameAst.Extent, - string.Empty); + commandNameAst.Extent); return AstVisitAction.StopVisit; } diff --git a/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs b/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs index af15b0f70..a91aec1e3 100644 --- a/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs @@ -40,6 +40,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun Text = functionDefinitionAst.Name, StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, StartColumnNumber = startColumnNumber, + EndLineNumber = functionDefinitionAst.Extent.StartLineNumber, EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length }; @@ -49,8 +50,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun this.FoundDeclartion = new SymbolReference( SymbolType.Function, - nameExtent, - string.Empty); + nameExtent); return AstVisitAction.StopVisit; } @@ -74,8 +74,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var this.FoundDeclartion = new SymbolReference( SymbolType.Variable, - variableExpressionAst.Extent, - string.Empty); + variableExpressionAst.Extent); return AstVisitAction.StopVisit; } diff --git a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs index fa1f13b1f..9e0546f00 100644 --- a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs @@ -38,8 +38,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) { this.FoundReferences.Add(new SymbolReference( SymbolType.Function, - commandNameAst.Extent, - string.Empty)); + commandNameAst.Extent)); } return base.VisitCommand(commandAst); } @@ -69,8 +68,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun { this.FoundReferences.Add(new SymbolReference( SymbolType.Function, - nameExtent, - string.Empty)); + nameExtent)); } return base.VisitFunctionDefinition(functionDefinitionAst); } @@ -88,8 +86,7 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command { this.FoundReferences.Add(new SymbolReference( SymbolType.Parameter, - commandParameterAst.Extent, - string.Empty)); + commandParameterAst.Extent)); } return AstVisitAction.Continue; } @@ -107,8 +104,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var { this.FoundReferences.Add(new SymbolReference( SymbolType.Variable, - variableExpressionAst.Extent, - string.Empty)); + variableExpressionAst.Extent)); } return AstVisitAction.Continue; } diff --git a/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs index b8e003fee..65f049eaf 100644 --- a/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs @@ -38,8 +38,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) this.FoundSymbolReference = new SymbolReference( SymbolType.Function, - commandNameAst.Extent, - string.Empty); + commandNameAst.Extent); return AstVisitAction.StopVisit; } @@ -72,8 +71,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun this.FoundSymbolReference = new SymbolReference( SymbolType.Function, - nameExtent, - string.Empty); + nameExtent); return AstVisitAction.StopVisit; } @@ -94,8 +92,7 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command this.FoundSymbolReference = new SymbolReference( SymbolType.Parameter, - commandParameterAst.Extent, - string.Empty); + commandParameterAst.Extent); return AstVisitAction.StopVisit; } return AstVisitAction.Continue; @@ -114,8 +111,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var this.FoundSymbolReference = new SymbolReference( SymbolType.Variable, - variableExpressionAst.Extent, - string.Empty); + variableExpressionAst.Extent); return AstVisitAction.StopVisit; } diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index 41c817ea4..49d7e2bb6 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -10,8 +10,10 @@ namespace Microsoft.PowerShell.EditorServices.Language { using Microsoft.PowerShell.EditorServices.Utility; + using System.IO; using System.Management.Automation; using System.Management.Automation.Runspaces; + using System.Text; /// /// Provides a high-level service for performing code completion and @@ -135,11 +137,16 @@ public SymbolReference FindSymbolAtLocation( int lineNumber, int columnNumber) { - return + SymbolReference symbolReference = AstOperations.FindSymbolAtPosition( file.ScriptAst, lineNumber, columnNumber); + if (symbolReference != null) + { + symbolReference.FilePath = file.FilePath; + } + return symbolReference; } /// @@ -171,6 +178,7 @@ public FindReferencesResult FindReferencesOfSymbol( { reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber); + reference.FilePath = file.FilePath; return reference; }); symbolReferences.AddRange(symbolReferencesinFile); @@ -199,16 +207,31 @@ public GetDefinitionResult GetDefinitionOfSymbol( { if (foundSymbol != null) { - SymbolReference foundDefinition = null; + // look through the referenced files until definition is found + // or there are no more file to look through int index = 0; + SymbolReference foundDefinition = null; while (foundDefinition == null && index < referencedFiles.Length) { foundDefinition = AstOperations.FindDefinitionOfSymbol( referencedFiles[index].ScriptAst, foundSymbol); + if (foundDefinition != null) + { + foundDefinition.FilePath = referencedFiles[index].FilePath; + } index++; } + + // if definition is not found in referenced files + // look for it in the builtin commands + if (foundDefinition == null) + { + CommandInfo cmdInfo = GetCommandInfo(foundSymbol.SymbolName); + foundDefinition = FindDeclarationForBuiltinCommand(cmdInfo, foundSymbol); + } + return new GetDefinitionResult(foundDefinition); } else { return null; } @@ -298,5 +321,69 @@ private CommandInfo GetCommandInfo(string commandName) return commandInfo; } + + private ScriptFile[] GetBuiltinCommandScriptFiles(PSModuleInfo moduleInfo) + { + if (moduleInfo != null) + { + string modPath = moduleInfo.Path; + List scriptFiles = new List(); + ScriptFile newFile; + + if (modPath.EndsWith(@".ps1") || modPath.EndsWith(@".psm1")) + { + using (StreamReader streamReader = new StreamReader(modPath, Encoding.UTF8)) + { + newFile = new ScriptFile(modPath, streamReader); + scriptFiles.Add(newFile); + } + } + if (moduleInfo.NestedModules.Count > 0) + { + string nestedModPath; + foreach (PSModuleInfo nestedInfo in moduleInfo.NestedModules) + { + nestedModPath = nestedInfo.Path; + if (nestedModPath.EndsWith(@".ps1") || nestedModPath.EndsWith(@".psm1")) + { + using (StreamReader streamReader = new StreamReader(nestedModPath, Encoding.UTF8)) + { + newFile = new ScriptFile(nestedModPath, streamReader); + scriptFiles.Add(newFile); + } + } + } + } + return scriptFiles.ToArray(); + } + return new List().ToArray(); + } + + private SymbolReference FindDeclarationForBuiltinCommand(CommandInfo cmdInfo, SymbolReference foundSymbol) + { + SymbolReference foundDefinition = null; + if (cmdInfo != null) + { + int index = 0; + ScriptFile[] nestedModuleFiles; + + nestedModuleFiles = + GetBuiltinCommandScriptFiles(GetCommandInfo(foundSymbol.SymbolName).Module); + + while (foundDefinition == null && index < nestedModuleFiles.Length) + { + foundDefinition = + AstOperations.FindDefinitionOfSymbol( + nestedModuleFiles[index].ScriptAst, + foundSymbol); + if (foundDefinition != null) + { + foundDefinition.FilePath = nestedModuleFiles[index].FilePath; + } + index++; + } + } + return foundDefinition; + } } } diff --git a/src/PowerShellEditorServices/Language/SymbolReference.cs b/src/PowerShellEditorServices/Language/SymbolReference.cs index 84e1609b3..123cd4340 100644 --- a/src/PowerShellEditorServices/Language/SymbolReference.cs +++ b/src/PowerShellEditorServices/Language/SymbolReference.cs @@ -61,18 +61,25 @@ public class SymbolReference public string SourceLine { get; internal set; } #endregion + /// + /// Gets the Filepath of the symbol + /// + public string FilePath { get; internal set; } + /// /// Constructs and instance of a SymbolReference /// /// The higher level type of the symbol /// The script extent of the symbol + /// The file path of the symbol /// The line contents of the given symbol (defaults to empty string) - public SymbolReference(SymbolType symbolType, IScriptExtent scriptExtent, string sourceLine = "") + public SymbolReference(SymbolType symbolType, IScriptExtent scriptExtent, string filePath = "", string sourceLine = "") { // TODO: Verify params this.SymbolType = symbolType; this.SymbolName = scriptExtent.Text; this.ScriptRegion = ScriptRegion.Create(scriptExtent); + this.FilePath = filePath; this.SourceLine = sourceLine; // TODO: Make sure end column number usage is correct From 8cd2c2ff3e97d06fd1854f16c08a857d76463ee3 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Mon, 3 Aug 2015 11:40:39 -0700 Subject: [PATCH 05/18] Add timeout for host process /waitForDebugger flag This change adds a 15 second timeout for the /waitForDebugger flag in debug builds. This prevents hangs when Visual Studio can't attach to the process automatically when debugging tests. --- src/PowerShellEditorServices.Host/Program.cs | 7 +++++-- .../LanguageServiceManager.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices.Host/Program.cs b/src/PowerShellEditorServices.Host/Program.cs index 448705d83..c36cd80cd 100644 --- a/src/PowerShellEditorServices.Host/Program.cs +++ b/src/PowerShellEditorServices.Host/Program.cs @@ -28,9 +28,12 @@ static void Main(string[] args) // Should we wait for the debugger before starting? if (waitForDebugger) { - while (!Debugger.IsAttached) + // Wait for 15 seconds and then continue + int waitCountdown = 15; + while (!Debugger.IsAttached && waitCountdown > 0) { - Thread.Sleep(500); + Thread.Sleep(1000); + waitCountdown--; } } diff --git a/test/PowerShellEditorServices.Test.Host/LanguageServiceManager.cs b/test/PowerShellEditorServices.Test.Host/LanguageServiceManager.cs index a3e12e052..c728dc7db 100644 --- a/test/PowerShellEditorServices.Test.Host/LanguageServiceManager.cs +++ b/test/PowerShellEditorServices.Test.Host/LanguageServiceManager.cs @@ -40,7 +40,7 @@ public void Start() { FileName = "Microsoft.PowerShell.EditorServices.Host.exe", Arguments = languageServiceArguments, - CreateNoWindow = false, + CreateNoWindow = true, UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, From e12019fc78af75b9171129d1cf7734b315caa376 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Mon, 3 Aug 2015 15:21:26 -0700 Subject: [PATCH 06/18] Add Workspace class to manage open session files This change introduces the Workspace class which is responsible for managing the set of open files in a session. This class is also responsible for resolving a script's references to other scripts and handling the necessary file operations so that those references scripts can be used for service operations. --- .../Request/DeclarationRequest.cs | 2 +- .../Request/ErrorRequest.cs | 2 +- .../Request/FileRequest.cs | 2 +- .../Request/OpenFileRequest.cs | 2 +- .../Request/ReferencesRequest.cs | 2 +- .../PowerShellEditorServices.csproj | 1 + .../Session/EditorSession.cs | 130 +----------- .../Session/Workspace.cs | 194 ++++++++++++++++++ 8 files changed, 209 insertions(+), 126 deletions(-) create mode 100644 src/PowerShellEditorServices/Session/Workspace.cs diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs index 134a3a863..23852550e 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs @@ -27,7 +27,7 @@ public override void ProcessMessage( GetDefinitionResult definition = editorSession.LanguageService.GetDefinitionOfSymbol( foundSymbol, - editorSession.ExpandScriptReferences(scriptFile)); + editorSession.Workspace.ExpandScriptReferences(scriptFile)); if (definition != null) { diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/ErrorRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/ErrorRequest.cs index db6eb197c..5bc19965d 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/ErrorRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/ErrorRequest.cs @@ -35,7 +35,7 @@ public override void ProcessMessage( { ScriptFile scriptFile = null; - if (!editorSession.TryGetFile(filePath, out scriptFile)) + if (!editorSession.Workspace.TryGetFile(filePath, out scriptFile)) { // Skip this file and log the file load error // TODO: Trace out the error message diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/FileRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/FileRequest.cs index f2b31c650..0943f1c22 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/FileRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/FileRequest.cs @@ -15,7 +15,7 @@ protected ScriptFile GetScriptFile(EditorSession editorSession) { ScriptFile scriptFile = null; - if(!editorSession.TryGetFile( + if(!editorSession.Workspace.TryGetFile( this.Arguments.File, out scriptFile)) { diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs index 4fd581994..956768393 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs @@ -27,7 +27,7 @@ public override void ProcessMessage( MessageWriter messageWriter) { // Open the file in the current session - editorSession.OpenFile(this.Arguments.File); + editorSession.Workspace.OpenFile(this.Arguments.File); } } } diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs index 8bad162e4..e5609c7d4 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/ReferencesRequest.cs @@ -27,7 +27,7 @@ public override void ProcessMessage( FindReferencesResult referencesResult = editorSession.LanguageService.FindReferencesOfSymbol( foundSymbol, - editorSession.ExpandScriptReferences(scriptFile)); + editorSession.Workspace.ExpandScriptReferences(scriptFile)); ReferencesResponse referencesResponse = ReferencesResponse.Create(referencesResult, this.Arguments.File); diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 818c52d10..cce64db17 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -89,6 +89,7 @@ + diff --git a/src/PowerShellEditorServices/Session/EditorSession.cs b/src/PowerShellEditorServices/Session/EditorSession.cs index bc5e3a33c..f90bbd388 100644 --- a/src/PowerShellEditorServices/Session/EditorSession.cs +++ b/src/PowerShellEditorServices/Session/EditorSession.cs @@ -26,12 +26,16 @@ public class EditorSession #region Private Fields private Runspace languageRunspace; - private Dictionary workspaceFiles = new Dictionary(); #endregion #region Properties + /// + /// Gets the Workspace instance for this session. + /// + public Workspace Workspace { get; private set; } + /// /// Gets the LanguageService instance for this session. /// @@ -46,7 +50,7 @@ public class EditorSession /// Gets the ConsoleService instance for this session. /// public ConsoleService ConsoleService { get; private set; } - + #endregion #region Public Methods @@ -63,6 +67,9 @@ public void StartSession(IConsoleHost consoleHost) { InitialSessionState initialSessionState = InitialSessionState.CreateDefault2(); + // Create a workspace to contain open files + this.Workspace = new Workspace(); + // Create a runspace to share between the language and analysis services this.languageRunspace = RunspaceFactory.CreateRunspace(initialSessionState); this.languageRunspace.ApartmentState = ApartmentState.STA; @@ -76,125 +83,6 @@ public void StartSession(IConsoleHost consoleHost) this.ConsoleService = new ConsoleService(consoleHost, initialSessionState); } - /// - /// Opens a script file with the given file path. - /// - /// The file path at which the script resides. - /// - /// is not found. - /// - /// - /// has already been loaded in the session. - /// - /// - /// contains a null or empty string. - /// - public ScriptFile OpenFile(string filePath) - { - Validate.IsNotNullOrEmptyString("filePath", filePath); - - // Make sure the file isn't already loaded into the session - if (!this.workspaceFiles.ContainsKey(filePath)) - { - // This method allows FileNotFoundException to bubble up - // if the file isn't found. - - using (StreamReader streamReader = new StreamReader(filePath, Encoding.UTF8)) - { - ScriptFile newFile = new ScriptFile(filePath, streamReader); - this.workspaceFiles.Add(filePath, newFile); - return newFile; - } - } - else - { - throw new ArgumentException( - "The specified file has already been loaded: " + filePath, - "filePath"); - } - } - - /// - /// Closes a currently open script file with the given file path. - /// - /// The file path at which the script resides. - public void CloseFile(ScriptFile scriptFile) - { - Validate.IsNotNull("scriptFile", scriptFile); - - this.workspaceFiles.Remove(scriptFile.FilePath); - } - - /// - /// Attempts to get a currently open script file with the given file path. - /// - /// The file path at which the script resides. - /// The output variable in which the ScriptFile will be stored. - /// A ScriptFile instance - public bool TryGetFile(string filePath, out ScriptFile scriptFile) - { - scriptFile = null; - return this.workspaceFiles.TryGetValue(filePath, out scriptFile); - } - - /// - /// Gets all open files in the session. - /// - /// A collection of all open ScriptFiles in the session. - public IEnumerable GetOpenFiles() - { - return this.workspaceFiles.Values; - } - - /// - /// Gets all file references by recursively searching - /// through referenced files in a scriptfile - /// - /// Contains the details and contents of an open script file - /// A scriptfile array where the first file - /// in the array is the "root file" of the search - public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile) - { - Dictionary referencedScriptFiles = new Dictionary(); - List expandedReferences = new List(); - - RecursivelyFindReferences(scriptFile, referencedScriptFiles); - expandedReferences.Add(scriptFile); // add original file first - if (referencedScriptFiles.Count != 0) - { - expandedReferences.AddRange(referencedScriptFiles.Values); - } - return expandedReferences.ToArray(); - } - - #endregion - - #region Private Methods - /// - /// Recusrively searches through referencedFiles in scriptFiles - /// and builds a Dictonary of the file references - /// - /// Details an contents of "root" script file - /// A Dictionary of referenced script files - private void RecursivelyFindReferences( - ScriptFile scriptFile, - Dictionary referencedScriptFiles) - { - ScriptFile newFile; - foreach (string filename in scriptFile.ReferencedFiles) - { - string filePath = Path.GetFullPath(filename); - if (referencedScriptFiles.ContainsKey(filePath)) - { - if (TryGetFile(filePath, out newFile)) - { - newFile = OpenFile(filePath); - referencedScriptFiles.Add(filePath, newFile); - } - RecursivelyFindReferences(newFile, referencedScriptFiles); - } - } - } #endregion #region IDisposable Implementation diff --git a/src/PowerShellEditorServices/Session/Workspace.cs b/src/PowerShellEditorServices/Session/Workspace.cs new file mode 100644 index 000000000..23f8dfa4a --- /dev/null +++ b/src/PowerShellEditorServices/Session/Workspace.cs @@ -0,0 +1,194 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.PowerShell.EditorServices.Utility; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Microsoft.PowerShell.EditorServices.Session +{ + public class Workspace + { + #region Private Fields + + private Dictionary workspaceFiles = new Dictionary(); + + #endregion + + #region Public Methods + + /// + /// Opens a script file with the given file path. + /// + /// The file path at which the script resides. + /// + /// is not found. + /// + /// + /// has already been loaded in the workspace. + /// + /// + /// contains a null or empty string. + /// + public ScriptFile OpenFile(string filePath) + { + Validate.IsNotNullOrEmptyString("filePath", filePath); + + // Resolve the full file path + string resolvedFilePath = + this.ResolveFilePath( + filePath); + + // Make sure the file isn't already loaded into the workspace + if (!this.workspaceFiles.ContainsKey(resolvedFilePath)) + { + // This method allows FileNotFoundException to bubble up + // if the file isn't found. + + using (StreamReader streamReader = new StreamReader(resolvedFilePath, Encoding.UTF8)) + { + ScriptFile newFile = new ScriptFile(resolvedFilePath, streamReader); + this.workspaceFiles.Add(resolvedFilePath, newFile); + return newFile; + } + } + else + { + throw new ArgumentException( + "The specified file has already been loaded: " + resolvedFilePath, + "filePath"); + } + } + + /// + /// Closes a currently open script file with the given file path. + /// + /// The file path at which the script resides. + public void CloseFile(ScriptFile scriptFile) + { + Validate.IsNotNull("scriptFile", scriptFile); + + this.workspaceFiles.Remove(scriptFile.FilePath); + } + + /// + /// Attempts to get a currently open script file with the given file path. + /// + /// The file path at which the script resides. + /// The output variable in which the ScriptFile will be stored. + /// A ScriptFile instance + public bool TryGetFile(string filePath, out ScriptFile scriptFile) + { + // Resolve the full file path + string resolvedFilePath = + this.ResolveFilePath( + filePath); + + scriptFile = null; + return this.workspaceFiles.TryGetValue(resolvedFilePath, out scriptFile); + } + + /// + /// Gets all open files in the workspace. + /// + /// A collection of all open ScriptFiles in the workspace. + public IEnumerable GetOpenFiles() + { + return this.workspaceFiles.Values; + } + + /// + /// Gets all file references by recursively searching + /// through referenced files in a scriptfile + /// + /// Contains the details and contents of an open script file + /// A scriptfile array where the first file + /// in the array is the "root file" of the search + public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile) + { + Dictionary referencedScriptFiles = new Dictionary(); + List expandedReferences = new List(); + + RecursivelyFindReferences(scriptFile, referencedScriptFiles); + expandedReferences.Add(scriptFile); // add original file first + if (referencedScriptFiles.Count != 0) + { + expandedReferences.AddRange(referencedScriptFiles.Values); + } + return expandedReferences.ToArray(); + } + + #endregion + + #region Private Methods + + /// + /// Recusrively searches through referencedFiles in scriptFiles + /// and builds a Dictonary of the file references + /// + /// Details an contents of "root" script file + /// A Dictionary of referenced script files + private void RecursivelyFindReferences( + ScriptFile scriptFile, + Dictionary referencedScriptFiles) + { + ScriptFile newFile; + foreach (string filename in scriptFile.ReferencedFiles) + { + string resolvedScriptPath = + this.ResolveRelativeScriptPath( + scriptFile.FilePath, + filename); + + if (referencedScriptFiles.ContainsKey(resolvedScriptPath)) + { + if (TryGetFile(resolvedScriptPath, out newFile)) + { + newFile = OpenFile(resolvedScriptPath); + referencedScriptFiles.Add(resolvedScriptPath, newFile); + } + + RecursivelyFindReferences(newFile, referencedScriptFiles); + } + } + } + + private string ResolveFilePath(string scriptPath) + { + return Path.GetFullPath(scriptPath); + } + + private string ResolveRelativeScriptPath(string originalScriptPath, string relativePath) + { + if (!Path.IsPathRooted(originalScriptPath)) + { + // TODO: Assert instead? + throw new InvalidOperationException( + string.Format( + "Must provide a full path for originalScriptPath: {0}", + originalScriptPath)); + } + + if (Path.IsPathRooted(relativePath)) + { + return relativePath; + } + + // Get the directory of the original script file, combine it + // with the given path and then resolve the absolute file path. + string combinedPath = + Path.GetFullPath( + Path.Combine( + Path.GetDirectoryName(originalScriptPath), + relativePath)); + + return combinedPath; + } + + #endregion + } +} From 32451c09d3a3e5734cef24fcbe58b90d270796e6 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Mon, 3 Aug 2015 15:24:21 -0700 Subject: [PATCH 07/18] Update LanguageServiceTests to use Workspace This change updates the LanguageServiceTests class to use Workspace for managing file operations. It also re-enables some older tests that were disabled due to API changes around script references. --- ...owerShellEditorServices.Test.Shared.csproj | 13 +- .../Utility/ResourceFileLoader.cs | 42 ----- .../Language/LanguageServiceTests.cs | 150 +++++++++--------- 3 files changed, 81 insertions(+), 124 deletions(-) delete mode 100644 test/PowerShellEditorServices.Test.Shared/Utility/ResourceFileLoader.cs diff --git a/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj b/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj index ae967bbcd..65704e267 100644 --- a/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj +++ b/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj @@ -51,10 +51,11 @@ - - + + PreserveNewest + @@ -63,14 +64,14 @@ - + PreserveNewest - + - + PreserveNewest - + diff --git a/test/PowerShellEditorServices.Test.Shared/Utility/ResourceFileLoader.cs b/test/PowerShellEditorServices.Test.Shared/Utility/ResourceFileLoader.cs deleted file mode 100644 index 66636d1ba..000000000 --- a/test/PowerShellEditorServices.Test.Shared/Utility/ResourceFileLoader.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using Microsoft.PowerShell.EditorServices.Session; -using System.IO; -using System.Reflection; - -namespace Microsoft.PowerShell.EditorServices.Test.Shared.Utility -{ - public class ResourceFileLoader - { - private Assembly resourceAssembly; - - public ResourceFileLoader(Assembly resourceAssembly = null) - { - if (resourceAssembly == null) - { - resourceAssembly = Assembly.GetExecutingAssembly(); - } - - this.resourceAssembly = resourceAssembly; - } - - public ScriptFile LoadFile(string fileName) - { - // Convert the filename to the proper format - string resourceName = - string.Format( - "{0}.{1}", - resourceAssembly.GetName().Name, - fileName.Replace('\\', '.')); - - using (Stream stream = resourceAssembly.GetManifestResourceStream(resourceName)) - using (StreamReader streamReader = new StreamReader(stream)) - { - return new ScriptFile(fileName, streamReader); - } - } - } -} diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index 0f5b17d86..f7a942e49 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -6,7 +6,6 @@ using Microsoft.PowerShell.EditorServices.Language; using Microsoft.PowerShell.EditorServices.Session; using Microsoft.PowerShell.EditorServices.Test.Shared.Completion; -using Microsoft.PowerShell.EditorServices.Test.Shared.Utility; using System; using System.Management.Automation.Runspaces; using System.Threading; @@ -14,23 +13,20 @@ using Xunit; using Microsoft.PowerShell.EditorServices.Test.Shared.ParameterHint; using Microsoft.PowerShell.EditorServices.Test.Shared.Definition; -using Microsoft.PowerShell.EditorServices.Test.Shared.References; using Microsoft.PowerShell.EditorServices.Test.Shared.Occurrences; +using System.IO; namespace Microsoft.PowerShell.EditorServices.Test.Language { public class LanguageServiceTests : IDisposable { - private ResourceFileLoader fileLoader; + private Workspace workspace; private Runspace languageServiceRunspace; private LanguageService languageService; public LanguageServiceTests() { - // Load script files from the shared assembly - this.fileLoader = - new ResourceFileLoader( - typeof(CompleteCommandInFile).Assembly); + this.workspace = new Workspace(); this.languageServiceRunspace = RunspaceFactory.CreateRunspace(); this.languageServiceRunspace.ApartmentState = ApartmentState.STA; @@ -108,55 +104,31 @@ public void LanguageServiceFindsCommandForParamHintsWithSpaces() Assert.Equal(1, paramSignatures.Signatures.Count()); } - //[Fact] - //public void LanguageServiceFindsFunctionDefinition() - //{ - // GetDefinitionResult definitionResult = - // this.GetDefinition( - // FindsFunctionDefinition.SourceDetails); - - // SymbolReference definition = definitionResult.FoundDefinition; - // Assert.Equal(1, definition.ScriptRegion.StartLineNumber); - // Assert.Equal(10, definition.ScriptRegion.StartColumnNumber); - // Assert.Equal("My-Function", definition.SymbolName); - //} - - //[Fact] - //public void LanguageServiceFindsVariableDefinition() - //{ - // GetDefinitionResult definitionResult = - // this.GetDefinition( - // FindsVariableDefinition.SourceDetails); - - // SymbolReference definition = definitionResult.FoundDefinition; - // Assert.Equal(6, definition.ScriptRegion.StartLineNumber); - // Assert.Equal(1, definition.ScriptRegion.StartColumnNumber); - // Assert.Equal("$things", definition.SymbolName); - //} - - //[Fact] - //public void LanguageServiceFindsFunctionReferences() - //{ - // FindReferencesResult referencesResult = - // this.GetReferences( - // FindsReferencesOnFunction.SourceDetails); - - // Assert.Equal(3, referencesResult.FoundReferences.Count()); - // Assert.Equal(1, referencesResult.FoundReferences.First().ScriptRegion.StartLineNumber); - // Assert.Equal(10, referencesResult.FoundReferences.First().ScriptRegion.StartColumnNumber); - //} - - //[Fact] - //public void LanguageServiceFindsVariableReferences() - //{ - // FindReferencesResult referencesResult = - // this.GetReferences( - // FindsReferencesOnVariable.SourceDetails); - - // Assert.Equal(3, referencesResult.FoundReferences.Count()); - // Assert.Equal(10, referencesResult.FoundReferences.Last().ScriptRegion.StartLineNumber); - // Assert.Equal(13, referencesResult.FoundReferences.Last().ScriptRegion.StartColumnNumber); - //} + [Fact] + public void LanguageServiceFindsFunctionDefinition() + { + GetDefinitionResult definitionResult = + this.GetDefinition( + FindsFunctionDefinition.SourceDetails); + + SymbolReference definition = definitionResult.FoundDefinition; + Assert.Equal(1, definition.ScriptRegion.StartLineNumber); + Assert.Equal(10, definition.ScriptRegion.StartColumnNumber); + Assert.Equal("My-Function", definition.SymbolName); + } + + [Fact] + public void LanguageServiceFindsVariableDefinition() + { + GetDefinitionResult definitionResult = + this.GetDefinition( + FindsVariableDefinition.SourceDetails); + + SymbolReference definition = definitionResult.FoundDefinition; + Assert.Equal(6, definition.ScriptRegion.StartLineNumber); + Assert.Equal(1, definition.ScriptRegion.StartColumnNumber); + Assert.Equal("$things", definition.SymbolName); + } [Fact] public void LanguageServiceFindsOccurrencesOnFunction() @@ -182,10 +154,18 @@ public void LanguageServiceFindsOccurrencesOnParameter() Assert.Equal(3, occurrencesResult.FoundOccurrences.Last().ScriptRegion.StartLineNumber); } - private ScriptFile GetScriptFile(ScriptRegion scriptRegion){ - return this.fileLoader.LoadFile(scriptRegion.File); - } + private ScriptFile GetScriptFile(ScriptRegion scriptRegion) + { + const string baseSharedScriptPath = + @"..\..\..\PowerShellEditorServices.Test.Shared\"; + string resolvedPath = + Path.Combine( + baseSharedScriptPath, + scriptRegion.File); + + return this.workspace.OpenFile(resolvedPath); + } private CompletionResults GetCompletionResults(ScriptRegion scriptRegion) { @@ -206,23 +186,41 @@ private ParameterSetSignatures GetParamSetSignatures(ScriptRegion scriptRegion) scriptRegion.StartColumnNumber); } - //private GetDefinitionResult GetDefinition(ScriptRegion scriptRegion) - //{ - // return - // this.languageService.GetDefinitionOfSymbol( - // GetScriptFile(scriptRegion), - // scriptRegion.StartLineNumber, - // scriptRegion.StartColumnNumber); - //} - - //private FindReferencesResult GetReferences(ScriptRegion scriptRegion) - //{ - // SymbolReference foundSymbol = - // this.languageService.FindSymbolAtLocation( - // GetScriptFile(scriptRegion), - // scriptRegion.StartLineNumber, - // scriptRegion.StartColumnNumber); - //} + private GetDefinitionResult GetDefinition(ScriptRegion scriptRegion) + { + ScriptFile scriptFile = GetScriptFile(scriptRegion); + + SymbolReference symbolReference = + this.languageService.FindSymbolAtLocation( + scriptFile, + scriptRegion.StartLineNumber, + scriptRegion.StartColumnNumber); + + Assert.NotNull(symbolReference); + + return + this.languageService.GetDefinitionOfSymbol( + symbolReference, + this.workspace.ExpandScriptReferences(scriptFile)); + } + + private FindReferencesResult GetReferences(ScriptRegion scriptRegion) + { + ScriptFile scriptFile = GetScriptFile(scriptRegion); + + SymbolReference symbolReference = + this.languageService.FindSymbolAtLocation( + scriptFile, + scriptRegion.StartLineNumber, + scriptRegion.StartColumnNumber); + + Assert.NotNull(symbolReference); + + return + this.languageService.FindReferencesOfSymbol( + symbolReference, + this.workspace.ExpandScriptReferences(scriptFile)); + } private FindOccurrencesResult GetOccurrences(ScriptRegion scriptRegion) { From f1a7882a511ec72099dc0305d18f647b99fff17f Mon Sep 17 00:00:00 2001 From: David Wilson Date: Mon, 3 Aug 2015 16:30:02 -0700 Subject: [PATCH 08/18] Add test for dot-sourced find function definition --- .../Language/LanguageService.cs | 71 +++++++++++-------- .../Session/Workspace.cs | 4 +- .../ScenarioTests.cs | 10 +-- .../TestFiles/FindReferences.ps1 | 1 - ...sFunctionDefinitionInDotSourceReference.cs | 20 ++++++ ...owerShellEditorServices.Test.Shared.csproj | 2 + .../References/FileWithReferences.ps1 | 3 + .../Language/LanguageServiceTests.cs | 25 ++++++- 8 files changed, 99 insertions(+), 37 deletions(-) create mode 100644 test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/References/FileWithReferences.ps1 diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index 49d7e2bb6..e502d04f8 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -120,7 +120,10 @@ public CompletionDetails GetCompletionDetailsInFile( result => result.CompletionText.Equals(entryName)); return completionResult; } - else { return null; } + else + { + return null; + } } /// @@ -146,6 +149,7 @@ public SymbolReference FindSymbolAtLocation( { symbolReference.FilePath = file.FilePath; } + return symbolReference; } @@ -205,36 +209,34 @@ public GetDefinitionResult GetDefinitionOfSymbol( SymbolReference foundSymbol, ScriptFile[] referencedFiles) { - if (foundSymbol != null) + Validate.IsNotNull("foundSymbol", foundSymbol); + + // look through the referenced files until definition is found + // or there are no more file to look through + SymbolReference foundDefinition = null; + for (int i = 0; i < referencedFiles.Length; i++) { - // look through the referenced files until definition is found - // or there are no more file to look through - int index = 0; - SymbolReference foundDefinition = null; - while (foundDefinition == null && index < referencedFiles.Length) - { - foundDefinition = - AstOperations.FindDefinitionOfSymbol( - referencedFiles[index].ScriptAst, - foundSymbol); - if (foundDefinition != null) - { - foundDefinition.FilePath = referencedFiles[index].FilePath; - } - index++; - } + foundDefinition = + AstOperations.FindDefinitionOfSymbol( + referencedFiles[i].ScriptAst, + foundSymbol); - // if definition is not found in referenced files - // look for it in the builtin commands - if (foundDefinition == null) + if (foundDefinition != null) { - CommandInfo cmdInfo = GetCommandInfo(foundSymbol.SymbolName); - foundDefinition = FindDeclarationForBuiltinCommand(cmdInfo, foundSymbol); + foundDefinition.FilePath = referencedFiles[i].FilePath; + break; } + } - return new GetDefinitionResult(foundDefinition); + // if definition is not found in referenced files + // look for it in the builtin commands + if (foundDefinition == null) + { + CommandInfo cmdInfo = GetCommandInfo(foundSymbol.SymbolName); + foundDefinition = FindDeclarationForBuiltinCommand(cmdInfo, foundSymbol); } - else { return null; } + + return new GetDefinitionResult(foundDefinition); } /// @@ -254,6 +256,7 @@ public FindOccurrencesResult FindOccurrencesInFile( file.ScriptAst, lineNumber, columnNumber); + if (foundSymbol != null) { IEnumerable symbolOccurrences = @@ -268,7 +271,10 @@ public FindOccurrencesResult FindOccurrencesInFile( FoundOccurrences = symbolOccurrences }; } - else { return null; } + else + { + return null; + } } /// @@ -300,9 +306,15 @@ public ParameterSetSignatures FindParameterSetsInFile( return new ParameterSetSignatures(commandInfoSet, foundSymbol); } - else { return null; } + else + { + return null; + } + } + else + { + return null; } - else { return null; } } #endregion @@ -354,8 +366,10 @@ private ScriptFile[] GetBuiltinCommandScriptFiles(PSModuleInfo moduleInfo) } } } + return scriptFiles.ToArray(); } + return new List().ToArray(); } @@ -383,6 +397,7 @@ private SymbolReference FindDeclarationForBuiltinCommand(CommandInfo cmdInfo, Sy index++; } } + return foundDefinition; } } diff --git a/src/PowerShellEditorServices/Session/Workspace.cs b/src/PowerShellEditorServices/Session/Workspace.cs index 23f8dfa4a..9efa27ffb 100644 --- a/src/PowerShellEditorServices/Session/Workspace.cs +++ b/src/PowerShellEditorServices/Session/Workspace.cs @@ -144,9 +144,9 @@ private void RecursivelyFindReferences( scriptFile.FilePath, filename); - if (referencedScriptFiles.ContainsKey(resolvedScriptPath)) + if (!referencedScriptFiles.ContainsKey(resolvedScriptPath)) { - if (TryGetFile(resolvedScriptPath, out newFile)) + if (!TryGetFile(resolvedScriptPath, out newFile)) { newFile = OpenFile(resolvedScriptPath); referencedScriptFiles.Add(resolvedScriptPath, newFile); diff --git a/test/PowerShellEditorServices.Test.Host/ScenarioTests.cs b/test/PowerShellEditorServices.Test.Host/ScenarioTests.cs index 976cf7d22..3b4fffffc 100644 --- a/test/PowerShellEditorServices.Test.Host/ScenarioTests.cs +++ b/test/PowerShellEditorServices.Test.Host/ScenarioTests.cs @@ -216,7 +216,7 @@ public void FindsNoReferencesOfEmptyLine() Arguments = new FileLocationRequestArgs { File = "TestFiles\\FindReferences.ps1", - Line = 9, + Line = 10, Offset = 1, } }); @@ -256,7 +256,7 @@ public void FindsReferencesOnCommand() Arguments = new FileLocationRequestArgs { File = "TestFiles\\FindReferences.ps1", - Line = 10, + Line = 9, Offset = 2, } }); @@ -297,7 +297,7 @@ public void FindsNoDefinitionOfBuiltinCommand() Arguments = new FileLocationRequestArgs { File = "TestFiles\\FindReferences.ps1", - Line = 12, + Line = 11, Offset = 10, } }); @@ -315,7 +315,7 @@ public void FindsDefintionOfVariable() Arguments = new FileLocationRequestArgs { File = "TestFiles\\FindReferences.ps1", - Line = 10, + Line = 9, Offset = 14, } }); @@ -358,7 +358,7 @@ public void GetsParameterHintsOnCommand() Arguments = new SignatureHelpRequestArgs { File = "TestFiles\\FindReferences.ps1", - Line = 14, + Line = 13, Offset = 15, } }); diff --git a/test/PowerShellEditorServices.Test.Host/TestFiles/FindReferences.ps1 b/test/PowerShellEditorServices.Test.Host/TestFiles/FindReferences.ps1 index 76d3c9101..c9ae71c5b 100644 --- a/test/PowerShellEditorServices.Test.Host/TestFiles/FindReferences.ps1 +++ b/test/PowerShellEditorServices.Test.Host/TestFiles/FindReferences.ps1 @@ -6,7 +6,6 @@ function My-Function ($myInput) $things = 4 $things -. simpleps.ps1 My-Function $things Write-Output "Hi"; diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs new file mode 100644 index 000000000..0e2bce134 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.PowerShell.EditorServices.Session; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition +{ + public class FindsFunctionDefinitionInDotSourceReference + { + public static readonly ScriptRegion SourceDetails = + new ScriptRegion + { + File = @"References\FileWithReferences.ps1", + StartLineNumber = 3, + StartColumnNumber = 6 + }; + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj b/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj index 65704e267..9f1696486 100644 --- a/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj +++ b/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj @@ -42,6 +42,7 @@ + @@ -69,6 +70,7 @@ + PreserveNewest diff --git a/test/PowerShellEditorServices.Test.Shared/References/FileWithReferences.ps1 b/test/PowerShellEditorServices.Test.Shared/References/FileWithReferences.ps1 new file mode 100644 index 000000000..fb39070e8 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/FileWithReferences.ps1 @@ -0,0 +1,3 @@ +. .\SimpleFile.ps1 + +My-Function "test" diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index f7a942e49..be8981214 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -117,6 +117,23 @@ public void LanguageServiceFindsFunctionDefinition() Assert.Equal("My-Function", definition.SymbolName); } + [Fact] + public void LanguageServiceFindsFunctionDefinitionInDotSourceReference() + { + GetDefinitionResult definitionResult = + this.GetDefinition( + FindsFunctionDefinitionInDotSourceReference.SourceDetails); + + SymbolReference definition = definitionResult.FoundDefinition; + Assert.True( + definitionResult.FoundDefinition.FilePath.EndsWith( + FindsFunctionDefinition.SourceDetails.File), + "Unexpected reference file: " + definitionResult.FoundDefinition.FilePath); + Assert.Equal(1, definition.ScriptRegion.StartLineNumber); + Assert.Equal(10, definition.ScriptRegion.StartColumnNumber); + Assert.Equal("My-Function", definition.SymbolName); + } + [Fact] public void LanguageServiceFindsVariableDefinition() { @@ -164,7 +181,13 @@ private ScriptFile GetScriptFile(ScriptRegion scriptRegion) baseSharedScriptPath, scriptRegion.File); - return this.workspace.OpenFile(resolvedPath); + ScriptFile scriptFile = null; + if (!this.workspace.TryGetFile(resolvedPath, out scriptFile)) + { + scriptFile = this.workspace.OpenFile(resolvedPath); + } + + return scriptFile; } private CompletionResults GetCompletionResults(ScriptRegion scriptRegion) From 6b24cd37d30bfb17898e283e574a56c0344a26e7 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 4 Aug 2015 13:23:19 -0700 Subject: [PATCH 09/18] Fix bugs in Find References implementation --- src/PowerShellEditorServices/Language/FindReferencesVisitor.cs | 1 + src/PowerShellEditorServices/Language/FindSymbolVisitor.cs | 1 + src/PowerShellEditorServices/Session/Workspace.cs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs index 9e0546f00..1e96dc10d 100644 --- a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs @@ -59,6 +59,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun { Text = functionDefinitionAst.Name, StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, + EndLineNumber = functionDefinitionAst.Extent.StartLineNumber, StartColumnNumber = startColumnNumber, EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length }; diff --git a/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs index 65f049eaf..b8771ab16 100644 --- a/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs @@ -62,6 +62,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun { Text = functionDefinitionAst.Name, StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, + EndLineNumber = functionDefinitionAst.Extent.EndLineNumber, StartColumnNumber = startColumnNumber, EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length }; diff --git a/src/PowerShellEditorServices/Session/Workspace.cs b/src/PowerShellEditorServices/Session/Workspace.cs index 9efa27ffb..efe9ec12f 100644 --- a/src/PowerShellEditorServices/Session/Workspace.cs +++ b/src/PowerShellEditorServices/Session/Workspace.cs @@ -149,9 +149,9 @@ private void RecursivelyFindReferences( if (!TryGetFile(resolvedScriptPath, out newFile)) { newFile = OpenFile(resolvedScriptPath); - referencedScriptFiles.Add(resolvedScriptPath, newFile); } + referencedScriptFiles.Add(resolvedScriptPath, newFile); RecursivelyFindReferences(newFile, referencedScriptFiles); } } From a9f974cec6afafa909ee1d14443ff752fec712e2 Mon Sep 17 00:00:00 2001 From: Kayla Davis Date: Tue, 4 Aug 2015 13:41:00 -0700 Subject: [PATCH 10/18] Fixing sementic highlighting --- .../Event/DiagnosticEvent.cs | 3 +++ src/PowerShellEditorServices/Analysis/AnalysisService.cs | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices.Transport.Stdio/Event/DiagnosticEvent.cs b/src/PowerShellEditorServices.Transport.Stdio/Event/DiagnosticEvent.cs index 25aa70e7a..e0dfd07ba 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Event/DiagnosticEvent.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Event/DiagnosticEvent.cs @@ -61,6 +61,7 @@ public static DiagnosticEventBody Create( new Diagnostic { Text = diagnosticMarker.Message, + Severity = (int)diagnosticMarker.Level + 1, Start = new Location { Line = diagnosticMarker.ScriptRegion.StartLineNumber, @@ -97,5 +98,7 @@ public class Diagnostic public Location End { get; set; } public string Text { get; set; } + + public int Severity { get; set; } } } diff --git a/src/PowerShellEditorServices/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Analysis/AnalysisService.cs index 181360154..287723c20 100644 --- a/src/PowerShellEditorServices/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Analysis/AnalysisService.cs @@ -40,7 +40,10 @@ public AnalysisService(Runspace analysisRunspace) this.scriptAnalyzer = new ScriptAnalyzer(); this.scriptAnalyzer.Initialize( analysisRunspace, - new AnalysisOutputWriter()); + new AnalysisOutputWriter(), + null, + null, + new string[] { "DscTestsPresent", "DscExamplesPresent" }); } #endregion From 0004c47dd29078a019226a0a7f71f550a1dc59b4 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 4 Aug 2015 16:53:21 -0700 Subject: [PATCH 11/18] Fix OpenFileRequest not checking for open files --- .../Request/OpenFileRequest.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs index 956768393..97182f571 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs @@ -26,8 +26,14 @@ public override void ProcessMessage( EditorSession editorSession, MessageWriter messageWriter) { - // Open the file in the current session - editorSession.Workspace.OpenFile(this.Arguments.File); + ScriptFile scriptFile = null; + + // Only load the file if it isn't loaded already + if (!editorSession.Workspace.TryGetFile(this.Arguments.File, out scriptFile)) + { + // Open the file in the current session + editorSession.Workspace.OpenFile(this.Arguments.File); + } } } } From 7755795e5c7ab58e7eb197225b615568dc2b05eb Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 4 Aug 2015 17:04:54 -0700 Subject: [PATCH 12/18] Fix wrong file path used in ReferencesResponse --- .../Response/ReferencesResponse.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices.Transport.Stdio/Response/ReferencesResponse.cs b/src/PowerShellEditorServices.Transport.Stdio/Response/ReferencesResponse.cs index 788ced37e..a7b6e9dc6 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Response/ReferencesResponse.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Response/ReferencesResponse.cs @@ -35,7 +35,7 @@ List referenceItems Offset = reference.ScriptRegion.EndColumnNumber }, IsWriteAccess = true, - File = thisFile, + File = reference.FilePath, LineText = reference.SourceLine }); } From 57392ba471e07fdab60c8c49424ed785717f3c78 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 4 Aug 2015 19:27:54 -0700 Subject: [PATCH 13/18] Simplify Workspace file operations and fix bugs --- .../Request/DeclarationRequest.cs | 3 +- .../Request/ErrorRequest.cs | 11 +-- .../Request/FileRequest.cs | 15 +--- .../Request/OpenFileRequest.cs | 10 +-- .../Analysis/AnalysisService.cs | 46 ++++++----- .../Language/LanguageService.cs | 50 +++++++----- .../Language/SymbolReference.cs | 5 +- .../Session/ScriptFile.cs | 9 +++ .../Session/Workspace.cs | 80 ++++++------------- .../Language/LanguageServiceTests.cs | 13 ++- 10 files changed, 108 insertions(+), 134 deletions(-) diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs index 23852550e..bc3d17d06 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs @@ -26,8 +26,9 @@ public override void ProcessMessage( GetDefinitionResult definition = editorSession.LanguageService.GetDefinitionOfSymbol( + scriptFile, foundSymbol, - editorSession.Workspace.ExpandScriptReferences(scriptFile)); + editorSession.Workspace); if (definition != null) { diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/ErrorRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/ErrorRequest.cs index 5bc19965d..098670afa 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/ErrorRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/ErrorRequest.cs @@ -33,14 +33,9 @@ public override void ProcessMessage( // Get the requested files foreach (string filePath in this.Arguments.Files) { - ScriptFile scriptFile = null; - - if (!editorSession.Workspace.TryGetFile(filePath, out scriptFile)) - { - // Skip this file and log the file load error - // TODO: Trace out the error message - continue; - } + ScriptFile scriptFile = + editorSession.Workspace.GetFile( + filePath); var semanticMarkers = editorSession.AnalysisService.GetSemanticMarkers( diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/FileRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/FileRequest.cs index 0943f1c22..0db64ed4b 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/FileRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/FileRequest.cs @@ -13,20 +13,9 @@ public abstract class FileRequest : RequestBase { protected ScriptFile GetScriptFile(EditorSession editorSession) { - ScriptFile scriptFile = null; - - if(!editorSession.Workspace.TryGetFile( - this.Arguments.File, - out scriptFile)) - { - // TODO: Throw an exception that the message loop can create a response out of - - throw new FileNotFoundException( - "A ScriptFile with the following path was not found in the EditorSession: {0}", + return + editorSession.Workspace.GetFile( this.Arguments.File); - } - - return scriptFile; } } diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs index 97182f571..f0a466bab 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/OpenFileRequest.cs @@ -26,14 +26,8 @@ public override void ProcessMessage( EditorSession editorSession, MessageWriter messageWriter) { - ScriptFile scriptFile = null; - - // Only load the file if it isn't loaded already - if (!editorSession.Workspace.TryGetFile(this.Arguments.File, out scriptFile)) - { - // Open the file in the current session - editorSession.Workspace.OpenFile(this.Arguments.File); - } + // Open the file in the current session + editorSession.Workspace.GetFile(this.Arguments.File); } } } diff --git a/src/PowerShellEditorServices/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Analysis/AnalysisService.cs index 287723c20..572dc50ca 100644 --- a/src/PowerShellEditorServices/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Analysis/AnalysisService.cs @@ -58,26 +58,34 @@ public AnalysisService(Runspace analysisRunspace) /// An array of ScriptFileMarkers containing semantic analysis results. public ScriptFileMarker[] GetSemanticMarkers(ScriptFile file) { - // TODO: This is a temporary fix until we can change how - // ScriptAnalyzer invokes their async tasks. - Task analysisTask = - Task.Factory.StartNew( - () => - { - return - this.scriptAnalyzer - .AnalyzeSyntaxTree( - file.ScriptAst, - file.ScriptTokens, - file.FilePath) - .Select(ScriptFileMarker.FromDiagnosticRecord) - .ToArray(); - }, - CancellationToken.None, - TaskCreationOptions.None, - TaskScheduler.Default); + if (file.IsAnalysisEnabled) + { + // TODO: This is a temporary fix until we can change how + // ScriptAnalyzer invokes their async tasks. + Task analysisTask = + Task.Factory.StartNew( + () => + { + return + this.scriptAnalyzer + .AnalyzeSyntaxTree( + file.ScriptAst, + file.ScriptTokens, + file.FilePath) + .Select(ScriptFileMarker.FromDiagnosticRecord) + .ToArray(); + }, + CancellationToken.None, + TaskCreationOptions.None, + TaskScheduler.Default); - return analysisTask.Result; + return analysisTask.Result; + } + else + { + // Return an empty marker list + return new ScriptFileMarker[0]; + } } #endregion diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index e502d04f8..c7aa10797 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -10,10 +10,8 @@ namespace Microsoft.PowerShell.EditorServices.Language { using Microsoft.PowerShell.EditorServices.Utility; - using System.IO; using System.Management.Automation; using System.Management.Automation.Runspaces; - using System.Text; /// /// Provides a high-level service for performing code completion and @@ -185,6 +183,7 @@ public FindReferencesResult FindReferencesOfSymbol( reference.FilePath = file.FilePath; return reference; }); + symbolReferences.AddRange(symbolReferencesinFile); } @@ -206,10 +205,17 @@ public FindReferencesResult FindReferencesOfSymbol( /// An array of scriptFiles too search for the definition in /// GetDefinitionResult public GetDefinitionResult GetDefinitionOfSymbol( + ScriptFile sourceFile, SymbolReference foundSymbol, - ScriptFile[] referencedFiles) + Workspace workspace) { + Validate.IsNotNull("sourceFile", sourceFile); Validate.IsNotNull("foundSymbol", foundSymbol); + Validate.IsNotNull("workspace", workspace); + + ScriptFile[] referencedFiles = + workspace.ExpandScriptReferences( + sourceFile); // look through the referenced files until definition is found // or there are no more file to look through @@ -233,7 +239,11 @@ public GetDefinitionResult GetDefinitionOfSymbol( if (foundDefinition == null) { CommandInfo cmdInfo = GetCommandInfo(foundSymbol.SymbolName); - foundDefinition = FindDeclarationForBuiltinCommand(cmdInfo, foundSymbol); + foundDefinition = + FindDeclarationForBuiltinCommand( + cmdInfo, + foundSymbol, + workspace); } return new GetDefinitionResult(foundDefinition); @@ -334,7 +344,9 @@ private CommandInfo GetCommandInfo(string commandName) return commandInfo; } - private ScriptFile[] GetBuiltinCommandScriptFiles(PSModuleInfo moduleInfo) + private ScriptFile[] GetBuiltinCommandScriptFiles( + PSModuleInfo moduleInfo, + Workspace workspace) { if (moduleInfo != null) { @@ -344,25 +356,20 @@ private ScriptFile[] GetBuiltinCommandScriptFiles(PSModuleInfo moduleInfo) if (modPath.EndsWith(@".ps1") || modPath.EndsWith(@".psm1")) { - using (StreamReader streamReader = new StreamReader(modPath, Encoding.UTF8)) - { - newFile = new ScriptFile(modPath, streamReader); - scriptFiles.Add(newFile); - } + newFile = workspace.GetFile(modPath); + newFile.IsAnalysisEnabled = false; + scriptFiles.Add(newFile); } if (moduleInfo.NestedModules.Count > 0) { - string nestedModPath; foreach (PSModuleInfo nestedInfo in moduleInfo.NestedModules) { - nestedModPath = nestedInfo.Path; + string nestedModPath = nestedInfo.Path; if (nestedModPath.EndsWith(@".ps1") || nestedModPath.EndsWith(@".psm1")) { - using (StreamReader streamReader = new StreamReader(nestedModPath, Encoding.UTF8)) - { - newFile = new ScriptFile(nestedModPath, streamReader); - scriptFiles.Add(newFile); - } + newFile = workspace.GetFile(nestedModPath); + newFile.IsAnalysisEnabled = false; + scriptFiles.Add(newFile); } } } @@ -373,7 +380,10 @@ private ScriptFile[] GetBuiltinCommandScriptFiles(PSModuleInfo moduleInfo) return new List().ToArray(); } - private SymbolReference FindDeclarationForBuiltinCommand(CommandInfo cmdInfo, SymbolReference foundSymbol) + private SymbolReference FindDeclarationForBuiltinCommand( + CommandInfo cmdInfo, + SymbolReference foundSymbol, + Workspace workspace) { SymbolReference foundDefinition = null; if (cmdInfo != null) @@ -382,7 +392,9 @@ private SymbolReference FindDeclarationForBuiltinCommand(CommandInfo cmdInfo, Sy ScriptFile[] nestedModuleFiles; nestedModuleFiles = - GetBuiltinCommandScriptFiles(GetCommandInfo(foundSymbol.SymbolName).Module); + GetBuiltinCommandScriptFiles( + GetCommandInfo(foundSymbol.SymbolName).Module, + workspace); while (foundDefinition == null && index < nestedModuleFiles.Length) { diff --git a/src/PowerShellEditorServices/Language/SymbolReference.cs b/src/PowerShellEditorServices/Language/SymbolReference.cs index 123cd4340..538f752b8 100644 --- a/src/PowerShellEditorServices/Language/SymbolReference.cs +++ b/src/PowerShellEditorServices/Language/SymbolReference.cs @@ -59,13 +59,14 @@ public class SymbolReference /// Gets the contents of the line the given symbol is on /// public string SourceLine { get; internal set; } - #endregion /// - /// Gets the Filepath of the symbol + /// Gets the path of the file in which the symbol was found. /// public string FilePath { get; internal set; } + #endregion + /// /// Constructs and instance of a SymbolReference /// diff --git a/src/PowerShellEditorServices/Session/ScriptFile.cs b/src/PowerShellEditorServices/Session/ScriptFile.cs index c0f444d28..d8d3b60b3 100644 --- a/src/PowerShellEditorServices/Session/ScriptFile.cs +++ b/src/PowerShellEditorServices/Session/ScriptFile.cs @@ -32,6 +32,13 @@ public class ScriptFile /// public string FilePath { get; private set; } + /// + /// Gets or sets a boolean that determines whether + /// semantic analysis should be enabled for this file. + /// For internal use only. + /// + internal bool IsAnalysisEnabled { get; set; } + /// /// Gets a string containing the full contents of the file. /// @@ -101,6 +108,8 @@ public string[] ReferencedFiles public ScriptFile(string filePath, TextReader textReader) { this.FilePath = filePath; + this.IsAnalysisEnabled = true; + this.ReadFile(textReader); } diff --git a/src/PowerShellEditorServices/Session/Workspace.cs b/src/PowerShellEditorServices/Session/Workspace.cs index efe9ec12f..32a391a4f 100644 --- a/src/PowerShellEditorServices/Session/Workspace.cs +++ b/src/PowerShellEditorServices/Session/Workspace.cs @@ -22,46 +22,43 @@ public class Workspace #region Public Methods /// - /// Opens a script file with the given file path. + /// Gets an open file in the workspace. If the file isn't open but + /// exists on the filesystem, load and return it. /// /// The file path at which the script resides. /// /// is not found. /// /// - /// has already been loaded in the workspace. - /// - /// /// contains a null or empty string. /// - public ScriptFile OpenFile(string filePath) + public ScriptFile GetFile(string filePath) { Validate.IsNotNullOrEmptyString("filePath", filePath); - // Resolve the full file path + // Resolve the full file path after making sure that slashes + // are in the right form string resolvedFilePath = this.ResolveFilePath( - filePath); + filePath.Replace('/', '\\')); + + string keyName = resolvedFilePath.ToLower(); // Make sure the file isn't already loaded into the workspace - if (!this.workspaceFiles.ContainsKey(resolvedFilePath)) + ScriptFile scriptFile = null; + if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile)) { // This method allows FileNotFoundException to bubble up // if the file isn't found. using (StreamReader streamReader = new StreamReader(resolvedFilePath, Encoding.UTF8)) { - ScriptFile newFile = new ScriptFile(resolvedFilePath, streamReader); - this.workspaceFiles.Add(resolvedFilePath, newFile); - return newFile; + scriptFile = new ScriptFile(resolvedFilePath, streamReader); + this.workspaceFiles.Add(keyName, scriptFile); } } - else - { - throw new ArgumentException( - "The specified file has already been loaded: " + resolvedFilePath, - "filePath"); - } + + return scriptFile; } /// @@ -75,32 +72,6 @@ public void CloseFile(ScriptFile scriptFile) this.workspaceFiles.Remove(scriptFile.FilePath); } - /// - /// Attempts to get a currently open script file with the given file path. - /// - /// The file path at which the script resides. - /// The output variable in which the ScriptFile will be stored. - /// A ScriptFile instance - public bool TryGetFile(string filePath, out ScriptFile scriptFile) - { - // Resolve the full file path - string resolvedFilePath = - this.ResolveFilePath( - filePath); - - scriptFile = null; - return this.workspaceFiles.TryGetValue(resolvedFilePath, out scriptFile); - } - - /// - /// Gets all open files in the workspace. - /// - /// A collection of all open ScriptFiles in the workspace. - public IEnumerable GetOpenFiles() - { - return this.workspaceFiles.Values; - } - /// /// Gets all file references by recursively searching /// through referenced files in a scriptfile @@ -115,10 +86,11 @@ public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile) RecursivelyFindReferences(scriptFile, referencedScriptFiles); expandedReferences.Add(scriptFile); // add original file first - if (referencedScriptFiles.Count != 0) + if (referencedScriptFiles.Count > 0) { expandedReferences.AddRange(referencedScriptFiles.Values); } + return expandedReferences.ToArray(); } @@ -136,24 +108,20 @@ private void RecursivelyFindReferences( ScriptFile scriptFile, Dictionary referencedScriptFiles) { - ScriptFile newFile; - foreach (string filename in scriptFile.ReferencedFiles) + ScriptFile referencedFile; + foreach (string referencedFileName in scriptFile.ReferencedFiles) { string resolvedScriptPath = this.ResolveRelativeScriptPath( scriptFile.FilePath, - filename); + referencedFileName); - if (!referencedScriptFiles.ContainsKey(resolvedScriptPath)) - { - if (!TryGetFile(resolvedScriptPath, out newFile)) - { - newFile = OpenFile(resolvedScriptPath); - } + // Get the referenced file + referencedFile = this.GetFile(resolvedScriptPath); + referencedScriptFiles.Add(resolvedScriptPath, referencedFile); - referencedScriptFiles.Add(resolvedScriptPath, newFile); - RecursivelyFindReferences(newFile, referencedScriptFiles); - } + // Recursively find references on this file` + RecursivelyFindReferences(referencedFile, referencedScriptFiles); } } diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index be8981214..f1278bb36 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -181,13 +181,9 @@ private ScriptFile GetScriptFile(ScriptRegion scriptRegion) baseSharedScriptPath, scriptRegion.File); - ScriptFile scriptFile = null; - if (!this.workspace.TryGetFile(resolvedPath, out scriptFile)) - { - scriptFile = this.workspace.OpenFile(resolvedPath); - } - - return scriptFile; + return + this.workspace.GetFile( + resolvedPath); } private CompletionResults GetCompletionResults(ScriptRegion scriptRegion) @@ -223,8 +219,9 @@ private GetDefinitionResult GetDefinition(ScriptRegion scriptRegion) return this.languageService.GetDefinitionOfSymbol( + scriptFile, symbolReference, - this.workspace.ExpandScriptReferences(scriptFile)); + this.workspace); } private FindReferencesResult GetReferences(ScriptRegion scriptRegion) From b5c5b54b8f57afe7c38ad207a73ffb91daa3adbe Mon Sep 17 00:00:00 2001 From: Kayla Davis Date: Wed, 5 Aug 2015 12:43:45 -0700 Subject: [PATCH 14/18] fixing declaration request bug --- .../Request/DeclarationRequest.cs | 18 +++++++++++++----- .../Language/FindDeclartionVisitor.cs | 5 +++-- .../Language/FindReferencesVisitor.cs | 9 +++++---- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs index bc3d17d06..bb96494ad 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs @@ -23,12 +23,17 @@ public override void ProcessMessage( scriptFile, this.Arguments.Line, this.Arguments.Offset); + + GetDefinitionResult definition = null; + if (foundSymbol != null) + { + definition = + editorSession.LanguageService.GetDefinitionOfSymbol( + scriptFile, + foundSymbol, + editorSession.Workspace); - GetDefinitionResult definition = - editorSession.LanguageService.GetDefinitionOfSymbol( - scriptFile, - foundSymbol, - editorSession.Workspace); + } if (definition != null) { @@ -45,6 +50,9 @@ public override void ProcessMessage( messageWriter.WriteMessage( this.PrepareResponse(defResponse)); } + + messageWriter.WriteMessage( + this.PrepareResponse(DefinitionResponse.Create())); } } } diff --git a/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs b/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs index a91aec1e3..f93967bfd 100644 --- a/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; using System.Management.Automation.Language; namespace Microsoft.PowerShell.EditorServices.Language @@ -45,7 +46,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun }; if (symbolRef.SymbolType.Equals(SymbolType.Function) && - nameExtent.Text.Equals(symbolRef.ScriptRegion.Text)) + nameExtent.Text.Equals(symbolRef.ScriptRegion.Text, StringComparison.InvariantCultureIgnoreCase)) { this.FoundDeclartion = new SymbolReference( @@ -69,7 +70,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { if(symbolRef.SymbolType.Equals(SymbolType.Variable) && - variableExpressionAst.Extent.Text.Equals(symbolRef.SymbolName)) + variableExpressionAst.Extent.Text.Equals(symbolRef.SymbolName, StringComparison.InvariantCultureIgnoreCase)) { this.FoundDeclartion = new SymbolReference( diff --git a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs index 1e96dc10d..abb825431 100644 --- a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; using System.Collections.Generic; using System.Management.Automation.Language; @@ -34,7 +35,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) { Ast commandNameAst = commandAst.CommandElements[0]; if(symbolRef.SymbolType.Equals(SymbolType.Function) && - commandNameAst.Extent.Text.Equals(symbolRef.ScriptRegion.Text)) + commandNameAst.Extent.Text.Equals(symbolRef.ScriptRegion.Text, StringComparison.InvariantCultureIgnoreCase)) { this.FoundReferences.Add(new SymbolReference( SymbolType.Function, @@ -65,7 +66,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun }; if (symbolRef.SymbolType.Equals(SymbolType.Function) && - nameExtent.Text.Equals(symbolRef.SymbolName)) + nameExtent.Text.Equals(symbolRef.SymbolName, StringComparison.InvariantCultureIgnoreCase)) { this.FoundReferences.Add(new SymbolReference( SymbolType.Function, @@ -83,7 +84,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) { if (symbolRef.SymbolType.Equals(SymbolType.Parameter) && - commandParameterAst.Extent.Text.Equals(symbolRef.SymbolName)) + commandParameterAst.Extent.Text.Equals(symbolRef.SymbolName, StringComparison.InvariantCultureIgnoreCase)) { this.FoundReferences.Add(new SymbolReference( SymbolType.Parameter, @@ -101,7 +102,7 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { if(symbolRef.SymbolType.Equals(SymbolType.Variable) && - variableExpressionAst.Extent.Text.Equals(symbolRef.SymbolName)) + variableExpressionAst.Extent.Text.Equals(symbolRef.SymbolName, StringComparison.InvariantCultureIgnoreCase)) { this.FoundReferences.Add(new SymbolReference( SymbolType.Variable, From f6708638d618603f10fb3e731d8d041b4d7fae29 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Wed, 5 Aug 2015 14:07:30 -0700 Subject: [PATCH 15/18] Improve ScriptFile loading of trailing newlines This change fixes a bug in ScriptFile where there would not be a final empty line if the last content line ended with a newline. This was causing some problems when editing the last line of the file in an editor because the line numbers would be out of range. --- .../Session/ScriptFile.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/PowerShellEditorServices/Session/ScriptFile.cs b/src/PowerShellEditorServices/Session/ScriptFile.cs index d8d3b60b3..560fb7bbd 100644 --- a/src/PowerShellEditorServices/Session/ScriptFile.cs +++ b/src/PowerShellEditorServices/Session/ScriptFile.cs @@ -227,19 +227,15 @@ public int GetOffsetAtPosition(int lineNumber, int columnNumber) /// A TextReader to use for reading file contents. private void ReadFile(TextReader textReader) { - this.FileLines = new List(); - - // Read the file contents line by line - string fileLine = null; - do - { - fileLine = textReader.ReadLine(); - if (fileLine != null) - { - FileLines.Add(fileLine); - } - } - while (fileLine != null); + string fileContents = textReader.ReadToEnd(); + + // Split the file contents into lines and trim + // any carriage returns from the strings. + this.FileLines = + fileContents + .Split('\n') + .Select(line => line.TrimEnd('\r')) + .ToList(); // Parse the contents to get syntax tree and errors this.ParseFileContents(); From 633e36b1ad837841e8ec180b5a66bebca0b8d8ba Mon Sep 17 00:00:00 2001 From: Kayla Davis Date: Mon, 10 Aug 2015 10:47:45 -0700 Subject: [PATCH 16/18] Add alias support on find references --- .../Request/DeclarationRequest.cs | 18 +--- .../Language/AstOperations.cs | 47 ++++++++-- .../Language/FindCommandVisitor.cs | 2 + .../Language/FindDeclartionVisitor.cs | 2 + .../Language/FindDotSourcedVisitor.cs | 2 - .../Language/FindReferencesVisitor.cs | 89 +++++++++++++++++-- .../Language/LanguageService.cs | 44 ++++++++- .../Session/EditorSession.cs | 5 -- .../Session/Workspace.cs | 20 +++-- ...owerShellEditorServices.Test.Shared.csproj | 5 ++ ...indsReferencesOnBuiltInCommandWithAlias.cs | 30 +++++++ ...sReferencesOnFunctionMultiFileDotSource.cs | 30 +++++++ .../References/ReferenceFileA.ps1 | 9 ++ .../References/ReferenceFileB.ps1 | 5 ++ .../References/ReferenceFileC.ps1 | 4 + .../References/SimpleFile.ps1 | 10 ++- .../Language/LanguageServiceTests.cs | 55 ++++++++++-- 17 files changed, 329 insertions(+), 48 deletions(-) create mode 100644 test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnBuiltInCommandWithAlias.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1 create mode 100644 test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1 create mode 100644 test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1 diff --git a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs index bb96494ad..14c70b88f 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs +++ b/src/PowerShellEditorServices.Transport.Stdio/Request/DeclarationRequest.cs @@ -35,24 +35,14 @@ public override void ProcessMessage( } - if (definition != null) + DefinitionResponse defResponse = DefinitionResponse.Create(); + if (definition != null && definition.FoundDefinition != null) { - DefinitionResponse defResponse; - if (definition.FoundDefinition != null) - { - defResponse = DefinitionResponse.Create(definition.FoundDefinition); - } - else - { - defResponse = DefinitionResponse.Create(); - } - - messageWriter.WriteMessage( - this.PrepareResponse(defResponse)); + defResponse = DefinitionResponse.Create(definition.FoundDefinition); } messageWriter.WriteMessage( - this.PrepareResponse(DefinitionResponse.Create())); + this.PrepareResponse(defResponse)); } } } diff --git a/src/PowerShellEditorServices/Language/AstOperations.cs b/src/PowerShellEditorServices/Language/AstOperations.cs index 93c3945c1..93b130d26 100644 --- a/src/PowerShellEditorServices/Language/AstOperations.cs +++ b/src/PowerShellEditorServices/Language/AstOperations.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; using System.Collections.Generic; using System.Linq; using System.Management.Automation; @@ -108,20 +109,50 @@ static public SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumbe } /// - /// Finds all references in a script of the given symbol + /// Finds all references (including aliases) in a script for the given symbol /// /// The abstract syntax tree of the given script /// The symbol that we are looking for referneces of - /// A collection of SymbolReference objects that are refrences to the symbolRefrence - static public IEnumerable FindReferencesOfSymbol(Ast scriptAst, SymbolReference symbolReference) + /// Dictionary maping cmdlets to aliases for finding alias references + /// Dictionary maping aliases to cmdlets for finding alias references + /// + static public IEnumerable FindReferencesOfSymbol( + Ast scriptAst, + SymbolReference symbolReference, + Dictionary> CmdletToAliasDictionary, + Dictionary AliasToCmdletDictionary) { // find the symbol evaluators for the node types we are handling - FindReferencesVisitor referencesVisitor = new FindReferencesVisitor(symbolReference); + FindReferencesVisitor referencesVisitor = + new FindReferencesVisitor( + symbolReference, + CmdletToAliasDictionary, + AliasToCmdletDictionary); scriptAst.Visit(referencesVisitor); return referencesVisitor.FoundReferences; } + /// + /// Finds all references (not including aliases) in a script for the given symbol + /// + /// The abstract syntax tree of the given script + /// The symbol that we are looking for referneces of + /// If this reference search needs aliases. + /// This should always be false and used for occurence requests + /// A collection of SymbolReference objects that are refrences to the symbolRefrence + /// not including aliases + static public IEnumerable FindReferencesOfSymbol( + ScriptBlockAst scriptAst, + SymbolReference foundSymbol, + bool needsAliases) + { + FindReferencesVisitor referencesVisitor = + new FindReferencesVisitor(foundSymbol); + scriptAst.Visit(referencesVisitor); + + return referencesVisitor.FoundReferences; + } /// /// Finds the definition of the symbol @@ -129,9 +160,13 @@ static public IEnumerable FindReferencesOfSymbol(Ast scriptAst, /// The abstract syntax tree of the given script /// The symbol that we are looking for the definition of /// A SymbolReference of the definition of the symbolReference - static public SymbolReference FindDefinitionOfSymbol(Ast scriptAst, SymbolReference symbolReference) + static public SymbolReference FindDefinitionOfSymbol( + Ast scriptAst, + SymbolReference symbolReference) { - FindDeclartionVisitor declarationVisitor = new FindDeclartionVisitor(symbolReference); + FindDeclartionVisitor declarationVisitor = + new FindDeclartionVisitor( + symbolReference); scriptAst.Visit(declarationVisitor); return declarationVisitor.FoundDeclartion; diff --git a/src/PowerShellEditorServices/Language/FindCommandVisitor.cs b/src/PowerShellEditorServices/Language/FindCommandVisitor.cs index 57e409e96..d2db440e2 100644 --- a/src/PowerShellEditorServices/Language/FindCommandVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindCommandVisitor.cs @@ -33,6 +33,8 @@ public FindCommandVisitor(int lineNumber, int columnNumber) public override AstVisitAction VisitCommand(CommandAst commandAst) { Ast commandNameAst = commandAst.CommandElements[0]; + + // Only want commands that are using a trigger character, which requires at least 2 cmd elements if (!(commandAst.CommandElements.Count > 1)) { return base.VisitCommand(commandAst); diff --git a/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs b/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs index f93967bfd..388989765 100644 --- a/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindDeclartionVisitor.cs @@ -32,6 +32,8 @@ public FindDeclartionVisitor(SymbolReference symbolRef) /// or a decision to continue if it wasn't found public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { + // Get the start column number of the function name, + // instead of the the start column of 'function' and create new extent for the functionName int startColumnNumber = functionDefinitionAst.Extent.Text.IndexOf( functionDefinitionAst.Name) + 1; diff --git a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs b/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs index 39ac35974..7748d3026 100644 --- a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs @@ -3,9 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Session; using System.Collections.Generic; -using System.IO; using System.Management.Automation.Language; namespace Microsoft.PowerShell.EditorServices.Language diff --git a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs index abb825431..af5fc534d 100644 --- a/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindReferencesVisitor.cs @@ -15,13 +15,47 @@ namespace Microsoft.PowerShell.EditorServices.Language internal class FindReferencesVisitor : AstVisitor { private SymbolReference symbolRef; + private Dictionary> CmdletToAliasDictionary; + private Dictionary AliasToCmdletDictionary; + private string symbolRefCommandName; + private bool needsAliases; public List FoundReferences { get; set; } - public FindReferencesVisitor(SymbolReference symbolRef) + /// + /// Constructor used when searching for aliases is needed + /// + /// The found symbolReference that other symbols are being compared to + /// Dictionary maping cmdlets to aliases for finding alias references + /// Dictionary maping aliases to cmdlets for finding alias references + public FindReferencesVisitor( + SymbolReference symbolReference, + Dictionary> CmdletToAliasDictionary, + Dictionary AliasToCmdletDictionary) { - this.symbolRef = symbolRef; + this.symbolRef = symbolReference; this.FoundReferences = new List(); + this.needsAliases = true; + this.CmdletToAliasDictionary = CmdletToAliasDictionary; + this.AliasToCmdletDictionary = AliasToCmdletDictionary; + + // Try to get the symbolReference's command name of an alias, + // if a command name does not exists (if the symbol isn't an alias to a command) + // set symbolRefCommandName to and empty string value + AliasToCmdletDictionary.TryGetValue(symbolReference.ScriptRegion.Text, out symbolRefCommandName); + if (symbolRefCommandName == null) { symbolRefCommandName = string.Empty; } + + } + + /// + /// Constructor used when searching for aliases is not needed + /// + /// The found symbolReference that other symbols are being compared to + public FindReferencesVisitor(SymbolReference foundSymbol) + { + this.symbolRef = foundSymbol; + this.FoundReferences = new List(); + this.needsAliases = false; } /// @@ -34,12 +68,51 @@ public FindReferencesVisitor(SymbolReference symbolRef) public override AstVisitAction VisitCommand(CommandAst commandAst) { Ast commandNameAst = commandAst.CommandElements[0]; - if(symbolRef.SymbolType.Equals(SymbolType.Function) && - commandNameAst.Extent.Text.Equals(symbolRef.ScriptRegion.Text, StringComparison.InvariantCultureIgnoreCase)) + string commandName = commandNameAst.Extent.Text; + + if(symbolRef.SymbolType.Equals(SymbolType.Function)) { - this.FoundReferences.Add(new SymbolReference( - SymbolType.Function, - commandNameAst.Extent)); + if (needsAliases) + { + // Try to get the commandAst's name and aliases, + // if a command does not exists (if the symbol isn't an alias to a command) + // set command to and empty string value string command + // if the aliases do not exist (if the symvol isn't a command that has aliases) + // set aliases to an empty List + string command; + List alaises; + CmdletToAliasDictionary.TryGetValue(commandName, out alaises); + AliasToCmdletDictionary.TryGetValue(commandName, out command); + if (alaises == null) { alaises = new List(); } + if (command == null) { command = string.Empty; } + + if (symbolRef.SymbolType.Equals(SymbolType.Function)) + { + // Check if the found symbol's name is the same as the commandAst's name OR + // if the symbol's name is an alias for this commandAst's name (commandAst is a cmdlet) OR + // if the symbol's name is the same as the commandAst's cmdlet name (commandAst is a alias) + if (commandName.Equals(symbolRef.SymbolName, StringComparison.InvariantCultureIgnoreCase) || + alaises.Contains(symbolRef.ScriptRegion.Text.ToLower()) || + command.Equals(symbolRef.ScriptRegion.Text, StringComparison.InvariantCultureIgnoreCase) || + (!command.Equals(string.Empty) && command.Equals(symbolRefCommandName, StringComparison.InvariantCultureIgnoreCase))) + { + this.FoundReferences.Add(new SymbolReference( + SymbolType.Function, + commandNameAst.Extent)); + } + } + + } + else // search does not include aliases + { + if (commandName.Equals(symbolRef.SymbolName, StringComparison.InvariantCultureIgnoreCase)) + { + this.FoundReferences.Add(new SymbolReference( + SymbolType.Function, + commandNameAst.Extent)); + } + } + } return base.VisitCommand(commandAst); } @@ -52,6 +125,8 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) /// A visit action that continues the search for references public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { + // Get the start column number of the function name, + // instead of the the start column of 'function' and create new extent for the functionName int startColumnNumber = functionDefinitionAst.Extent.Text.IndexOf( functionDefinitionAst.Name) + 1; diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index c7aa10797..df64d83d0 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -10,6 +10,7 @@ namespace Microsoft.PowerShell.EditorServices.Language { using Microsoft.PowerShell.EditorServices.Utility; + using System; using System.Management.Automation; using System.Management.Automation.Runspaces; @@ -26,7 +27,8 @@ public class LanguageService private int mostRecentRequestLine; private int mostRecentRequestOffest; private string mostRecentRequestFile; - + private Dictionary> CmdletToAliasDictionary; + private Dictionary AliasToCmdletDictionary; #endregion #region Constructors @@ -43,6 +45,9 @@ public LanguageService(Runspace languageServiceRunspace) Validate.IsNotNull("languageServiceRunspace", languageServiceRunspace); this.runspace = languageServiceRunspace; + this.CmdletToAliasDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); + this.AliasToCmdletDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + GetAliases(); } #endregion @@ -109,6 +114,7 @@ public CompletionDetails GetCompletionDetailsInFile( int columnNumber, string entryName) { + // Makes sure the most recent completions request was the same line and column as this request if (file.FilePath.Equals(mostRecentRequestFile) && lineNumber == mostRecentRequestLine && columnNumber == mostRecentRequestOffest) @@ -174,7 +180,9 @@ public FindReferencesResult FindReferencesOfSymbol( AstOperations .FindReferencesOfSymbol( file.ScriptAst, - foundSymbol) + foundSymbol, + CmdletToAliasDictionary, + AliasToCmdletDictionary) .Select( reference => { @@ -269,11 +277,13 @@ public FindOccurrencesResult FindOccurrencesInFile( if (foundSymbol != null) { + // find all references, and indicate that looking for aliases is not needed IEnumerable symbolOccurrences = AstOperations .FindReferencesOfSymbol( file.ScriptAst, - foundSymbol); + foundSymbol, + false); return new FindOccurrencesResult @@ -329,6 +339,30 @@ public ParameterSetSignatures FindParameterSetsInFile( #endregion + #region Private Fields + + /// + /// Gets all aliases found in the runspace + /// + private void GetAliases() + { + CommandInvocationIntrinsics invokeCommand = runspace.SessionStateProxy.InvokeCommand; + IEnumerable aliases = invokeCommand.GetCommands("*", CommandTypes.Alias, true); + foreach (AliasInfo aliasInfo in aliases) + { + if (!CmdletToAliasDictionary.ContainsKey(aliasInfo.Definition)) + { + CmdletToAliasDictionary.Add(aliasInfo.Definition, new List() { aliasInfo.Name }); + } + else + { + CmdletToAliasDictionary[aliasInfo.Definition].Add(aliasInfo.Name); + } + + AliasToCmdletDictionary.Add(aliasInfo.Name, aliasInfo.Definition); + } + } + private CommandInfo GetCommandInfo(string commandName) { CommandInfo commandInfo = null; @@ -348,12 +382,15 @@ private ScriptFile[] GetBuiltinCommandScriptFiles( PSModuleInfo moduleInfo, Workspace workspace) { + // if there is module info for this command if (moduleInfo != null) { string modPath = moduleInfo.Path; List scriptFiles = new List(); ScriptFile newFile; + // find any files where the moduleInfo's path ends with ps1 or psm1 + // and add it to allowed script files if (modPath.EndsWith(@".ps1") || modPath.EndsWith(@".psm1")) { newFile = workspace.GetFile(modPath); @@ -412,5 +449,6 @@ private SymbolReference FindDeclarationForBuiltinCommand( return foundDefinition; } + #endregion } } diff --git a/src/PowerShellEditorServices/Session/EditorSession.cs b/src/PowerShellEditorServices/Session/EditorSession.cs index f90bbd388..45bca21d6 100644 --- a/src/PowerShellEditorServices/Session/EditorSession.cs +++ b/src/PowerShellEditorServices/Session/EditorSession.cs @@ -6,13 +6,8 @@ using Microsoft.PowerShell.EditorServices.Analysis; using Microsoft.PowerShell.EditorServices.Console; using Microsoft.PowerShell.EditorServices.Language; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.IO; using System.Management.Automation; using System.Management.Automation.Runspaces; -using System.Text; using System.Threading; namespace Microsoft.PowerShell.EditorServices.Session diff --git a/src/PowerShellEditorServices/Session/Workspace.cs b/src/PowerShellEditorServices/Session/Workspace.cs index 32a391a4f..4662fd9e6 100644 --- a/src/PowerShellEditorServices/Session/Workspace.cs +++ b/src/PowerShellEditorServices/Session/Workspace.cs @@ -84,8 +84,15 @@ public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile) Dictionary referencedScriptFiles = new Dictionary(); List expandedReferences = new List(); + // add original file so it's not searched for, then find all file references + referencedScriptFiles.Add(scriptFile.FilePath, scriptFile); RecursivelyFindReferences(scriptFile, referencedScriptFiles); - expandedReferences.Add(scriptFile); // add original file first + + // remove original file from referened file and add it as the first element of the + // expanded referenced list to maintain order so the original file is always first in the list + referencedScriptFiles.Remove(scriptFile.FilePath); + expandedReferences.Add(scriptFile); + if (referencedScriptFiles.Count > 0) { expandedReferences.AddRange(referencedScriptFiles.Values); @@ -116,12 +123,15 @@ private void RecursivelyFindReferences( scriptFile.FilePath, referencedFileName); - // Get the referenced file + // Get the referenced file if it's not already in referencedScriptFiles referencedFile = this.GetFile(resolvedScriptPath); - referencedScriptFiles.Add(resolvedScriptPath, referencedFile); + if (!referencedScriptFiles.ContainsKey(resolvedScriptPath)) + { + referencedScriptFiles.Add(resolvedScriptPath, referencedFile); + RecursivelyFindReferences(referencedFile, referencedScriptFiles); + } + - // Recursively find references on this file` - RecursivelyFindReferences(referencedFile, referencedScriptFiles); } } diff --git a/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj b/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj index 9f1696486..4c676a9f5 100644 --- a/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj +++ b/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj @@ -50,7 +50,9 @@ + + @@ -71,6 +73,9 @@ + + + PreserveNewest diff --git a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnBuiltInCommandWithAlias.cs b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnBuiltInCommandWithAlias.cs new file mode 100644 index 000000000..ac18ec9f2 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnBuiltInCommandWithAlias.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.PowerShell.EditorServices.Language; +using Microsoft.PowerShell.EditorServices.Session; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.References +{ + public class FindsReferencesOnBuiltInCommandWithAlias + { + public static readonly ScriptRegion SourceDetails = + new ScriptRegion + { + File = @"References\SimpleFile.ps1", + StartLineNumber = 14, + StartColumnNumber = 3 + }; + } + public class FindsReferencesOnBuiltInAlias + { + public static readonly ScriptRegion SourceDetails = + new ScriptRegion + { + File = @"References\SimpleFile.ps1", + StartLineNumber = 15, + StartColumnNumber = 2 + }; + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs new file mode 100644 index 000000000..bcbd70327 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.PowerShell.EditorServices.Language; +using Microsoft.PowerShell.EditorServices.Session; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.References +{ + public class FindsReferencesOnFunctionMultiFileDotSourceFileB + { + public static readonly ScriptRegion SourceDetails = + new ScriptRegion + { + File = @"References\ReferenceFileB.ps1", + StartLineNumber = 5, + StartColumnNumber = 8 + }; + } + public class FindsReferencesOnFunctionMultiFileDotSourceFileC + { + public static readonly ScriptRegion SourceDetails = + new ScriptRegion + { + File = @"References\ReferenceFileC.ps1", + StartLineNumber = 4, + StartColumnNumber = 10 + }; + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1 new file mode 100644 index 000000000..a43f59fb2 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1 @@ -0,0 +1,9 @@ +. .\ReferenceFileA.ps1 +. .\ReferenceFileB.ps1 +. .\ReferenceFileC.ps1 + +function My-Function ($myInput) +{ + My-Function $myInput +} +Get-ChildItem \ No newline at end of file diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1 new file mode 100644 index 000000000..add70c1d6 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1 @@ -0,0 +1,5 @@ +. .\ReferenceFileC.ps1 + +Get-ChildItem + +My-Function "testb" \ No newline at end of file diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1 new file mode 100644 index 000000000..e17fd096f --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1 @@ -0,0 +1,4 @@ +. .\ReferenceFileA.ps1 +Get-ChildItem + +My-Function "testc" \ No newline at end of file diff --git a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 index 43d36b86a..7e9022cf2 100644 --- a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 @@ -9,4 +9,12 @@ $things My-Function $things -Write-Output "Hello World"; \ No newline at end of file +Write-Output "Hello World"; + +Get-ChildItem +ls +gci +dir +LS +Write-Host +Get-ChildItem \ No newline at end of file diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index f1278bb36..aeb9a5377 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -6,15 +6,16 @@ using Microsoft.PowerShell.EditorServices.Language; using Microsoft.PowerShell.EditorServices.Session; using Microsoft.PowerShell.EditorServices.Test.Shared.Completion; +using Microsoft.PowerShell.EditorServices.Test.Shared.Definition; +using Microsoft.PowerShell.EditorServices.Test.Shared.Occurrences; +using Microsoft.PowerShell.EditorServices.Test.Shared.ParameterHint; +using Microsoft.PowerShell.EditorServices.Test.Shared.References; using System; +using System.IO; +using System.Linq; using System.Management.Automation.Runspaces; using System.Threading; -using System.Linq; using Xunit; -using Microsoft.PowerShell.EditorServices.Test.Shared.ParameterHint; -using Microsoft.PowerShell.EditorServices.Test.Shared.Definition; -using Microsoft.PowerShell.EditorServices.Test.Shared.Occurrences; -using System.IO; namespace Microsoft.PowerShell.EditorServices.Test.Language { @@ -171,6 +172,50 @@ public void LanguageServiceFindsOccurrencesOnParameter() Assert.Equal(3, occurrencesResult.FoundOccurrences.Last().ScriptRegion.StartLineNumber); } + [Fact] + public void LanguageServiceFindsReferencesOnCommandWithAlias() + { + FindReferencesResult refsResult = + this.GetReferences( + FindsReferencesOnBuiltInCommandWithAlias.SourceDetails); + + Assert.Equal(6, refsResult.FoundReferences.Count()); + Assert.Equal("Get-ChildItem", refsResult.FoundReferences.Last().SymbolName); + Assert.Equal("ls", refsResult.FoundReferences.ToArray()[1].SymbolName); + } + + [Fact] + public void LanguageServiceFindsReferencesOnAlias() + { + FindReferencesResult refsResult = + this.GetReferences( + FindsReferencesOnBuiltInCommandWithAlias.SourceDetails); + + Assert.Equal(6, refsResult.FoundReferences.Count()); + Assert.Equal("Get-ChildItem", refsResult.FoundReferences.Last().SymbolName); + Assert.Equal("gci", refsResult.FoundReferences.ToArray()[2].SymbolName); + Assert.Equal("LS", refsResult.FoundReferences.ToArray()[4].SymbolName); + } + + [Fact] + public void LanguageServiceFindsReferencesOnFileWithReferencesFileB() + { + FindReferencesResult refsResult = + this.GetReferences( + FindsReferencesOnFunctionMultiFileDotSourceFileB.SourceDetails); + + Assert.Equal(4, refsResult.FoundReferences.Count()); + } + + [Fact] + public void LanguageServiceFindsReferencesOnFileWithReferencesFileC() + { + FindReferencesResult refsResult = + this.GetReferences( + FindsReferencesOnFunctionMultiFileDotSourceFileC.SourceDetails); + Assert.Equal(4, refsResult.FoundReferences.Count()); + } + private ScriptFile GetScriptFile(ScriptRegion scriptRegion) { const string baseSharedScriptPath = From 2e5bc40c1612cd65af6811fb8af2eaa415e2ff44 Mon Sep 17 00:00:00 2001 From: Kayla Davis Date: Tue, 11 Aug 2015 15:28:06 -0700 Subject: [PATCH 17/18] Fix dot sourcing a non existant file bug --- .../Session/Workspace.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/PowerShellEditorServices/Session/Workspace.cs b/src/PowerShellEditorServices/Session/Workspace.cs index 4662fd9e6..9a3b164ae 100644 --- a/src/PowerShellEditorServices/Session/Workspace.cs +++ b/src/PowerShellEditorServices/Session/Workspace.cs @@ -123,15 +123,17 @@ private void RecursivelyFindReferences( scriptFile.FilePath, referencedFileName); - // Get the referenced file if it's not already in referencedScriptFiles - referencedFile = this.GetFile(resolvedScriptPath); - if (!referencedScriptFiles.ContainsKey(resolvedScriptPath)) + // make sure file exists before trying to get the file + if (File.Exists(resolvedScriptPath)) { - referencedScriptFiles.Add(resolvedScriptPath, referencedFile); - RecursivelyFindReferences(referencedFile, referencedScriptFiles); + // Get the referenced file if it's not already in referencedScriptFiles + referencedFile = this.GetFile(resolvedScriptPath); + if (!referencedScriptFiles.ContainsKey(resolvedScriptPath)) + { + referencedScriptFiles.Add(resolvedScriptPath, referencedFile); + RecursivelyFindReferences(referencedFile, referencedScriptFiles); + } } - - } } From 9de1f3ab61b1254f03af184ed8e418d419038c05 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Wed, 26 Aug 2015 15:36:49 -0700 Subject: [PATCH 18/18] Add missing API documentation --- src/PowerShellEditorServices.Host/Program.cs | 2 ++ .../Language/LanguageService.cs | 10 ++++++---- src/PowerShellEditorServices/Session/Workspace.cs | 4 ++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices.Host/Program.cs b/src/PowerShellEditorServices.Host/Program.cs index c36cd80cd..d6ac8f619 100644 --- a/src/PowerShellEditorServices.Host/Program.cs +++ b/src/PowerShellEditorServices.Host/Program.cs @@ -16,6 +16,7 @@ class Program [STAThread] static void Main(string[] args) { +#if DEBUG // In the future, a more robust argument parser will be added here bool waitForDebugger = args.Any( @@ -36,6 +37,7 @@ static void Main(string[] args) waitCountdown--; } } +#endif // TODO: Select host, console host, and transport based on command line arguments diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index df64d83d0..775273bd4 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -207,11 +207,13 @@ public FindReferencesResult FindReferencesOfSymbol( } /// - /// Finds the definition of a symbol in the script + /// Finds the definition of a symbol in the script file or any of the + /// files that it references. /// - /// The symbol to find a definition for - /// An array of scriptFiles too search for the definition in - /// GetDefinitionResult + /// The initial script file to be searched for the symbol's definition. + /// The symbol for which a definition will be found. + /// The Workspace to which the ScriptFile belongs. + /// The resulting GetDefinitionResult for the symbol's definition. public GetDefinitionResult GetDefinitionOfSymbol( ScriptFile sourceFile, SymbolReference foundSymbol, diff --git a/src/PowerShellEditorServices/Session/Workspace.cs b/src/PowerShellEditorServices/Session/Workspace.cs index 9a3b164ae..f63eafdca 100644 --- a/src/PowerShellEditorServices/Session/Workspace.cs +++ b/src/PowerShellEditorServices/Session/Workspace.cs @@ -11,6 +11,10 @@ namespace Microsoft.PowerShell.EditorServices.Session { + /// + /// Manages a "workspace" of script files that are open for a particular + /// editing session. Also helps to navigate references between ScriptFiles. + /// public class Workspace { #region Private Fields