Skip to content

Commit 20e749b

Browse files
committed
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.
1 parent 5e88358 commit 20e749b

File tree

12 files changed

+140
-28
lines changed

12 files changed

+140
-28
lines changed

src/PowerShellEditorServices/PowerShellEditorServices.csproj

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,16 @@
1313
<FileAlignment>512</FileAlignment>
1414
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
1515
<RestorePackages>true</RestorePackages>
16-
<DefineConstants Condition=" '$(PowerShellVersion)' == 'v3'">PowerShellv3</DefineConstants>
17-
<DefineConstants Condition=" '$(PowerShellVersion)' == 'v4'">PowerShellv4</DefineConstants>
18-
<DefineConstants Condition=" '$(PowerShellVersion)' == '' Or '$(PowerShellVersion)' == 'v5'">PowerShellv5</DefineConstants>
16+
<DefineConstants Condition="'$(PowerShellVersion)' == 'v3'">$(DefineConstants);PowerShellv3</DefineConstants>
17+
<DefineConstants Condition="'$(PowerShellVersion)' == 'v4'">$(DefineConstants);PowerShellv4</DefineConstants>
18+
<!-- NOTE: -->
19+
<!-- For PowerShell v5 there are some differences between the APIs released with -->
20+
<!-- Windows 10 RTM and Windows 10 Update 1, thus we need to distinguish between -->
21+
<!-- the two in our code. v5r1 indicates Win 10 RTM (10240) and v5r2 indicates -->
22+
<!-- Win10 Update 1 (10586). The PowerShellv5 constant will be defined for both -->
23+
<!-- of these versions for anything that should work against both APIs. -->
24+
<DefineConstants Condition="'$(PowerShellVersion)' == 'v5r1'">$(DefineConstants);PowerShellv5r1;PowerShellv5</DefineConstants>
25+
<DefineConstants Condition="'$(PowerShellVersion)' == 'v5r2' Or '$(PowerShellVersion)' == ''">$(DefineConstants);PowerShellv5r2;PowerShellv5</DefineConstants>
1926
</PropertyGroup>
2027
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
2128
<DebugSymbols>true</DebugSymbols>

src/PowerShellEditorServices/Session/EditorSession.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,6 @@ public class EditorSession
6060
/// </summary>
6161
public void StartSession()
6262
{
63-
// Create a workspace to contain open files
64-
this.Workspace = new Workspace();
65-
6663
// Initialize all services
6764
this.PowerShellContext = new PowerShellContext();
6865
this.LanguageService = new LanguageService(this.PowerShellContext);
@@ -81,6 +78,9 @@ public void StartSession()
8178
LogLevel.Warning,
8279
"Script Analyzer binaries not found, AnalysisService will be disabled.");
8380
}
81+
82+
// Create a workspace to contain open files
83+
this.Workspace = new Workspace(this.PowerShellContext.PowerShellVersion);
8484
}
8585

8686
#endregion

src/PowerShellEditorServices/Workspace/ScriptFile.cs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class ScriptFile
2121
#region Private Fields
2222

2323
private Token[] scriptTokens;
24+
private Version powerShellVersion;
2425

2526
#endregion
2627

@@ -126,12 +127,18 @@ public string[] ReferencedFiles
126127
/// <param name="filePath">The path at which the script file resides.</param>
127128
/// <param name="clientFilePath">The path which the client uses to identify the file.</param>
128129
/// <param name="textReader">The TextReader to use for reading the file's contents.</param>
129-
public ScriptFile(string filePath, string clientFilePath, TextReader textReader)
130+
/// <param name="powerShellVersion">The version of PowerShell for which the script is being parsed.</param>
131+
public ScriptFile(
132+
string filePath,
133+
string clientFilePath,
134+
TextReader textReader,
135+
Version powerShellVersion)
130136
{
131137
this.FilePath = filePath;
132138
this.ClientFilePath = clientFilePath;
133139
this.IsAnalysisEnabled = true;
134140
this.IsInMemory = Workspace.IsPathInMemory(filePath);
141+
this.powerShellVersion = powerShellVersion;
135142

136143
this.SetFileContents(textReader.ReadToEnd());
137144
}
@@ -142,11 +149,17 @@ public ScriptFile(string filePath, string clientFilePath, TextReader textReader)
142149
/// <param name="filePath">The path at which the script file resides.</param>
143150
/// <param name="clientFilePath">The path which the client uses to identify the file.</param>
144151
/// <param name="initialBuffer">The initial contents of the script file.</param>
145-
public ScriptFile(string filePath, string clientFilePath, string initialBuffer)
152+
/// <param name="powerShellVersion">The version of PowerShell for which the script is being parsed.</param>
153+
public ScriptFile(
154+
string filePath,
155+
string clientFilePath,
156+
string initialBuffer,
157+
Version powerShellVersion)
146158
{
147159
this.FilePath = filePath;
148160
this.ClientFilePath = clientFilePath;
149161
this.IsAnalysisEnabled = true;
162+
this.powerShellVersion = powerShellVersion;
150163

151164
this.SetFileContents(initialBuffer);
152165
}
@@ -358,15 +371,39 @@ private void ParseFileContents()
358371

