Skip to content

Commit 576718c

Browse files
Add definition handler (PowerShell#1013)
* add definition handler * codacy * sneak in powerShell/executionStatusChanged * codacy
1 parent 93ce344 commit 576718c

File tree

8 files changed

+368
-9
lines changed

8 files changed

+368
-9
lines changed

src/PowerShellEditorServices.Engine/Hosting/EditorServicesHost.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ private PowerShellContextService GetFullyInitializedPowerShellContext(
310310
// issues arise when redirecting stdio.
311311
var powerShellContext = new PowerShellContextService(
312312
logger,
313+
languageServer,
313314
_featureFlags.Contains("PSReadLine") && _enableConsoleRepl);
314315

315316
EditorServicesPSHostUserInterface hostUserInterface =

src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public async Task StartAsync()
113113
.WithHandler<CompletionHandler>()
114114
.WithHandler<HoverHandler>()
115115
.WithHandler<SignatureHelpHandler>()
116+
.WithHandler<DefinitionHandler>()
116117
.OnInitialize(
117118
async (languageServer, request) =>
118119
{

src/PowerShellEditorServices.Engine/Services/PowerShellContext/PowerShellContextService.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
using System.Threading;
1818
using System.Threading.Tasks;
1919
using Microsoft.Extensions.Logging;
20+
using Microsoft.PowerShell.EditorServices.Engine;
2021
using Microsoft.PowerShell.EditorServices.Session;
2122
using Microsoft.PowerShell.EditorServices.Utility;
2223

2324
namespace Microsoft.PowerShell.EditorServices
2425
{
2526
using System.Management.Automation;
26-
using Microsoft.PowerShell.EditorServices.Engine;
2727

2828
/// <summary>
2929
/// Manages the lifetime and usage of a PowerShell session.
@@ -49,6 +49,7 @@ static PowerShellContextService()
4949

5050
private readonly SemaphoreSlim resumeRequestHandle = AsyncUtils.CreateSimpleLockingSemaphore();
5151

52+
private readonly OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServer _languageServer;
5253
private bool isPSReadLineEnabled;
5354
private ILogger logger;
5455
private PowerShell powerShell;
@@ -145,11 +146,16 @@ public RunspaceDetails CurrentRunspace
145146
/// <param name="isPSReadLineEnabled">
146147
/// Indicates whether PSReadLine should be used if possible
147148
/// </param>
148-
public PowerShellContextService(ILogger logger, bool isPSReadLineEnabled)
149+
public PowerShellContextService(
150+
ILogger logger,
151+
OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServer languageServer,
152+
bool isPSReadLineEnabled)
149153
{
150-
154+
_languageServer = languageServer;
151155
this.logger = logger;
152156
this.isPSReadLineEnabled = isPSReadLineEnabled;
157+
158+
ExecutionStatusChanged += PowerShellContext_ExecutionStatusChangedAsync;
153159
}
154160

155161
/// <summary>
@@ -1720,6 +1726,18 @@ private void OnExecutionStatusChanged(
17201726
hadErrors));
17211727
}
17221728

1729+
/// <summary>
1730+
/// Event hook on the PowerShell context to listen for changes in script execution status
1731+
/// </summary>
1732+
/// <param name="sender">the PowerShell context sending the execution event</param>
1733+
/// <param name="e">details of the execution status change</param>
1734+
private void PowerShellContext_ExecutionStatusChangedAsync(object sender, ExecutionStatusChangedEventArgs e)
1735+
{
1736+
_languageServer.SendNotification(
1737+
"powerShell/executionStatusChanged",
1738+
e);
1739+
}
1740+
17231741
#endregion
17241742

17251743
#region Private Methods

src/PowerShellEditorServices.Engine/Services/Symbols/SymbolsService.cs

Lines changed: 186 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
using System;
77
using System.Collections.Generic;
88
using System.Collections.Specialized;
9+
using System.IO;
910
using System.Linq;
1011
using System.Management.Automation;
12+
using System.Management.Automation.Language;
1113
using System.Runtime.InteropServices;
14+
using System.Text.RegularExpressions;
1215
using System.Threading.Tasks;
1316
using Microsoft.Extensions.Logging;
1417
using Microsoft.PowerShell.EditorServices.Symbols;
18+
using PowerShellEditorServices.Engine.Utility;
1519

1620
namespace Microsoft.PowerShell.EditorServices
1721
{
@@ -25,6 +29,7 @@ public class SymbolsService
2529

2630
private readonly ILogger _logger;
2731
private readonly PowerShellContextService _powerShellContextService;
32+
private readonly WorkspaceService _workspaceService;
2833
private readonly IDocumentSymbolProvider[] _documentSymbolProviders;
2934

3035
#endregion
@@ -38,10 +43,12 @@ public class SymbolsService
3843
/// <param name="factory">An ILoggerFactory implementation used for writing log messages.</param>
3944
public SymbolsService(
4045
ILoggerFactory factory,
41-
PowerShellContextService powerShellContextService)
46+
PowerShellContextService powerShellContextService,
47+
WorkspaceService workspaceService)
4248
{
4349
_logger = factory.CreateLogger<SymbolsService>();
4450
_powerShellContextService = powerShellContextService;
51+
_workspaceService = workspaceService;
4552
_documentSymbolProviders = new IDocumentSymbolProvider[]
4653
{
4754
new ScriptDocumentSymbolProvider(VersionUtils.PSVersion),
@@ -320,5 +327,183 @@ await CommandHelpers.GetCommandInfoAsync(
320327
return null;
321328
}
322329
}
330+
331+
/// <summary>
332+
/// Finds the definition of a symbol in the script file or any of the
333+
/// files that it references.
334+
/// </summary>
335+
/// <param name="sourceFile">The initial script file to be searched for the symbol's definition.</param>
336+
/// <param name="foundSymbol">The symbol for which a definition will be found.</param>
337+
/// <returns>The resulting GetDefinitionResult for the symbol's definition.</returns>
338+
public async Task<SymbolReference> GetDefinitionOfSymbolAsync(
339+
ScriptFile sourceFile,
340+
SymbolReference foundSymbol)
341+
{
342+
Validate.IsNotNull(nameof(sourceFile), sourceFile);
343+
Validate.IsNotNull(nameof(foundSymbol), foundSymbol);
344+
345+
ScriptFile[] referencedFiles =
346+
_workspaceService.ExpandScriptReferences(
347+
sourceFile);
348+
349+
var filesSearched = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
350+
351+
// look through the referenced files until definition is found
352+
// or there are no more file to look through
353+
SymbolReference foundDefinition = null;
354+
foreach (ScriptFile scriptFile in referencedFiles)
355+
{
356+
foundDefinition =
357+
AstOperations.FindDefinitionOfSymbol(
358+
scriptFile.ScriptAst,
359+
foundSymbol);
360+
361+
filesSearched.Add(scriptFile.FilePath);
362+
if (foundDefinition != null)
363+
{
364+
foundDefinition.FilePath = scriptFile.FilePath;
365+
break;
366+
}
367+
368+
if (foundSymbol.SymbolType == SymbolType.Function)
369+
{
370+
// Dot-sourcing is parsed as a "Function" Symbol.
371+
string dotSourcedPath = GetDotSourcedPath(foundSymbol, scriptFile);
372+
if (scriptFile.FilePath == dotSourcedPath)
373+
{
374+
foundDefinition = new SymbolReference(SymbolType.Function, foundSymbol.SymbolName, scriptFile.ScriptAst.Extent, scriptFile.FilePath);
375+
break;
376+
}
377+
}
378+
}
379+
380+
// if the definition the not found in referenced files
381+
// look for it in all the files in the workspace
382+
if (foundDefinition == null)
383+
{
384+
// Get a list of all powershell files in the workspace path
385+
IEnumerable<string> allFiles = _workspaceService.EnumeratePSFiles();
386+
foreach (string file in allFiles)
387+
{
388+
if (filesSearched.Contains(file))
389+
{
390+
continue;
391+
}
392+
393+
foundDefinition =
394+
AstOperations.FindDefinitionOfSymbol(
395+
Parser.ParseFile(file, out Token[] tokens, out ParseError[] parseErrors),
396+
foundSymbol);
397+
398+
filesSearched.Add(file);
399+
if (foundDefinition != null)
400+
{
401+
foundDefinition.FilePath = file;
402+
break;
403+
}
404+
}
405+
}
406+
407+
// if definition is not found in file in the workspace
408+
// look for it in the builtin commands
409+
if (foundDefinition == null)
410+
{
411+
CommandInfo cmdInfo =
412+
await CommandHelpers.GetCommandInfoAsync(
413+
foundSymbol.SymbolName,
414+
_powerShellContextService);
415+
416+
foundDefinition =
417+
FindDeclarationForBuiltinCommand(
418+
cmdInfo,
419+
foundSymbol);
420+
}
421+
422+
return foundDefinition;
423+
}
424+
425+
/// <summary>
426+
/// Gets a path from a dot-source symbol.
427+
/// </summary>
428+
/// <param name="symbol">The symbol representing the dot-source expression.</param>
429+
/// <param name="scriptFile">The script file containing the symbol</param>
430+
/// <returns></returns>
431+
private string GetDotSourcedPath(SymbolReference symbol, ScriptFile scriptFile)
432+
{
433+
string cleanedUpSymbol = PathUtils.NormalizePathSeparators(symbol.SymbolName.Trim('\'', '"'));
434+
string psScriptRoot = Path.GetDirectoryName(scriptFile.FilePath);
435+
return _workspaceService.ResolveRelativeScriptPath(psScriptRoot,
436+
Regex.Replace(cleanedUpSymbol, @"\$PSScriptRoot|\${PSScriptRoot}", psScriptRoot, RegexOptions.IgnoreCase));
437+
}
438+
439+
private SymbolReference FindDeclarationForBuiltinCommand(
440+
CommandInfo commandInfo,
441+
SymbolReference foundSymbol)
442+
{
443+
if (commandInfo == null)
444+
{
445+
return null;
446+
}
447+
448+
ScriptFile[] nestedModuleFiles =
449+
GetBuiltinCommandScriptFiles(
450+
commandInfo.Module);
451+
452+
SymbolReference foundDefinition = null;
453+
foreach (ScriptFile nestedModuleFile in nestedModuleFiles)
454+
{
455+
foundDefinition = AstOperations.FindDefinitionOfSymbol(
456+
nestedModuleFile.ScriptAst,
457+
foundSymbol);
458+
459+
if (foundDefinition != null)
460+
{
461+
foundDefinition.FilePath = nestedModuleFile.FilePath;
462+
break;
463+
}
464+
}
465+
466+
return foundDefinition;
467+
}
468+
469+
private ScriptFile[] GetBuiltinCommandScriptFiles(
470+
PSModuleInfo moduleInfo)
471+
{
472+
if (moduleInfo == null)
473+
{
474+
return new ScriptFile[0];
475+
}
476+
477+
string modPath = moduleInfo.Path;
478+
List<ScriptFile> scriptFiles = new List<ScriptFile>();
479+
ScriptFile newFile;
480+
481+
// find any files where the moduleInfo's path ends with ps1 or psm1
482+
// and add it to allowed script files
483+
if (modPath.EndsWith(@".ps1", StringComparison.OrdinalIgnoreCase) ||
484+
modPath.EndsWith(@".psm1", StringComparison.OrdinalIgnoreCase))
485+
{
486+
newFile = _workspaceService.GetFile(modPath);
487+
newFile.IsAnalysisEnabled = false;
488+
scriptFiles.Add(newFile);
489+
}
490+
491+
if (moduleInfo.NestedModules.Count > 0)
492+
{
493+
foreach (PSModuleInfo nestedInfo in moduleInfo.NestedModules)
494+
{
495+
string nestedModPath = nestedInfo.Path;
496+
if (nestedModPath.EndsWith(@".ps1", StringComparison.OrdinalIgnoreCase) ||
497+
nestedModPath.EndsWith(@".psm1", StringComparison.OrdinalIgnoreCase))
498+
{
499+
newFile = _workspaceService.GetFile(nestedModPath);
500+
newFile.IsAnalysisEnabled = false;
501+
scriptFiles.Add(newFile);
502+
}
503+
}
504+
}
505+
506+
return scriptFiles.ToArray();
507+
}
323508
}
324509
}

0 commit comments

Comments
 (0)