Skip to content

Commit 7507fec

Browse files
committed
Support $PSScriptRoot in dot-sourced files
1 parent c774e7c commit 7507fec

File tree

7 files changed

+68
-18
lines changed

7 files changed

+68
-18
lines changed

src/PowerShellEditorServices/Language/AstOperations.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -320,12 +320,12 @@ static private bool IsPowerShellDataFileAstNode(dynamic node, Type[] levelAstMap
320320
/// <summary>
321321
/// Finds all files dot sourced in a script
322322
/// </summary>
323-
/// <param name="scriptAst">The abstract syntax tree of the given script</param>
323+
/// <param name="scriptFile">The script file to use to find dot sourced files.</param>
324324
/// <returns></returns>
325-
static public string[] FindDotSourcedIncludes(Ast scriptAst)
325+
static public string[] FindDotSourcedIncludes(ScriptFile scriptFile)
326326
{
327-
FindDotSourcedVisitor dotSourcedVisitor = new FindDotSourcedVisitor();
328-
scriptAst.Visit(dotSourcedVisitor);
327+
FindDotSourcedVisitor dotSourcedVisitor = new FindDotSourcedVisitor(scriptFile.FilePath);
328+
scriptFile.ScriptAst.Visit(dotSourcedVisitor);
329329

330330
return dotSourcedVisitor.DotSourcedFiles.ToArray();
331331
}

src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs

+42-7
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
44
//
55

6+
using System;
67
using System.Collections.Generic;
8+
using System.IO;
79
using System.Management.Automation.Language;
10+
using System.Text.RegularExpressions;
811
using Microsoft.PowerShell.EditorServices.Utility;
912

1013
namespace Microsoft.PowerShell.EditorServices
@@ -14,14 +17,17 @@ namespace Microsoft.PowerShell.EditorServices
1417
/// </summary>
1518
internal class FindDotSourcedVisitor : AstVisitor
1619
{
20+
private readonly string _scriptDirectory;
21+
1722
/// <summary>
1823
/// A hash set of the dot sourced files (because we don't want duplicates)
1924
/// </summary>
2025
public HashSet<string> DotSourcedFiles { get; private set; }
2126

22-
public FindDotSourcedVisitor()
27+
public FindDotSourcedVisitor(string scriptPath)
2328
{
24-
this.DotSourcedFiles = new HashSet<string>();
29+
DotSourcedFiles = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
30+
_scriptDirectory = Path.GetDirectoryName(scriptPath);
2531
}
2632

2733
/// <summary>
@@ -33,15 +39,44 @@ public FindDotSourcedVisitor()
3339
/// or a decision to continue if it wasn't found</returns>
3440
public override AstVisitAction VisitCommand(CommandAst commandAst)
3541
{
36-
if (commandAst.InvocationOperator.Equals(TokenKind.Dot) &&
37-
commandAst.CommandElements[0] is StringConstantExpressionAst)
42+
CommandElementAst commandElementAst = commandAst.CommandElements[0];
43+
if (commandAst.InvocationOperator.Equals(TokenKind.Dot))
3844
{
39-
// Strip any quote characters off of the string
40-
string fileName = PathUtils.NormalizePathSeparators(commandAst.CommandElements[0].Extent.Text.Trim('\'', '"'));
41-
DotSourcedFiles.Add(fileName);
45+
if (commandElementAst is StringConstantExpressionAst stringConstantExpressionAst)
46+
{
47+
// Strip any quote characters off of the string
48+
DotSourcedFiles.Add(PathUtils.NormalizePathSeparators(stringConstantExpressionAst.Value));
49+
}
50+
else if (commandElementAst is ExpandableStringExpressionAst expandableStringExpressionAst)
51+
{
52+
var path = GetPathFromExpandableStringExpression(expandableStringExpressionAst);
53+
if (path != null)
54+
{
55+
DotSourcedFiles.Add(PathUtils.NormalizePathSeparators(path));
56+
}
57+
}
4258
}
4359

4460
return base.VisitCommand(commandAst);
4561
}
62+
63+
private string GetPathFromExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst)
64+
{
65+
var path = expandableStringExpressionAst.Value;
66+
foreach (var nestedExpression in expandableStringExpressionAst.NestedExpressions)
67+
{
68+
if (nestedExpression is VariableExpressionAst variableExpressionAst
69+
&& variableExpressionAst.VariablePath.UserPath.Equals("PSScriptRoot", StringComparison.CurrentCultureIgnoreCase))
70+
{
71+
path = path.Replace(variableExpressionAst.ToString(), _scriptDirectory);
72+
}
73+
else
74+
{
75+
return null; // We're going to get an invalid path anyway.
76+
}
77+
}
78+
79+
return path;
80+
}
4681
}
4782
}

