Skip to content

Commit 9aa300b

Browse files
Merge pull request #1656 from PowerShell/andschwa/resolve-aliases
Resurrect support to resolve aliased references
2 parents b362c5e + 627e915 commit 9aa300b

File tree

12 files changed

+178
-199
lines changed

12 files changed

+178
-199
lines changed

src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Threading.Tasks;
45
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
56
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
67

@@ -34,13 +35,12 @@ internal interface ICodeLensProvider
3435
/// The CodeLens to resolve.
3536
/// </param>
3637
/// <param name="scriptFile">
37-
/// A CancellationToken which can be used to cancel the
38-
/// request.
38+
/// The ScriptFile to resolve it in (sometimes unused).
3939
/// </param>
4040
/// <returns>
4141
/// A Task which returns the resolved CodeLens when completed.
4242
/// </returns>
43-
CodeLens ResolveCodeLens(
43+
Task<CodeLens> ResolveCodeLens(
4444
CodeLens codeLens,
4545
ScriptFile scriptFile);
4646
}

src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33
using System;
44
using System.Collections.Generic;
5+
using System.Threading.Tasks;
56
using Microsoft.PowerShell.EditorServices.Services;
67
using Microsoft.PowerShell.EditorServices.Services.Symbols;
78
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
@@ -133,11 +134,11 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
133134
/// <param name="codeLens">The code lens to resolve.</param>
134135
/// <param name="scriptFile">The script file.</param>
135136
/// <returns>The given CodeLens, wrapped in a task.</returns>
136-
public CodeLens ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile)
137+
public Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile)
137138
{
138139
// This provider has no specific behavior for
139140
// resolving CodeLenses.
140-
return codeLens;
141+
return Task.FromResult(codeLens);
141142
}
142143
}
143144
}

src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Text;
7+
using System.Threading.Tasks;
78
using Microsoft.PowerShell.EditorServices.Services;
89
using Microsoft.PowerShell.EditorServices.Services.Symbols;
910
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
@@ -79,10 +80,10 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
7980
/// Take a codelens and create a new codelens object with updated references.
8081
/// </summary>
8182
/// <param name="codeLens">The old code lens to get updated references for.</param>
83+
/// <param name="scriptFile"></param>
8284
/// <returns>A new code lens object describing the same data as the old one but with updated references.</returns>
83-
public CodeLens ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile)
85+
public async Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile)
8486
{
85-
8687
ScriptFile[] references = _workspaceService.ExpandScriptReferences(
8788
scriptFile);
8889

@@ -91,10 +92,10 @@ public CodeLens ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile)
9192
codeLens.Range.Start.Line + 1,
9293
codeLens.Range.Start.Character + 1);
9394

94-
List<SymbolReference> referencesResult = _symbolsService.FindReferencesOfSymbol(
95+
List<SymbolReference> referencesResult = await _symbolsService.FindReferencesOfSymbol(
9596
foundSymbol,
9697
references,
97-
_workspaceService);
98+
_workspaceService).ConfigureAwait(false);
9899

99100
Location[] referenceLocations;
100101
if (referencesResult == null)

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

