Skip to content

Fix #17: Add go to definition support for dot sourced file paths #786

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 3, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System.Collections.Generic;
using System.Management.Automation.Language;
using Microsoft.PowerShell.EditorServices.Utility;

namespace Microsoft.PowerShell.EditorServices
{
Expand All @@ -13,9 +14,9 @@ namespace Microsoft.PowerShell.EditorServices
/// </summary>
internal class FindDotSourcedVisitor : AstVisitor
{
/// <summary>
/// A hash set of the dot sourced files (because we don't want duplicates)
/// </summary>
/// <summary>
/// A hash set of the dot sourced files (because we don't want duplicates)
/// </summary>
public HashSet<string> DotSourcedFiles { get; private set; }

public FindDotSourcedVisitor()
Expand All @@ -36,7 +37,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst)
commandAst.CommandElements[0] is StringConstantExpressionAst)
{
// Strip any quote characters off of the string
string fileName = commandAst.CommandElements[0].Extent.Text.Trim('\'', '"');
string fileName = PathUtils.NormalizePathSeparators(commandAst.CommandElements[0].Extent.Text.Trim('\'', '"'));
DotSourcedFiles.Add(fileName);
}

Expand Down
22 changes: 17 additions & 5 deletions src/PowerShellEditorServices/Language/LanguageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -409,19 +409,31 @@ public async Task<GetDefinitionResult> GetDefinitionOfSymbol(
// 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++)
foreach (ScriptFile scriptFile in referencedFiles)
{
foundDefinition =
AstOperations.FindDefinitionOfSymbol(
referencedFiles[i].ScriptAst,
scriptFile.ScriptAst,
foundSymbol);

filesSearched.Add(referencedFiles[i].FilePath);
filesSearched.Add(scriptFile.FilePath);
if (foundDefinition != null)
{
foundDefinition.FilePath = referencedFiles[i].FilePath;
foundDefinition.FilePath = scriptFile.FilePath;
break;
}

if (foundSymbol.SymbolType == SymbolType.Function)
{
// Dot-sourcing is parsed as a "Function" Symbol.
string trimmedName = PathUtils.NormalizePathSeparators(foundSymbol.SymbolName.Trim('\'', '"'));
string dotSourcedPath = workspace.ResolveRelativeScriptPath(Path.GetDirectoryName(scriptFile.FilePath), trimmedName);
if (scriptFile.FilePath == dotSourcedPath)
{
foundDefinition = new SymbolReference(SymbolType.Function, trimmedName, scriptFile.ScriptAst.Extent, scriptFile.FilePath);
break;
}
}
}

// if the definition the not found in referenced files
Expand Down Expand Up @@ -712,7 +724,7 @@ await _powerShellContext.GetRunspaceHandle(
{
if (!_cmdletToAliasDictionary.ContainsKey(aliasInfo.Definition))
{
_cmdletToAliasDictionary.Add(aliasInfo.Definition, new List<String>{ aliasInfo.Name });
_cmdletToAliasDictionary.Add(aliasInfo.Definition, new List<String> { aliasInfo.Name });
}
else
{
Expand Down
52 changes: 52 additions & 0 deletions src/PowerShellEditorServices/Utility/PathUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.IO;

namespace Microsoft.PowerShell.EditorServices.Utility
{
/// <summary>
/// Utility to help handling paths across different platforms.
/// </summary>
/// <remarks>
/// Some constants were copied from the internal System.Management.Automation.StringLiterals class.
/// </remarks>
internal static class PathUtils
{
/// <summary>
/// The default path separator used by the base implementation of the providers.
///
/// Porting note: IO.Path.DirectorySeparatorChar is correct for all platforms. On Windows,
/// it is '\', and on Linux, it is '/', as expected.
/// </summary>
internal static readonly char DefaultPathSeparator = Path.DirectorySeparatorChar;
internal static readonly string DefaultPathSeparatorString = DefaultPathSeparator.ToString();

/// <summary>
/// The alternate path separator used by the base implementation of the providers.
///
/// Porting note: we do not use .NET's AlternatePathSeparatorChar here because it correctly
/// states that both the default and alternate are '/' on Linux. However, for PowerShell to
/// be "slash agnostic", we need to use the assumption that a '\' is the alternate path
/// separator on Linux.
/// </summary>
#if CoreCLR
internal static readonly char AlternatePathSeparator = System.Management.Automation.Platform.IsWindows ? '/' : '\\';
#else
internal static readonly char AlternatePathSeparator = '/';
#endif
internal static readonly string AlternatePathSeparatorString = AlternatePathSeparator.ToString();

/// <summary>
/// Converts all alternate path separators to the current platform's main path separators.
/// </summary>
/// <param name="path">The path to normalize.</param>
/// <returns>The normalized path.</returns>
public static string NormalizePathSeparators(string path)
{
return string.IsNullOrWhiteSpace(path) ? path : path.Replace(AlternatePathSeparator, DefaultPathSeparator);
}
}
}
2 changes: 1 addition & 1 deletion src/PowerShellEditorServices/Workspace/Workspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ private string GetBaseFilePath(string filePath)
return Path.GetDirectoryName(filePath);
}

private string ResolveRelativeScriptPath(string baseFilePath, string relativePath)
internal string ResolveRelativeScriptPath(string baseFilePath, string relativePath)
{
string combinedPath = null;
Exception resolveException = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition
{
public class FindsDotSourcedFile
{
public static readonly ScriptRegion SourceDetails =
new ScriptRegion
{
File = @"References\DotSources.ps1",
StartLineNumber = 1,
StartColumnNumber = 3
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
. ./ReferenceFileE.ps1
. './ReferenceFileE.ps1'
. "./ReferenceFileE.ps1"
. .\ReferenceFileE.ps1
. '.\ReferenceFileE.ps1'
. ".\ReferenceFileE.ps1"
. ReferenceFileE.ps1
. 'ReferenceFileE.ps1'
. "ReferenceFileE.ps1"
. ./dir/../ReferenceFileE.ps1
. ./invalidfile.ps1
. ""
. $someVar
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,23 @@ await this.GetDefinition(
Assert.Equal("My-Function", definition.SymbolName);
}

[Fact]
public async Task LanguageServiceFindsDotSourcedFile()
{
GetDefinitionResult definitionResult =
await this.GetDefinition(
FindsDotSourcedFile.SourceDetails);

SymbolReference definition = definitionResult.FoundDefinition;
Assert.True(
definitionResult.FoundDefinition.FilePath.EndsWith(
Path.Combine("References", "ReferenceFileE.ps1")),
"Unexpected reference file: " + definitionResult.FoundDefinition.FilePath);
Assert.Equal(1, definition.ScriptRegion.StartLineNumber);
Assert.Equal(1, definition.ScriptRegion.StartColumnNumber);
Assert.Equal(PathUtils.NormalizePathSeparators("./ReferenceFileE.ps1"), definition.SymbolName);
}

[Fact]
public async Task LanguageServiceFindsFunctionDefinitionInWorkspace()
{
Expand Down