src/PowerShellEditorServices/Language/LanguageService.cs

+17-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using System.Management.Automation.Language;
1515
using System.Runtime.InteropServices;
1616
using System.Security;
17+
using System.Text.RegularExpressions;
1718
using System.Threading;
1819
using System.Threading.Tasks;
1920

@@ -426,11 +427,10 @@ public async Task<GetDefinitionResult> GetDefinitionOfSymbol(
426427
if (foundSymbol.SymbolType == SymbolType.Function)
427428
{
428429
// Dot-sourcing is parsed as a "Function" Symbol.
429-
string trimmedName = PathUtils.NormalizePathSeparators(foundSymbol.SymbolName.Trim('\'', '"'));
430-
string dotSourcedPath = workspace.ResolveRelativeScriptPath(Path.GetDirectoryName(scriptFile.FilePath), trimmedName);
430+
string dotSourcedPath = GetDotSourcedPath(foundSymbol, workspace, scriptFile);
431431
if (scriptFile.FilePath == dotSourcedPath)
432432
{
433-
foundDefinition = new SymbolReference(SymbolType.Function, trimmedName, scriptFile.ScriptAst.Extent, scriptFile.FilePath);
433+
foundDefinition = new SymbolReference(SymbolType.Function, foundSymbol.SymbolName, scriptFile.ScriptAst.Extent, scriptFile.FilePath);
434434
break;
435435
}
436436
}
@@ -487,6 +487,20 @@ await CommandHelpers.GetCommandInfo(
487487
null;
488488
}
489489

490+
/// <summary>
491+
/// Gets a path from a dot-source symbol.
492+
/// </summary>
493+
/// <param name="symbol">The symbol representing the dot-source expression.</param>
494+
/// <param name="workspace">The current workspace</param>
495+
/// <param name="scriptFile">The script file containing the symbol</param>
496+
/// <returns></returns>
497+
private static string GetDotSourcedPath(SymbolReference symbol, Workspace workspace, ScriptFile scriptFile)
498+
{
499+
string cleanedUpSymbol = PathUtils.NormalizePathSeparators(symbol.SymbolName.Trim('\'', '"'));
500+
return workspace.ResolveRelativeScriptPath(Path.GetDirectoryName(scriptFile.FilePath),
501+
Regex.Replace(cleanedUpSymbol, @"\$PSScriptRoot", Path.GetDirectoryName(scriptFile.FilePath), RegexOptions.IgnoreCase));
502+
}
503+
490504
/// <summary>
491505
/// Finds all the occurences of a symbol in the script given a file location
492506
/// </summary>

src/PowerShellEditorServices/Workspace/ScriptFile.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ private void ParseFileContents()
650650

651651
//Get all dot sourced referenced files and store them
652652
this.ReferencedFiles =
653-
AstOperations.FindDotSourcedIncludes(this.ScriptAst);
653+
AstOperations.FindDotSourcedIncludes(this);
654654
}
655655

656656
#endregion

test/PowerShellEditorServices.Test.Shared/References/DotSources.ps1

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
. ./ReferenceFileE.ps1
2+
. "$PSScriptRoot/ReferenceFileE.ps1"
23
. './ReferenceFileE.ps1'
34
. "./ReferenceFileE.ps1"
45
. .\ReferenceFileE.ps1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
. .\ReferenceFileC.ps1
1+
. "$PSScriptRoot\ReferenceFileC.ps1"
22

33
Get-ChildItem
44

5-
My-Function "testb"
5+
My-Function "testb"

test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ await this.GetDefinition(
172172
"Unexpected reference file: " + definitionResult.FoundDefinition.FilePath);
173173
Assert.Equal(1, definition.ScriptRegion.StartLineNumber);
174174
Assert.Equal(1, definition.ScriptRegion.StartColumnNumber);
175-
Assert.Equal(PathUtils.NormalizePathSeparators("./ReferenceFileE.ps1"), definition.SymbolName);
175+
Assert.Equal("./ReferenceFileE.ps1", definition.SymbolName);
176176
}
177177

178178
[Fact]

0 commit comments

Comments
 (0)