+56-18
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility
1717
/// </summary>
1818
internal static class CommandHelpers
1919
{
20-
private static readonly HashSet<string> s_nounExclusionList = new HashSet<string>
21-
{
20+
private static readonly HashSet<string> s_nounExclusionList = new()
21+
{
2222
// PowerShellGet v2 nouns
2323
"CredsFromCredentialProvider",
2424
"DscResource",
@@ -36,8 +36,8 @@ internal static class CommandHelpers
3636
};
3737

3838
// This is used when a noun exists in multiple modules (for example, "Command" is used in Microsoft.PowerShell.Core and also PowerShellGet)
39-
private static readonly HashSet<string> s_cmdletExclusionList = new HashSet<string>
40-
{
39+
private static readonly HashSet<string> s_cmdletExclusionList = new()
40+
{
4141
// Commands in PowerShellGet with conflicting nouns
4242
"Find-Command",
4343
"Find-Module",
@@ -49,17 +49,17 @@ internal static class CommandHelpers
4949
"Update-ModuleManifest",
5050
};
5151

52-
private static readonly ConcurrentDictionary<string, CommandInfo> s_commandInfoCache =
53-
new ConcurrentDictionary<string, CommandInfo>();
54-
55-
private static readonly ConcurrentDictionary<string, string> s_synopsisCache =
56-
new ConcurrentDictionary<string, string>();
52+
private static readonly ConcurrentDictionary<string, CommandInfo> s_commandInfoCache = new();
53+
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);
5756

5857
/// <summary>
5958
/// Gets the CommandInfo instance for a command with a particular name.
6059
/// </summary>
6160
/// <param name="commandName">The name of the command.</param>
62-
/// <param name="powerShellContext">The PowerShellContext to use for running Get-Command.</param>
61+
/// <param name="currentRunspace">The current runspace.</param>
62+
/// <param name="executionService">The execution service.</param>
6363
/// <returns>A CommandInfo object with details about the specified command.</returns>
6464
public static async Task<CommandInfo> GetCommandInfoAsync(
6565
string commandName,
@@ -97,7 +97,11 @@ public static async Task<CommandInfo> GetCommandInfoAsync(
9797
.AddArgument(commandName)
9898
.AddParameter("ErrorAction", "Ignore");
9999

100-
CommandInfo commandInfo = (await executionService.ExecutePSCommandAsync<CommandInfo>(command, CancellationToken.None).ConfigureAwait(false)).FirstOrDefault();
100+
IReadOnlyList<CommandInfo> results = await executionService
101+
.ExecutePSCommandAsync<CommandInfo>(command, CancellationToken.None)
102+
.ConfigureAwait(false);
103+
104+
CommandInfo commandInfo = results.Count > 0 ? results[0] : null;
101105

102106
// Only cache CmdletInfos since they're exposed in binaries they are likely to not change throughout the session.
103107
if (commandInfo?.CommandType == CommandTypes.Cmdlet)
@@ -112,8 +116,8 @@ public static async Task<CommandInfo> GetCommandInfoAsync(
112116
/// Gets the command's "Synopsis" documentation section.
113117
/// </summary>
114118
/// <param name="commandInfo">The CommandInfo instance for the command.</param>
115-
/// <param name="executionService">The PowerShellContext to use for getting command documentation.</param>
116-
/// <returns></returns>
119+
/// <param name="executionService">The exeuction service to use for getting command documentation.</param>
120+
/// <returns>The synopsis.</returns>
117121
public static async Task<string> GetCommandSynopsisAsync(
118122
CommandInfo commandInfo,
119123
IInternalPowerShellExecutionService executionService)
@@ -146,13 +150,13 @@ public static async Task<string> GetCommandSynopsisAsync(
146150
.AddParameter("Online", false)
147151
.AddParameter("ErrorAction", "Ignore");
148152

149-
IReadOnlyList<PSObject> results = await executionService.ExecutePSCommandAsync<PSObject>(command, CancellationToken.None).ConfigureAwait(false);
150-
PSObject helpObject = results.FirstOrDefault();
153+
IReadOnlyList<PSObject> results = await executionService
154+
.ExecutePSCommandAsync<PSObject>(command, CancellationToken.None)
155+
.ConfigureAwait(false);
151156

152157
// Extract the synopsis string from the object
153-
string synopsisString =
154-
(string)helpObject?.Properties["synopsis"].Value ??
155-
string.Empty;
158+
PSObject helpObject = results.Count > 0 ? results[0] : null;
159+
string synopsisString = (string)helpObject?.Properties["synopsis"].Value ?? string.Empty;
156160

157161
// Only cache cmdlet infos because since they're exposed in binaries, the can never change throughout the session.
158162
if (commandInfo.CommandType == CommandTypes.Cmdlet)
@@ -168,5 +172,39 @@ public static async Task<string> GetCommandSynopsisAsync(
168172

169173
return synopsisString;
170174
}
175+
176+
/// <summary>
177+
/// Gets all aliases found in the runspace
178+
/// </summary>
179+
/// <param name="executionService"></param>
180+
public static async Task<(Dictionary<string, List<string>>, Dictionary<string, string>)> GetAliasesAsync(IInternalPowerShellExecutionService executionService)
181+
{
182+
Validate.IsNotNull(nameof(executionService), executionService);
183+
184+
IEnumerable<CommandInfo> aliases = await executionService.ExecuteDelegateAsync<IEnumerable<CommandInfo>>(
185+
nameof(GetAliasesAsync),
186+
Execution.ExecutionOptions.Default,
187+
(pwsh, _) =>
188+
{
189+
CommandInvocationIntrinsics invokeCommand = pwsh.Runspace.SessionStateProxy.InvokeCommand;
190+
return invokeCommand.GetCommands("*", CommandTypes.Alias, nameIsPattern: true);
191+
},
192+
CancellationToken.None).ConfigureAwait(false);
193+
194+
foreach (AliasInfo aliasInfo in aliases)
195+
{
196+
// TODO: When we move to netstandard2.1, we can use another overload which generates
197+
// static delegates and thus reduces allocations.
198+
s_cmdletToAliasCache.AddOrUpdate(
199+
aliasInfo.Definition,
200+
(_) => new List<string> { aliasInfo.Name },
201+
(_, v) => { v.Add(aliasInfo.Name); return v; });
202+
203+
s_aliasToCmdletCache.TryAdd(aliasInfo.Name, aliasInfo.Definition);
204+
}
205+
206+
return (new Dictionary<string, List<string>>(s_cmdletToAliasCache),
207+
new Dictionary<string, string>(s_aliasToCmdletCache));
208+
}
171209
}
172210
}

src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs

+17-19
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ internal class SymbolsService
3939

4040
private readonly ConcurrentDictionary<string, ICodeLensProvider> _codeLensProviders;
4141
private readonly ConcurrentDictionary<string, IDocumentSymbolProvider> _documentSymbolProviders;
42-
4342
#endregion
4443

4544
#region Constructors
@@ -49,6 +48,10 @@ internal class SymbolsService
4948
/// the given Runspace to execute language service operations.
5049
/// </summary>
5150
/// <param name="factory">An ILoggerFactory implementation used for writing log messages.</param>
51+
/// <param name="runspaceContext"></param>
52+
/// <param name="executionService"></param>
53+
/// <param name="workspaceService"></param>
54+
/// <param name="configurationService"></param>
5255
public SymbolsService(
5356
ILoggerFactory factory,
5457
IRunspaceContext runspaceContext,
@@ -176,7 +179,7 @@ public static SymbolReference FindSymbolAtLocation(
176179
/// <param name="referencedFiles">An array of scriptFiles too search for references in</param>
177180
/// <param name="workspace">The workspace that will be searched for symbols</param>
178181
/// <returns>FindReferencesResult</returns>
179-
public List<SymbolReference> FindReferencesOfSymbol(
182+
public async Task<List<SymbolReference>> FindReferencesOfSymbol(
180183
SymbolReference foundSymbol,
181184
ScriptFile[] referencedFiles,
182185
WorkspaceService workspace)
@@ -186,7 +189,7 @@ public List<SymbolReference> FindReferencesOfSymbol(
186189
return null;
187190
}
188191

189-
// NOTE: we use to make sure aliases were loaded but took it out because we needed the pipeline thread.
192+
(Dictionary<string, List<string>> cmdletToAliases, Dictionary<string, string> aliasToCmdlets) = await CommandHelpers.GetAliasesAsync(_executionService).ConfigureAwait(false);
190193

191194
// We want to look for references first in referenced files, hence we use ordered dictionary
192195
// TODO: File system case-sensitivity is based on filesystem not OS, but OS is a much cheaper heuristic
@@ -221,7 +224,8 @@ public List<SymbolReference> FindReferencesOfSymbol(
221224
IEnumerable<SymbolReference> references = AstOperations.FindReferencesOfSymbol(
222225
file.ScriptAst,
223226
foundSymbol,
224-
needsAliases: false);
227+
cmdletToAliases,
228+
aliasToCmdlets);
225229

226230
foreach (SymbolReference reference in references)
227231
{
@@ -264,10 +268,7 @@ public static IReadOnlyList<SymbolReference> FindOccurrencesInFile(
264268
return null;
265269
}
266270

267-
return AstOperations.FindReferencesOfSymbol(
268-
file.ScriptAst,
269-
foundSymbol,
270-
needsAliases: false).ToArray();
271+
return AstOperations.FindReferencesOfSymbol(file.ScriptAst, foundSymbol).ToArray();
271272
}
272273

273274
/// <summary>
@@ -306,7 +307,7 @@ public static SymbolReference FindFunctionDefinitionAtLocation(
306307
/// <param name="lineNumber">The line number at which the symbol can be located.</param>
307308
/// <param name="columnNumber">The column number at which the symbol can be located.</param>
308309
/// <returns></returns>
309-
public async Task<SymbolDetails> FindSymbolDetailsAtLocationAsync(
310+
public Task<SymbolDetails> FindSymbolDetailsAtLocationAsync(
310311
ScriptFile scriptFile,
311312
int lineNumber,
312313
int columnNumber)
@@ -319,16 +320,14 @@ public async Task<SymbolDetails> FindSymbolDetailsAtLocationAsync(
319320

320321
if (symbolReference == null)
321322
{
322-
return null;
323+
return Task.FromResult<SymbolDetails>(null);
323324
}
324325

325326
symbolReference.FilePath = scriptFile.FilePath;
326-
SymbolDetails symbolDetails = await SymbolDetails.CreateAsync(
327+
return SymbolDetails.CreateAsync(
327328
symbolReference,
328329
_runspaceContext.CurrentRunspace,
329-
_executionService).ConfigureAwait(false);
330-
331-
return symbolDetails;
330+
_executionService);
332331
}
333332

334333
/// <summary>
@@ -446,8 +445,7 @@ public async Task<SymbolReference> GetDefinitionOfSymbolAsync(
446445
if (foundDefinition == null)
447446
{
448447
// Get a list of all powershell files in the workspace path
449-
IEnumerable<string> allFiles = _workspaceService.EnumeratePSFiles();
450-
foreach (string file in allFiles)
448+
foreach (string file in _workspaceService.EnumeratePSFiles())
451449
{
452450
if (filesSearched.Contains(file))
453451
{
@@ -543,7 +541,7 @@ private ScriptFile[] GetBuiltinCommandScriptFiles(
543541
}
544542

545543
string modPath = moduleInfo.Path;
546-
List<ScriptFile> scriptFiles = new List<ScriptFile>();
544+
List<ScriptFile> scriptFiles = new();
547545
ScriptFile newFile;
548546

549547
// find any files where the moduleInfo's path ends with ps1 or psm1
@@ -598,7 +596,7 @@ public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment(
598596
IEnumerable<Ast> foundAsts = scriptFile.ScriptAst.FindAll(
599597
ast =>
600598
{
601-
if (!(ast is FunctionDefinitionAst fdAst))
599+
if (ast is not FunctionDefinitionAst fdAst)
602600
{
603601
return false;
604602
}
@@ -608,7 +606,7 @@ public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment(
608606
},
609607
true);
610608

611-
if (foundAsts == null || !foundAsts.Any())
609+
if (foundAsts?.Any() != true)
612610
{
613611
helpLocation = null;
614612
return null;

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

+8-29
Original file line numberDiff line numberDiff line change
@@ -153,42 +153,21 @@ public static SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumbe
153153
/// </summary>
154154
/// <param name="scriptAst">The abstract syntax tree of the given script</param>
155155
/// <param name="symbolReference">The symbol that we are looking for referneces of</param>
156-
/// <param name="CmdletToAliasDictionary">Dictionary maping cmdlets to aliases for finding alias references</param>
157-
/// <param name="AliasToCmdletDictionary">Dictionary maping aliases to cmdlets for finding alias references</param>
156+
/// <param name="cmdletToAliasDictionary">Dictionary maping cmdlets to aliases for finding alias references</param>
157+
/// <param name="aliasToCmdletDictionary">Dictionary maping aliases to cmdlets for finding alias references</param>
158158
/// <returns></returns>
159159
public static IEnumerable<SymbolReference> FindReferencesOfSymbol(
160160
Ast scriptAst,
161161
SymbolReference symbolReference,
162-
Dictionary<String, List<String>> CmdletToAliasDictionary,
163-
Dictionary<String, String> AliasToCmdletDictionary)
162+
IDictionary<string, List<string>> cmdletToAliasDictionary = default,
163+
IDictionary<string, string> aliasToCmdletDictionary = default)
164164
{
165165
// find the symbol evaluators for the node types we are handling
166-
FindReferencesVisitor referencesVisitor =
167-
new FindReferencesVisitor(
168-
symbolReference,
169-
CmdletToAliasDictionary,
170-
AliasToCmdletDictionary);
171-
scriptAst.Visit(referencesVisitor);
166+
FindReferencesVisitor referencesVisitor = new(
167+
symbolReference,
168+
cmdletToAliasDictionary,
169+
aliasToCmdletDictionary);
172170

173-
return referencesVisitor.FoundReferences;
174-
}
175-
176-
/// <summary>
177-
/// Finds all references (not including aliases) in a script for the given symbol
178-
/// </summary>
179-
/// <param name="scriptAst">The abstract syntax tree of the given script</param>
180-
/// <param name="foundSymbol">The symbol that we are looking for referneces of</param>
181-
/// <param name="needsAliases">If this reference search needs aliases.
182-
/// This should always be false and used for occurence requests</param>
183-
/// <returns>A collection of SymbolReference objects that are refrences to the symbolRefrence
184-
/// not including aliases</returns>
185-
public static IEnumerable<SymbolReference> FindReferencesOfSymbol(
186-
ScriptBlockAst scriptAst,
187-
SymbolReference foundSymbol,
188-
bool needsAliases)
189-
{
190-
FindReferencesVisitor referencesVisitor =
191-
new FindReferencesVisitor(foundSymbol);
192171
scriptAst.Visit(referencesVisitor);
193172

194173
return referencesVisitor.FoundReferences;

0 commit comments

Comments
 (0)