359372
try
360373
{
374+
#if PowerShellv5r2
375+
// This overload appeared with Windows 10 Update 1
376+
if (this.powerShellVersion.Major >= 5 &&
377+
this.powerShellVersion.Build >= 10586)
378+
{
379+
// Include the file path so that module relative
380+
// paths are evaluated correctly
381+
this.ScriptAst =
382+
Parser.ParseInput(
383+
this.Contents,
384+
this.FilePath,
385+
out this.scriptTokens,
386+
out parseErrors);
387+
}
388+
else
389+
{
390+
this.ScriptAst =
391+
Parser.ParseInput(
392+
this.Contents,
393+
out this.scriptTokens,
394+
out parseErrors);
395+
}
396+
#else
361397
this.ScriptAst =
362398
Parser.ParseInput(
363-
this.Contents,
364-
out this.scriptTokens,
399+
this.Contents,
400+
out this.scriptTokens,
365401
out parseErrors);
402+
#endif
366403
}
367404
catch (RuntimeException ex)
368405
{
369-
var parseError =
406+
var parseError =
370407
new ParseError(
371408
null,
372409
ex.ErrorRecord.FullyQualifiedErrorId,
@@ -388,6 +425,6 @@ private void ParseFileContents()
388425
AstOperations.FindDotSourcedIncludes(this.ScriptAst);
389426
}
390427

391-
#endregion
428+
#endregion
392429
}
393430
}

src/PowerShellEditorServices/Workspace/Workspace.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class Workspace
2020
{
2121
#region Private Fields
2222

23+
private Version powerShellVersion;
2324
private Dictionary<string, ScriptFile> workspaceFiles = new Dictionary<string, ScriptFile>();
2425

2526
#endregion
@@ -33,6 +34,19 @@ public class Workspace
3334

3435
#endregion
3536

37+
#region Constructors
38+
39+
/// <summary>
40+
/// Creates a new instance of the Workspace class.
41+
/// </summary>
42+
/// <param name="powerShellVersion">The version of PowerShell for which scripts will be parsed.</param>
43+
public Workspace(Version powerShellVersion)
44+
{
45+
this.powerShellVersion = powerShellVersion;
46+
}
47+
48+
#endregion
49+
3650
#region Public Methods
3751

3852
/// <summary>
@@ -63,7 +77,13 @@ public ScriptFile GetFile(string filePath)
6377

6478
using (StreamReader streamReader = new StreamReader(resolvedFilePath, Encoding.UTF8))
6579
{
66-
scriptFile = new ScriptFile(resolvedFilePath, filePath, streamReader);
80+
scriptFile =
81+
new ScriptFile(
82+
resolvedFilePath,
83+
filePath,
84+
streamReader,
85+
this.powerShellVersion);
86+
6787
this.workspaceFiles.Add(keyName, scriptFile);
6888
}
6989

@@ -92,7 +112,13 @@ public ScriptFile GetFileBuffer(string filePath, string initialBuffer)
92112
ScriptFile scriptFile = null;
93113
if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile))
94114
{
95-
scriptFile = new ScriptFile(resolvedFilePath, filePath, initialBuffer);
115+
scriptFile =
116+
new ScriptFile(
117+
resolvedFilePath,
118+
filePath,
119+
initialBuffer,
120+
this.powerShellVersion);
121+
96122
this.workspaceFiles.Add(keyName, scriptFile);
97123

98124
Logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath);

test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,21 @@ await this.WaitForEvent(
8181
Assert.Contains("unapproved", diagnostics.Diagnostics[0].Message);
8282
}
8383

