Skip to content

Commit f3d4bf2

Browse files
Enable resolution of an alias to its function definition (#1662)
Two caveats: 1. The alias must be set in the runspace, which means the script needs to have been executed. 2. The definition is the function itself, not the `Set-Alias` command.
1 parent a91a1f0 commit f3d4bf2

File tree

6 files changed

+76
-4
lines changed

6 files changed

+76
-4
lines changed

src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ internal static class CommandHelpers
5151

5252
private static readonly ConcurrentDictionary<string, CommandInfo> s_commandInfoCache = new();
5353
private static readonly ConcurrentDictionary<string, string> s_synopsisCache = new();
54-
private static readonly ConcurrentDictionary<string, List<string>> s_cmdletToAliasCache = new(System.StringComparer.OrdinalIgnoreCase);
55-
private static readonly ConcurrentDictionary<string, string> s_aliasToCmdletCache = new(System.StringComparer.OrdinalIgnoreCase);
54+
internal static readonly ConcurrentDictionary<string, List<string>> s_cmdletToAliasCache = new(System.StringComparer.OrdinalIgnoreCase);
55+
internal static readonly ConcurrentDictionary<string, string> s_aliasToCmdletCache = new(System.StringComparer.OrdinalIgnoreCase);
5656

5757
/// <summary>
5858
/// Gets the CommandInfo instance for a command with a particular name.

src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs

+14
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,20 @@ public async Task<SymbolReference> GetDefinitionOfSymbolAsync(
405405
Validate.IsNotNull(nameof(sourceFile), sourceFile);
406406
Validate.IsNotNull(nameof(foundSymbol), foundSymbol);
407407

408+
// If symbol is an alias, resolve it.
409+
(Dictionary<string, List<string>> _, Dictionary<string, string> aliasToCmdlets) =
410+
await CommandHelpers.GetAliasesAsync(_executionService).ConfigureAwait(false);
411+
412+
if (aliasToCmdlets.ContainsKey(foundSymbol.SymbolName))
413+
{
414+
foundSymbol = new SymbolReference(
415+
foundSymbol.SymbolType,
416+
aliasToCmdlets[foundSymbol.SymbolName],
417+
foundSymbol.ScriptRegion,
418+
foundSymbol.FilePath,
419+
foundSymbol.SourceLine);
420+
}
421+
408422
ScriptFile[] referencedFiles =
409423
_workspaceService.ExpandScriptReferences(
410424
sourceFile);

src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun
5252
File = functionDefinitionAst.Extent.File
5353
};
5454

55+
// We compare to the SymbolName instead of its text because it may have been resolved
56+
// from an alias.
5557
if (symbolRef.SymbolType.Equals(SymbolType.Function) &&
56-
nameExtent.Text.Equals(symbolRef.ScriptRegion.Text, StringComparison.CurrentCultureIgnoreCase))
58+
nameExtent.Text.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase))
5759
{
5860
this.FoundDeclaration =
5961
new SymbolReference(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
5+
6+
namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition
7+
{
8+
public static class FindsFunctionDefinitionOfAliasData
9+
{
10+
public static readonly ScriptRegion SourceDetails = new(
11+
file: TestUtilities.NormalizePath("References/SimpleFile.ps1"),
12+
text: string.Empty,
13+
startLineNumber: 20,
14+
startColumnNumber: 4,
15+
startOffset: 0,
16+
endLineNumber: 0,
17+
endColumnNumber: 0,
18+
endOffset: 0);
19+
}
20+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ gci
1616
dir
1717
Write-Host
1818
Get-ChildItem
19+
20+
My-Alias

test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs

+35-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Linq;
8+
using System.Management.Automation;
9+
using System.Threading;
810
using System.Threading.Tasks;
911
using Microsoft.Extensions.Logging.Abstractions;
1012
using Microsoft.PowerShell.EditorServices.Services;
1113
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
14+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
1215
using Microsoft.PowerShell.EditorServices.Services.Symbols;
1316
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
1417
using Microsoft.PowerShell.EditorServices.Test.Shared;
@@ -44,6 +47,8 @@ public SymbolsServiceTests()
4447
public void Dispose()
4548
{
4649
psesHost.StopAsync().GetAwaiter().GetResult();
50+
CommandHelpers.s_cmdletToAliasCache.Clear();
51+
CommandHelpers.s_aliasToCmdletCache.Clear();
4752
GC.SuppressFinalize(this);
4853
}
4954

@@ -126,14 +131,43 @@ public async Task FindsFunctionDefinition()
126131
}
127132

128133
[Fact]
129-
public async Task FindsReferencesOnFunction()
134+
public async Task FindsFunctionDefinitionForAlias()
135+
{
136+
// TODO: Eventually we should get the alises through the AST instead of relying on them
137+
// being defined in the runspace.
138+
await psesHost.ExecutePSCommandAsync(
139+
new PSCommand().AddScript("Set-Alias -Name My-Alias -Value My-Function"),
140+
CancellationToken.None).ConfigureAwait(true);
141+
142+
SymbolReference definitionResult = await GetDefinition(FindsFunctionDefinitionOfAliasData.SourceDetails).ConfigureAwait(true);
143+
Assert.Equal(1, definitionResult.ScriptRegion.StartLineNumber);
144+
Assert.Equal(10, definitionResult.ScriptRegion.StartColumnNumber);
145+
Assert.Equal("My-Function", definitionResult.SymbolName);
146+
}
147+
148+
[Fact]
149+
public async Task FindsReferencesOnFunction()
130150
{
131151
List<SymbolReference> referencesResult = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true);
132152
Assert.Equal(3, referencesResult.Count);
133153
Assert.Equal(1, referencesResult[0].ScriptRegion.StartLineNumber);
134154
Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber);
135155
}
136156

157+
[Fact]
158+
public async Task FindsReferencesOnFunctionIncludingAliases()
159+
{
160+
// TODO: Same as in FindsFunctionDefinitionForAlias.
161+
await psesHost.ExecutePSCommandAsync(
162+
new PSCommand().AddScript("Set-Alias -Name My-Alias -Value My-Function"),
163+
CancellationToken.None).ConfigureAwait(true);
164+
165+
List<SymbolReference> referencesResult = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true);
166+
Assert.Equal(4, referencesResult.Count);
167+
Assert.Equal(1, referencesResult[0].ScriptRegion.StartLineNumber);
168+
Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber);
169+
}
170+
137171
[Fact]
138172
public async Task FindsFunctionDefinitionInDotSourceReference()
139173
{

0 commit comments

Comments
 (0)