diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index 9c83169b7..031727b02 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -51,8 +51,8 @@ internal static class CommandHelpers private static readonly ConcurrentDictionary s_commandInfoCache = new(); private static readonly ConcurrentDictionary s_synopsisCache = new(); - private static readonly ConcurrentDictionary> s_cmdletToAliasCache = new(System.StringComparer.OrdinalIgnoreCase); - private static readonly ConcurrentDictionary s_aliasToCmdletCache = new(System.StringComparer.OrdinalIgnoreCase); + internal static readonly ConcurrentDictionary> s_cmdletToAliasCache = new(System.StringComparer.OrdinalIgnoreCase); + internal static readonly ConcurrentDictionary s_aliasToCmdletCache = new(System.StringComparer.OrdinalIgnoreCase); /// /// Gets the CommandInfo instance for a command with a particular name. diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index bf8e3e8c1..1b3293972 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -405,6 +405,20 @@ public async Task GetDefinitionOfSymbolAsync( Validate.IsNotNull(nameof(sourceFile), sourceFile); Validate.IsNotNull(nameof(foundSymbol), foundSymbol); + // If symbol is an alias, resolve it. + (Dictionary> _, Dictionary aliasToCmdlets) = + await CommandHelpers.GetAliasesAsync(_executionService).ConfigureAwait(false); + + if (aliasToCmdlets.ContainsKey(foundSymbol.SymbolName)) + { + foundSymbol = new SymbolReference( + foundSymbol.SymbolType, + aliasToCmdlets[foundSymbol.SymbolName], + foundSymbol.ScriptRegion, + foundSymbol.FilePath, + foundSymbol.SourceLine); + } + ScriptFile[] referencedFiles = _workspaceService.ExpandScriptReferences( sourceFile); diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs index 8ac6e5bcf..d48d17ab9 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs @@ -52,8 +52,10 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun File = functionDefinitionAst.Extent.File }; + // We compare to the SymbolName instead of its text because it may have been resolved + // from an alias. if (symbolRef.SymbolType.Equals(SymbolType.Function) && - nameExtent.Text.Equals(symbolRef.ScriptRegion.Text, StringComparison.CurrentCultureIgnoreCase)) + nameExtent.Text.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { this.FoundDeclaration = new SymbolReference( diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionOfAlias.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionOfAlias.cs new file mode 100644 index 000000000..8704e1a26 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionOfAlias.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition +{ + public static class FindsFunctionDefinitionOfAliasData + { + public static readonly ScriptRegion SourceDetails = new( + file: TestUtilities.NormalizePath("References/SimpleFile.ps1"), + text: string.Empty, + startLineNumber: 20, + startColumnNumber: 4, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 index 527045b1e..e7a271447 100644 --- a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 @@ -16,3 +16,5 @@ gci dir Write-Host Get-ChildItem + +My-Alias diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 24d0a6fbe..3e68491ac 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -5,10 +5,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Management.Automation; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test.Shared; @@ -44,6 +47,8 @@ public SymbolsServiceTests() public void Dispose() { psesHost.StopAsync().GetAwaiter().GetResult(); + CommandHelpers.s_cmdletToAliasCache.Clear(); + CommandHelpers.s_aliasToCmdletCache.Clear(); GC.SuppressFinalize(this); } @@ -126,7 +131,22 @@ public async Task FindsFunctionDefinition() } [Fact] - public async Task FindsReferencesOnFunction() + public async Task FindsFunctionDefinitionForAlias() + { + // TODO: Eventually we should get the alises through the AST instead of relying on them + // being defined in the runspace. + await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("Set-Alias -Name My-Alias -Value My-Function"), + CancellationToken.None).ConfigureAwait(true); + + SymbolReference definitionResult = await GetDefinition(FindsFunctionDefinitionOfAliasData.SourceDetails).ConfigureAwait(true); + Assert.Equal(1, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(10, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("My-Function", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnFunction() { List referencesResult = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); Assert.Equal(3, referencesResult.Count); @@ -134,6 +154,20 @@ public async Task FindsReferencesOnFunction() Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber); } + [Fact] + public async Task FindsReferencesOnFunctionIncludingAliases() + { + // TODO: Same as in FindsFunctionDefinitionForAlias. + await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("Set-Alias -Name My-Alias -Value My-Function"), + CancellationToken.None).ConfigureAwait(true); + + List referencesResult = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); + Assert.Equal(4, referencesResult.Count); + Assert.Equal(1, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber); + } + [Fact] public async Task FindsFunctionDefinitionInDotSourceReference() {