84+
[Fact]
85+
public async Task ServiceReturnsNoErrorsForUsingRelativeModulePaths()
86+
{
87+
// Send the 'didOpen' event
88+
await this.SendOpenFileEvent("TestFiles\\Module.psm1", false);
89+
90+
// Wait for the diagnostic event
91+
PublishDiagnosticsNotification diagnostics =
92+
await this.WaitForEvent(
93+
PublishDiagnosticsNotification.Type);
94+
95+
// Was there a syntax error?
96+
Assert.Equal(0, diagnostics.Diagnostics.Length);
97+
}
98+
8499
[Fact]
85100
public async Task ServiceCompletesFunctionName()
86101
{

test/PowerShellEditorServices.Test.Host/PowerShellEditorServices.Test.Host.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,18 @@
7474
<ItemGroup>
7575
<None Include="App.config" />
7676
<None Include="packages.config" />
77+
<None Include="TestFiles\ChildModule\ChildModule.psm1">
78+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
79+
</None>
7780
<None Include="TestFiles\CompleteFunctionName.ps1">
7881
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
7982
</None>
8083
<None Include="TestFiles\FindReferences.ps1">
8184
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
8285
</None>
86+
<None Include="TestFiles\Module.psm1">
87+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
88+
</None>
8389
<None Include="TestFiles\MultiLineReplace.ps1">
8490
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
8591
</None>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
function Get-Stuff {
2+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
using module ".\ChildModule"

test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ public class DebugServiceTests : IDisposable
3030

3131
public DebugServiceTests()
3232
{
33-
this.workspace = new Workspace();
33+
this.powerShellContext = new PowerShellContext();
34+
this.powerShellContext.SessionStateChanged += powerShellContext_SessionStateChanged;
35+
36+
this.workspace = new Workspace(this.powerShellContext.PowerShellVersion);
3437

3538
// Load the test debug file
3639
this.debugScriptFile =
@@ -41,13 +44,15 @@ public DebugServiceTests()
4144
this.workspace.GetFile(
4245
@"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1");
4346

44-
this.powerShellContext = new PowerShellContext();
45-
this.powerShellContext.SessionStateChanged += powerShellContext_SessionStateChanged;
46-
4747
this.debugService = new DebugService(this.powerShellContext);
4848
this.debugService.DebuggerStopped += debugService_DebuggerStopped;
4949
this.debugService.BreakpointUpdated += debugService_BreakpointUpdated;
5050
this.runnerContext = SynchronizationContext.Current;
51+
52+
// Load the test debug file
53+
this.debugScriptFile =
54+
this.workspace.GetFile(
55+
@"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\DebugTest.ps1");
5156
}
5257

5358
async void powerShellContext_SessionStateChanged(object sender, SessionStateChangedEventArgs e)

test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ public class LanguageServiceTests : IDisposable
3232

3333
public LanguageServiceTests()
3434
{
35-
this.workspace = new Workspace();
36-
3735
this.powerShellContext = new PowerShellContext();
36+
this.workspace = new Workspace(this.powerShellContext.PowerShellVersion);
3837
this.languageService = new LanguageService(this.powerShellContext);
3938
}
4039

test/PowerShellEditorServices.Test/Language/PowerShellVersionTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language
1616
public class PowerShellVersionTests
1717
{
1818
[Theory]
19-
[InlineData("3")]
20-
[InlineData("4")]
21-
[InlineData("5")]
22-
public void CompilesWithPowerShellVersion(string version)
19+
[InlineData("3", "4")]
20+
[InlineData("4", "4")]
21+
[InlineData("5", "5r1")]
22+
public void CompilesWithPowerShellVersion(string version, string versionSuffix)
2323
{
2424
var assemblyPath =
2525
Path.GetFullPath(
@@ -40,15 +40,15 @@ public void CompilesWithPowerShellVersion(string version)
4040

4141
try
4242
{
43-
Compile(projectVersion, version);
43+
Compile(projectVersion, version, versionSuffix);
4444
}
4545
finally
4646
{
4747
File.Delete(projectVersion);
4848
}
4949
}
5050

51-
private void Compile(string project, string version)
51+
private void Compile(string project, string version, string versionSuffix)
5252
{
5353
string msbuild;
5454
using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0"))

test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
//
55

66
using Microsoft.PowerShell.EditorServices;
7+
using System;
78
using System.IO;
89
using Xunit;
910

1011
namespace PSLanguageService.Test
1112
{
1213
public class FileChangeTests
1314
{
15+
private static readonly Version PowerShellVersion = new Version("5.0");
16+
1417
[Fact]
1518
public void CanApplySingleLineInsert()
1619
{
@@ -135,7 +138,13 @@ public void FindsDotSourcedFiles()
135138

136139
using (StringReader stringReader = new StringReader(exampleScriptContents))
137140
{
138-
ScriptFile scriptFile = new ScriptFile("DotSourceTestFile.ps1", "DotSourceTestFile.ps1", stringReader);
141+
ScriptFile scriptFile =
142+
new ScriptFile(
143+
"DotSourceTestFile.ps1",
144+
"DotSourceTestFile.ps1",
145+
stringReader,
146+
PowerShellVersion);
147+
139148
Assert.Equal(3, scriptFile.ReferencedFiles.Length);
140149
System.Console.Write("a" + scriptFile.ReferencedFiles[0]);
141150
Assert.Equal(@".\athing.ps1", scriptFile.ReferencedFiles[0]);
@@ -150,7 +159,12 @@ private void AssertFileChange(
150159
using (StringReader stringReader = new StringReader(initialString))
151160
{
152161
// Create an in-memory file from the StringReader
153-
ScriptFile fileToChange = new ScriptFile("TestFile.ps1", "TestFile.ps1", stringReader);
162+
ScriptFile fileToChange =
163+
new ScriptFile(
164+
"TestFile.ps1",
165+
"TestFile.ps1",
166+
stringReader,
167+
PowerShellVersion);
154168

155169
// Apply the FileChange and assert the resulting contents
156170
fileToChange.ApplyChange(fileChange);

0 commit comments

Comments
 (0)