From 20e749b4ddb05f0a0c373725587b9d77929b75ab Mon Sep 17 00:00:00 2001 From: David Wilson Date: Sun, 10 Jan 2016 08:41:24 -0800 Subject: [PATCH 1/2] Fix #99: 'using module' relative path resolution This change fixes an issue with relative module path resolution with the 'using module' statement in more recent versions of PowerShell v5. A new method overload was added to the PowerShell API's Parser.ParseInput method which allows the script's path to be specified. By using this parameter, the PowerShell parser resolves relative module paths correctly. This change also required a change to how PowerShell API usage is verified for different PowerShell versions. Since the PowerShell v5 reference assemblies don't (yet) contain Parser.ParseInput overload, we now have to make a distinction between the PowerShell v5 which is specified by the reference assemblies and the one that is currently shipped in the latest official Windows 10 release (10586). Once updated reference assemblies have been shipped for PowerShell v5 we may revisit this approach. --- .../PowerShellEditorServices.csproj | 13 +++-- .../Session/EditorSession.cs | 6 +-- .../Workspace/ScriptFile.cs | 49 ++++++++++++++++--- .../Workspace/Workspace.cs | 30 +++++++++++- .../LanguageServerTests.cs | 15 ++++++ .../PowerShellEditorServices.Test.Host.csproj | 6 +++ .../TestFiles/ChildModule/ChildModule.psm1 | 2 + .../TestFiles/Module.psm1 | 1 + .../Debugging/DebugServiceTests.cs | 13 +++-- .../Language/LanguageServiceTests.cs | 3 +- .../Language/PowerShellVersionTests.cs | 12 ++--- .../Session/ScriptFileTests.cs | 18 ++++++- 12 files changed, 140 insertions(+), 28 deletions(-) create mode 100644 test/PowerShellEditorServices.Test.Host/TestFiles/ChildModule/ChildModule.psm1 create mode 100644 test/PowerShellEditorServices.Test.Host/TestFiles/Module.psm1 diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 6fa634a43..e813124e4 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -13,9 +13,16 @@ 512 ..\..\ true - PowerShellv3 - PowerShellv4 - PowerShellv5 + $(DefineConstants);PowerShellv3 + $(DefineConstants);PowerShellv4 + + + + + + + $(DefineConstants);PowerShellv5r1;PowerShellv5 + $(DefineConstants);PowerShellv5r2;PowerShellv5 true diff --git a/src/PowerShellEditorServices/Session/EditorSession.cs b/src/PowerShellEditorServices/Session/EditorSession.cs index 463bac882..7ab2f3b59 100644 --- a/src/PowerShellEditorServices/Session/EditorSession.cs +++ b/src/PowerShellEditorServices/Session/EditorSession.cs @@ -60,9 +60,6 @@ public class EditorSession /// public void StartSession() { - // Create a workspace to contain open files - this.Workspace = new Workspace(); - // Initialize all services this.PowerShellContext = new PowerShellContext(); this.LanguageService = new LanguageService(this.PowerShellContext); @@ -81,6 +78,9 @@ public void StartSession() LogLevel.Warning, "Script Analyzer binaries not found, AnalysisService will be disabled."); } + + // Create a workspace to contain open files + this.Workspace = new Workspace(this.PowerShellContext.PowerShellVersion); } #endregion diff --git a/src/PowerShellEditorServices/Workspace/ScriptFile.cs b/src/PowerShellEditorServices/Workspace/ScriptFile.cs index 52d48c1fd..730a7b3f4 100644 --- a/src/PowerShellEditorServices/Workspace/ScriptFile.cs +++ b/src/PowerShellEditorServices/Workspace/ScriptFile.cs @@ -21,6 +21,7 @@ public class ScriptFile #region Private Fields private Token[] scriptTokens; + private Version powerShellVersion; #endregion @@ -126,12 +127,18 @@ public string[] ReferencedFiles /// The path at which the script file resides. /// The path which the client uses to identify the file. /// The TextReader to use for reading the file's contents. - public ScriptFile(string filePath, string clientFilePath, TextReader textReader) + /// The version of PowerShell for which the script is being parsed. + public ScriptFile( + string filePath, + string clientFilePath, + TextReader textReader, + Version powerShellVersion) { this.FilePath = filePath; this.ClientFilePath = clientFilePath; this.IsAnalysisEnabled = true; this.IsInMemory = Workspace.IsPathInMemory(filePath); + this.powerShellVersion = powerShellVersion; this.SetFileContents(textReader.ReadToEnd()); } @@ -142,11 +149,17 @@ public ScriptFile(string filePath, string clientFilePath, TextReader textReader) /// The path at which the script file resides. /// The path which the client uses to identify the file. /// The initial contents of the script file. - public ScriptFile(string filePath, string clientFilePath, string initialBuffer) + /// The version of PowerShell for which the script is being parsed. + public ScriptFile( + string filePath, + string clientFilePath, + string initialBuffer, + Version powerShellVersion) { this.FilePath = filePath; this.ClientFilePath = clientFilePath; this.IsAnalysisEnabled = true; + this.powerShellVersion = powerShellVersion; this.SetFileContents(initialBuffer); } @@ -358,15 +371,39 @@ private void ParseFileContents() try { +#if PowerShellv5r2 + // This overload appeared with Windows 10 Update 1 + if (this.powerShellVersion.Major >= 5 && + this.powerShellVersion.Build >= 10586) + { + // Include the file path so that module relative + // paths are evaluated correctly + this.ScriptAst = + Parser.ParseInput( + this.Contents, + this.FilePath, + out this.scriptTokens, + out parseErrors); + } + else + { + this.ScriptAst = + Parser.ParseInput( + this.Contents, + out this.scriptTokens, + out parseErrors); + } +#else this.ScriptAst = Parser.ParseInput( - this.Contents, - out this.scriptTokens, + this.Contents, + out this.scriptTokens, out parseErrors); +#endif } catch (RuntimeException ex) { - var parseError = + var parseError = new ParseError( null, ex.ErrorRecord.FullyQualifiedErrorId, @@ -388,6 +425,6 @@ private void ParseFileContents() AstOperations.FindDotSourcedIncludes(this.ScriptAst); } - #endregion +#endregion } } diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index e59406bb1..7bece0f93 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -20,6 +20,7 @@ public class Workspace { #region Private Fields + private Version powerShellVersion; private Dictionary workspaceFiles = new Dictionary(); #endregion @@ -33,6 +34,19 @@ public class Workspace #endregion + #region Constructors + + /// + /// Creates a new instance of the Workspace class. + /// + /// The version of PowerShell for which scripts will be parsed. + public Workspace(Version powerShellVersion) + { + this.powerShellVersion = powerShellVersion; + } + + #endregion + #region Public Methods /// @@ -63,7 +77,13 @@ public ScriptFile GetFile(string filePath) using (StreamReader streamReader = new StreamReader(resolvedFilePath, Encoding.UTF8)) { - scriptFile = new ScriptFile(resolvedFilePath, filePath, streamReader); + scriptFile = + new ScriptFile( + resolvedFilePath, + filePath, + streamReader, + this.powerShellVersion); + this.workspaceFiles.Add(keyName, scriptFile); } @@ -92,7 +112,13 @@ public ScriptFile GetFileBuffer(string filePath, string initialBuffer) ScriptFile scriptFile = null; if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile)) { - scriptFile = new ScriptFile(resolvedFilePath, filePath, initialBuffer); + scriptFile = + new ScriptFile( + resolvedFilePath, + filePath, + initialBuffer, + this.powerShellVersion); + this.workspaceFiles.Add(keyName, scriptFile); Logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath); diff --git a/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs b/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs index 325f03677..a73e32ab5 100644 --- a/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs +++ b/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs @@ -81,6 +81,21 @@ await this.WaitForEvent( Assert.Contains("unapproved", diagnostics.Diagnostics[0].Message); } + [Fact] + public async Task ServiceReturnsNoErrorsForUsingRelativeModulePaths() + { + // Send the 'didOpen' event + await this.SendOpenFileEvent("TestFiles\\Module.psm1", false); + + // Wait for the diagnostic event + PublishDiagnosticsNotification diagnostics = + await this.WaitForEvent( + PublishDiagnosticsNotification.Type); + + // Was there a syntax error? + Assert.Equal(0, diagnostics.Diagnostics.Length); + } + [Fact] public async Task ServiceCompletesFunctionName() { diff --git a/test/PowerShellEditorServices.Test.Host/PowerShellEditorServices.Test.Host.csproj b/test/PowerShellEditorServices.Test.Host/PowerShellEditorServices.Test.Host.csproj index 6829c0aa3..a11df7a8a 100644 --- a/test/PowerShellEditorServices.Test.Host/PowerShellEditorServices.Test.Host.csproj +++ b/test/PowerShellEditorServices.Test.Host/PowerShellEditorServices.Test.Host.csproj @@ -74,12 +74,18 @@ + + PreserveNewest + PreserveNewest PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/test/PowerShellEditorServices.Test.Host/TestFiles/ChildModule/ChildModule.psm1 b/test/PowerShellEditorServices.Test.Host/TestFiles/ChildModule/ChildModule.psm1 new file mode 100644 index 000000000..b13e23032 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Host/TestFiles/ChildModule/ChildModule.psm1 @@ -0,0 +1,2 @@ +function Get-Stuff { +} \ No newline at end of file diff --git a/test/PowerShellEditorServices.Test.Host/TestFiles/Module.psm1 b/test/PowerShellEditorServices.Test.Host/TestFiles/Module.psm1 new file mode 100644 index 000000000..5fb8eeb7c --- /dev/null +++ b/test/PowerShellEditorServices.Test.Host/TestFiles/Module.psm1 @@ -0,0 +1 @@ +using module ".\ChildModule" \ No newline at end of file diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 1c9f66f5d..e9a220a06 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -30,7 +30,10 @@ public class DebugServiceTests : IDisposable public DebugServiceTests() { - this.workspace = new Workspace(); + this.powerShellContext = new PowerShellContext(); + this.powerShellContext.SessionStateChanged += powerShellContext_SessionStateChanged; + + this.workspace = new Workspace(this.powerShellContext.PowerShellVersion); // Load the test debug file this.debugScriptFile = @@ -41,13 +44,15 @@ public DebugServiceTests() this.workspace.GetFile( @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1"); - this.powerShellContext = new PowerShellContext(); - this.powerShellContext.SessionStateChanged += powerShellContext_SessionStateChanged; - this.debugService = new DebugService(this.powerShellContext); this.debugService.DebuggerStopped += debugService_DebuggerStopped; this.debugService.BreakpointUpdated += debugService_BreakpointUpdated; this.runnerContext = SynchronizationContext.Current; + + // Load the test debug file + this.debugScriptFile = + this.workspace.GetFile( + @"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\DebugTest.ps1"); } async void powerShellContext_SessionStateChanged(object sender, SessionStateChangedEventArgs e) diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index 24fbd7aa8..1d8374581 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -32,9 +32,8 @@ public class LanguageServiceTests : IDisposable public LanguageServiceTests() { - this.workspace = new Workspace(); - this.powerShellContext = new PowerShellContext(); + this.workspace = new Workspace(this.powerShellContext.PowerShellVersion); this.languageService = new LanguageService(this.powerShellContext); } diff --git a/test/PowerShellEditorServices.Test/Language/PowerShellVersionTests.cs b/test/PowerShellEditorServices.Test/Language/PowerShellVersionTests.cs index ec334f24b..e4d1dd995 100644 --- a/test/PowerShellEditorServices.Test/Language/PowerShellVersionTests.cs +++ b/test/PowerShellEditorServices.Test/Language/PowerShellVersionTests.cs @@ -16,10 +16,10 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language public class PowerShellVersionTests { [Theory] - [InlineData("3")] - [InlineData("4")] - [InlineData("5")] - public void CompilesWithPowerShellVersion(string version) + [InlineData("3", "4")] + [InlineData("4", "4")] + [InlineData("5", "5r1")] + public void CompilesWithPowerShellVersion(string version, string versionSuffix) { var assemblyPath = Path.GetFullPath( @@ -40,7 +40,7 @@ public void CompilesWithPowerShellVersion(string version) try { - Compile(projectVersion, version); + Compile(projectVersion, version, versionSuffix); } finally { @@ -48,7 +48,7 @@ public void CompilesWithPowerShellVersion(string version) } } - private void Compile(string project, string version) + private void Compile(string project, string version, string versionSuffix) { string msbuild; using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0")) diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index f28280273..2f488cfe1 100644 --- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -4,6 +4,7 @@ // using Microsoft.PowerShell.EditorServices; +using System; using System.IO; using Xunit; @@ -11,6 +12,8 @@ namespace PSLanguageService.Test { public class FileChangeTests { + private static readonly Version PowerShellVersion = new Version("5.0"); + [Fact] public void CanApplySingleLineInsert() { @@ -135,7 +138,13 @@ public void FindsDotSourcedFiles() using (StringReader stringReader = new StringReader(exampleScriptContents)) { - ScriptFile scriptFile = new ScriptFile("DotSourceTestFile.ps1", "DotSourceTestFile.ps1", stringReader); + ScriptFile scriptFile = + new ScriptFile( + "DotSourceTestFile.ps1", + "DotSourceTestFile.ps1", + stringReader, + PowerShellVersion); + Assert.Equal(3, scriptFile.ReferencedFiles.Length); System.Console.Write("a" + scriptFile.ReferencedFiles[0]); Assert.Equal(@".\athing.ps1", scriptFile.ReferencedFiles[0]); @@ -150,7 +159,12 @@ private void AssertFileChange( using (StringReader stringReader = new StringReader(initialString)) { // Create an in-memory file from the StringReader - ScriptFile fileToChange = new ScriptFile("TestFile.ps1", "TestFile.ps1", stringReader); + ScriptFile fileToChange = + new ScriptFile( + "TestFile.ps1", + "TestFile.ps1", + stringReader, + PowerShellVersion); // Apply the FileChange and assert the resulting contents fileToChange.ApplyChange(fileChange); From 8d2f9840e5e9bcd7f78d158e88af25d0e0c26abe Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 8 Mar 2016 11:50:46 -0800 Subject: [PATCH 2/2] Update appveyor.yml to use WMF 5.0 RTM image This change updates appveyor.yml to use the new AppVeyor image that includes WMF 5.0 RTM. This is needed for a small API change that intruduced after WMF 5.0 Production Preview. --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 654f78f97..bef29824a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: '$(core_version).{build}' -os: Unstable +os: WMF 5 configuration: Release clone_depth: 10