From 22bb348d05e229a54b4ad8e4a1164e3e26e1a42a Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Mon, 27 Jun 2022 13:44:48 -0700 Subject: [PATCH 001/327] Small cleanups --- .../Debugging/PowerShellDebugContext.cs | 2 +- .../DebugAdapterProtocolMessageTests.cs | 44 ++----- .../Debugging/DebugServiceTests.cs | 4 +- .../Language/CompletionHandlerTests.cs | 1 + .../Session/PsesInternalHostTests.cs | 7 +- .../Session/WorkspaceTests.cs | 111 ++++++++---------- 6 files changed, 64 insertions(+), 105 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index a66b0d93b..36aaad0e2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -187,7 +187,7 @@ public void ProcessDebuggerResult(DebuggerCommandResults debuggerResult) RaiseDebuggerResumingEvent(new DebuggerResumingEventArgs(debuggerResult.ResumeAction.Value)); // The Terminate exception is used by the engine for flow control - // when it needs to unwind the callstack out of the debugger. + // when it needs to unwind the call stack out of the debugger. if (debuggerResult.ResumeAction is DebuggerResumeAction.Stop) { throw new TerminateException(); diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 34352291f..14e250823 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -18,6 +18,7 @@ namespace PowerShellEditorServices.Test.E2E { + [Trait("Category", "DAP")] public class DebugAdapterProtocolMessageTests : IAsyncLifetime { private const string TestOutputFileName = "__dapTestOutputFile.txt"; @@ -148,7 +149,6 @@ private string GenerateScriptFromLoggingStatements(params string[] logStatements private static string[] GetLog() => File.ReadLines(s_testOutputPath).ToArray(); - [Trait("Category", "DAP")] [Fact] public void CanInitializeWithCorrectServerSettings() { @@ -160,7 +160,6 @@ public void CanInitializeWithCorrectServerSettings() Assert.True(PsesDebugAdapterClient.ServerSettings.SupportsSetVariable); } - [Trait("Category", "DAP")] [Fact] public async Task CanLaunchScriptWithNoBreakpointsAsync() { @@ -170,15 +169,12 @@ public async Task CanLaunchScriptWithNoBreakpointsAsync() ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); Assert.NotNull(configDoneResponse); - - // At this point the script should be running so lets give it time await Task.Delay(2000).ConfigureAwait(false); string[] log = GetLog(); Assert.Equal("works", log[0]); } - [Trait("Category", "DAP")] [SkippableFact] public async Task CanSetBreakpointsAsync() { @@ -197,19 +193,8 @@ public async Task CanSetBreakpointsAsync() // {"command":"setBreakpoints","arguments":{"source":{"name":"dfsdfg.ps1","path":"/Users/tyleonha/Code/PowerShell/Misc/foo/dfsdfg.ps1"},"lines":[2],"breakpoints":[{"line":2}],"sourceModified":false},"type":"request","seq":3} SetBreakpointsResponse setBreakpointsResponse = await PsesDebugAdapterClient.SetBreakpoints(new SetBreakpointsArguments { - Source = new Source - { - Name = Path.GetFileName(filePath), - Path = filePath - }, - Lines = new long[] { 2 }, - Breakpoints = new SourceBreakpoint[] - { - new SourceBreakpoint - { - Line = 2, - } - }, + Source = new Source { Name = Path.GetFileName(filePath), Path = filePath }, + Breakpoints = new SourceBreakpoint[] { new SourceBreakpoint { Line = 2 } }, SourceModified = false, }).ConfigureAwait(false); @@ -220,21 +205,15 @@ public async Task CanSetBreakpointsAsync() ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); Assert.NotNull(configDoneResponse); - - // At this point the script should be running so lets give it time await Task.Delay(2000).ConfigureAwait(false); string[] log = GetLog(); Assert.Single(log, (i) => i == "before breakpoint"); - ContinueResponse continueResponse = await PsesDebugAdapterClient.RequestContinue(new ContinueArguments - { - ThreadId = 1, - }).ConfigureAwait(true); + ContinueResponse continueResponse = await PsesDebugAdapterClient.RequestContinue( + new ContinueArguments { ThreadId = 1 }).ConfigureAwait(true); Assert.NotNull(continueResponse); - - // At this point the script should be running so lets give it time await Task.Delay(2000).ConfigureAwait(false); log = GetLog(); @@ -255,7 +234,6 @@ public async Task CanSetBreakpointsAsync() // PowerShell, we avoid all issues with our test project (and the xUnit executable) not // having System.Windows.Forms deployed, and can instead rely on the Windows Global Assembly // Cache (GAC) to find it. - [Trait("Category", "DAP")] [SkippableFact] public async Task CanStepPastSystemWindowsForms() { @@ -283,15 +261,10 @@ public async Task CanStepPastSystemWindowsForms() ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); Assert.NotNull(configDoneResponse); - - // At this point the script should be running so lets give it time await Task.Delay(2000).ConfigureAwait(false); VariablesResponse variablesResponse = await PsesDebugAdapterClient.RequestVariables( - new VariablesArguments - { - VariablesReference = 1 - }).ConfigureAwait(false); + new VariablesArguments { VariablesReference = 1 }).ConfigureAwait(false); Variable form = variablesResponse.Variables.FirstOrDefault(v => v.Name == "$form"); Assert.NotNull(form); @@ -302,7 +275,6 @@ public async Task CanStepPastSystemWindowsForms() // commented. Since in some cases (such as Windows PowerShell, or the script not having a // backing ScriptFile) we just wrap the script with braces, we had a bug where the last // brace would be after the comment. We had to ensure we wrapped with newlines instead. - [Trait("Category", "DAP")] [Fact] public async Task CanLaunchScriptWithCommentedLastLineAsync() { @@ -318,8 +290,6 @@ public async Task CanLaunchScriptWithCommentedLastLineAsync() ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); Assert.NotNull(configDoneResponse); - - // At this point the script should be running so lets give it time await Task.Delay(2000).ConfigureAwait(false); Assert.Collection(GetLog(), (i) => Assert.Equal("a log statement", i)); diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index b7b55ad61..38f3b6231 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -183,7 +183,7 @@ await debugService.SetCommandBreakpointsAsync( public async Task DebuggerAcceptsScriptArgs(string[] args) { // The path is intentionally odd (some escaped chars but not all) because we are testing - // the internal path escaping mechanism - it should escape certains chars ([, ] and space) but + // the internal path escaping mechanism - it should escape certain chars ([, ] and space) but // it should not escape already escaped chars. ScriptFile debugWithParamsFile = GetDebugScript("Debug W&ith Params [Test].ps1"); @@ -453,7 +453,7 @@ await debugService.SetLineBreakpointsAsync( } [Fact] - public async Task DebuggerFindsParseableButInvalidSimpleBreakpointConditions() + public async Task DebuggerFindsParsableButInvalidSimpleBreakpointConditions() { BreakpointDetails[] breakpoints = await debugService.SetLineBreakpointsAsync( diff --git a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs index 4ba77bf1f..fc1590d35 100644 --- a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs @@ -109,6 +109,7 @@ public async Task CompletesVariableInFile() public async Task CompletesAttributeValue() { (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteAttributeValue.SourceDetails).ConfigureAwait(true); + Assert.Equal(3, results.Count()); Assert.Collection(results.OrderBy(c => c.SortText), actual => Assert.Equal(actual with { Data = null }, CompleteAttributeValue.ExpectedCompletion1), actual => Assert.Equal(actual with { Data = null }, CompleteAttributeValue.ExpectedCompletion2), diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index 179da5f8c..c85aa0d2f 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -137,7 +137,7 @@ public async Task CanResolveAndLoadProfilesForHostId() new PSCommand().AddScript("Assert-ProfileLoaded"), CancellationToken.None).ConfigureAwait(true); - Assert.Collection(profileLoaded, (p) => Assert.True(p)); + Assert.Collection(profileLoaded, Assert.True); } [Fact] @@ -150,7 +150,8 @@ public async Task CanHandleNoProfiles() await psesHost.ExecuteDelegateAsync( "LoadProfiles", executionOptions: null, - (pwsh, _) => { + (pwsh, _) => + { pwsh.LoadProfiles(emptyProfilePaths); Assert.Empty(pwsh.Commands.Commands); }, @@ -162,7 +163,7 @@ public async Task CanLoadPSReadLine() { // NOTE: This is slightly more complicated than one would expect because we explicitly // need it to run on the pipeline thread otherwise Windows complains about the the - // thread's appartment state not matching. + // thread's apartment state not matching. Assert.True(await psesHost.ExecuteDelegateAsync( nameof(psesHost.TryLoadPSReadLine), executionOptions: null, diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs index 6d07530ca..5342ee7e6 100644 --- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -12,6 +12,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Session { + [Trait("Category", "Workspace")] public class WorkspaceTests { private static readonly Lazy s_lazyDriveLetter = new(() => Path.GetFullPath("\\").Substring(0, 1)); @@ -21,7 +22,6 @@ public class WorkspaceTests : string.Empty; [Fact] - [Trait("Category", "Workspace")] public void CanResolveWorkspaceRelativePath() { string workspacePath = TestUtilities.NormalizePath("c:/Test/Workspace/"); @@ -64,29 +64,26 @@ internal static List ExecuteEnumeratePSFiles( string[] excludeGlobs, string[] includeGlobs, int maxDepth, - bool ignoreReparsePoints - ) + bool ignoreReparsePoints) { - IEnumerable result = workspace.EnumeratePSFiles( + List fileList = new(workspace.EnumeratePSFiles( excludeGlobs: excludeGlobs, includeGlobs: includeGlobs, maxDepth: maxDepth, ignoreReparsePoints: ignoreReparsePoints - ); - List fileList = new(); - fileList.AddRange(result); - // Assume order is not important from EnumeratePSFiles and sort the array so we can use deterministic asserts - fileList.Sort(); + )); + // Assume order is not important from EnumeratePSFiles and sort the array so we can use + // deterministic asserts + fileList.Sort(); return fileList; } [Fact] - [Trait("Category", "Workspace")] public void CanRecurseDirectoryTree() { WorkspaceService workspace = FixturesWorkspace(); - List fileList = ExecuteEnumeratePSFiles( + List actual = ExecuteEnumeratePSFiles( workspace: workspace, excludeGlobs: s_defaultExcludeGlobs, includeGlobs: s_defaultIncludeGlobs, @@ -94,65 +91,58 @@ public void CanRecurseDirectoryTree() ignoreReparsePoints: s_defaultIgnoreReparsePoints ); - if (!RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) + List expected = new() { - // .Net Core doesn't appear to use the same three letter pattern matching rule although the docs - // suggest it should be find the '.ps1xml' files because we search for the pattern '*.ps1' - // ref https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netcore-2.1#System_IO_Directory_GetFiles_System_String_System_String_System_IO_EnumerationOptions_ - Assert.Equal(4, fileList.Count); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "donotfind.ps1"), fileList[0]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), fileList[1]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psm1"), fileList[2]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "rootfile.ps1"), fileList[3]); - } - else + Path.Combine(workspace.WorkspacePath, "nested", "donotfind.ps1"), + Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), + Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psm1"), + Path.Combine(workspace.WorkspacePath, "rootfile.ps1") + }; + + // .NET Core doesn't appear to use the same three letter pattern matching rule although the docs + // suggest it should be find the '.ps1xml' files because we search for the pattern '*.ps1' + // ref https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netcore-2.1#System_IO_Directory_GetFiles_System_String_System_String_System_IO_EnumerationOptions_ + if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) { - Assert.Equal(5, fileList.Count); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "donotfind.ps1"), fileList[0]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), fileList[1]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psm1"), fileList[2]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "other", "other.ps1xml"), fileList[3]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "rootfile.ps1"), fileList[4]); + expected.Insert(3, Path.Combine(workspace.WorkspacePath, "other", "other.ps1xml")); } + + Assert.Equal(expected, actual); } [Fact] - [Trait("Category", "Workspace")] public void CanRecurseDirectoryTreeWithLimit() { WorkspaceService workspace = FixturesWorkspace(); - List fileList = ExecuteEnumeratePSFiles( + List actual = ExecuteEnumeratePSFiles( workspace: workspace, excludeGlobs: s_defaultExcludeGlobs, includeGlobs: s_defaultIncludeGlobs, maxDepth: 1, ignoreReparsePoints: s_defaultIgnoreReparsePoints ); - - Assert.Single(fileList); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "rootfile.ps1"), fileList[0]); + Assert.Equal(new[] { Path.Combine(workspace.WorkspacePath, "rootfile.ps1") }, actual); } [Fact] - [Trait("Category", "Workspace")] public void CanRecurseDirectoryTreeWithGlobs() { WorkspaceService workspace = FixturesWorkspace(); - List fileList = ExecuteEnumeratePSFiles( + List actual = ExecuteEnumeratePSFiles( workspace: workspace, - excludeGlobs: new[] { "**/donotfind*" }, // Exclude any files starting with donotfind + excludeGlobs: new[] { "**/donotfind*" }, // Exclude any files starting with donotfind includeGlobs: new[] { "**/*.ps1", "**/*.psd1" }, // Only include PS1 and PSD1 files maxDepth: s_defaultMaxDepth, ignoreReparsePoints: s_defaultIgnoreReparsePoints ); - Assert.Equal(2, fileList.Count); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), fileList[0]); - Assert.Equal(Path.Combine(workspace.WorkspacePath, "rootfile.ps1"), fileList[1]); + Assert.Equal(new[] { + Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), + Path.Combine(workspace.WorkspacePath, "rootfile.ps1") + }, actual); } [Fact] - [Trait("Category", "Workspace")] public void CanDetermineIsPathInMemory() { string tempDir = Path.GetTempPath(); @@ -161,32 +151,29 @@ public void CanDetermineIsPathInMemory() const string shortUriForm = "git:/c%3A/Users/Keith/GitHub/dahlbyk/posh-git/src/PoshGitTypes.ps1?%7B%22path%22%3A%22c%3A%5C%5CUsers%5C%5CKeith%5C%5CGitHub%5C%5Cdahlbyk%5C%5Cposh-git%5C%5Csrc%5C%5CPoshGitTypes.ps1%22%2C%22ref%22%3A%22~%22%7D"; const string longUriForm = "gitlens-git:c%3A%5CUsers%5CKeith%5CGitHub%5Cdahlbyk%5Cposh-git%5Csrc%5CPoshGitTypes%3Ae0022701.ps1?%7B%22fileName%22%3A%22src%2FPoshGitTypes.ps1%22%2C%22repoPath%22%3A%22c%3A%2FUsers%2FKeith%2FGitHub%2Fdahlbyk%2Fposh-git%22%2C%22sha%22%3A%22e0022701fa12e0bc22d0458673d6443c942b974a%22%7D"; - var testCases = new[] { - // Test short file absolute paths - new { IsInMemory = false, Path = shortDirPath }, - new { IsInMemory = false, Path = shortFilePath }, - new { IsInMemory = false, Path = new Uri(shortDirPath).ToString() }, - new { IsInMemory = false, Path = new Uri(shortFilePath).ToString() }, - - // Test short file relative paths - not sure we'll ever get these but just in case - new { IsInMemory = false, Path = "foo.ps1" }, - new { IsInMemory = false, Path = Path.Combine(new [] { "..", "foo.ps1" }) }, - + string[] inMemoryPaths = new[] { // Test short non-file paths - new { IsInMemory = true, Path = "untitled:untitled-1" }, - new { IsInMemory = true, Path = shortUriForm }, - new { IsInMemory = true, Path = "inmemory://foo.ps1" }, + "untitled:untitled-1", + shortUriForm, + "inmemory://foo.ps1", + // Test long non-file path + longUriForm + }; - // Test long non-file path - known to have crashed PSES - new { IsInMemory = true, Path = longUriForm }, + Assert.All(inMemoryPaths, (p) => Assert.True(WorkspaceService.IsPathInMemory(p))); + + string[] notInMemoryPaths = new[] { + // Test short file absolute paths + shortDirPath, + shortFilePath, + new Uri(shortDirPath).ToString(), + new Uri(shortFilePath).ToString(), + // Test short file relative paths + "foo.ps1", + Path.Combine(new[] { "..", "foo.ps1" }) }; - foreach (var testCase in testCases) - { - Assert.True( - WorkspaceService.IsPathInMemory(testCase.Path) == testCase.IsInMemory, - $"Testing path {testCase.Path}"); - } + Assert.All(notInMemoryPaths, (p) => Assert.False(WorkspaceService.IsPathInMemory(p))); } } } From b17e5a3399ab4d2a65ed17d4ca5d5990e6ec0cf6 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 29 Jun 2022 11:49:18 -0700 Subject: [PATCH 002/327] Fixes 'processses' typo --- .../PowerShell/Handlers/IGetPSHostProcessesHandler.cs | 4 ++-- .../PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs | 4 ++-- .../LanguageServerProtocolMessageTests.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetPSHostProcessesHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetPSHostProcessesHandler.cs index 896564d1e..0772f4338 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetPSHostProcessesHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetPSHostProcessesHandler.cs @@ -7,9 +7,9 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { [Serial, Method("powerShell/getPSHostProcesses")] - internal interface IGetPSHostProcessesHandler : IJsonRpcRequestHandler { } + internal interface IGetPSHostProcessesHandler : IJsonRpcRequestHandler { } - internal class GetPSHostProcesssesParams : IRequest { } + internal class GetPSHostProcessesParams : IRequest { } internal class PSHostProcessResponse { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs index f288a9584..ed0b37b04 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -9,8 +9,8 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { - using Microsoft.PowerShell.EditorServices.Services.PowerShell; using System.Management.Automation; + using Microsoft.PowerShell.EditorServices.Services.PowerShell; internal class PSHostProcessAndRunspaceHandlers : IGetPSHostProcessesHandler, IGetRunspaceHandler { @@ -23,7 +23,7 @@ public PSHostProcessAndRunspaceHandlers(ILoggerFactory factory, IInternalPowerSh _executionService = executionService; } - public Task Handle(GetPSHostProcesssesParams request, CancellationToken cancellationToken) + public Task Handle(GetPSHostProcessesParams request, CancellationToken cancellationToken) { List psHostProcesses = new(); diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 2ecc29ebc..1ed6f3733 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -576,7 +576,7 @@ public async Task CanSendPowerShellGetPSHostProcessesRequestAsync() await PsesLanguageClient .SendRequest( "powerShell/getPSHostProcesses", - new GetPSHostProcesssesParams()) + new GetPSHostProcessesParams()) .Returning(CancellationToken.None).ConfigureAwait(true); } finally From e4f91300c0d677c9100b2c852a39284ae5074ba8 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 29 Jun 2022 12:23:54 -0700 Subject: [PATCH 003/327] Ignore intentional sync aways in `Dispose` methods --- .../Server/PsesServiceCollectionExtensions.cs | 3 ++- .../DebugAdapter/Handlers/LaunchAndAttachHandler.cs | 2 +- .../Services/PowerShell/Context/PowerShellContextFrame.cs | 2 +- .../PowerShell/Context/PowerShellVersionDetails.cs | 6 +++--- .../Processes/ServerProcess.cs | 8 ++++++-- .../Debugging/DebugServiceTests.cs | 2 ++ .../Extensions/ExtensionCommandTests.cs | 2 ++ .../Language/CompletionHandlerTests.cs | 2 ++ .../Language/SymbolsServiceTests.cs | 2 ++ .../Session/PsesInternalHostTests.cs | 2 ++ 10 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index c552515de..3c02510b9 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -48,8 +48,9 @@ public static IServiceCollection AddPsesLanguageServices( // is ready, it will be available. NOTE: We cannot await this because it // uses a lazy initialization to avoid a race with the dependency injection // framework, see the EditorObject class for that! +#pragma warning disable VSTHRD110 extensionService.InitializeAsync(); - +#pragma warning restore VSTHRD110 return extensionService; }) .AddSingleton(); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 4b7cd9acc..a93d4bb02 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -453,7 +453,7 @@ await _executionService.ExecutePSCommandAsync( // PSES sends the initialized event at the end of the Launch/Attach handler // The way that the Omnisharp server works is that this OnStarted handler runs after OnInitialized - // (after the Initialize DAP response is sent to the client) but before the _Initalized_ DAP event + // (after the Initialize DAP response is sent to the client) but before the _Initialized_ DAP event // gets sent to the client. Because of the way PSES handles breakpoints, // we can't send the Initialized event until _after_ we finish the Launch/Attach handler. // The flow above depicts this. To achieve this, we wait until _debugStateService.ServerStarted diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs index d837fb5d2..f75027ba8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs @@ -79,7 +79,7 @@ protected virtual void Dispose(bool disposing) public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + Dispose(true); GC.SuppressFinalize(this); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs index 957a032f8..4bbdd47e1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; using System.Collections; using System.Linq; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { @@ -91,7 +91,7 @@ public PowerShellVersionDetails( /// Gets the PowerShell version details for the given runspace. /// /// An ILogger implementation used for writing log messages. - /// The PowerShell instance for which to to get the version. + /// The PowerShell instance for which to get the version. /// A new PowerShellVersionDetails instance. public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerShell pwsh) { diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs index 020ee8779..8d744dc11 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs @@ -43,7 +43,7 @@ protected ServerProcess(ILoggerFactory loggerFactory) } /// - /// Finaliser for . + /// Finalizer for . /// ~ServerProcess() { @@ -53,7 +53,11 @@ protected ServerProcess(ILoggerFactory loggerFactory) /// /// Dispose of resources being used by the launcher. /// - public void Dispose() => Dispose(true); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } /// /// Dispose of resources being used by the launcher. diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 38f3b6231..74a233fb6 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -63,7 +63,9 @@ public void Dispose() { debugService.Abort(); debuggerStoppedQueue.Dispose(); +#pragma warning disable VSTHRD002 psesHost.StopAsync().Wait(); +#pragma warning restore VSTHRD002 GC.SuppressFinalize(this); } diff --git a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs index 1f7998ec5..2ae739c26 100644 --- a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs +++ b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs @@ -39,7 +39,9 @@ public ExtensionCommandTests() public void Dispose() { +#pragma warning disable VSTHRD002 psesHost.StopAsync().Wait(); +#pragma warning restore VSTHRD002 GC.SuppressFinalize(this); } diff --git a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs index fc1590d35..21d8cce5b 100644 --- a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs @@ -35,7 +35,9 @@ public CompletionHandlerTests() public void Dispose() { +#pragma warning disable VSTHRD002 psesHost.StopAsync().Wait(); +#pragma warning restore VSTHRD002 GC.SuppressFinalize(this); } diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 06879666d..959a9b1fa 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -46,7 +46,9 @@ public SymbolsServiceTests() public void Dispose() { +#pragma warning disable VSTHRD002 psesHost.StopAsync().GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 CommandHelpers.s_cmdletToAliasCache.Clear(); CommandHelpers.s_aliasToCmdletCache.Clear(); GC.SuppressFinalize(this); diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index c85aa0d2f..e07b504eb 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -27,7 +27,9 @@ public class PsesInternalHostTests : IDisposable public void Dispose() { +#pragma warning disable VSTHRD002 psesHost.StopAsync().Wait(); +#pragma warning restore VSTHRD002 GC.SuppressFinalize(this); } From 65456bd862cee289cbdf98f99dab64dedf48da9b Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 29 Jun 2022 11:18:16 -0700 Subject: [PATCH 004/327] Remove arbitrary timeouts from tests Our CI can be quite slow, and these were just arbitrary timeouts. If the tests succeed, they should never be hit. Let's instead give them as much time as possible. CI will eventually kill the run if it hangs, and locally devs can do the same. This should help improve CI stability. Also fixes the disposal bug in the tests. --- .../PowerShell/Host/PsesInternalHost.cs | 7 +- .../PowerShell/Utility/CancellationContext.cs | 13 ++- .../DebugAdapterClientExtensions.cs | 5 +- .../DebugAdapterProtocolMessageTests.cs | 88 +++++++++---------- .../LSPTestsFixures.cs | 13 +-- .../LanguageServerProtocolMessageTests.cs | 12 ++- .../Debugging/DebugServiceTests.cs | 4 +- .../Session/PsesInternalHostTests.cs | 3 +- 8 files changed, 68 insertions(+), 77 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 828cbf890..97ee2edd7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -291,11 +291,6 @@ public void TriggerShutdown() if (Interlocked.Exchange(ref _shuttingDown, 1) == 0) { _cancellationContext.CancelCurrentTaskStack(); - // NOTE: This is mostly for sanity's sake, as during debugging of tests I became - // concerned that the repeated creation and disposal of the host was not also - // joining and disposing this thread, leaving the tests in a weird state. Because - // the tasks have been canceled, we should be able to join this thread. - _pipelineThread.Join(); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index 10dba79a3..24d121ba0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -103,7 +103,18 @@ internal CancellationScope( public CancellationToken CancellationToken => _cancellationSource.Token; - public void Cancel() => _cancellationSource.Cancel(); + public void Cancel() + { + try + { + _cancellationSource.Cancel(); + } + catch (ObjectDisposedException) + { + // We don't want this race condition to cause flaky tests. + // TODO: Find out the cause of the race! + } + } public bool IsIdleScope { get; } diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs index 65ac1d20c..3ae9748d1 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterClientExtensions.cs @@ -6,7 +6,6 @@ using Microsoft.PowerShell.EditorServices.Handlers; using OmniSharp.Extensions.DebugAdapter.Client; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; -using System.Threading; namespace PowerShellEditorServices.Test.E2E { @@ -29,9 +28,7 @@ public static async Task LaunchScript(this DebugAdapterClient debugAdapterClient } // This will check to see if we received the Initialized event from the server. - await Task.Run( - async () => await started.Task.ConfigureAwait(true), - new CancellationTokenSource(2000).Token).ConfigureAwait(true); + await started.Task.ConfigureAwait(true); } } } diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 14e250823..f80f08f49 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -39,7 +39,7 @@ public async Task InitializeAsync() { LoggerFactory factory = new(); _psesProcess = new PsesStdioProcess(factory, true); - await _psesProcess.Start().ConfigureAwait(false); + await _psesProcess.Start().ConfigureAwait(true); TaskCompletionSource initialized = new(); @@ -90,26 +90,21 @@ public async Task InitializeAsync() // that gets completed when we receive the response to Initialize // This tells us that we are ready to send messages to PSES... but are not stuck waiting for // Initialized. - PsesDebugAdapterClient.Initialize(CancellationToken.None).ConfigureAwait(false); - await initialized.Task.ConfigureAwait(false); +#pragma warning disable CS4014 + PsesDebugAdapterClient.Initialize(CancellationToken.None).ConfigureAwait(true); +#pragma warning restore CS4014 + await initialized.Task.ConfigureAwait(true); } public async Task DisposeAsync() { - try + await PsesDebugAdapterClient.RequestDisconnect(new DisconnectArguments { - await PsesDebugAdapterClient.RequestDisconnect(new DisconnectArguments - { - Restart = false, - TerminateDebuggee = true - }).ConfigureAwait(false); - await _psesProcess.Stop().ConfigureAwait(false); - PsesDebugAdapterClient?.Dispose(); - } - catch (ObjectDisposedException) - { - // Language client has a disposal bug in it - } + Restart = false, + TerminateDebuggee = true + }).ConfigureAwait(true); + await _psesProcess.Stop().ConfigureAwait(true); + PsesDebugAdapterClient?.Dispose(); } private static string NewTestFile(string script, bool isPester = false) @@ -147,7 +142,16 @@ private string GenerateScriptFromLoggingStatements(params string[] logStatements return builder.ToString(); } - private static string[] GetLog() => File.ReadLines(s_testOutputPath).ToArray(); + private static async Task GetLog() + { + while (!File.Exists(s_testOutputPath)) + { + await Task.Delay(1000).ConfigureAwait(true); + } + // Sleep one more time after the file exists so whatever is writing can finish. + await Task.Delay(1000).ConfigureAwait(true); + return File.ReadLines(s_testOutputPath).ToArray(); + } [Fact] public void CanInitializeWithCorrectServerSettings() @@ -165,14 +169,12 @@ public async Task CanLaunchScriptWithNoBreakpointsAsync() { string filePath = NewTestFile(GenerateScriptFromLoggingStatements("works")); - await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(false); + await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(true); - ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); + ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); Assert.NotNull(configDoneResponse); - await Task.Delay(2000).ConfigureAwait(false); - - string[] log = GetLog(); - Assert.Equal("works", log[0]); + Assert.Collection(await GetLog().ConfigureAwait(true), + (i) => Assert.Equal("works", i)); } [SkippableFact] @@ -188,7 +190,7 @@ public async Task CanSetBreakpointsAsync() "after breakpoint" )); - await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(false); + await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(true); // {"command":"setBreakpoints","arguments":{"source":{"name":"dfsdfg.ps1","path":"/Users/tyleonha/Code/PowerShell/Misc/foo/dfsdfg.ps1"},"lines":[2],"breakpoints":[{"line":2}],"sourceModified":false},"type":"request","seq":3} SetBreakpointsResponse setBreakpointsResponse = await PsesDebugAdapterClient.SetBreakpoints(new SetBreakpointsArguments @@ -196,29 +198,24 @@ public async Task CanSetBreakpointsAsync() Source = new Source { Name = Path.GetFileName(filePath), Path = filePath }, Breakpoints = new SourceBreakpoint[] { new SourceBreakpoint { Line = 2 } }, SourceModified = false, - }).ConfigureAwait(false); + }).ConfigureAwait(true); Breakpoint breakpoint = setBreakpointsResponse.Breakpoints.First(); Assert.True(breakpoint.Verified); Assert.Equal(filePath, breakpoint.Source.Path, ignoreCase: s_isWindows); Assert.Equal(2, breakpoint.Line); - ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); + ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); Assert.NotNull(configDoneResponse); - await Task.Delay(2000).ConfigureAwait(false); - - string[] log = GetLog(); - Assert.Single(log, (i) => i == "before breakpoint"); + Assert.Collection(await GetLog().ConfigureAwait(true), + (i) => Assert.Equal("before breakpoint", i)); + File.Delete(s_testOutputPath); ContinueResponse continueResponse = await PsesDebugAdapterClient.RequestContinue( new ContinueArguments { ThreadId = 1 }).ConfigureAwait(true); Assert.NotNull(continueResponse); - await Task.Delay(2000).ConfigureAwait(false); - - log = GetLog(); - Assert.Collection(log, - (i) => Assert.Equal("before breakpoint", i), + Assert.Collection(await GetLog().ConfigureAwait(true), (i) => Assert.Equal("at breakpoint", i), (i) => Assert.Equal("after breakpoint", i)); } @@ -247,24 +244,24 @@ public async Task CanStepPastSystemWindowsForms() "Write-Host $form" })); - await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(false); + await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(true); SetFunctionBreakpointsResponse setBreakpointsResponse = await PsesDebugAdapterClient.SetFunctionBreakpoints( new SetFunctionBreakpointsArguments { Breakpoints = new FunctionBreakpoint[] { new FunctionBreakpoint { Name = "Write-Host", } } - }).ConfigureAwait(false); + }).ConfigureAwait(true); Breakpoint breakpoint = setBreakpointsResponse.Breakpoints.First(); Assert.True(breakpoint.Verified); - ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); + ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); Assert.NotNull(configDoneResponse); - await Task.Delay(2000).ConfigureAwait(false); + await Task.Delay(5000).ConfigureAwait(true); VariablesResponse variablesResponse = await PsesDebugAdapterClient.RequestVariables( - new VariablesArguments { VariablesReference = 1 }).ConfigureAwait(false); + new VariablesArguments { VariablesReference = 1 }).ConfigureAwait(true); Variable form = variablesResponse.Variables.FirstOrDefault(v => v.Name == "$form"); Assert.NotNull(form); @@ -286,13 +283,12 @@ public async Task CanLaunchScriptWithCommentedLastLineAsync() // PsesLaunchRequestArguments.Script, which is then assigned to // DebugStateService.ScriptToLaunch in that handler, and finally used by the // ConfigurationDoneHandler in LaunchScriptAsync. - await PsesDebugAdapterClient.LaunchScript(script, Started).ConfigureAwait(false); + await PsesDebugAdapterClient.LaunchScript(script, Started).ConfigureAwait(true); - ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(false); + ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); Assert.NotNull(configDoneResponse); - await Task.Delay(2000).ConfigureAwait(false); - - Assert.Collection(GetLog(), (i) => Assert.Equal("a log statement", i)); + Assert.Collection(await GetLog().ConfigureAwait(true), + (i) => Assert.Equal("a log statement", i)); } } } diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs index 12e29ee07..c31333d89 100644 --- a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs +++ b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs @@ -95,16 +95,9 @@ public async Task InitializeAsync() public async Task DisposeAsync() { - try - { - await PsesLanguageClient.Shutdown().ConfigureAwait(false); - await _psesProcess.Stop().ConfigureAwait(false); - PsesLanguageClient?.Dispose(); - } - catch (ObjectDisposedException) - { - // Language client has a disposal bug in it - } + await PsesLanguageClient.Shutdown().ConfigureAwait(false); + await _psesProcess.Stop().ConfigureAwait(false); + PsesLanguageClient?.Dispose(); } } } diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 1ed6f3733..43ca01a13 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -12,6 +12,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Handlers; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.Configuration; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.Template; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client; @@ -21,10 +25,6 @@ using Xunit; using Xunit.Abstractions; using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; -using Microsoft.PowerShell.EditorServices.Logging; -using Microsoft.PowerShell.EditorServices.Services.Configuration; -using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.Template; namespace PowerShellEditorServices.Test.E2E { @@ -1149,8 +1149,6 @@ await PsesLanguageClient [Fact] public async Task CanSendEvaluateRequestAsync() { - using CancellationTokenSource cancellationSource = new(millisecondsDelay: 5000); - EvaluateResponseBody evaluateResponseBody = await PsesLanguageClient .SendRequest( @@ -1159,7 +1157,7 @@ await PsesLanguageClient { Expression = "Get-ChildItem" }) - .Returning(cancellationSource.Token).ConfigureAwait(true); + .Returning(CancellationToken.None).ConfigureAwait(true); // These always gets returned so this test really just makes sure we get _any_ response. Assert.Equal("", evaluateResponseBody.Result); diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 74a233fb6..cb018ae5e 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -103,7 +103,7 @@ private Task ExecutePowerShellCommand(string command, params string[] args) private void AssertDebuggerPaused() { - DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(new CancellationTokenSource(5000).Token); + DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(CancellationToken.None); Assert.Empty(eventArgs.OriginalEvent.Breakpoints); } @@ -112,7 +112,7 @@ private void AssertDebuggerStopped( int lineNumber = -1, CommandBreakpointDetails commandBreakpointDetails = default) { - DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(new CancellationTokenSource(5000).Token); + DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(CancellationToken.None); Assert.True(psesHost.DebugContext.IsStopped); diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index e07b504eb..7412ac59d 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -86,11 +86,12 @@ public async Task CanQueueParallelPSCommands() [Fact] public async Task CanCancelExecutionWithToken() { + using CancellationTokenSource cancellationSource = new(millisecondsDelay: 1000); await Assert.ThrowsAsync(() => { return psesHost.ExecutePSCommandAsync( new PSCommand().AddScript("Start-Sleep 10"), - new CancellationTokenSource(1000).Token); + cancellationSource.Token); }).ConfigureAwait(true); } From d29dc6da6c7a0dc5e0ae538b3ac93579907fb7ce Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 30 Jun 2022 16:05:11 -0700 Subject: [PATCH 005/327] Set `ServerStarted` with `TrySetResult` since unit tests might do it twice --- .../Services/DebugAdapter/DebugEventHandlerService.cs | 2 +- .../Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index 69ddb1209..e3237a3b0 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -101,7 +101,7 @@ private void OnRunspaceChanged(object sender, RunspaceChangedEventArgs e) // Sends the InitializedEvent so that the debugger will continue // sending configuration requests _debugStateService.WaitingForAttach = false; - _debugStateService.ServerStarted.SetResult(true); + _debugStateService.ServerStarted.TrySetResult(true); } return; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index a93d4bb02..580b7cc89 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -210,7 +210,7 @@ public async Task Handle(PsesLaunchRequestArguments request, Can // Sends the InitializedEvent so that the debugger will continue // sending configuration requests - _debugStateService.ServerStarted.SetResult(true); + _debugStateService.ServerStarted.TrySetResult(true); return new LaunchResponse(); } @@ -440,7 +440,7 @@ await _executionService.ExecutePSCommandAsync( if (runspaceVersion.Version.Major >= 7) { - _debugStateService.ServerStarted.SetResult(true); + _debugStateService.ServerStarted.TrySetResult(true); } return new AttachResponse(); } From 9480cd1dabdfc9e5284a1cc324b25229ed8ec038 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 1 Jul 2022 11:56:49 -0700 Subject: [PATCH 006/327] Improve skipped test messages --- .../DebugAdapterProtocolMessageTests.cs | 11 ++++--- .../LanguageServerProtocolMessageTests.cs | 31 +++++++------------ 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index f80f08f49..b151b57d0 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -180,9 +180,8 @@ public async Task CanLaunchScriptWithNoBreakpointsAsync() [SkippableFact] public async Task CanSetBreakpointsAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode, - "You can't set breakpoints in ConstrainedLanguage mode."); + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode, + "Breakpoints can't be set in Constrained Language Mode."); string filePath = NewTestFile(GenerateScriptFromLoggingStatements( "before breakpoint", @@ -234,8 +233,10 @@ public async Task CanSetBreakpointsAsync() [SkippableFact] public async Task CanStepPastSystemWindowsForms() { - Skip.IfNot(PsesStdioProcess.IsWindowsPowerShell); - Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode); + Skip.IfNot(PsesStdioProcess.IsWindowsPowerShell, + "Windows Forms requires Windows PowerShell."); + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode, + "Breakpoints can't be set in Constrained Language Mode."); string filePath = NewTestFile(string.Join(Environment.NewLine, new[] { diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 43ca01a13..bcfd49de9 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -154,8 +154,7 @@ function CanSendWorkspaceSymbolRequest { [SkippableFact] public async Task CanReceiveDiagnosticsFromFileOpenAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); NewTestFile("$a = 4"); @@ -177,8 +176,7 @@ public async Task WontReceiveDiagnosticsFromFileOpenThatIsNotPowerShellAsync() [SkippableFact] public async Task CanReceiveDiagnosticsFromFileChangedAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string filePath = NewTestFile("$a = 4"); @@ -229,8 +227,7 @@ public async Task CanReceiveDiagnosticsFromFileChangedAsync() [SkippableFact] public async Task CanReceiveDiagnosticsFromConfigurationChangeAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); NewTestFile("gci | % { $_ }"); @@ -330,8 +327,7 @@ await PsesLanguageClient [SkippableFact] public async Task CanSendFormattingRequestAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string scriptPath = NewTestFile(@" @@ -367,8 +363,7 @@ public async Task CanSendFormattingRequestAsync() [SkippableFact] public async Task CanSendRangeFormattingRequestAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string scriptPath = NewTestFile(@" @@ -891,8 +886,7 @@ function CanSendReferencesCodeLensRequest { [SkippableFact] public async Task CanSendCodeActionRequestAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string filePath = NewTestFile("gci"); @@ -971,7 +965,7 @@ public async Task CanSendCompletionAndCompletionResolveRequestAsync() Assert.Contains("Writes customized output to a host", updatedCompletionItem.Documentation.String); } - [SkippableFact(Skip = "This test is too flaky right now.")] + [SkippableFact(Skip = "Completion for Expand-SlowArchive is flaky.")] public async Task CanSendCompletionResolveWithModulePrefixRequestAsync() { await PsesLanguageClient @@ -1090,7 +1084,8 @@ await PsesLanguageClient [SkippableFact] public async Task CanSendGetProjectTemplatesRequestAsync() { - Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode, "Plaster doesn't work in ConstrainedLanguage mode."); + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode, + "Plaster doesn't work in Constrained Language Mode."); GetProjectTemplatesResponse getProjectTemplatesResponse = await PsesLanguageClient @@ -1109,8 +1104,7 @@ await PsesLanguageClient [SkippableFact] public async Task CanSendGetCommentHelpRequestAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode && PsesStdioProcess.IsWindowsPowerShell, "Windows PowerShell doesn't trust PSScriptAnalyzer by default so it won't load."); string scriptPath = NewTestFile(@" @@ -1180,9 +1174,8 @@ await PsesLanguageClient [SkippableFact] public async Task CanSendExpandAliasRequestAsync() { - Skip.If( - PsesStdioProcess.RunningInConstrainedLanguageMode, - "This feature currently doesn't support ConstrainedLanguage Mode."); + Skip.If(PsesStdioProcess.RunningInConstrainedLanguageMode, + "The expand alias request doesn't work in Constrained Language Mode."); ExpandAliasResult expandAliasResult = await PsesLanguageClient From b972fb24312670be988b5ca9b3d937b7006c3bea Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 1 Jul 2022 11:58:14 -0700 Subject: [PATCH 007/327] Quiet build/test output and format build script --- PowerShellEditorServices.build.ps1 | 81 +++++++++++++++--------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 6bde9dd8a..bb7fe352a 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -11,12 +11,13 @@ param( [string]$DefaultModuleRepository = "PSGallery", + [string[]]$VerbosityArgs = @("--verbosity", "quiet", "--nologo"), + # See: https://docs.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests [string]$TestFilter = '', # See: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test - # E.g. use @("--logger", "console;verbosity=detailed") for detailed console output instead - [string[]]$TestArgs = @("--logger", "trx") + [string[]]$TestArgs = $VerbosityArgs + @("--logger", "console;verbosity=normal", "--logger", "trx") ) #Requires -Modules @{ModuleName="InvokeBuild"; ModuleVersion="5.0.0"} @@ -38,9 +39,9 @@ $script:BuildInfoPath = [System.IO.Path]::Combine($PSScriptRoot, "src", "PowerSh $script:PsesCommonProps = [xml](Get-Content -Raw "$PSScriptRoot/PowerShellEditorServices.Common.props") $script:NetRuntime = @{ - PS7 = 'netcoreapp3.1' - PS72 = 'net6.0' - Desktop = 'net462' + PS7 = 'netcoreapp3.1' + PS72 = 'net6.0' + Desktop = 'net462' Standard = 'netstandard2.0' } @@ -54,31 +55,31 @@ if (Get-Command git -ErrorAction SilentlyContinue) { git update-index --assume-unchanged "$PSScriptRoot/src/PowerShellEditorServices.Hosting/BuildInfo.cs" } -task FindDotNet { - assert (Get-Command dotnet -ErrorAction SilentlyContinue) "dotnet not found, please install it: https://aka.ms/dotnet-cli" +Task FindDotNet { + Assert (Get-Command dotnet -ErrorAction SilentlyContinue) "dotnet not found, please install it: https://aka.ms/dotnet-cli" # Strip out semantic version metadata so it can be cast to `Version` $existingVersion, $null = (dotnet --version) -split '-' - assert ([Version]$existingVersion -ge [Version]("6.0")) ".NET SDK 6.0 or higher is required, please update it: https://aka.ms/dotnet-cli" + Assert ([Version]$existingVersion -ge [Version]("6.0")) ".NET SDK 6.0 or higher is required, please update it: https://aka.ms/dotnet-cli" # Anywhere other than on a Mac with an M1 processor, we additionally # need the .NET 3.1 runtime for our netcoreapp3.1 framework. if (-not $script:IsAppleM1 -and -not $script:IsArm64) { $runtimes = dotnet --list-runtimes - assert ($runtimes -match "Microsoft.NETCore.App 3.1") ".NET Runtime 3.1 required but not found!" + Assert ($runtimes -match "Microsoft.NETCore.App 3.1") ".NET Runtime 3.1 required but not found!" } Write-Host "Using dotnet v$(dotnet --version) at path $((Get-Command dotnet).Source)" -ForegroundColor Green } -task BinClean { +Task BinClean { Remove-Item $PSScriptRoot\.tmp -Recurse -Force -ErrorAction Ignore Remove-Item $PSScriptRoot\module\PowerShellEditorServices\bin -Recurse -Force -ErrorAction Ignore Remove-Item $PSScriptRoot\module\PowerShellEditorServices.VSCode\bin -Recurse -Force -ErrorAction Ignore } -task Clean FindDotNet, BinClean, { - exec { & dotnet clean } +Task Clean FindDotNet, BinClean, { + Exec { & dotnet clean $VerbosityArgs } Get-ChildItem -Recurse $PSScriptRoot\src\*.nupkg | Remove-Item -Force -ErrorAction Ignore Get-ChildItem $PSScriptRoot\PowerShellEditorServices*.zip | Remove-Item -Force -ErrorAction Ignore Get-ChildItem $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US\*-help.xml | Remove-Item -Force -ErrorAction Ignore @@ -93,7 +94,7 @@ task Clean FindDotNet, BinClean, { } } -task CreateBuildInfo { +Task CreateBuildInfo { $buildVersion = "" $buildOrigin = "Development" $buildCommit = git rev-parse HEAD @@ -147,33 +148,33 @@ namespace Microsoft.PowerShell.EditorServices.Hosting "@ if (Compare-Object $buildInfoContents.Split([Environment]::NewLine) (Get-Content $script:BuildInfoPath)) { - Write-Host "Updating Build Info" + Write-Host "Updating build info." Set-Content -LiteralPath $script:BuildInfoPath -Value $buildInfoContents -Force } } -task SetupHelpForTests { +Task SetupHelpForTests { if (-not (Get-Help Write-Host).Examples) { - Write-Host "Updating help for tests" + Write-Host "Updating help for tests." Update-Help -Module Microsoft.PowerShell.Utility -Force -Scope CurrentUser } } Task Build FindDotNet, CreateBuildInfo, { - exec { & dotnet restore } - exec { & dotnet publish -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } - exec { & dotnet publish -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } + Exec { & dotnet restore $VerbosityArgs } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } if (-not $script:IsNix) { - exec { & dotnet publish -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } } # Build PowerShellEditorServices.VSCode module - exec { & dotnet publish -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } } -task Test TestServer, TestE2E +Task Test TestServer, TestE2E -task TestServer TestServerWinPS, TestServerPS7, TestServerPS72 +Task TestServer TestServerWinPS, TestServerPS7, TestServerPS72 Task TestServerWinPS -If (-not $script:IsNix) Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ @@ -181,43 +182,43 @@ Task TestServerWinPS -If (-not $script:IsNix) Build, SetupHelpForTests, { # that is debuggable! If architecture is added, the assembly path gets an # additional folder, necesstiating fixes to find the commands definition # file and test files. - exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.Desktop } + Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.Desktop } } -task TestServerPS7 -If (-not $script:IsAppleM1 -and -not $script:IsArm64) Build, SetupHelpForTests, { +Task TestServerPS7 -If (-not $script:IsAppleM1 -and -not $script:IsArm64) Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ - exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } + Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } } -task TestServerPS72 Build, SetupHelpForTests, { +Task TestServerPS72 Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ - exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } + Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } } -task TestE2E Build, SetupHelpForTests, { +Task TestE2E Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test.E2E\ $env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" } $NetRuntime = if ($IsAppleM1 -or $script:IsArm64) { $script:NetRuntime.PS72 } else { $script:NetRuntime.PS7 } - exec { & dotnet $script:dotnetTestArgs $NetRuntime } + Exec { & dotnet $script:dotnetTestArgs $NetRuntime } - # Run E2E tests in ConstrainedLanguage mode. if (!$script:IsNix) { if (-not [Security.Principal.WindowsIdentity]::GetCurrent().Owner.IsWellKnown("BuiltInAdministratorsSid")) { - Write-Warning 'Skipping E2E CLM tests as they must be ran in an elevated process.' + Write-Warning "Skipping Constrained Language Mode tests as they must be ran in an elevated process." return } try { + Write-Host "Running end-to-end tests in Constrained Language Mode." [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine); - exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } + Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } } finally { [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine); } } } -task LayoutModule -After Build { +Task LayoutModule -After Build { $modulesDir = "$PSScriptRoot/module" $psesVSCodeBinOutputPath = "$modulesDir/PowerShellEditorServices.VSCode/bin" $psesOutputPath = "$modulesDir/PowerShellEditorServices" @@ -226,8 +227,8 @@ task LayoutModule -After Build { $psesCoreHostPath = "$psesBinOutputPath/Core" $psesDeskHostPath = "$psesBinOutputPath/Desktop" - foreach ($dir in $psesDepsPath,$psesCoreHostPath,$psesDeskHostPath,$psesVSCodeBinOutputPath) { - New-Item -Force -Path $dir -ItemType Directory + foreach ($dir in $psesDepsPath, $psesCoreHostPath, $psesDeskHostPath, $psesVSCodeBinOutputPath) { + New-Item -Force -Path $dir -ItemType Directory | Out-Null } # Copy Third Party Notices.txt to module folder @@ -310,7 +311,7 @@ task RestorePsesModules -After Build { # Save each module in the modules.json file foreach ($moduleName in $moduleInfos.Keys) { if (Test-Path -Path (Join-Path -Path $submodulePath -ChildPath $moduleName)) { - Write-Host "`tModule '${moduleName}' already detected. Skipping" + Write-Host "`tModule '${moduleName}' already detected, skipping!" continue } @@ -331,9 +332,9 @@ task RestorePsesModules -After Build { } Task BuildCmdletHelp -After LayoutModule { - New-ExternalHelp -Path $PSScriptRoot\module\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US -Force - New-ExternalHelp -Path $PSScriptRoot\module\PowerShellEditorServices.VSCode\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices.VSCode\en-US -Force + New-ExternalHelp -Path $PSScriptRoot\module\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US -Force | Out-Null + New-ExternalHelp -Path $PSScriptRoot\module\PowerShellEditorServices.VSCode\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices.VSCode\en-US -Force | Out-Null } # The default task is to run the entire CI build -task . Clean, Build, Test +Task . Clean, Build, Test From 80ab5a47cfb748bbbbb0468f2e622fdf21e7b117 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 1 Jul 2022 12:18:40 -0700 Subject: [PATCH 008/327] Fix flaky `CompletesAttributeValue` test --- .../Completion/CompleteAttributeValue.cs | 3 --- .../Language/CompletionHandlerTests.cs | 12 +++++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/test/PowerShellEditorServices.Test.Shared/Completion/CompleteAttributeValue.cs b/test/PowerShellEditorServices.Test.Shared/Completion/CompleteAttributeValue.cs index d38c430a4..629e50aec 100644 --- a/test/PowerShellEditorServices.Test.Shared/Completion/CompleteAttributeValue.cs +++ b/test/PowerShellEditorServices.Test.Shared/Completion/CompleteAttributeValue.cs @@ -25,7 +25,6 @@ internal static class CompleteAttributeValue FilterText = "ValueFromPipeline", InsertText = "ValueFromPipeline", Label = "ValueFromPipeline", - SortText = "0001ValueFromPipeline", TextEdit = new TextEdit { NewText = "ValueFromPipeline", @@ -44,7 +43,6 @@ internal static class CompleteAttributeValue FilterText = "ValueFromPipelineByPropertyName", InsertText = "ValueFromPipelineByPropertyName", Label = "ValueFromPipelineByPropertyName", - SortText = "0002ValueFromPipelineByPropertyName", TextEdit = new TextEdit { NewText = "ValueFromPipelineByPropertyName", @@ -63,7 +61,6 @@ internal static class CompleteAttributeValue FilterText = "ValueFromRemainingArguments", InsertText = "ValueFromRemainingArguments", Label = "ValueFromRemainingArguments", - SortText = "0003ValueFromRemainingArguments", TextEdit = new TextEdit { NewText = "ValueFromRemainingArguments", diff --git a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs index 21d8cce5b..3ddedb89f 100644 --- a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs @@ -111,11 +111,13 @@ public async Task CompletesVariableInFile() public async Task CompletesAttributeValue() { (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteAttributeValue.SourceDetails).ConfigureAwait(true); - Assert.Equal(3, results.Count()); - Assert.Collection(results.OrderBy(c => c.SortText), - actual => Assert.Equal(actual with { Data = null }, CompleteAttributeValue.ExpectedCompletion1), - actual => Assert.Equal(actual with { Data = null }, CompleteAttributeValue.ExpectedCompletion2), - actual => Assert.Equal(actual with { Data = null }, CompleteAttributeValue.ExpectedCompletion3)); + // NOTE: Since the completions come through un-ordered from PowerShell, their SortText + // (which has an index prepended from the original order) will mis-match our assumed + // order; hence we ignore it. + Assert.Collection(results.OrderBy(c => c.Label), + actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion1), + actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion2), + actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion3)); } [Fact] From 4e4fdc3a08ba4a7cb76479574acb4649e9b4cd71 Mon Sep 17 00:00:00 2001 From: Andy Schwartzmeyer Date: Fri, 1 Jul 2022 13:29:01 -0700 Subject: [PATCH 009/327] Add end-to-end Pester unit test (#1846) --- .../DebugAdapterProtocolMessageTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index b151b57d0..ab99aa6eb 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -291,5 +291,45 @@ public async Task CanLaunchScriptWithCommentedLastLineAsync() Assert.Collection(await GetLog().ConfigureAwait(true), (i) => Assert.Equal("a log statement", i)); } + + [SkippableFact] + public async Task CanRunPesterTestFile() + { + Skip.If(s_isWindows, "Windows CI Pester is broken."); + /* TODO: Get this to work on Windows. + string pesterLog = Path.Combine(s_binDir, Path.GetRandomFileName() + ".log"); + + string testCommand = @" + Start-Transcript -Path '" + pesterLog + @"' + Install-Module -Name Pester -RequiredVersion 5.3.3 -Force -PassThru | Write-Host + Import-Module -Name Pester -RequiredVersion 5.3.3 -PassThru | Write-Host + Get-Content '" + pesterTest + @"' + Stop-Transcript"; + + using CancellationTokenSource cts = new(5000); + while (!File.Exists(pesterLog) && !cts.Token.IsCancellationRequested) + { + await Task.Delay(1000).ConfigureAwait(true); + } + await Task.Delay(15000).ConfigureAwait(true); + _output.WriteLine(File.ReadAllText(pesterLog)); + */ + + string pesterTest = NewTestFile(@" + Describe 'A' { + Context 'B' { + It 'C' { + { throw 'error' } | Should -Throw + } + It 'D' { + " + GenerateScriptFromLoggingStatements("pester") + @" + } + } + }", isPester: true); + + await PsesDebugAdapterClient.LaunchScript($"Invoke-Pester -Script '{pesterTest}'", Started).ConfigureAwait(true); + await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); + Assert.Collection(await GetLog().ConfigureAwait(true), (i) => Assert.Equal("pester", i)); + } } } From b30934e240d7fc811fc9fe52104bf678be131e96 Mon Sep 17 00:00:00 2001 From: Andy Schwartzmeyer Date: Wed, 6 Jul 2022 11:19:52 -0700 Subject: [PATCH 010/327] Add `Directory.Exists()` check to `SetInitialWorkingDirectoryAsync()` (#1851) There was an edge case where, after setting and un-setting the `powershell.cwd` property in the VS Code client, instead of being `null` the setting would be the empty string `""`. The client did not care about this, and so left the server to silently crash on startup. We previously assumed that by the time the server started, the directory would be validated by the client, but this was wrong. So now we check its existence first. --- .../Services/PowerShell/Host/PsesInternalHost.cs | 8 +++++--- .../Session/PsesInternalHostTests.cs | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 97ee2edd7..1826d4ce7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -475,9 +475,11 @@ internal Task LoadHostProfilesAsync(CancellationToken cancellationToken) public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cancellationToken) { - return ExecutePSCommandAsync( - new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", path), - cancellationToken); + return Directory.Exists(path) + ? ExecutePSCommandAsync( + new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", path), + cancellationToken) + : Task.CompletedTask; } private void Run() diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index 7412ac59d..f0d82c0d9 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -186,5 +186,20 @@ await psesHost.ExecutePSCommandAsync( new PSCommand().AddScript("\"protocol=https`nhost=myhost.com`nusername=john`npassword=doe`n`n\" | git.exe credential approve; if ($LastExitCode) { throw }"), CancellationToken.None).ConfigureAwait(true); } + + [Theory] + [InlineData("")] // Regression test for "unset" path. + [InlineData("C:\\Some\\Bad\\Directory")] // Non-existent directory. + [InlineData("testhost.dll")] // Existent file. + public async Task CanHandleBadInitialWorkingDirectory(string path) + { + string cwd = Environment.CurrentDirectory; + await psesHost.SetInitialWorkingDirectoryAsync(path, CancellationToken.None).ConfigureAwait(true); + + IReadOnlyList getLocation = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddCommand("Get-Location"), + CancellationToken.None).ConfigureAwait(true); + Assert.Collection(getLocation, (d) => Assert.Equal(cwd, d, ignoreCase: true)); + } } } From a383c7745152aa36dc8b97ba7f7363902e5b89a6 Mon Sep 17 00:00:00 2001 From: Andy Schwartzmeyer Date: Wed, 6 Jul 2022 12:26:12 -0700 Subject: [PATCH 011/327] Catch `OperationCanceledException` in both command loops (#1850) Our flaky extension command test seems to be flaky because sometimes another task gets queued, and since it runs in the foreground it cancels that task. Interactively, this happens in the first loop (with `DoOneRepl`) which catches the cancellation exception; but when under tests that is a no-op, so it happens in the second loop. Hence we should duplicate the same logic and so catch that exception. --- .../Commands/StartEditorServicesCommand.cs | 4 +++- .../Services/PowerShell/Host/PsesInternalHost.cs | 9 ++++++++- .../Extensions/ExtensionCommandTests.cs | 5 ++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 3d31ede06..325e7220f 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -228,8 +228,10 @@ protected override void EndProcessing() using EditorServicesLoader psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, sessionFileWriter, _loggerUnsubscribers); _logger.Log(PsesLogLevel.Verbose, "Loading EditorServices"); - // Start editor services and wait here until it shuts down + // Synchronously start editor services and wait here until it shuts down. +#pragma warning disable VSTHRD002 psesLoader.LoadAndRunEditorServicesAsync().GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 } catch (Exception e) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 1826d4ce7..81f4815ab 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -771,7 +771,14 @@ private void RunExecutionLoop(bool isForDebug = false) && !cancellationScope.CancellationToken.IsCancellationRequested && _taskQueue.TryTake(out ISynchronousTask task)) { - task.ExecuteSynchronously(cancellationScope.CancellationToken); + try + { + task.ExecuteSynchronously(cancellationScope.CancellationToken); + } + catch (OperationCanceledException e) + { + _logger.LogDebug(e, "Task {Task} was canceled!", task); + } } if (_shouldExit diff --git a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs index 2ae739c26..b2e5e6b46 100644 --- a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs +++ b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs @@ -33,7 +33,9 @@ public ExtensionCommandTests() serviceProvider: null, editorOperations: null, executionService: psesHost); +#pragma warning disable VSTHRD002 extensionService.InitializeAsync().Wait(); +#pragma warning restore VSTHRD002 extensionCommandService = new(extensionService); } @@ -110,7 +112,8 @@ await psesHost.ExecutePSCommandAsync( Assert.Equal(commandName, commandAdded.Name); Assert.Equal(commandDisplayName, commandAdded.DisplayName); - // Invoke the command + // Invoke the command. + // TODO: What task was this cancelling? await extensionCommandService.InvokeCommandAsync("test.scriptblock", editorContext).ConfigureAwait(true); // Assert the expected value From 00e48e797a43aa3627ec7e6676dd7837b8f6475f Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Mon, 11 Jul 2022 10:35:48 -0700 Subject: [PATCH 012/327] Update CHANGELOG for `v3.4.6` --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6da2dd67..bc3bc688a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # PowerShell Editor Services Release History +## v3.4.6 +### Monday, July 11, 2022 + +- 🐛 🛫 [PowerShellEditorServices #1849](https://github.com/PowerShell/PowerShellEditorServices/pull/1851) - Add `Directory.Exists()` check to `SetInitialWorkingDirectoryAsync()`. +- ✨ 🚨 [PowerShellEditorServices #1850](https://github.com/PowerShell/PowerShellEditorServices/pull/1850) - Catch `OperationCanceledException` in both command loops. +- ✨ 🚨 [PowerShellEditorServices #1793](https://github.com/PowerShell/PowerShellEditorServices/pull/1848) - Improve CI stability. +- ✨ 🚨 [PowerShellEditorServices #1846](https://github.com/PowerShell/PowerShellEditorServices/pull/1846) - Add end-to-end Pester unit test. + ## v3.4.5 ### Wednesday, June 29, 2022 From 00b8fc7c165c01961ec588014247744cd00de7be Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Mon, 11 Jul 2022 10:35:49 -0700 Subject: [PATCH 013/327] Bump version to `v3.4.6` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 2fb544491..8fd220ad4 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.4.5 + 3.4.6 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 2640a8239..83b50cd1e 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.4.5' +ModuleVersion = '3.4.6' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 81f92402ecd137647ac216444101e4bedc85abd6 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 20 Jul 2022 09:57:11 -0700 Subject: [PATCH 014/327] Add regression test for `$PSDebugContext` in `prompt` function --- .../Debugging/PSDebugContextTest.ps1 | 11 +++++++++++ .../Debugging/DebugServiceTests.cs | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 test/PowerShellEditorServices.Test.Shared/Debugging/PSDebugContextTest.ps1 diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/PSDebugContextTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/PSDebugContextTest.ps1 new file mode 100644 index 000000000..103af9918 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Debugging/PSDebugContextTest.ps1 @@ -0,0 +1,11 @@ +$promptSawDebug = $false + +function prompt { + if (Test-Path variable:/PSDebugContext -ErrorAction SilentlyContinue) { + $promptSawDebug = $true + } + + return "$promptSawDebug > " +} + +Write-Host "Debug over" diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index cb018ae5e..2603a3186 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -501,6 +501,24 @@ public async Task DebuggerRunsCommandsWhileStopped() Assert.Equal(17, (await executeTask.ConfigureAwait(true))[0]); } + // Regression test asserting that the PSDebugContext variable is available when running the + // "prompt" function. While we're unable to test the REPL loop, this still covers the + // behavior as I verified that it stepped through "ExecuteInDebugger" (which was the + // original problem). + [Fact] + public async Task DebugContextAvailableInPrompt() + { + await debugService.SetCommandBreakpointsAsync( + new[] { CommandBreakpointDetails.Create("Write-Host") }).ConfigureAwait(true); + + ScriptFile testScript = GetDebugScript("PSDebugContextTest.ps1"); + Task _ = ExecutePowerShellCommand(testScript.FilePath); + AssertDebuggerStopped(testScript.FilePath, 11); + + VariableDetails prompt = await debugService.EvaluateExpressionAsync("prompt", false).ConfigureAwait(true); + Assert.Equal("\"True > \"", prompt.ValueString); + } + [Fact] public async Task DebuggerVariableStringDisplaysCorrectly() { From 49500fa48b9502595641f654a3bf63bd4b5d9901 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 21 Jul 2022 09:38:21 -0700 Subject: [PATCH 015/327] Skip flaky test on Linux --- .../Debugging/DebugServiceTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 2603a3186..02adda0d3 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -587,9 +587,10 @@ await debugService.SetLineBreakpointsAsync( Assert.Equal("$false", falseVar.ValueString); } - [Fact] + [SkippableFact] public async Task DebuggerSetsVariablesNoConversion() { + Skip.If(VersionUtils.IsLinux, "Test hangs on Linux for some reason"); await debugService.SetLineBreakpointsAsync( variableScriptFile, new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 14) }).ConfigureAwait(true); From ece47e9aba8779c572f1e2074e7e184984808d41 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 22 Jul 2022 08:39:05 -0700 Subject: [PATCH 016/327] Dispose `_psesProcess` in DAP tests --- .../DebugAdapterProtocolMessageTests.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index ab99aa6eb..a091d5cd2 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -19,7 +19,7 @@ namespace PowerShellEditorServices.Test.E2E { [Trait("Category", "DAP")] - public class DebugAdapterProtocolMessageTests : IAsyncLifetime + public class DebugAdapterProtocolMessageTests : IAsyncLifetime, IDisposable { private const string TestOutputFileName = "__dapTestOutputFile.txt"; private static readonly bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); @@ -104,7 +104,13 @@ await PsesDebugAdapterClient.RequestDisconnect(new DisconnectArguments TerminateDebuggee = true }).ConfigureAwait(true); await _psesProcess.Stop().ConfigureAwait(true); + } + + public void Dispose() + { + GC.SuppressFinalize(this); PsesDebugAdapterClient?.Dispose(); + _psesProcess?.Dispose(); } private static string NewTestFile(string script, bool isPester = false) From 7e302871db5a042755060f1c7613a584db8b8729 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 22 Jul 2022 08:39:42 -0700 Subject: [PATCH 017/327] Use double instead of single quotes in DAP tests So we can interpolate variables. --- .../DebugAdapterProtocolMessageTests.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index a091d5cd2..9052e2bde 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -136,11 +136,23 @@ private string GenerateScriptFromLoggingStatements(params string[] logStatements } // Have script create file first with `>` (but don't rely on overwriting). - StringBuilder builder = new StringBuilder().Append('\'').Append(logStatements[0]).Append("' > '").Append(s_testOutputPath).AppendLine("'"); + // NOTE: We uses double quotes so that we can use PowerShell variables. + StringBuilder builder = new StringBuilder() + .Append("Write-Output \"") + .Append(logStatements[0]) + .Append("\" > '") + .Append(s_testOutputPath) + .AppendLine("'"); + for (int i = 1; i < logStatements.Length; i++) { // Then append to that script with `>>`. - builder.Append('\'').Append(logStatements[i]).Append("' >> '").Append(s_testOutputPath).AppendLine("'"); + builder + .Append("Write-Output \"") + .Append(logStatements[i]) + .Append("\" >> '") + .Append(s_testOutputPath) + .AppendLine("'"); } _output.WriteLine("Script is:"); From dcd9d8f1c9eba29e7b7a508640c0ecc90b6be494 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 22 Jul 2022 08:41:56 -0700 Subject: [PATCH 018/327] Add 10s timeout to DAP tests Since this logic will spin forever if a test fails. --- .../DebugAdapterProtocolMessageTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 9052e2bde..19fe31cd1 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -162,7 +162,7 @@ private string GenerateScriptFromLoggingStatements(params string[] logStatements private static async Task GetLog() { - while (!File.Exists(s_testOutputPath)) + for (int i = 0; !File.Exists(s_testOutputPath) && i < 10; i++) { await Task.Delay(1000).ConfigureAwait(true); } From 402ab4f5056d8ae6b8f2a96cb24dc928c03f54a2 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 21 Jul 2022 12:44:59 -0700 Subject: [PATCH 019/327] Add regression test for debugging script with dot-source operator --- .../DebugAdapterProtocolMessageTests.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 19fe31cd1..b2e841310 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -182,6 +182,17 @@ public void CanInitializeWithCorrectServerSettings() Assert.True(PsesDebugAdapterClient.ServerSettings.SupportsSetVariable); } + [Fact] + public async Task UsesDotSourceOperatorAndQuotesAsync() + { + string filePath = NewTestFile(GenerateScriptFromLoggingStatements("$($MyInvocation.Line)")); + await PsesDebugAdapterClient.LaunchScript(filePath, Started).ConfigureAwait(true); + ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); + Assert.NotNull(configDoneResponse); + Assert.Collection(await GetLog().ConfigureAwait(true), + (i) => Assert.StartsWith(". \"", i)); + } + [Fact] public async Task CanLaunchScriptWithNoBreakpointsAsync() { From 0759d9d4fdc9be5081327cbbea5c9adfc9e76aa4 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 22 Jul 2022 09:00:13 -0700 Subject: [PATCH 020/327] Revert "Skip flaky test on Linux" and use timeout instead This reverts commit 49500fa48b9502595641f654a3bf63bd4b5d9901. Something weird is going on with the Ubuntu CI where some of these tests hang, needs more investigation. --- .../Debugging/DebugServiceTests.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 02adda0d3..26214bff1 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -103,7 +103,8 @@ private Task ExecutePowerShellCommand(string command, params string[] args) private void AssertDebuggerPaused() { - DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(CancellationToken.None); + using CancellationTokenSource cts = new(10000); + DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(cts.Token); Assert.Empty(eventArgs.OriginalEvent.Breakpoints); } @@ -112,7 +113,8 @@ private void AssertDebuggerStopped( int lineNumber = -1, CommandBreakpointDetails commandBreakpointDetails = default) { - DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(CancellationToken.None); + using CancellationTokenSource cts = new(10000); + DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(cts.Token); Assert.True(psesHost.DebugContext.IsStopped); @@ -587,10 +589,9 @@ await debugService.SetLineBreakpointsAsync( Assert.Equal("$false", falseVar.ValueString); } - [SkippableFact] + [Fact] public async Task DebuggerSetsVariablesNoConversion() { - Skip.If(VersionUtils.IsLinux, "Test hangs on Linux for some reason"); await debugService.SetLineBreakpointsAsync( variableScriptFile, new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 14) }).ConfigureAwait(true); From c047479da71f4aa951d10b72e288507640ade371 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 22 Jul 2022 11:24:46 -0700 Subject: [PATCH 021/327] Update and extend CI matrix (#1859) Now includes Windows Server 2022 and macOS 11 Big Sur. --- .vsts-ci/azure-pipelines-ci.yml | 34 ++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/.vsts-ci/azure-pipelines-ci.yml b/.vsts-ci/azure-pipelines-ci.yml index d809e3231..c76a32ba3 100644 --- a/.vsts-ci/azure-pipelines-ci.yml +++ b/.vsts-ci/azure-pipelines-ci.yml @@ -9,8 +9,24 @@ variables: value: 'true' jobs: -- job: PS51_Win2019 - displayName: PowerShell 5.1 - Windows Server 2019 +- job: windows2022 + displayName: Windows 2022 PowerShell 5.1 + pool: + vmImage: windows-2022 + steps: + - template: templates/ci-general.yml + parameters: + pwsh: false + +- job: windows2022pwsh + displayName: Windows 2022 PowerShell 7 + pool: + vmImage: windows-2022 + steps: + - template: templates/ci-general.yml + +- job: windows2019 + displayName: Windows 2019 PowerShell 5.1 pool: vmImage: windows-2019 steps: @@ -18,22 +34,22 @@ jobs: parameters: pwsh: false -- job: PS7_Win2019 - displayName: PowerShell 7 - Windows Server 2019 +- job: windows2019pwsh + displayName: Windows 2019 PowerShell 7 pool: vmImage: windows-2019 steps: - template: templates/ci-general.yml -- job: PS7_macOS - displayName: PowerShell 7 - macOS 10.15 +- job: macOS11 + displayName: macOS 11 pool: - vmImage: macOS-10.15 + vmImage: macOS-11 steps: - template: templates/ci-general.yml -- job: PS7_Ubuntu - displayName: PowerShell 7 - Ubuntu 20.04 +- job: ubuntu2004 + displayName: Ubuntu 20.04 pool: vmImage: ubuntu-20.04 steps: From 932d3b2a530226d968a078bbfe8843b662debdfd Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Mon, 25 Jul 2022 10:02:59 -0700 Subject: [PATCH 022/327] Rename "Integrated Console" to "Extension Terminal" (#1860) Except the startup banner as its the default, and may not be for the VS Code extension but for any client that connects to PSES and supports a terminal. And bump problematic test timeouts up. --- README.md | 18 +++++++++--------- .../Commands/StartEditorServicesCommand.cs | 4 ++-- .../Configuration/EditorServicesConfig.cs | 8 ++++---- .../Internal/EditorServicesRunner.cs | 2 +- .../Hosting/EditorServicesServerFactory.cs | 2 +- .../Hosting/HostStartupInfo.cs | 2 +- .../Handlers/LaunchAndAttachHandler.cs | 4 ++-- .../PowerShell/Host/PsesInternalHost.cs | 8 ++++---- .../DebugAdapterProtocolMessageTests.cs | 2 +- .../Debugging/DebugServiceTests.cs | 4 ++-- test/emacs-test.el | 2 +- 11 files changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index fb0de20d7..9e427c99d 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The functionality in PowerShell Editor Services is already available in the foll - Real-time semantic analysis of scripts using PowerShell Script Analyzer - The Debugging Service simplifies interaction with the PowerShell debugger (breakpoints, variables, call stack, etc.) - The [$psEditor API](http://powershell.github.io/PowerShellEditorServices/guide/extensions.html) enables scripting of the host editor -- A full, terminal-based Integrated Console experience for interactive development and debugging +- A full, Extension Terminal experience for interactive development and debugging ## Usage @@ -37,7 +37,7 @@ If you're looking for a more feature-rich experience, Named Pipes are the way to go. They give you all the benefits of the Language Server Protocol with extra capabilities that you can take advantage of: -- The PowerShell Integrated Console +- The PowerShell Extension Terminal - Debugging using the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) The typical command to start PowerShell Editor Services using named pipes is as follows: @@ -59,20 +59,20 @@ and once you connect to when you launch the debugger for Debug Adapter Protocol The Visual Studio Code, Vim, and IntelliJ extensions currently use Named Pipes. -#### PowerShell Integrated Console +#### PowerShell Extension Terminal ![image](https://user-images.githubusercontent.com/2644648/66245084-6985da80-e6c0-11e9-9c7b-4c8476190df5.png) -The PowerShell Integrated Console uses the host process' Stdio streams for console input and output. Please note that this is mutually exclusive from using Stdio for the language server protocol messages. +The PowerShell Extension Terminal uses the host process' Stdio streams for console input and output. Please note that this is mutually exclusive from using Stdio for the language server protocol messages. -If you want to take advantage of the PowerShell Integrated Console which automatically shares state with the editor-side, +If you want to take advantage of the PowerShell Extension Terminal which automatically shares state with the editor-side, you must include the `-EnableConsoleRepl` switch when called `Start-EditorServices.ps1`. This is typically used if your client can create arbitrary terminals in the editor like below: -![integrated console in vscode](https://user-images.githubusercontent.com/2644648/66245018-04ca8000-e6c0-11e9-808c-b86144149444.png) +![Extension Terminal in vscode](https://user-images.githubusercontent.com/2644648/66245018-04ca8000-e6c0-11e9-808c-b86144149444.png) -The Visual Studio Code, Vim, and IntelliJ extensions currently use the PowerShell Integrated Console. +The Visual Studio Code, Vim, and IntelliJ extensions currently use the PowerShell Extension Terminal. #### Debugging @@ -87,9 +87,9 @@ Currently, only the Visual Studio Code extension supports debugging. ### Stdio -Stdio is a simpler and more universal mechanism for the Language Server Protocol. We recommend using it if your editor/client doesn't need to support the PowerShell Integrated Console or debugging. +Stdio is a simpler and more universal mechanism for the Language Server Protocol. We recommend using it if your editor/client doesn't need to support the PowerShell Extension Terminal or debugging. -> NOTE: Debugging and the Integrated Console are not features of the Stdio channel because each feature requires its own IO streams and since the Stdio model only provides a single set of streams (Stdio), +> NOTE: Debugging and the Extension Terminal are not features of the Stdio channel because each feature requires its own IO streams and since the Stdio model only provides a single set of streams (Stdio), > these features cannot be leveraged. The typical command to start PowerShell Editor Services using stdio is as follows: diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 325e7220f..2774dfa0f 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -149,7 +149,7 @@ public StartEditorServicesCommand() public string[] FeatureFlags { get; set; } /// - /// When set, enables the integrated console. + /// When set, enables the Extension Terminal. /// [Parameter] public SwitchParameter EnableConsoleRepl { get; set; } @@ -186,7 +186,7 @@ public StartEditorServicesCommand() public SwitchParameter SplitInOutPipes { get; set; } /// - /// The banner/logo to display when the Integrated Console is first started. + /// The banner/logo to display when the extension terminal is first started. /// [Parameter] public string StartupBanner { get; set; } diff --git a/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs b/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs index f8b6ef30f..3ec99db46 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs @@ -8,7 +8,7 @@ namespace Microsoft.PowerShell.EditorServices.Hosting { /// - /// Describes the desired console REPL for the integrated console. + /// Describes the desired console REPL for the Extension Terminal. /// public enum ConsoleReplKind { @@ -84,8 +84,8 @@ public EditorServicesConfig( public IReadOnlyList FeatureFlags { get; set; } /// - /// The console REPL experience to use in the integrated console - /// (including none to disable the integrated console). + /// The console REPL experience to use in the Extension Terminal + /// (including none to disable the Extension Terminal). /// public ConsoleReplKind ConsoleRepl { get; set; } = ConsoleReplKind.None; @@ -117,7 +117,7 @@ public EditorServicesConfig( public string StartupBanner { get; set; } = @" - =====> PowerShell Integrated Console <===== + =====> PowerShell Editor Services <===== "; } diff --git a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs index 3e6626f20..b9dbb0b2e 100644 --- a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs +++ b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs @@ -148,7 +148,7 @@ private async Task CreateEditorServicesAndRunUntilShutdown() // - Possibly start the debug server // - Wait for the LSP server to finish - // Unsubscribe the host logger here so that the integrated console is not polluted with input after the first prompt + // Unsubscribe the host logger here so that the Extension Terminal is not polluted with input after the first prompt _logger.Log(PsesLogLevel.Verbose, "Starting server, deregistering host logger and registering shutdown listener"); if (_loggersToUnsubscribe != null) { diff --git a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs index 6a8d07f17..2ff417029 100644 --- a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs +++ b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs @@ -163,7 +163,7 @@ public PsesDebugServer CreateDebugServerForTempSession( // This gets the ExtensionService which triggers the creation of the `$psEditor` variable. // (because services are created only when they are first retrieved) // Keep in mind, for Temp sessions, the `$psEditor` API is a no-op and the user is warned - // to run the command in the main PS Integrated Console. + // to run the command in the main extension terminal. serviceProvider.GetService(); return new PsesDebugServer( diff --git a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs index e34df5912..a038e1844 100644 --- a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs +++ b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs @@ -71,7 +71,7 @@ public sealed class HostStartupInfo public IReadOnlyList AdditionalModules { get; } /// - /// True if the integrated console is to be enabled. + /// True if the Extension Terminal is to be enabled. /// public bool ConsoleReplEnabled { get; } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 580b7cc89..c2ae8becb 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -50,7 +50,7 @@ internal record PsesLaunchRequestArguments : LaunchRequestArguments /// /// Gets or sets a boolean value that determines whether to create a temporary - /// integrated console for the debug session. Default is false. + /// Extension Terminal for the debug session. Default is false. /// public bool CreateTemporaryIntegratedConsole { get; set; } @@ -190,7 +190,7 @@ public async Task Handle(PsesLaunchRequestArguments request, Can && !string.IsNullOrEmpty(request.Script) && ScriptFile.IsUntitledPath(request.Script)) { - throw new RpcErrorException(0, "Running an Untitled file in a temporary integrated console is currently not supported."); + throw new RpcErrorException(0, "Running an Untitled file in a temporary Extension Terminal is currently not supported."); } // If the current session is remote, map the script path to the remote diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 81f4815ab..114a78de4 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -24,14 +24,14 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { using System.Management.Automation; using System.Management.Automation.Runspaces; - // NOTE: These last three are for a workaround for temporary integrated consoles. + // NOTE: These last three are for a workaround for temporary Extension Terminals. using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Server; using OmniSharp.Extensions.DebugAdapter.Protocol.Server; internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext, IInternalPowerShellExecutionService { - private const string DefaultPrompt = "PSIC> "; + private const string DefaultPrompt = "> "; // This is a default that can be overriden at runtime by the user or tests. private static string s_bundledModulePath = Path.GetFullPath(Path.Combine( Path.GetDirectoryName(typeof(PsesInternalHost).Assembly.Location), "..", "..", "..")); @@ -1119,12 +1119,12 @@ private ConsoleKeyInfo ReadKey(bool intercept) using CancellationTokenRegistration registration = _readKeyCancellationToken.Register( () => { - // For the regular integrated console, we have an associated language server on + // For the regular Extension Terminal, we have an associated language server on // which we can send a notification, and have the client subscribe an action to // send a key press. _languageServer?.SendNotification("powerShell/sendKeyPress"); - // When temporary integrated consoles are spawned, there will be no associated + // When temporary Extension Terminals are spawned, there will be no associated // language server, but instead a debug adaptor server. In this case, the // notification sent here will come across as a DebugSessionCustomEvent to which // we can subscribe in the same way. diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index b2e841310..7505ad89e 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -162,7 +162,7 @@ private string GenerateScriptFromLoggingStatements(params string[] logStatements private static async Task GetLog() { - for (int i = 0; !File.Exists(s_testOutputPath) && i < 10; i++) + for (int i = 0; !File.Exists(s_testOutputPath) && i < 60; i++) { await Task.Delay(1000).ConfigureAwait(true); } diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 26214bff1..b58997005 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -103,7 +103,7 @@ private Task ExecutePowerShellCommand(string command, params string[] args) private void AssertDebuggerPaused() { - using CancellationTokenSource cts = new(10000); + using CancellationTokenSource cts = new(60000); DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(cts.Token); Assert.Empty(eventArgs.OriginalEvent.Breakpoints); } @@ -113,7 +113,7 @@ private void AssertDebuggerStopped( int lineNumber = -1, CommandBreakpointDetails commandBreakpointDetails = default) { - using CancellationTokenSource cts = new(10000); + using CancellationTokenSource cts = new(60000); DebuggerStoppedEventArgs eventArgs = debuggerStoppedQueue.Take(cts.Token); Assert.True(psesHost.DebugContext.IsStopped); diff --git a/test/emacs-test.el b/test/emacs-test.el index 94671a65f..88b3d1a50 100644 --- a/test/emacs-test.el +++ b/test/emacs-test.el @@ -60,7 +60,7 @@ (should (string= (oref lsp project-nickname) "PowerShellEditorServices")) (should (eq (oref lsp major-mode) 'powershell-mode)) (should (string= (oref lsp language-id) "powershell"))) - (sleep-for 3) ; TODO: Wait for "textDocument/publishDiagnostics" instead + (sleep-for 5) ; TODO: Wait for "textDocument/publishDiagnostics" instead (flymake-start) (goto-char (point-min)) (flymake-goto-next-error) From 2a861fb0ae5a1257bebd63be2e8912cb386b6681 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Mon, 25 Jul 2022 10:24:22 -0700 Subject: [PATCH 023/327] Improve untitled script test by checking `$MyInvocation` --- .../DebugAdapterProtocolMessageTests.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 7505ad89e..553024856 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -305,9 +305,8 @@ public async Task CanStepPastSystemWindowsForms() [Fact] public async Task CanLaunchScriptWithCommentedLastLineAsync() { - string script = GenerateScriptFromLoggingStatements("a log statement") + "# a comment at the end"; - Assert.Contains(Environment.NewLine + "# a comment", script); - Assert.EndsWith("at the end", script); + string script = GenerateScriptFromLoggingStatements("$($MyInvocation.Line)") + "# a comment at the end"; + Assert.EndsWith(Environment.NewLine + "# a comment at the end", script); // NOTE: This is horribly complicated, but the "script" parameter here is assigned to // PsesLaunchRequestArguments.Script, which is then assigned to @@ -317,8 +316,13 @@ public async Task CanLaunchScriptWithCommentedLastLineAsync() ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); Assert.NotNull(configDoneResponse); + // We can check that the script was invoked as expected, which is to dot-source a script + // block with the contents surrounded by newlines. While we can't check that the last + // line was a curly brace by itself, we did check that the contents ended with a + // comment, so if this output exists then the bug did not recur. Assert.Collection(await GetLog().ConfigureAwait(true), - (i) => Assert.Equal("a log statement", i)); + (i) => Assert.Equal(". {", i), + (i) => Assert.Equal("", i)); } [SkippableFact] From eec176d728a8d94e5e0efaa78cd5ecc806ee9085 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Mon, 25 Jul 2022 11:57:38 -0700 Subject: [PATCH 024/327] Add `DebuggerBreaksInUntitledScript` unit test --- .../Handlers/ConfigurationDoneHandler.cs | 14 +++++--- .../Debugging/DebugServiceTests.cs | 35 +++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 2cfc24a2d..14bd5389e 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -102,14 +102,17 @@ public Task Handle(ConfigurationDoneArguments request return Task.FromResult(new ConfigurationDoneResponse()); } - private async Task LaunchScriptAsync(string scriptToLaunch) + // NOTE: We test this function in `DebugServiceTests` so it both needs to be internal, and + // use conditional-access on `_debugStateService` and `_debugAdapterServer` as its not set + // by tests. + internal async Task LaunchScriptAsync(string scriptToLaunch) { PSCommand command; if (System.IO.File.Exists(scriptToLaunch)) { // For a saved file we just execute its path (after escaping it). command = PSCommandHelpers.BuildDotSourceCommandWithArguments( - string.Concat('"', scriptToLaunch, '"'), _debugStateService.Arguments); + string.Concat('"', scriptToLaunch, '"'), _debugStateService?.Arguments); } else // It's a URI to an untitled script, or a raw script. { @@ -135,7 +138,7 @@ private async Task LaunchScriptAsync(string scriptToLaunch) // on each invocation, so passing the user's arguments directly in the initial // `AddScript` surprisingly works. command = PSCommandHelpers - .BuildDotSourceCommandWithArguments("$args[0]", _debugStateService.Arguments) + .BuildDotSourceCommandWithArguments("$args[0]", _debugStateService?.Arguments) .AddArgument(ast.GetScriptBlock()); } else @@ -148,7 +151,7 @@ private async Task LaunchScriptAsync(string scriptToLaunch) "{" + System.Environment.NewLine, isScriptFile ? untitledScript.Contents : scriptToLaunch, System.Environment.NewLine + "}"), - _debugStateService.Arguments); + _debugStateService?.Arguments); } } @@ -156,7 +159,8 @@ await _executionService.ExecutePSCommandAsync( command, CancellationToken.None, s_debuggerExecutionOptions).ConfigureAwait(false); - _debugAdapterServer.SendNotification(EventNames.Terminated); + + _debugAdapterServer?.SendNotification(EventNames.Terminated); } } } diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index b58997005..eef37f1ca 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; @@ -521,6 +522,40 @@ await debugService.SetCommandBreakpointsAsync( Assert.Equal("\"True > \"", prompt.ValueString); } + [SkippableFact] + public async Task DebuggerBreaksInUntitledScript() + { + Skip.IfNot(VersionUtils.PSEdition == "Core", "Untitled script breakpoints only supported in PowerShell Core"); + const string contents = "Write-Output $($MyInvocation.Line)"; + const string scriptPath = "untitled:Untitled-1"; + Assert.True(ScriptFile.IsUntitledPath(scriptPath)); + ScriptFile scriptFile = workspace.GetFileBuffer(scriptPath, contents); + Assert.Equal(scriptFile.DocumentUri, scriptPath); + Assert.Equal(scriptFile.Contents, contents); + Assert.True(workspace.TryGetFile(scriptPath, out ScriptFile _)); + + await debugService.SetCommandBreakpointsAsync( + new[] { CommandBreakpointDetails.Create("Write-Output") }).ConfigureAwait(true); + + ConfigurationDoneHandler configurationDoneHandler = new( + NullLoggerFactory.Instance, null, debugService, null, null, psesHost, workspace, null, psesHost); + + Task _ = configurationDoneHandler.LaunchScriptAsync(scriptPath); + AssertDebuggerStopped(scriptPath, 1); + + VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.CommandVariablesName); + VariableDetailsBase myInvocation = Array.Find(variables, v => v.Name == "$MyInvocation"); + Assert.NotNull(myInvocation); + Assert.True(myInvocation.IsExpandable); + + // Here we're asserting that our hacky workaround to support breakpoints in untitled + // scripts is working, namely that we're actually dot-sourcing our first argument, which + // should be a cached script block. See the `LaunchScriptAsync` for more info. + VariableDetailsBase[] myInvocationChildren = debugService.GetVariables(myInvocation.Id); + VariableDetailsBase myInvocationLine = Array.Find(myInvocationChildren, v => v.Name == "Line"); + Assert.Equal("\". $args[0]\"", myInvocationLine.ValueString); + } + [Fact] public async Task DebuggerVariableStringDisplaysCorrectly() { From 7176f799582b0619c82a52ccd74465d834c5a8a0 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Mon, 25 Jul 2022 13:55:57 -0700 Subject: [PATCH 025/327] Update CHANGELOG for `v3.4.7` --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc3bc688a..aac88843f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # PowerShell Editor Services Release History +## v3.4.7 +### Monday, July 25, 2022 + +- ✨ 🚨 [PowerShellEditorServices #1861](https://github.com/PowerShell/PowerShellEditorServices/pull/1861) - Add `DebuggerBreaksInUntitledScript` unit test. +- ✨ 📟 [PowerShellEditorServices #1860](https://github.com/PowerShell/PowerShellEditorServices/pull/1860) - Rename "Integrated Console" to "Extension Terminal". +- ✨ 🚨 [PowerShellEditorServices #1859](https://github.com/PowerShell/PowerShellEditorServices/pull/1859) - Update and extend CI matrix. +- ✨ 🚨 [PowerShellEditorServices #1858](https://github.com/PowerShell/PowerShellEditorServices/pull/1858) - Add regression test for debugging script with dot-source operator. +- ✨ 🚨 [PowerShellEditorServices #1857](https://github.com/PowerShell/PowerShellEditorServices/pull/1857) - Add regression test for `$PSDebugContext` in `prompt` function. + ## v3.4.6 ### Monday, July 11, 2022 From 302b169a14dd20201a1b153e76775e5253c4759e Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Mon, 25 Jul 2022 13:55:58 -0700 Subject: [PATCH 026/327] Bump version to `v3.4.7` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 8fd220ad4..a7cd77be1 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.4.6 + 3.4.7 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 83b50cd1e..2671614bf 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.4.6' +ModuleVersion = '3.4.7' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 00c45232bf2b33f34b3b7f3399eacee6248060a6 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 28 Jul 2022 10:54:49 -0700 Subject: [PATCH 027/327] Use `HostInfo.BundledModulePath` to find PSScriptAnalyzer Also refactor a bit so we're not triplicating this logic. --- .../Hosting/HostStartupInfo.cs | 13 +++++++-- .../Services/Analysis/AnalysisService.cs | 19 ++++++------- .../Analysis/PssaCmdletAnalysisEngine.cs | 28 ++++++------------- .../PowerShell/Host/PsesInternalHost.cs | 24 ++++++---------- 4 files changed, 35 insertions(+), 49 deletions(-) diff --git a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs index a038e1844..ba241a0ac 100644 --- a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs +++ b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Management.Automation.Host; using System.Management.Automation.Runspaces; @@ -92,7 +93,7 @@ public sealed class HostStartupInfo public string LogPath { get; } /// - /// The InitialSessionState will be inherited from the orginal PowerShell process. This will + /// The InitialSessionState will be inherited from the original PowerShell process. This will /// be used when creating runspaces so that we honor the same InitialSessionState. /// public InitialSessionState InitialSessionState { get; } @@ -167,7 +168,15 @@ public HostStartupInfo( LogLevel = logLevel; ConsoleReplEnabled = consoleReplEnabled; UsesLegacyReadLine = usesLegacyReadLine; - BundledModulePath = bundledModulePath; + + // Respect a user provided bundled module path. + BundledModulePath = Directory.Exists(bundledModulePath) + ? bundledModulePath + : Path.GetFullPath(Path.Combine( + Path.GetDirectoryName(typeof(HostStartupInfo).Assembly.Location), + "..", + "..", + "..")); } #endregion diff --git a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index 072115bbc..f694e2131 100644 --- a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services.Analysis; using Microsoft.PowerShell.EditorServices.Services.Configuration; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -52,8 +53,7 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic) .Append(':') .Append(end.Character); - string id = sb.ToString(); - return id; + return sb.ToString(); } /// @@ -96,20 +96,16 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic) private CancellationTokenSource _diagnosticsCancellationTokenSource; + private readonly string _pssaModulePath; + private string _pssaSettingsFilePath; - /// - /// Construct a new AnalysisService. - /// - /// Logger factory to create logger instances with. - /// The LSP language server for notifications. - /// The configuration service to query for configurations. - /// The workspace service for file handling within a workspace. public AnalysisService( ILoggerFactory loggerFactory, ILanguageServerFacade languageServer, ConfigurationService configurationService, - WorkspaceService workspaceService) + WorkspaceService workspaceService, + HostStartupInfo hostInfo) { _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); @@ -119,6 +115,7 @@ public AnalysisService( _analysisDelayMillis = 750; _mostRecentCorrectionsByFile = new ConcurrentDictionary(); _analysisEngineLazy = new Lazy(InstantiateAnalysisEngine); + _pssaModulePath = Path.Combine(hostInfo.BundledModulePath, "PSScriptAnalyzer"); _pssaSettingsFilePath = null; } @@ -302,7 +299,7 @@ private PssaCmdletAnalysisEngine InstantiateAnalysisEngine() pssaCmdletEngineBuilder.WithIncludedRules(s_defaultRules); } - return pssaCmdletEngineBuilder.Build(); + return pssaCmdletEngineBuilder.Build(_pssaModulePath); } private PssaCmdletAnalysisEngine RecreateAnalysisEngine(PssaCmdletAnalysisEngine oldAnalysisEngine) diff --git a/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs b/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs index 7ae41e0eb..65a393399 100644 --- a/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs +++ b/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -67,35 +66,27 @@ public Builder WithIncludedRules(string[] rules) /// If PSScriptAnalyzer cannot be found, this will return null. /// /// A newly configured PssaCmdletAnalysisEngine, or null if PSScriptAnalyzer cannot be found. - public PssaCmdletAnalysisEngine Build() + public PssaCmdletAnalysisEngine Build(string pssaModulePath) { // RunspacePool takes care of queuing commands for us so we do not // need to worry about executing concurrent commands ILogger logger = _loggerFactory.CreateLogger(); try { - RunspacePool pssaRunspacePool = CreatePssaRunspacePool(); - - PssaCmdletAnalysisEngine cmdletAnalysisEngine = _settingsParameter is not null - ? new PssaCmdletAnalysisEngine(logger, pssaRunspacePool, _settingsParameter) - : new PssaCmdletAnalysisEngine(logger, pssaRunspacePool, _rules); - + logger.LogDebug("Creating PSScriptAnalyzer runspace with module at: '{Path}'", pssaModulePath); + RunspacePool pssaRunspacePool = CreatePssaRunspacePool(pssaModulePath); + PssaCmdletAnalysisEngine cmdletAnalysisEngine = new(logger, pssaRunspacePool, _settingsParameter ?? _rules); cmdletAnalysisEngine.LogAvailablePssaFeatures(); return cmdletAnalysisEngine; } - catch (FileNotFoundException e) + catch (Exception ex) { - logger.LogError(e, $"Unable to find PSScriptAnalyzer. Disabling script analysis. PSModulePath: '{Environment.GetEnvironmentVariable("PSModulePath")}'"); + logger.LogError(ex, "Unable to load PSScriptAnalyzer, disabling script analysis!"); return null; } } } - // This is a default that can be overriden at runtime by the user or tests. - // TODO: Deduplicate this logic with PsesInternalHost. - private static readonly string s_pssaModulePath = Path.GetFullPath(Path.Combine( - Path.GetDirectoryName(typeof(PssaCmdletAnalysisEngine).Assembly.Location), "..", "..", "..", "PSScriptAnalyzer")); - /// /// The indentation to add when the logger lists errors. /// @@ -365,7 +356,7 @@ private IEnumerable GetPSScriptAnalyzerRules() /// This looks for the latest version of PSScriptAnalyzer on the path and loads that. /// /// A runspace pool with PSScriptAnalyzer loaded for running script analysis tasks. - private static RunspacePool CreatePssaRunspacePool() + private static RunspacePool CreatePssaRunspacePool(string pssaModulePath) { using PowerShell pwsh = PowerShell.Create(RunspaceMode.NewRunspace); @@ -375,10 +366,7 @@ private static RunspacePool CreatePssaRunspacePool() // We intentionally use `CreateDefault2()` as it loads `Microsoft.PowerShell.Core` // only, which is a more minimal and therefore safer state. InitialSessionState sessionState = InitialSessionState.CreateDefault2(); - - sessionState.ImportPSModulesFromPath(s_pssaModulePath); - // pwsh.ImportModule(s_pssaModulePath); - // sessionState.ImportPSModule(new[] { pssaModuleInfo.ModuleBase }); + sessionState.ImportPSModulesFromPath(pssaModulePath); RunspacePool runspacePool = RunspaceFactory.CreateRunspacePool(sessionState); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 114a78de4..d259a8ac9 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -32,12 +32,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext, IInternalPowerShellExecutionService { private const string DefaultPrompt = "> "; - // This is a default that can be overriden at runtime by the user or tests. - private static string s_bundledModulePath = Path.GetFullPath(Path.Combine( - Path.GetDirectoryName(typeof(PsesInternalHost).Assembly.Location), "..", "..", "..")); - - private static string CommandsModulePath => Path.GetFullPath(Path.Combine( - s_bundledModulePath, "PowerShellEditorServices", "Commands", "PowerShellEditorServices.Commands.psd1")); private static readonly PropertyInfo s_scriptDebuggerTriggerObjectProperty; @@ -115,14 +109,6 @@ public PsesInternalHost( _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; _hostInfo = hostInfo; - - // Respect a user provided bundled module path. - if (Directory.Exists(hostInfo.BundledModulePath)) - { - _logger.LogTrace($"Using new bundled module path: {hostInfo.BundledModulePath}"); - s_bundledModulePath = hostInfo.BundledModulePath; - } - _readLineProvider = new ReadLineProvider(loggerFactory); _taskQueue = new BlockingConcurrentDeque(); _psFrameStack = new Stack(); @@ -1012,7 +998,13 @@ private static PowerShell CreatePowerShellForRunspace(Runspace runspace) pwsh.SetCorrectExecutionPolicy(_logger); } - pwsh.ImportModule(CommandsModulePath); + string commandsModulePath = Path.Combine( + _hostInfo.BundledModulePath, + "PowerShellEditorServices", + "Commands", + "PowerShellEditorServices.Commands.psd1"); + + pwsh.ImportModule(commandsModulePath); if (hostStartupInfo.AdditionalModules?.Count > 0) { @@ -1311,7 +1303,7 @@ internal bool TryLoadPSReadLine(PowerShell pwsh, EngineIntrinsics engineIntrinsi psrlReadLine = null; try { - PSReadLineProxy psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, s_bundledModulePath, pwsh); + PSReadLineProxy psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _hostInfo.BundledModulePath, pwsh); psrlReadLine = new PsrlReadLine(psrlProxy, this, engineIntrinsics, ReadKey, OnPowerShellIdle); return true; } From 0cd86529879629fd455bcf9dcc9f066ba6e778b8 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 28 Jul 2022 11:54:45 -0700 Subject: [PATCH 028/327] Fix namespaces in `PowerShellEditorServices.Test` --- .../Debugging/DebugServiceTests.cs | 4 +++- .../Extensions/ExtensionCommandTests.cs | 11 ++++++----- .../Language/CompletionHandlerTests.cs | 3 ++- .../Language/SemanticTokenTest.cs | 4 ++-- .../Language/SymbolsServiceTests.cs | 3 ++- .../Language/TokenOperationsTests.cs | 2 +- .../Services/Symbols/AstOperationsTests.cs | 2 +- .../Session/PathEscapingTests.cs | 2 +- .../Session/PsesInternalHostTests.cs | 3 ++- .../Session/ScriptFileTests.cs | 4 ++-- .../Session/WorkspaceTests.cs | 2 +- 11 files changed, 23 insertions(+), 17 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index eef37f1ca..d0f1f0ef0 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -15,10 +15,12 @@ using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Test; using Microsoft.PowerShell.EditorServices.Test.Shared; using Microsoft.PowerShell.EditorServices.Utility; using Xunit; -namespace Microsoft.PowerShell.EditorServices.Test.Debugging + +namespace PowerShellEditorServices.Test.Debugging { [Trait("Category", "DebugService")] public class DebugServiceTests : IDisposable diff --git a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs index b2e5e6b46..b6fbd881e 100644 --- a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs +++ b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs @@ -13,10 +13,11 @@ using Microsoft.PowerShell.EditorServices.Services.Extension; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Test; using Microsoft.PowerShell.EditorServices.Test.Shared; using Xunit; -namespace Microsoft.PowerShell.EditorServices.Test.Extensions +namespace PowerShellEditorServices.Test.Extensions { [Trait("Category", "Extensions")] public class ExtensionCommandTests : IDisposable @@ -59,7 +60,7 @@ public async Task CanRegisterAndInvokeCommandWithCmdletName() BufferRange.None); EditorCommand commandAdded = null; - extensionCommandService.CommandAdded += (object _, EditorCommand command) => commandAdded = command; + extensionCommandService.CommandAdded += (_, command) => commandAdded = command; const string commandName = "test.function"; const string commandDisplayName = "Function extension"; @@ -95,7 +96,7 @@ public async Task CanRegisterAndInvokeCommandWithScriptBlock() BufferRange.None); EditorCommand commandAdded = null; - extensionCommandService.CommandAdded += (object _, EditorCommand command) => commandAdded = command; + extensionCommandService.CommandAdded += (_, command) => commandAdded = command; const string commandName = "test.scriptblock"; const string commandDisplayName = "ScriptBlock extension"; @@ -126,7 +127,7 @@ await psesHost.ExecutePSCommandAsync( public async Task CanUpdateRegisteredCommand() { EditorCommand updatedCommand = null; - extensionCommandService.CommandUpdated += (object _, EditorCommand command) => updatedCommand = command; + extensionCommandService.CommandUpdated += (_, command) => updatedCommand = command; const string commandName = "test.function"; const string commandDisplayName = "Updated function extension"; @@ -160,7 +161,7 @@ public async Task CanUnregisterCommand() const string commandDisplayName = "ScriptBlock extension"; EditorCommand removedCommand = null; - extensionCommandService.CommandRemoved += (object _, EditorCommand command) => removedCommand = command; + extensionCommandService.CommandRemoved += (_, command) => removedCommand = command; // Add the command and wait for the add event await psesHost.ExecutePSCommandAsync( diff --git a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs index 3ddedb89f..4c83fa805 100644 --- a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs @@ -11,13 +11,14 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Test; using Microsoft.PowerShell.EditorServices.Test.Shared; using Microsoft.PowerShell.EditorServices.Test.Shared.Completion; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Xunit; -namespace Microsoft.PowerShell.EditorServices.Test.Language +namespace PowerShellEditorServices.Test.Language { [Trait("Category", "Completions")] public class CompletionHandlerTests : IDisposable diff --git a/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs b/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs index 172aa27a6..6c66fd697 100644 --- a/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs +++ b/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs @@ -5,13 +5,13 @@ using System.Collections.Generic; using System.IO; using System.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Handlers; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Xunit; -namespace Microsoft.PowerShell.EditorServices.Test.Language +namespace PowerShellEditorServices.Test.Language { public class SemanticTokenTest { diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 959a9b1fa..fb3b2301a 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -14,6 +14,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Test; using Microsoft.PowerShell.EditorServices.Test.Shared; using Microsoft.PowerShell.EditorServices.Test.Shared.Definition; using Microsoft.PowerShell.EditorServices.Test.Shared.Occurrences; @@ -23,7 +24,7 @@ using Microsoft.PowerShell.EditorServices.Test.Shared.Symbols; using Xunit; -namespace Microsoft.PowerShell.EditorServices.Test.Language +namespace PowerShellEditorServices.Test.Language { [Trait("Category", "Symbols")] public class SymbolsServiceTests : IDisposable diff --git a/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs b/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs index d8dc653c2..325384d05 100644 --- a/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs @@ -8,7 +8,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Xunit; -namespace Microsoft.PowerShell.EditorServices.Test.Language +namespace PowerShellEditorServices.Test.Language { public class TokenOperationsTests { diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs index 75fa1c4f4..b713d900f 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs @@ -8,7 +8,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Xunit; -namespace Microsoft.PowerShell.EditorServices.Test.Services.Symbols +namespace PowerShellEditorServices.Test.Services.Symbols { [Trait("Category", "AstOperations")] public class AstOperationsTests diff --git a/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs b/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs index ef4a7a935..ed591a395 100644 --- a/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs @@ -4,7 +4,7 @@ using Xunit; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Test.Session +namespace PowerShellEditorServices.Test.Session { public class PathEscapingTests { diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index f0d82c0d9..45f14e97c 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -11,9 +11,10 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using Microsoft.PowerShell.EditorServices.Test; using Xunit; -namespace Microsoft.PowerShell.EditorServices.Test.Console +namespace PowerShellEditorServices.Test.Session { using System.Management.Automation; using System.Management.Automation.Runspaces; diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index 0351dec52..df70f0463 100644 --- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -10,7 +10,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol; using Xunit; -namespace PSLanguageService.Test +namespace PowerShellEditorServices.Test.Session { public class ScriptFileChangeTests { @@ -194,7 +194,7 @@ public void FindsDotSourcedFiles() PowerShellVersion); Assert.Equal(3, scriptFile.ReferencedFiles.Length); - System.Console.Write("a" + scriptFile.ReferencedFiles[0]); + Console.Write("a" + scriptFile.ReferencedFiles[0]); Assert.Equal(TestUtilities.NormalizePath("./athing.ps1"), scriptFile.ReferencedFiles[0]); } diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs index 5342ee7e6..33e3c653b 100644 --- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -10,7 +10,7 @@ using Microsoft.PowerShell.EditorServices.Test.Shared; using Xunit; -namespace Microsoft.PowerShell.EditorServices.Test.Session +namespace PowerShellEditorServices.Test.Session { [Trait("Category", "Workspace")] public class WorkspaceTests From b5e7c77387aa8540913afb4047ec5157f983ee12 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 28 Jul 2022 12:29:55 -0700 Subject: [PATCH 029/327] Add PSScriptAnalyzer unit test --- .../Services/Analysis/AnalysisService.cs | 8 +-- .../Services/Symbols/PSScriptAnalyzerTests.cs | 58 +++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs diff --git a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index f694e2131..c376da311 100644 --- a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -199,7 +199,7 @@ public async Task GetCommentHelpText(string functionText, string helpLoc return null; } - Hashtable commentHelpSettings = AnalysisService.GetCommentHelpRuleSettings(helpLocation, forBlockComment); + Hashtable commentHelpSettings = GetCommentHelpRuleSettings(helpLocation, forBlockComment); ScriptFileMarker[] analysisResults = await AnalysisEngine.AnalyzeScriptAsync(functionText, commentHelpSettings).ConfigureAwait(false); @@ -282,7 +282,7 @@ private void InitializeAnalysisEngineToCurrentSettings() _analysisEngineLazy = new Lazy(() => RecreateAnalysisEngine(currentAnalysisEngine)); } - private PssaCmdletAnalysisEngine InstantiateAnalysisEngine() + internal PssaCmdletAnalysisEngine InstantiateAnalysisEngine() { PssaCmdletAnalysisEngine.Builder pssaCmdletEngineBuilder = new(_loggerFactory); @@ -317,7 +317,7 @@ private PssaCmdletAnalysisEngine RecreateAnalysisEngine(PssaCmdletAnalysisEngine private bool TryFindSettingsFile(out string settingsFilePath) { - string configuredPath = _configurationService.CurrentSettings.ScriptAnalysis.SettingsPath; + string configuredPath = _configurationService?.CurrentSettings.ScriptAnalysis.SettingsPath; if (string.IsNullOrEmpty(configuredPath)) { @@ -325,7 +325,7 @@ private bool TryFindSettingsFile(out string settingsFilePath) return false; } - settingsFilePath = _workspaceService.ResolveWorkspacePath(configuredPath); + settingsFilePath = _workspaceService?.ResolveWorkspacePath(configuredPath); if (settingsFilePath == null || !File.Exists(settingsFilePath)) diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs new file mode 100644 index 000000000..05f069f2c --- /dev/null +++ b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Analysis; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Test; +using Xunit; + +namespace PowerShellEditorServices.Test.Services.Symbols +{ + [Trait("Category", "PSScriptAnalyzer")] + public class PSScriptAnalyzerTests + { + private readonly AnalysisService analysisService; + + public PSScriptAnalyzerTests() => analysisService = new( + NullLoggerFactory.Instance, + languageServer: null, + configurationService: null, + workspaceService: null, + new HostStartupInfo( + name: "", + profileId: "", + version: null, + psHost: null, + profilePaths: null, + featureFlags: null, + additionalModules: null, + initialSessionState: null, + logPath: null, + logLevel: 0, + consoleReplEnabled: false, + usesLegacyReadLine: false, + bundledModulePath: PsesHostFactory.BundledModulePath)); + + [Fact] + public async Task CanLoadPSScriptAnalyzer() + { + PssaCmdletAnalysisEngine engine = analysisService.InstantiateAnalysisEngine(); + Assert.NotNull(engine); + ScriptFileMarker[] violations = await engine.AnalyzeScriptAsync("function Get-Widgets {}").ConfigureAwait(true); + Assert.Collection(violations, + (actual) => + { + Assert.Single(actual.Corrections); + Assert.Equal("Singularized correction of 'Get-Widgets'", actual.Corrections.First().Name); + Assert.Equal(ScriptFileMarkerLevel.Warning, actual.Level); + Assert.Equal("PSUseSingularNouns", actual.RuleName); + Assert.Equal("PSScriptAnalyzer", actual.Source); + }); + } + } +} From 8de0b11b94fb7ed665c457be812d5284e5d3e476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Aug 2022 20:58:19 +0000 Subject: [PATCH 030/327] Bump xunit from 2.4.1 to 2.4.2 (#1865) Bumps [xunit](https://github.com/xunit/xunit) from 2.4.1 to 2.4.2. - [Release notes](https://github.com/xunit/xunit/releases) - [Commits](https://github.com/xunit/xunit/compare/2.4.1...2.4.2) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 1432614ee..073fb5c60 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -12,7 +12,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 8d1bfa60f..378f698d4 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -33,7 +33,7 @@ - + From 9dec136e74e412d4c64cd3d63ad37571080c2e6c Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Wed, 3 Aug 2022 08:10:58 -0700 Subject: [PATCH 031/327] Fix bug where error in `prompt` function crashed REPL (#1866) Double-whammy fix by both setting `ThrowOnError` to false, and catching a more generic PowerShell `RuntimeException`. Plus a regression test! --- .../PowerShell/Host/PsesInternalHost.cs | 12 ++++++---- .../Session/PsesInternalHostTests.cs | 24 ++++++++++++++++--- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index d259a8ac9..e1ee99345 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -31,7 +31,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext, IInternalPowerShellExecutionService { - private const string DefaultPrompt = "> "; + internal const string DefaultPrompt = "> "; private static readonly PropertyInfo s_scriptDebuggerTriggerObjectProperty; @@ -865,7 +865,7 @@ private void DoOneRepl(CancellationToken cancellationToken) } } - private string GetPrompt(CancellationToken cancellationToken) + internal string GetPrompt(CancellationToken cancellationToken) { Runspace.ThrowCancelledIfUnusable(); string prompt = DefaultPrompt; @@ -873,13 +873,17 @@ private string GetPrompt(CancellationToken cancellationToken) { // TODO: Should we cache PSCommands like this as static members? PSCommand command = new PSCommand().AddCommand("prompt"); - IReadOnlyList results = InvokePSCommand(command, executionOptions: null, cancellationToken); + IReadOnlyList results = InvokePSCommand( + command, + executionOptions: new PowerShellExecutionOptions { ThrowOnError = false }, + cancellationToken); + if (results?.Count > 0) { prompt = results[0]; } } - catch (CommandNotFoundException) { } // Use default prompt + catch (RuntimeException) { } // Use default prompt if (CurrentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index 45f14e97c..41d5cbb82 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -162,12 +162,30 @@ await psesHost.ExecuteDelegateAsync( CancellationToken.None).ConfigureAwait(true); } + // NOTE: Tests where we call functions that use PowerShell runspaces are slightly more + // complicated than one would expect because we explicitly need the methods to run on the + // pipeline thread, otherwise Windows complains about the the thread's apartment state not + // matching. Hence we use a delegate where it looks like we could just call the method. + + [Fact] + public async Task CanHandleBrokenPrompt() + { + await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("function prompt { throw }"), + CancellationToken.None).ConfigureAwait(true); + + string prompt = await psesHost.ExecuteDelegateAsync( + nameof(psesHost.GetPrompt), + executionOptions: null, + (_, _) => psesHost.GetPrompt(CancellationToken.None), + CancellationToken.None).ConfigureAwait(true); + + Assert.Equal(PsesInternalHost.DefaultPrompt, prompt); + } + [Fact] public async Task CanLoadPSReadLine() { - // NOTE: This is slightly more complicated than one would expect because we explicitly - // need it to run on the pipeline thread otherwise Windows complains about the the - // thread's apartment state not matching. Assert.True(await psesHost.ExecuteDelegateAsync( nameof(psesHost.TryLoadPSReadLine), executionOptions: null, From e49fa0ae24d1286502ec27ef0b036978522c7e88 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Wed, 3 Aug 2022 14:22:14 -0700 Subject: [PATCH 032/327] Add regression test for when `prompt` is undefined (#1867) Also improve regression test for when it throws. Co-authored-by: Patrick Meinecke --- .../Session/PsesInternalHostTests.cs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index 41d5cbb82..d66c46938 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -88,7 +88,7 @@ public async Task CanQueueParallelPSCommands() public async Task CanCancelExecutionWithToken() { using CancellationTokenSource cancellationSource = new(millisecondsDelay: 1000); - await Assert.ThrowsAsync(() => + _ = await Assert.ThrowsAsync(() => { return psesHost.ExecutePSCommandAsync( new PSCommand().AddScript("Start-Sleep 10"), @@ -170,10 +170,29 @@ await psesHost.ExecuteDelegateAsync( [Fact] public async Task CanHandleBrokenPrompt() { - await psesHost.ExecutePSCommandAsync( - new PSCommand().AddScript("function prompt { throw }"), + _ = await Assert.ThrowsAsync(() => + { + return psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("function prompt { throw }; prompt"), + CancellationToken.None); + }).ConfigureAwait(true); + + string prompt = await psesHost.ExecuteDelegateAsync( + nameof(psesHost.GetPrompt), + executionOptions: null, + (_, _) => psesHost.GetPrompt(CancellationToken.None), CancellationToken.None).ConfigureAwait(true); + Assert.Equal(PsesInternalHost.DefaultPrompt, prompt); + } + + [Fact] + public async Task CanHandleUndefinedPrompt() + { + Assert.Empty(await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("Remove-Item function:prompt; Get-Item function:prompt -ErrorAction Ignore"), + CancellationToken.None).ConfigureAwait(true)); + string prompt = await psesHost.ExecuteDelegateAsync( nameof(psesHost.GetPrompt), executionOptions: null, From 306ff45a6cb602613cd62e718322b1f5e3d5f39f Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 3 Aug 2022 14:36:17 -0700 Subject: [PATCH 033/327] Update CHANGELOG for `v3.4.8` --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aac88843f..489ade310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # PowerShell Editor Services Release History +## v3.4.8 +### Wednesday, August 03, 2022 + +- ✨ 🚨 [PowerShellEditorServices #1867](https://github.com/PowerShell/PowerShellEditorServices/pull/1867) - Add regression test for when `prompt` is undefined. +- 🐛 🛫 [vscode-powershell #4073](https://github.com/PowerShell/PowerShellEditorServices/pull/1866) - Fix bug where error in `prompt` function crashed REPL. +- #️⃣ 🙏 [vscode-powershell #2697](https://github.com/PowerShell/PowerShellEditorServices/pull/1864) - Use `HostInfo.BundledModulePath` to find PSScriptAnalyzer. + ## v3.4.7 ### Monday, July 25, 2022 From e86b60c779199092a8210585b00239dff9f8a052 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 3 Aug 2022 14:36:17 -0700 Subject: [PATCH 034/327] Bump version to `v3.4.8` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index a7cd77be1..502691664 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.4.7 + 3.4.8 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 2671614bf..7c0b224e9 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.4.7' +ModuleVersion = '3.4.8' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From c5a55fe5b0e13c7a8027f026f45e5ff1bf7b93fb Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Thu, 4 Aug 2022 20:32:50 +0200 Subject: [PATCH 035/327] Fix duplicate `DiagnosticMarkers` when reopening a file (#1869) Fixes https://github.com/PowerShell/vscode-powershell/issues/3252. Co-authored-by: Andy Jordan <2226434+andschwa@users.noreply.github.com> --- .../Services/Analysis/AnalysisService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index c376da311..73e5cfa27 100644 --- a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -373,6 +373,9 @@ private async Task DelayThenInvokeDiagnosticsAsync(ScriptFile[] filesToAnalyze, { ScriptFileMarker[] semanticMarkers = await AnalysisEngine.AnalyzeScriptAsync(scriptFile.Contents).ConfigureAwait(false); + // Clear existing PSScriptAnalyzer markers (but keep parser errors where the source is "PowerShell") + // so that they are not duplicated when re-opening files. + scriptFile.DiagnosticMarkers.RemoveAll(m => m.Source == "PSScriptAnalyzer"); scriptFile.DiagnosticMarkers.AddRange(semanticMarkers); PublishScriptDiagnostics(scriptFile); From 7853976f5d4ed0b96cacd424db6eb53ce4226194 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Thu, 4 Aug 2022 12:12:42 -0700 Subject: [PATCH 036/327] Add `DoesNotDuplicateScriptMarkersAsync` regression test (#1870) --- .../Services/Analysis/AnalysisService.cs | 6 ++-- .../Debugging/DebugServiceTests.cs | 4 +-- .../Extensions/ExtensionCommandTests.cs | 4 +-- .../Services/Symbols/PSScriptAnalyzerTests.cs | 34 +++++++++++++++---- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index 73e5cfa27..514e629a5 100644 --- a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -122,7 +122,7 @@ public AnalysisService( /// /// The analysis engine to use for running script analysis. /// - private PssaCmdletAnalysisEngine AnalysisEngine => _analysisEngineLazy?.Value; + internal PssaCmdletAnalysisEngine AnalysisEngine => _analysisEngineLazy?.Value; /// /// Sets up a script analysis run, eventually returning the result. @@ -346,7 +346,7 @@ private void ClearOpenFileMarkers() } } - private async Task DelayThenInvokeDiagnosticsAsync(ScriptFile[] filesToAnalyze, CancellationToken cancellationToken) + internal async Task DelayThenInvokeDiagnosticsAsync(ScriptFile[] filesToAnalyze, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -409,7 +409,7 @@ private void PublishScriptDiagnostics(ScriptFile scriptFile, IReadOnlyList(diagnostics) diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index d0f1f0ef0..02a5a23bc 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -532,8 +532,8 @@ public async Task DebuggerBreaksInUntitledScript() const string scriptPath = "untitled:Untitled-1"; Assert.True(ScriptFile.IsUntitledPath(scriptPath)); ScriptFile scriptFile = workspace.GetFileBuffer(scriptPath, contents); - Assert.Equal(scriptFile.DocumentUri, scriptPath); - Assert.Equal(scriptFile.Contents, contents); + Assert.Equal(scriptPath, scriptFile.DocumentUri); + Assert.Equal(contents, scriptFile.Contents); Assert.True(workspace.TryGetFile(scriptPath, out ScriptFile _)); await debugService.SetCommandBreakpointsAsync( diff --git a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs index b6fbd881e..430687f93 100644 --- a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs +++ b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs @@ -72,8 +72,8 @@ await psesHost.ExecutePSCommandAsync( CancellationToken.None).ConfigureAwait(true); Assert.NotNull(commandAdded); - Assert.Equal(commandAdded.Name, commandName); - Assert.Equal(commandAdded.DisplayName, commandDisplayName); + Assert.Equal(commandName, commandAdded.Name); + Assert.Equal(commandDisplayName, commandAdded.DisplayName); // Invoke the command await extensionCommandService.InvokeCommandAsync(commandName, editorContext).ConfigureAwait(true); diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs index 05f069f2c..9e8c00dde 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs @@ -2,11 +2,11 @@ // Licensed under the MIT License. using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; -using Microsoft.PowerShell.EditorServices.Services.Analysis; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test; using Xunit; @@ -16,13 +16,15 @@ namespace PowerShellEditorServices.Test.Services.Symbols [Trait("Category", "PSScriptAnalyzer")] public class PSScriptAnalyzerTests { + private readonly WorkspaceService workspaceService = new(NullLoggerFactory.Instance); private readonly AnalysisService analysisService; + private const string script = "function Get-Widgets {}"; public PSScriptAnalyzerTests() => analysisService = new( NullLoggerFactory.Instance, languageServer: null, configurationService: null, - workspaceService: null, + workspaceService: workspaceService, new HostStartupInfo( name: "", profileId: "", @@ -39,11 +41,13 @@ public class PSScriptAnalyzerTests bundledModulePath: PsesHostFactory.BundledModulePath)); [Fact] - public async Task CanLoadPSScriptAnalyzer() + public async Task CanLoadPSScriptAnalyzerAsync() { - PssaCmdletAnalysisEngine engine = analysisService.InstantiateAnalysisEngine(); - Assert.NotNull(engine); - ScriptFileMarker[] violations = await engine.AnalyzeScriptAsync("function Get-Widgets {}").ConfigureAwait(true); + ScriptFileMarker[] violations = await analysisService + .AnalysisEngine + .AnalyzeScriptAsync(script) + .ConfigureAwait(true); + Assert.Collection(violations, (actual) => { @@ -54,5 +58,23 @@ public async Task CanLoadPSScriptAnalyzer() Assert.Equal("PSScriptAnalyzer", actual.Source); }); } + + [Fact] + public async Task DoesNotDuplicateScriptMarkersAsync() + { + ScriptFile scriptFile = workspaceService.GetFileBuffer("untitled:Untitled-1", script); + ScriptFile[] scriptFiles = { scriptFile }; + + await analysisService + .DelayThenInvokeDiagnosticsAsync(scriptFiles, CancellationToken.None) + .ConfigureAwait(true); + Assert.Single(scriptFile.DiagnosticMarkers); + + // This is repeated to test that the markers are not duplicated. + await analysisService + .DelayThenInvokeDiagnosticsAsync(scriptFiles, CancellationToken.None) + .ConfigureAwait(true); + Assert.Single(scriptFile.DiagnosticMarkers); + } } } From 1d23697afae4eea8d6f8a1337d1ec8a74c2c18ce Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Thu, 4 Aug 2022 23:52:02 +0200 Subject: [PATCH 037/327] Add regression tests for parse error `DiagnosticMarkers` (#1872) * Add `DoesNotClearParseErrorsAsync` regression test * Add `UpdatesParseErrorDiagnosticMarkers` test --- .../Services/Symbols/PSScriptAnalyzerTests.cs | 24 +++++++++++ .../Session/ScriptFileTests.cs | 42 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs index 9e8c00dde..93cb08122 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs @@ -76,5 +76,29 @@ await analysisService .ConfigureAwait(true); Assert.Single(scriptFile.DiagnosticMarkers); } + + [Fact] + public async Task DoesNotClearParseErrorsAsync() + { + // Causing a missing closing } parser error + ScriptFile scriptFile = workspaceService.GetFileBuffer("untitled:Untitled-2", script.TrimEnd('}')); + ScriptFile[] scriptFiles = { scriptFile }; + + await analysisService + .DelayThenInvokeDiagnosticsAsync(scriptFiles, CancellationToken.None) + .ConfigureAwait(true); + + Assert.Collection(scriptFile.DiagnosticMarkers, + (actual) => + { + Assert.Equal("Missing closing '}' in statement block or type definition.", actual.Message); + Assert.Equal("PowerShell", actual.Source); + }, + (actual) => + { + Assert.Equal("PSUseSingularNouns", actual.RuleName); + Assert.Equal("PSScriptAnalyzer", actual.Source); + }); + } } } diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index df70f0463..40efc5c26 100644 --- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -237,6 +237,48 @@ public void CanDeleteFromEndOfFile() ); } + [Trait("Category", "ScriptFile")] + [Fact] + public void UpdatesParseErrorDiagnosticMarkers() + { + ScriptFile myScript = CreateScriptFile(TestUtilities.NormalizeNewlines("{\n{")); + + // Verify parse errors were detected on file open + Assert.Collection(myScript.DiagnosticMarkers.OrderBy(dm => dm.ScriptRegion.StartLineNumber), + (actual) => + { + Assert.Equal(1, actual.ScriptRegion.StartLineNumber); + Assert.Equal("Missing closing '}' in statement block or type definition.", actual.Message); + Assert.Equal("PowerShell", actual.Source); + }, + (actual) => + { + Assert.Equal(2, actual.ScriptRegion.StartLineNumber); + Assert.Equal("Missing closing '}' in statement block or type definition.", actual.Message); + Assert.Equal("PowerShell", actual.Source); + }); + + // Remove second { + myScript.ApplyChange( + new FileChange + { + Line = 2, + EndLine = 2, + Offset = 1, + EndOffset = 2, + InsertString = "" + }); + + // Verify parse errors were updated on file change + Assert.Collection(myScript.DiagnosticMarkers, + (actual) => + { + Assert.Equal(1, actual.ScriptRegion.StartLineNumber); + Assert.Equal("Missing closing '}' in statement block or type definition.", actual.Message); + Assert.Equal("PowerShell", actual.Source); + }); + } + internal static ScriptFile CreateScriptFile(string initialString) { using StringReader stringReader = new(initialString); From 9666cedfaea31acb66ba0687134adc4b29c76abb Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 5 Aug 2022 12:59:24 -0700 Subject: [PATCH 038/327] Fix (and test) regression with PSScriptAnalyzer default rules (#1873) When we swapped a ternary for a null-coalescing operator we ran into a problem because the two constructor overloads took `string[]` and `object`, and we were now calling the constructor first, which meant the `object` version was always being called. This erroneously sent the rule list to the settings object parameter. --- .../Services/Analysis/AnalysisService.cs | 42 +++++++++---------- .../Analysis/PssaCmdletAnalysisEngine.cs | 36 ++++++++-------- .../Services/Symbols/PSScriptAnalyzerTests.cs | 18 +++++--- 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index 514e629a5..aaa77fe1c 100644 --- a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -39,19 +39,19 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic) Position end = diagnostic.Range.End; StringBuilder sb = new StringBuilder(256) - .Append(diagnostic.Source ?? "?") - .Append('_') - .Append(diagnostic.Code?.IsString ?? true ? diagnostic.Code?.String : diagnostic.Code?.Long.ToString()) - .Append('_') - .Append(diagnostic.Severity?.ToString() ?? "?") - .Append('_') - .Append(start.Line) - .Append(':') - .Append(start.Character) - .Append('-') - .Append(end.Line) - .Append(':') - .Append(end.Character); + .Append(diagnostic.Source ?? "?") + .Append('_') + .Append(diagnostic.Code?.IsString ?? true ? diagnostic.Code?.String : diagnostic.Code?.Long.ToString()) + .Append('_') + .Append(diagnostic.Severity?.ToString() ?? "?") + .Append('_') + .Append(start.Line) + .Append(':') + .Append(start.Character) + .Append('-') + .Append(end.Line) + .Append(':') + .Append(end.Character); return sb.ToString(); } @@ -60,7 +60,7 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic) /// Defines the list of Script Analyzer rules to include by default if /// no settings file is specified. /// - private static readonly string[] s_defaultRules = { + internal static readonly string[] s_defaultRules = { "PSAvoidAssignmentToAutomaticVariable", "PSUseToExportFieldsInManifest", "PSMisleadingBacktick", @@ -141,7 +141,7 @@ public void StartScriptDiagnostics(ScriptFile[] filesToAnalyze) // If there's an existing task, we want to cancel it here; CancellationTokenSource cancellationSource = new(); CancellationTokenSource oldTaskCancellation = Interlocked.Exchange(ref _diagnosticsCancellationTokenSource, cancellationSource); - if (oldTaskCancellation != null) + if (oldTaskCancellation is not null) { try { @@ -194,7 +194,7 @@ public Task FormatAsync(string scriptFileContents, Hashtable formatSetti /// public async Task GetCommentHelpText(string functionText, string helpLocation, bool forBlockComment) { - if (AnalysisEngine == null) + if (AnalysisEngine is null) { return null; } @@ -215,7 +215,7 @@ public async Task GetCommentHelpText(string functionText, string helpLoc /// Get the most recent corrections computed for a given script file. /// /// The URI string of the file to get code actions for. - /// A threadsafe readonly dictionary of the code actions of the particular file. + /// A thread-safe readonly dictionary of the code actions of the particular file. public async Task>> GetMostRecentCodeActionsForFileAsync(DocumentUri uri) { if (!_workspaceService.TryGetFile(uri, out ScriptFile file) @@ -252,8 +252,8 @@ public void OnConfigurationUpdated(object _, LanguageServerSettings settings) private void EnsureEngineSettingsCurrent() { - if (_analysisEngineLazy == null - || (_pssaSettingsFilePath != null + if (_analysisEngineLazy is null + || (_pssaSettingsFilePath is not null && !File.Exists(_pssaSettingsFilePath))) { InitializeAnalysisEngineToCurrentSettings(); @@ -264,7 +264,7 @@ private void InitializeAnalysisEngineToCurrentSettings() { // We may be triggered after the lazy factory is set, // but before it's been able to instantiate - if (_analysisEngineLazy == null) + if (_analysisEngineLazy is null) { _analysisEngineLazy = new Lazy(InstantiateAnalysisEngine); return; @@ -327,7 +327,7 @@ private bool TryFindSettingsFile(out string settingsFilePath) settingsFilePath = _workspaceService?.ResolveWorkspacePath(configuredPath); - if (settingsFilePath == null + if (settingsFilePath is null || !File.Exists(settingsFilePath)) { _logger.LogInformation($"Unable to find PSSA settings file at '{configuredPath}'. Loading default rules."); diff --git a/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs b/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs index 65a393399..371950bc0 100644 --- a/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs +++ b/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs @@ -75,7 +75,7 @@ public PssaCmdletAnalysisEngine Build(string pssaModulePath) { logger.LogDebug("Creating PSScriptAnalyzer runspace with module at: '{Path}'", pssaModulePath); RunspacePool pssaRunspacePool = CreatePssaRunspacePool(pssaModulePath); - PssaCmdletAnalysisEngine cmdletAnalysisEngine = new(logger, pssaRunspacePool, _settingsParameter ?? _rules); + PssaCmdletAnalysisEngine cmdletAnalysisEngine = new(logger, pssaRunspacePool, _rules, _settingsParameter); cmdletAnalysisEngine.LogAvailablePssaFeatures(); return cmdletAnalysisEngine; } @@ -105,28 +105,20 @@ public PssaCmdletAnalysisEngine Build(string pssaModulePath) private readonly RunspacePool _analysisRunspacePool; - private readonly object _settingsParameter; + internal readonly object _settingsParameter; - private readonly string[] _rulesToInclude; + internal readonly string[] _rulesToInclude; private PssaCmdletAnalysisEngine( ILogger logger, RunspacePool analysisRunspacePool, - string[] rulesToInclude) - : this(logger, analysisRunspacePool) => _rulesToInclude = rulesToInclude; - - private PssaCmdletAnalysisEngine( - ILogger logger, - RunspacePool analysisRunspacePool, - object analysisSettingsParameter) - : this(logger, analysisRunspacePool) => _settingsParameter = analysisSettingsParameter; - - private PssaCmdletAnalysisEngine( - ILogger logger, - RunspacePool analysisRunspacePool) + string[] rulesToInclude = default, + object analysisSettingsParameter = default) { _logger = logger; _analysisRunspacePool = analysisRunspacePool; + _rulesToInclude = rulesToInclude; + _settingsParameter = analysisSettingsParameter; } /// @@ -228,9 +220,17 @@ public Task AnalyzeScriptAsync(string scriptContent, Hashtab return GetSemanticMarkersFromCommandAsync(command); } - public PssaCmdletAnalysisEngine RecreateWithNewSettings(string settingsPath) => new(_logger, _analysisRunspacePool, settingsPath); - - public PssaCmdletAnalysisEngine RecreateWithRules(string[] rules) => new(_logger, _analysisRunspacePool, rules); + public PssaCmdletAnalysisEngine RecreateWithNewSettings(string settingsPath) => new( + _logger, + _analysisRunspacePool, + rulesToInclude: null, + analysisSettingsParameter: settingsPath); + + public PssaCmdletAnalysisEngine RecreateWithRules(string[] rules) => new( + _logger, + _analysisRunspacePool, + rulesToInclude: rules, + analysisSettingsParameter: null); #region IDisposable Support private bool disposedValue; // To detect redundant calls diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs index 93cb08122..ca3f3ae04 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; @@ -18,7 +17,7 @@ public class PSScriptAnalyzerTests { private readonly WorkspaceService workspaceService = new(NullLoggerFactory.Instance); private readonly AnalysisService analysisService; - private const string script = "function Get-Widgets {}"; + private const string script = "function Do-Work {}"; public PSScriptAnalyzerTests() => analysisService = new( NullLoggerFactory.Instance, @@ -40,6 +39,13 @@ public class PSScriptAnalyzerTests usesLegacyReadLine: false, bundledModulePath: PsesHostFactory.BundledModulePath)); + [Fact] + public void IncludesDefaultRules() + { + Assert.Null(analysisService.AnalysisEngine._settingsParameter); + Assert.Equal(AnalysisService.s_defaultRules, analysisService.AnalysisEngine._rulesToInclude); + } + [Fact] public async Task CanLoadPSScriptAnalyzerAsync() { @@ -51,10 +57,10 @@ public async Task CanLoadPSScriptAnalyzerAsync() Assert.Collection(violations, (actual) => { - Assert.Single(actual.Corrections); - Assert.Equal("Singularized correction of 'Get-Widgets'", actual.Corrections.First().Name); + Assert.Empty(actual.Corrections); Assert.Equal(ScriptFileMarkerLevel.Warning, actual.Level); - Assert.Equal("PSUseSingularNouns", actual.RuleName); + Assert.Equal("The cmdlet 'Do-Work' uses an unapproved verb.", actual.Message); + Assert.Equal("PSUseApprovedVerbs", actual.RuleName); Assert.Equal("PSScriptAnalyzer", actual.Source); }); } @@ -96,7 +102,7 @@ await analysisService }, (actual) => { - Assert.Equal("PSUseSingularNouns", actual.RuleName); + Assert.Equal("PSUseApprovedVerbs", actual.RuleName); Assert.Equal("PSScriptAnalyzer", actual.Source); }); } From c31a39f5a5b6e5f0808acdca32985945d369e068 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 5 Aug 2022 13:09:48 -0700 Subject: [PATCH 039/327] Delete out-of-date `devcontainer` setup (#1871) We'd welcome an up-to-date and tested `devcontainer` in the client repo that works with the existing VS Code workspace, but this one is confusing as it's unused and so untested. --- .devcontainer/Dockerfile | 32 -------------------------------- .devcontainer/devcontainer.json | 14 -------------- 2 files changed, 46 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index e9ece8067..000000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -FROM mcr.microsoft.com/dotnet/core/sdk:2.1.802 - -# Avoid warnings by switching to noninteractive -ENV DEBIAN_FRONTEND=noninteractive - -# Configure apt and install packages -RUN apt-get update && apt-get -y install --no-install-recommends apt-utils 2>&1 \ - # Verify git, process tools, lsb-release (common in install instructions for CLIs) installed - && apt-get -y install git procps lsb-release \ - # Install PowerShell - && apt-get install curl gnupg apt-transport-https -y \ - && curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ - && echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main" > /etc/apt/sources.list.d/microsoft.list \ - && apt-get update \ - && apt-get install -y powershell \ - # Clean up - && apt-get autoremove -y \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* - -# Switch back to dialog for any ad-hoc use of apt-get -ENV DEBIAN_FRONTEND=dialog - -SHELL ["/usr/bin/pwsh", "-Command"] - -# Install PowerShell modules required for building and testing -RUN Install-Module InvokeBuild -Force -RUN Install-Module platyPS -Force -RUN Install-Module Pester -Force diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 7cd083c55..000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,14 +0,0 @@ -// See https://aka.ms/vscode-remote/devcontainer.json for format details. -{ - "name": "C# (.NET Core 2.1) and PowerShell on Debian 9", - "dockerFile": "Dockerfile", - - "postCreateCommand": "dotnet restore", - - "extensions": [ - "ms-dotnettools.csharp", - "ms-vscode.powershell-preview", - "davidanson.vscode-markdownlint", - "editorconfig.editorconfig" - ] -} From f3c504c0355fc1567a08b62b3953443614599956 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Mon, 8 Aug 2022 14:28:44 -0700 Subject: [PATCH 040/327] Add end-to-end integration test with Vim (#1874) --- .github/workflows/emacs-test.yml | 2 +- .github/workflows/vim-test.yml | 53 ++++++++++++++++++++++++++++++++ test/.themisrc | 6 ++++ test/vim-test.ps1 | 1 + test/vim-test.vim | 44 ++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/vim-test.yml create mode 100644 test/.themisrc create mode 100644 test/vim-test.ps1 create mode 100644 test/vim-test.vim diff --git a/.github/workflows/emacs-test.yml b/.github/workflows/emacs-test.yml index aa41e7b61..4a62a3ae7 100644 --- a/.github/workflows/emacs-test.yml +++ b/.github/workflows/emacs-test.yml @@ -11,7 +11,7 @@ on: jobs: test: - name: Test PSES with Emacs via Eglot + name: Test via Eglot runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/vim-test.yml b/.github/workflows/vim-test.yml new file mode 100644 index 000000000..5dcf11f88 --- /dev/null +++ b/.github/workflows/vim-test.yml @@ -0,0 +1,53 @@ +name: "Vim" + +on: + push: + branches: [ main ] + tags: [ v* ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + paths-ignore: [ '**/*.md' ] + +jobs: + test: + name: Test via LanguageClient-neovim + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Build PSES + shell: pwsh + run: tools/azurePipelinesBuild.ps1 + + - name: Install Vim + uses: rhysd/action-setup-vim@v1 + id: vim + + - name: Checkout vim-ps1 + uses: actions/checkout@v3 + with: + repository: PProvost/vim-ps1 + path: vim-ps1 + + - name: Checkout LanguageClient-neovim + uses: actions/checkout@v3 + with: + repository: autozimu/LanguageClient-neovim + path: LanguageClient-neovim + + - name: Install LanguageClient-neovim + run: ./install.sh + working-directory: LanguageClient-neovim + + - name: Checkout Themis + uses: actions/checkout@v3 + with: + repository: thinca/vim-themis + path: vim-themis + + - name: Run Themis + env: + THEMIS_VIM: ${{ steps.vim.outputs.executable }} + run: ./vim-themis/bin/themis ./test/vim-test.vim diff --git a/test/.themisrc b/test/.themisrc new file mode 100644 index 000000000..d684e00fa --- /dev/null +++ b/test/.themisrc @@ -0,0 +1,6 @@ +filetype plugin on + +let g:repo_root = fnamemodify(expand(''), ':h:h') + +call themis#option('runtimepath', g:repo_root . '/LanguageClient-neovim') +call themis#option('runtimepath', g:repo_root . '/vim-ps1') diff --git a/test/vim-test.ps1 b/test/vim-test.ps1 new file mode 100644 index 000000000..8175124cc --- /dev/null +++ b/test/vim-test.ps1 @@ -0,0 +1 @@ +function Do-Work {} diff --git a/test/vim-test.vim b/test/vim-test.vim new file mode 100644 index 000000000..05c8919fd --- /dev/null +++ b/test/vim-test.vim @@ -0,0 +1,44 @@ +let s:suite = themis#suite('pses') +let s:assert = themis#helper('assert') + +function s:suite.before() + let l:pses_path = g:repo_root . '/module' + let g:LanguageClient_serverCommands = { + \ 'ps1': ['pwsh', '-NoLogo', '-NoProfile', '-Command', + \ l:pses_path . '/PowerShellEditorServices/Start-EditorServices.ps1', + \ '-HostName', 'vim', '-HostProfileId', 'vim', '-HostVersion', '1.0.0', + \ '-BundledModulesPath', l:pses_path, '-Stdio', + \ '-LogPath', g:repo_root . '/pses.log', '-LogLevel', 'Diagnostic', + \ '-SessionDetailsPath', g:repo_root . '/pses_session.json' ] + \ } + let g:LanguageClient_serverStderr = 'DEBUG' + let g:LanguageClient_loggingFile = g:repo_root . '/LanguageClient.log' + let g:LanguageClient_serverStderr = g:repo_root . '/LanguageServer.log' +endfunction + +function s:suite.has_language_client() + call s:assert.includes(&runtimepath, g:repo_root . '/LanguageClient-neovim') + call s:assert.cmd_exists('LanguageClientStart') + call s:assert.not_empty(g:LanguageClient_serverCommands) + call s:assert.true(LanguageClient#HasCommand('ps1')) +endfunction + +function s:suite.analyzes_powershell_file() + view test/vim-test.ps1 " This must not use quotes! + + let l:bufnr = bufnr('vim-test.ps1$') + call s:assert.not_equal(l:bufnr, -1) + let l:bufinfo = getbufinfo(l:bufnr)[0] + + call s:assert.equal(l:bufinfo.name, g:repo_root . '/test/vim-test.ps1') + call s:assert.includes(getbufline(l:bufinfo.name, 1), 'function Do-Work {}') + " TODO: This shouldn't be necessary, vim-ps1 works locally but not in CI. + call setbufvar(l:bufinfo.bufnr, '&filetype', 'ps1') + call s:assert.equal(getbufvar(l:bufinfo.bufnr, '&filetype'), 'ps1') + + execute 'LanguageClientStart' + execute 'sleep' 5 + call s:assert.equal(getbufvar(l:bufinfo.name, 'LanguageClient_isServerRunning'), 1) + call s:assert.equal(getbufvar(l:bufinfo.name, 'LanguageClient_projectRoot'), g:repo_root) + call s:assert.equal(getbufvar(l:bufinfo.name, 'LanguageClient_statusLineDiagnosticsCounts'), {'E': 0, 'W': 1, 'H': 0, 'I': 0}) +endfunction From 8b6c2b9b853f888e37e075dc472b410f844725a1 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Thu, 11 Aug 2022 13:09:27 -0700 Subject: [PATCH 041/327] Pin .NET to 6.0.302 and 3.1.27 (#1878) The latest update has blocked the CI/CD pipeline, and so while we investigate that, we'll pin to the last known working version. --- .vsts-ci/templates/ci-general.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vsts-ci/templates/ci-general.yml b/.vsts-ci/templates/ci-general.yml index e252fcf40..c903b8a29 100644 --- a/.vsts-ci/templates/ci-general.yml +++ b/.vsts-ci/templates/ci-general.yml @@ -15,14 +15,14 @@ steps: displayName: Install .NET 6.0.x SDK inputs: packageType: sdk - version: 6.0.x + version: 6.0.302 performMultiLevelLookup: true - task: UseDotNet@2 displayName: Install .NET 3.1.x runtime inputs: packageType: runtime - version: 3.1.x + version: 3.1.27 performMultiLevelLookup: true - task: PowerShell@2 From 3a57c006079b0c6b1406facb5dd93824c72756f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Aug 2022 20:30:44 +0000 Subject: [PATCH 042/327] Bump Microsoft.NET.Test.Sdk from 17.2.0 to 17.3.0 (#1875) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.2.0 to 17.3.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.2.0...v17.3.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 073fb5c60..f2f97497a 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 378f698d4..72fc00a0b 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From 06c69819ac38e0de29256f1fb41fa3edd7df42ad Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 11 Aug 2022 13:40:02 -0700 Subject: [PATCH 043/327] Update CHANGELOG for `v3.4.9` --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 489ade310..44c3e3d1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # PowerShell Editor Services Release History +## v3.4.9 +### Thursday, August 11, 2022 + +- ✨ 🚨 [PowerShellEditorServices #1874](https://github.com/PowerShell/PowerShellEditorServices/pull/1874) - Add end-to-end integration test with Vim. +- 🐛 ‍🕵️ [vscode-powershell #4112](https://github.com/PowerShell/PowerShellEditorServices/pull/1873) - Fix (and test) regression with PSScriptAnalyzer default rules. +- ✨ 🚨 [PowerShellEditorServices #1872](https://github.com/PowerShell/PowerShellEditorServices/pull/1872) - Add regression tests for parse error DiagnosticMarkers. (Thanks @fflaten!) +- ✨ 🚨 [PowerShellEditorServices #1870](https://github.com/PowerShell/PowerShellEditorServices/pull/1870) - Add `DoesNotDuplicateScriptMarkersAsync` regression test. +- 🐛 ‍🕵️ [PowerShellEditorServices #1869](https://github.com/PowerShell/PowerShellEditorServices/pull/1869) - Fix duplicate DiagnosticMarkers when reopening a file. (Thanks @fflaten!) + ## v3.4.8 ### Wednesday, August 03, 2022 From 5a1877931e915b9097421b95f37ead4e3debafb5 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 11 Aug 2022 13:40:03 -0700 Subject: [PATCH 044/327] Bump version to `v3.4.9` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 502691664..6b972c0d9 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.4.8 + 3.4.9 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 7c0b224e9..422a21c24 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.4.8' +ModuleVersion = '3.4.9' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 84776007c7ca4cf33f224e700c996462edb5a05c Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 12 Aug 2022 10:22:09 -0700 Subject: [PATCH 045/327] Only run Windows PowerShell 5.1 tests on CI with `powershell.exe` (#1881) --- PowerShellEditorServices.build.ps1 | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index bb7fe352a..94b5d6207 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -34,7 +34,7 @@ $script:IsNix = $IsLinux -or $IsMacOS # For Apple M1, pwsh might be getting emulated, in which case we need to check # for the proc_translated flag, otherwise we can check the architecture. $script:IsAppleM1 = $IsMacOS -and ((sysctl -n sysctl.proc_translated) -eq 1 -or (uname -m) -eq "arm64") -$script:IsArm64 = -not $script:IsNix -and @("ARM64", "AMD64") -contains $env:PROCESSOR_ARCHITECTURE +$script:IsArm64 = -not $script:IsNix -and @("ARM64") -contains $env:PROCESSOR_ARCHITECTURE $script:BuildInfoPath = [System.IO.Path]::Combine($PSScriptRoot, "src", "PowerShellEditorServices.Hosting", "BuildInfo.cs") $script:PsesCommonProps = [xml](Get-Content -Raw "$PSScriptRoot/PowerShellEditorServices.Common.props") @@ -176,7 +176,9 @@ Task Test TestServer, TestE2E Task TestServer TestServerWinPS, TestServerPS7, TestServerPS72 -Task TestServerWinPS -If (-not $script:IsNix) Build, SetupHelpForTests, { +# NOTE: While these can run under `pwsh.exe` we only want them to run under +# `powershell.exe` so that the CI time isn't doubled. +Task TestServerWinPS -If ($PSVersionTable.PSEdition -eq "Desktop") Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ # TODO: See https://github.com/dotnet/sdk/issues/18353 for x64 test host # that is debuggable! If architecture is added, the assembly path gets an @@ -185,12 +187,12 @@ Task TestServerWinPS -If (-not $script:IsNix) Build, SetupHelpForTests, { Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.Desktop } } -Task TestServerPS7 -If (-not $script:IsAppleM1 -and -not $script:IsArm64) Build, SetupHelpForTests, { +Task TestServerPS7 -If ($PSVersionTable.PSEdition -eq "Core" -and -not $script:IsAppleM1 -and -not $script:IsArm64) Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } } -Task TestServerPS72 Build, SetupHelpForTests, { +Task TestServerPS72 -If ($PSVersionTable.PSEdition -eq "Core") Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } } From ed24bf82dc77b7176651cbe4280a2e5b74efabd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 20:54:26 +0000 Subject: [PATCH 046/327] Bump Microsoft.PowerShell.SDK from 7.2.5 to 7.2.6 (#1883) Bumps [Microsoft.PowerShell.SDK](https://github.com/PowerShell/PowerShell) from 7.2.5 to 7.2.6. - [Release notes](https://github.com/PowerShell/PowerShell/releases) - [Commits](https://github.com/PowerShell/PowerShell/compare/v7.2.5...v7.2.6) --- updated-dependencies: - dependency-name: Microsoft.PowerShell.SDK dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 72fc00a0b..5cb80c5b7 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -19,12 +19,12 @@ - + - + From b5585de693e6bff92c1ff8c384b5f19435f6b165 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 12 Aug 2022 15:07:22 -0700 Subject: [PATCH 047/327] Update CHANGELOG for `v3.4.10` --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c3e3d1c..452f81d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # PowerShell Editor Services Release History +## v3.4.10 +### Friday, August 12, 2022 + +- Just dependency updates. + ## v3.4.9 ### Thursday, August 11, 2022 From e82e825e1fd897424ac46974757f29ee0e3fcdd0 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 12 Aug 2022 15:07:24 -0700 Subject: [PATCH 048/327] Bump version to `v3.4.10` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 6b972c0d9..f3bae823a 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.4.9 + 3.4.10 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 422a21c24..ee4b20c69 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.4.9' +ModuleVersion = '3.4.10' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From b0bfce7c1d0174b79b66f07f9bb0174165d8185a Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Mon, 15 Aug 2022 21:34:23 +0200 Subject: [PATCH 049/327] Fix whitespace in Pester symbol and add test (#1893) Trim leading whitespace in Pester symbol name and add tests. --- .../Symbols/PesterDocumentSymbolProvider.cs | 2 +- .../Language/SymbolsServiceTests.cs | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index 12aa7f68a..59f132f1c 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -199,7 +199,7 @@ internal PesterSymbolReference( IScriptExtent scriptExtent) : base( SymbolType.Function, - testLine.TrimEnd(DefinitionTrimChars), + testLine.TrimStart().TrimEnd(DefinitionTrimChars), scriptExtent, scriptFile.FilePath, testLine) diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index fb3b2301a..33fdaea0e 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -314,8 +314,26 @@ public void FindsSymbolsInFile() [Fact] public void FindsSymbolsInPesterFile() { - List symbolsResult = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails); - Assert.Equal(5, symbolsResult.Count); + List symbolsResult = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails).OfType().ToList(); + Assert.Equal(5, symbolsResult.Count(r => r.SymbolType == SymbolType.Function)); + + Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.Describe)); + SymbolReference firstDescribeSymbol = symbolsResult.First(r => r.Command == PesterCommandType.Describe); + Assert.Equal("Describe \"A dummy test\"", firstDescribeSymbol.SymbolName); + Assert.Equal(1, firstDescribeSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstDescribeSymbol.ScriptRegion.StartColumnNumber); + + Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.Context)); + SymbolReference firstContextSymbol = symbolsResult.First(r => r.Command == PesterCommandType.Context); + Assert.Equal("Context \"When a pester file is given\"", firstContextSymbol.SymbolName); + Assert.Equal(2, firstContextSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstContextSymbol.ScriptRegion.StartColumnNumber); + + Assert.Equal(3, symbolsResult.Count(r => r.Command == PesterCommandType.It)); + SymbolReference lastItSymbol = symbolsResult.Last(r => r.Command == PesterCommandType.It); + Assert.Equal("It \"Should return describe symbols\"", lastItSymbol.SymbolName); + Assert.Equal(11, lastItSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(9, lastItSymbol.ScriptRegion.StartColumnNumber); } [Fact] From 9d18d81e5f54bb481efb70320af80228c966c265 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Mon, 15 Aug 2022 17:13:10 -0400 Subject: [PATCH 050/327] Fix cancelling a debug task `CancelDebugExecution` was supposed to exit early when the runspace was unusable. Basically forgot the `!` in the `if` statement. --- .../Services/PowerShell/Execution/SynchronousPowerShellTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 0661ce962..2fdefbfdc 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -382,7 +382,7 @@ internal void MaybeAddToHistory() private void CancelDebugExecution() { - if (_pwsh.Runspace.RunspaceStateInfo.IsUsable()) + if (!_pwsh.Runspace.RunspaceStateInfo.IsUsable()) { return; } From d490e7a95336b43c0061dce58d2a88951cae6cc7 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Mon, 15 Aug 2022 17:15:39 -0400 Subject: [PATCH 051/327] Abort execution when in a nested debug frame When attempting to step, if we're in a nested debug frame that isn't a REPL we should stop execution before trying to exit the REPL. --- .../Debugging/PowerShellDebugContext.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 36aaad0e2..236403e5a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; @@ -145,15 +146,27 @@ public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction) // TODO: We need to assign cancellation tokens to each frame, because the current // logic results in a deadlock here when we try to cancel the scopes...which // includes ourself (hence running it in a separate thread). - Task.Run(() => _psesHost.UnwindCallStack()); + _ = Task.Run(() => _psesHost.UnwindCallStack()); return; } // Otherwise we're continuing or stepping (i.e. resuming) so we need to cancel the // debugger REPL. - if (_psesHost.CurrentFrame.IsRepl) + PowerShellFrameType frameType = _psesHost.CurrentFrame.FrameType; + if (frameType.HasFlag(PowerShellFrameType.Repl)) { _psesHost.CancelIdleParentTask(); + return; + } + + // If the user is running something via the REPL like `while ($true) { sleep 1 }` + // and then tries to step, we want to stop that so that execution can resume. + // + // This also applies to anything we're running on debugger stop like watch variables. + if (frameType.HasFlag(PowerShellFrameType.Debug | PowerShellFrameType.Nested)) + { + _psesHost.ForceSetExit(); + _psesHost.CancelIdleParentTask(); } } From 60e37b4270ffa08f02ec82744de2b5b178271e3d Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Mon, 15 Aug 2022 17:17:19 -0400 Subject: [PATCH 052/327] Write exception messages when `watch` fails Instead of a blank watch message we now write the exception message that was thrown from the attempt. --- .../Services/DebugAdapter/DebugService.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 5d32451ed..906b9fe4d 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -497,10 +497,22 @@ public async Task EvaluateExpressionAsync( bool writeResultAsOutput) { PSCommand command = new PSCommand().AddScript(expressionString); - IReadOnlyList results = await _executionService.ExecutePSCommandAsync( - command, - CancellationToken.None, - new PowerShellExecutionOptions { WriteOutputToHost = writeResultAsOutput, ThrowOnError = !writeResultAsOutput }).ConfigureAwait(false); + IReadOnlyList results; + try + { + results = await _executionService.ExecutePSCommandAsync( + command, + CancellationToken.None, + new PowerShellExecutionOptions { WriteOutputToHost = writeResultAsOutput, ThrowOnError = !writeResultAsOutput }).ConfigureAwait(false); + } + catch (Exception e) + { + // If a watch expression throws we want to show the exception. + // TODO: Show the exception as an expandable object. + return new VariableDetails( + expressionString, + $"{e.GetType().Name}: {e.Message}"); + } // Since this method should only be getting invoked in the debugger, // we can assume that Out-String will be getting used to format results From 87cd72184989c10d14fda66dd053af73f7220d63 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Mon, 15 Aug 2022 23:41:14 +0200 Subject: [PATCH 053/327] Fix symbol highlight when hovering function name (#1890) * move helper to VistiorUtils class * find function name if indented or after newline * extend tests to cover function definition name * extend test to include end position * Fix `postion` to `position` typo Co-authored-by: Andy Jordan <2226434+andschwa@users.noreply.github.com> --- .../Symbols/Vistors/FindReferencesVisitor.cs | 37 +------------- .../Symbols/Vistors/FindSymbolVisitor.cs | 21 +++++--- .../Utility/VisitorUtils.cs | 51 +++++++++++++++++++ .../Services/Symbols/AstOperationsTests.cs | 32 ++++++++---- 4 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 src/PowerShellEditorServices/Utility/VisitorUtils.cs diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs index 0dcbe7e0d..44b64c8f5 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { @@ -116,7 +117,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) /// A visit action that continues the search for references public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - (int startColumnNumber, int startLineNumber) = GetStartColumnAndLineNumbersFromAst(functionDefinitionAst); + (int startColumnNumber, int startLineNumber) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst); IScriptExtent nameExtent = new ScriptExtent() { @@ -167,39 +168,5 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var } return AstVisitAction.Continue; } - - // Computes where the start of the actual function name is. - private static (int, int) GetStartColumnAndLineNumbersFromAst(FunctionDefinitionAst ast) - { - int startColumnNumber = ast.Extent.StartColumnNumber; - int startLineNumber = ast.Extent.StartLineNumber; - int astOffset = ast.IsFilter ? "filter".Length : ast.IsWorkflow ? "workflow".Length : "function".Length; - string astText = ast.Extent.Text; - // The line offset represents the offset on the line that we're on where as - // astOffset is the offset on the entire text of the AST. - int lineOffset = astOffset; - for (; astOffset < astText.Length; astOffset++, lineOffset++) - { - if (astText[astOffset] == '\n') - { - // reset numbers since we are operating on a different line and increment the line number. - startColumnNumber = 0; - startLineNumber++; - lineOffset = 0; - } - else if (astText[astOffset] == '\r') - { - // Do nothing with carriage returns... we only look for line feeds since those - // are used on every platform. - } - else if (!char.IsWhiteSpace(astText[astOffset])) - { - // This is the start of the function name so we've found our start column and line number. - break; - } - } - - return (startColumnNumber + lineOffset, startLineNumber); - } } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index 0a1e1ec2a..bf2520a3c 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { @@ -57,22 +58,28 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) /// or a decision to continue if it wasn't found public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - int startColumnNumber = 1; + int startLineNumber = functionDefinitionAst.Extent.StartLineNumber; + int startColumnNumber = functionDefinitionAst.Extent.StartColumnNumber; + int endLineNumber = functionDefinitionAst.Extent.EndLineNumber; + int endColumnNumber = functionDefinitionAst.Extent.EndColumnNumber; if (!includeFunctionDefinitions) { - startColumnNumber = - functionDefinitionAst.Extent.Text.IndexOf( - functionDefinitionAst.Name) + 1; + // We only want the function name + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst); + startLineNumber = startLine; + startColumnNumber = startColumn; + endLineNumber = startLine; + endColumnNumber = startColumn + functionDefinitionAst.Name.Length; } IScriptExtent nameExtent = new ScriptExtent() { Text = functionDefinitionAst.Name, - StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, - EndLineNumber = functionDefinitionAst.Extent.EndLineNumber, + StartLineNumber = startLineNumber, + EndLineNumber = endLineNumber, StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length, + EndColumnNumber = endColumnNumber, File = functionDefinitionAst.Extent.File }; diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs new file mode 100644 index 000000000..861d04acc --- /dev/null +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.EditorServices.Utility +{ + /// + /// General common utilities for AST visitors to prevent reimplementation. + /// + internal static class VisitorUtils + { + /// + /// Calculates the start line and column of the actual function name in a function definition AST. + /// + /// A FunctionDefinitionAst object in the script's AST + /// A tuple with start column and line for the function name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineNumbersFromAst(FunctionDefinitionAst ast) + { + int startColumnNumber = ast.Extent.StartColumnNumber; + int startLineNumber = ast.Extent.StartLineNumber; + int astOffset = ast.IsFilter ? "filter".Length : ast.IsWorkflow ? "workflow".Length : "function".Length; + string astText = ast.Extent.Text; + // The line offset represents the offset on the line that we're on where as + // astOffset is the offset on the entire text of the AST. + int lineOffset = astOffset; + for (; astOffset < astText.Length; astOffset++, lineOffset++) + { + if (astText[astOffset] == '\n') + { + // reset numbers since we are operating on a different line and increment the line number. + startColumnNumber = 0; + startLineNumber++; + lineOffset = 0; + } + else if (astText[astOffset] == '\r') + { + // Do nothing with carriage returns... we only look for line feeds since those + // are used on every platform. + } + else if (!char.IsWhiteSpace(astText[astOffset])) + { + // This is the start of the function name so we've found our start column and line number. + break; + } + } + + return (startColumnNumber + lineOffset, startLineNumber); + } + } +} diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs index b713d900f..6789c4323 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs @@ -35,14 +35,21 @@ function FunctionWithExtraSpace FunctionNameOnDifferentLine + + function IndentedFunction { } IndentedFunction "; private static readonly ScriptBlockAst s_ast = (ScriptBlockAst)ScriptBlock.Create(s_scriptString).Ast; [Theory] + [InlineData(1, 15, "BasicFunction")] [InlineData(2, 3, "BasicFunction")] + [InlineData(4, 31, "FunctionWithExtraSpace")] [InlineData(7, 18, "FunctionWithExtraSpace")] + [InlineData(12, 22, "FunctionNameOnDifferentLine")] [InlineData(22, 13, "FunctionNameOnDifferentLine")] - public void CanFindSymbolAtPostion(int lineNumber, int columnNumber, string expectedName) + [InlineData(24, 30, "IndentedFunction")] + [InlineData(24, 52, "IndentedFunction")] + public void CanFindSymbolAtPosition(int lineNumber, int columnNumber, string expectedName) { SymbolReference reference = AstOperations.FindSymbolAtPosition(s_ast, lineNumber, columnNumber); Assert.NotNull(reference); @@ -50,8 +57,8 @@ public void CanFindSymbolAtPostion(int lineNumber, int columnNumber, string expe } [Theory] - [MemberData(nameof(FindReferencesOfSymbolAtPostionData))] - public void CanFindReferencesOfSymbolAtPostion(int lineNumber, int columnNumber, Position[] positions) + [MemberData(nameof(FindReferencesOfSymbolAtPositionData))] + public void CanFindReferencesOfSymbolAtPosition(int lineNumber, int columnNumber, Range[] symbolRange) { SymbolReference symbol = AstOperations.FindSymbolAtPosition(s_ast, lineNumber, columnNumber); @@ -60,18 +67,25 @@ public void CanFindReferencesOfSymbolAtPostion(int lineNumber, int columnNumber, int positionsIndex = 0; foreach (SymbolReference reference in references) { - Assert.Equal(positions[positionsIndex].Line, reference.ScriptRegion.StartLineNumber); - Assert.Equal(positions[positionsIndex].Character, reference.ScriptRegion.StartColumnNumber); + Assert.Equal(symbolRange[positionsIndex].Start.Line, reference.ScriptRegion.StartLineNumber); + Assert.Equal(symbolRange[positionsIndex].Start.Character, reference.ScriptRegion.StartColumnNumber); + Assert.Equal(symbolRange[positionsIndex].End.Line, reference.ScriptRegion.EndLineNumber); + Assert.Equal(symbolRange[positionsIndex].End.Character, reference.ScriptRegion.EndColumnNumber); positionsIndex++; } } - public static object[][] FindReferencesOfSymbolAtPostionData { get; } = new object[][] + public static object[][] FindReferencesOfSymbolAtPositionData { get; } = new object[][] { - new object[] { 2, 3, new[] { new Position(1, 10), new Position(2, 1) } }, - new object[] { 7, 18, new[] { new Position(4, 19), new Position(7, 3) } }, - new object[] { 22, 13, new[] { new Position(12, 8), new Position(22, 5) } }, + new object[] { 1, 15, new[] { new Range(1, 10, 1, 23), new Range(2, 1, 2, 14) } }, + new object[] { 2, 3, new[] { new Range(1, 10, 1, 23), new Range(2, 1, 2, 14) } }, + new object[] { 4, 31, new[] { new Range(4, 19, 4, 41), new Range(7, 3, 7, 25) } }, + new object[] { 7, 18, new[] { new Range(4, 19, 4, 41), new Range(7, 3, 7, 25) } }, + new object[] { 22, 13, new[] { new Range(12, 8, 12, 35), new Range(22, 5, 22, 32) } }, + new object[] { 12, 22, new[] { new Range(12, 8, 12, 35), new Range(22, 5, 22, 32) } }, + new object[] { 24, 30, new[] { new Range(24, 22, 24, 38), new Range(24, 44, 24, 60) } }, + new object[] { 24, 52, new[] { new Range(24, 22, 24, 38), new Range(24, 44, 24, 60) } }, }; } } From da55e8f96f61e59d877de069f51e6bae7fa4909d Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 12 Aug 2022 15:01:47 -0700 Subject: [PATCH 054/327] Revert "Pin .NET to 6.0.302 and 3.1.27 (#1878)" This reverts commit 8b6c2b9b853f888e37e075dc472b410f844725a1. --- .vsts-ci/templates/ci-general.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vsts-ci/templates/ci-general.yml b/.vsts-ci/templates/ci-general.yml index c903b8a29..e252fcf40 100644 --- a/.vsts-ci/templates/ci-general.yml +++ b/.vsts-ci/templates/ci-general.yml @@ -15,14 +15,14 @@ steps: displayName: Install .NET 6.0.x SDK inputs: packageType: sdk - version: 6.0.302 + version: 6.0.x performMultiLevelLookup: true - task: UseDotNet@2 displayName: Install .NET 3.1.x runtime inputs: packageType: runtime - version: 3.1.27 + version: 3.1.x performMultiLevelLookup: true - task: PowerShell@2 From e55f54fa828772b28bd0e7bd43b6f0cf72d6a62c Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 18 Aug 2022 09:30:35 -0700 Subject: [PATCH 055/327] Skip failing completion tests on Windows PowerShell Some update, presumably to the CI machine as it's now no longer tied to the .NET 3.1.28 update, is causing these two tests to fail on Windows PowerShell. Unfortunately, this probably indicates a real bug, but we haven't found the cause, and don't have a known change to point at. --- .../Language/CompletionHandlerTests.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs index 4c83fa805..d47afa38e 100644 --- a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs @@ -71,9 +71,10 @@ public async Task CompletesCommandFromModule() Assert.StartsWith(CompleteCommandFromModule.GetRandomDetail, actual.Detail); } - [Fact] + [SkippableFact] public async Task CompletesTypeName() { + Skip.If(VersionUtils.PSEdition == "Desktop", "Windows PowerShell has trouble with this test right now."); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteTypeName.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); if (VersionUtils.IsNetCore) @@ -91,10 +92,10 @@ public async Task CompletesTypeName() } } - [Trait("Category", "Completions")] - [Fact] + [SkippableFact] public async Task CompletesNamespace() { + Skip.If(VersionUtils.PSEdition == "Desktop", "Windows PowerShell has trouble with this test right now."); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteNamespace.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); Assert.Equal(CompleteNamespace.ExpectedCompletion, actual); From 17d56d926bb49eeda260533a7a23b8b1cdc4b32d Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Thu, 18 Aug 2022 20:04:42 +0200 Subject: [PATCH 056/327] Add symbols for Pester setup and teardown blocks (#1899) * add symbols for Pester setup and teardown blocks * update tests * exclude Pester setup/teardown symbols from search --- .../CodeLens/PesterCodeLensProvider.cs | 9 +- .../Symbols/PesterDocumentSymbolProvider.cs | 84 ++++++++++++++----- .../Handlers/WorkspaceSymbolsHandler.cs | 7 ++ .../Symbols/PesterFile.tests.ps1 | 32 ++++++- .../Language/SymbolsServiceTests.cs | 46 ++++++++-- 5 files changed, 145 insertions(+), 33 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs index 386fb83a8..881bbc037 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs @@ -113,12 +113,19 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken can List lenses = new(); foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile)) { + cancellationToken.ThrowIfCancellationRequested(); + if (symbol is not PesterSymbolReference pesterSymbol) { continue; } - cancellationToken.ThrowIfCancellationRequested(); + // Skip codelense for setup/teardown block + if (!PesterSymbolReference.IsPesterTestCommand(pesterSymbol.Command)) + { + continue; + } + if (_configurationService.CurrentSettings.Pester.UseLegacyCodeLens && pesterSymbol.Command != PesterCommandType.Describe) { diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index 59f132f1c..3c6469bc6 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -91,33 +91,35 @@ private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFil return null; } - // Search for a name for the test - // If the test has more than one argument for names, we set it to null string testName = null; - bool alreadySawName = false; - for (int i = 1; i < pesterCommandAst.CommandElements.Count; i++) - { - CommandElementAst currentCommandElement = pesterCommandAst.CommandElements[i]; - - // Check for an explicit "-Name" parameter - if (currentCommandElement is CommandParameterAst) + if (PesterSymbolReference.IsPesterTestCommand(commandName.Value)) { + // Search for a name for the test + // If the test has more than one argument for names, we set it to null + bool alreadySawName = false; + for (int i = 1; i < pesterCommandAst.CommandElements.Count; i++) { - // Found -Name parameter, move to next element which is the argument for -TestName - i++; + CommandElementAst currentCommandElement = pesterCommandAst.CommandElements[i]; + + // Check for an explicit "-Name" parameter + if (currentCommandElement is CommandParameterAst) + { + // Found -Name parameter, move to next element which is the argument for -TestName + i++; + if (!alreadySawName && TryGetTestNameArgument(pesterCommandAst.CommandElements[i], out testName)) + { + alreadySawName = true; + } + + continue; + } + + // Otherwise, if an argument is given with no parameter, we assume it's the name + // If we've already seen a name, we set the name to null if (!alreadySawName && TryGetTestNameArgument(pesterCommandAst.CommandElements[i], out testName)) { alreadySawName = true; } - - continue; - } - - // Otherwise, if an argument is given with no parameter, we assume it's the name - // If we've already seen a name, we set the name to null - if (!alreadySawName && TryGetTestNameArgument(pesterCommandAst.CommandElements[i], out testName)) - { - alreadySawName = true; } } @@ -145,7 +147,7 @@ private static bool TryGetTestNameArgument(CommandElementAst commandElementAst, } /// - /// Defines command types for Pester test blocks. + /// Defines command types for Pester blocks. /// internal enum PesterCommandType { @@ -162,7 +164,32 @@ internal enum PesterCommandType /// /// Identifies an It block. /// - It + It, + + /// + /// Identifies an BeforeAll block. + /// + BeforeAll, + + /// + /// Identifies an BeforeEach block. + /// + BeforeEach, + + /// + /// Identifies an AfterAll block. + /// + AfterAll, + + /// + /// Identifies an AfterEach block. + /// + AfterEach, + + /// + /// Identifies an BeforeDiscovery block. + /// + BeforeDiscovery } /// @@ -216,5 +243,18 @@ internal PesterSymbolReference( } return pesterCommandType; } + + /// + /// Checks if the PesterCommandType is a block with executable tests (Describe/Context/It). + /// + /// the PesterCommandType representing the Pester command + /// True if command type is a block used to trigger test run. False if setup/teardown/support-block. + internal static bool IsPesterTestCommand(PesterCommandType pesterCommandType) + { + return pesterCommandType is + PesterCommandType.Describe or + PesterCommandType.Context or + PesterCommandType.It; + } } } diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 04572f431..8a9aaa815 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -56,6 +56,13 @@ public override async Task> Handle(WorkspaceSymbolP continue; } + // Exclude Pester setup/teardown symbols as they're unnamed + if (foundOccurrence is PesterSymbolReference pesterSymbol && + !PesterSymbolReference.IsPesterTestCommand(pesterSymbol.Command)) + { + continue; + } + Location location = new() { Uri = DocumentUri.From(foundOccurrence.FilePath), diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.tests.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.tests.ps1 index 086b071e0..54b291713 100644 --- a/test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.tests.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.tests.ps1 @@ -1,5 +1,21 @@ -Describe "A dummy test" { - Context "When a pester file is given" { +BeforeDiscovery { + +} + +BeforeAll { + +} + +Describe "Testing Pester symbols" { + Context "When a Pester file is given" { + BeforeAll { + + } + + BeforeEach { + + } + It "Should return it symbols" { } @@ -11,5 +27,17 @@ It "Should return describe symbols" { } + + It "Should return setup and teardown symbols" { + + } + + AfterEach { + + } + } + + AfterAll { + } } diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 33fdaea0e..34f1720ce 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -315,25 +315,55 @@ public void FindsSymbolsInFile() public void FindsSymbolsInPesterFile() { List symbolsResult = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails).OfType().ToList(); - Assert.Equal(5, symbolsResult.Count(r => r.SymbolType == SymbolType.Function)); + Assert.Equal(12, symbolsResult.Count(r => r.SymbolType == SymbolType.Function)); Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.Describe)); SymbolReference firstDescribeSymbol = symbolsResult.First(r => r.Command == PesterCommandType.Describe); - Assert.Equal("Describe \"A dummy test\"", firstDescribeSymbol.SymbolName); - Assert.Equal(1, firstDescribeSymbol.ScriptRegion.StartLineNumber); + Assert.Equal("Describe \"Testing Pester symbols\"", firstDescribeSymbol.SymbolName); + Assert.Equal(9, firstDescribeSymbol.ScriptRegion.StartLineNumber); Assert.Equal(1, firstDescribeSymbol.ScriptRegion.StartColumnNumber); Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.Context)); SymbolReference firstContextSymbol = symbolsResult.First(r => r.Command == PesterCommandType.Context); - Assert.Equal("Context \"When a pester file is given\"", firstContextSymbol.SymbolName); - Assert.Equal(2, firstContextSymbol.ScriptRegion.StartLineNumber); + Assert.Equal("Context \"When a Pester file is given\"", firstContextSymbol.SymbolName); + Assert.Equal(10, firstContextSymbol.ScriptRegion.StartLineNumber); Assert.Equal(5, firstContextSymbol.ScriptRegion.StartColumnNumber); - Assert.Equal(3, symbolsResult.Count(r => r.Command == PesterCommandType.It)); + Assert.Equal(4, symbolsResult.Count(r => r.Command == PesterCommandType.It)); SymbolReference lastItSymbol = symbolsResult.Last(r => r.Command == PesterCommandType.It); - Assert.Equal("It \"Should return describe symbols\"", lastItSymbol.SymbolName); - Assert.Equal(11, lastItSymbol.ScriptRegion.StartLineNumber); + Assert.Equal("It \"Should return setup and teardown symbols\"", lastItSymbol.SymbolName); + Assert.Equal(31, lastItSymbol.ScriptRegion.StartLineNumber); Assert.Equal(9, lastItSymbol.ScriptRegion.StartColumnNumber); + + Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeDiscovery)); + SymbolReference firstBeforeDisocverySymbol = symbolsResult.First(r => r.Command == PesterCommandType.BeforeDiscovery); + Assert.Equal("BeforeDiscovery", firstBeforeDisocverySymbol.SymbolName); + Assert.Equal(1, firstBeforeDisocverySymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstBeforeDisocverySymbol.ScriptRegion.StartColumnNumber); + + Assert.Equal(2, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeAll)); + SymbolReference lastBeforeAllSymbol = symbolsResult.Last(r => r.Command == PesterCommandType.BeforeAll); + Assert.Equal("BeforeAll", lastBeforeAllSymbol.SymbolName); + Assert.Equal(11, lastBeforeAllSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(9, lastBeforeAllSymbol.ScriptRegion.StartColumnNumber); + + Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeEach)); + SymbolReference firstBeforeEachSymbol = symbolsResult.First(r => r.Command == PesterCommandType.BeforeEach); + Assert.Equal("BeforeEach", firstBeforeEachSymbol.SymbolName); + Assert.Equal(15, firstBeforeEachSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(9, firstBeforeEachSymbol.ScriptRegion.StartColumnNumber); + + Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.AfterEach)); + SymbolReference firstAfterEachSymbol = symbolsResult.First(r => r.Command == PesterCommandType.AfterEach); + Assert.Equal("AfterEach", firstAfterEachSymbol.SymbolName); + Assert.Equal(35, firstAfterEachSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(9, firstAfterEachSymbol.ScriptRegion.StartColumnNumber); + + Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.AfterAll)); + SymbolReference firstAfterAllSymbol = symbolsResult.First(r => r.Command == PesterCommandType.AfterAll); + Assert.Equal("AfterAll", firstAfterAllSymbol.SymbolName); + Assert.Equal(40, firstAfterAllSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstAfterAllSymbol.ScriptRegion.StartColumnNumber); } [Fact] From 75111b6d00655c60fa3d0a0913899ef35e28e1ce Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Thu, 18 Aug 2022 20:10:45 +0200 Subject: [PATCH 057/327] Fix file close in workspace service for Linux (#1902) --- .../Services/Workspace/WorkspaceService.cs | 19 ++++++++++++------- .../Session/WorkspaceTests.cs | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs index 89681b7bc..b4e6a8b30 100644 --- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs @@ -120,9 +120,7 @@ public ScriptFile GetFile(DocumentUri documentUri) { Validate.IsNotNull(nameof(documentUri), documentUri); - string keyName = VersionUtils.IsLinux - ? documentUri.ToString() - : documentUri.ToString().ToLower(); + string keyName = GetFileKey(documentUri); // Make sure the file isn't already loaded into the workspace if (!workspaceFiles.TryGetValue(keyName, out ScriptFile scriptFile)) @@ -258,9 +256,7 @@ public ScriptFile GetFileBuffer(DocumentUri documentUri, string initialBuffer) { Validate.IsNotNull(nameof(documentUri), documentUri); - string keyName = VersionUtils.IsLinux - ? documentUri.ToString() - : documentUri.ToString().ToLower(); + string keyName = GetFileKey(documentUri); // Make sure the file isn't already loaded into the workspace if (!workspaceFiles.TryGetValue(keyName, out ScriptFile scriptFile) && initialBuffer != null) @@ -293,7 +289,8 @@ public void CloseFile(ScriptFile scriptFile) { Validate.IsNotNull(nameof(scriptFile), scriptFile); - workspaceFiles.TryRemove(scriptFile.Id, out ScriptFile _); + string keyName = GetFileKey(scriptFile.DocumentUri); + workspaceFiles.TryRemove(keyName, out ScriptFile _); } /// @@ -540,6 +537,14 @@ internal string ResolveRelativeScriptPath(string baseFilePath, string relativePa return combinedPath; } + /// + /// Returns a normalized string for a given documentUri to be used as key name. + /// Case-sensitive uri on Linux and lowercase for other platforms. + /// + /// A DocumentUri object to get a normalized key name from + private static string GetFileKey(DocumentUri documentUri) + => VersionUtils.IsLinux ? documentUri.ToString() : documentUri.ToString().ToLower(); + #endregion } } diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs index 33e3c653b..c76da5328 100644 --- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Test.Shared; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Xunit; namespace PowerShellEditorServices.Test.Session @@ -175,5 +176,18 @@ public void CanDetermineIsPathInMemory() Assert.All(notInMemoryPaths, (p) => Assert.False(WorkspaceService.IsPathInMemory(p))); } + + [Fact] + public void CanOpenAndCloseFile() + { + WorkspaceService workspace = FixturesWorkspace(); + string filePath = Path.GetFullPath(Path.Combine(workspace.WorkspacePath, "rootfile.ps1")); + + ScriptFile file = workspace.GetFile(filePath); + Assert.Equal(workspace.GetOpenedFiles(), new[] { file }); + + workspace.CloseFile(file); + Assert.Equal(workspace.GetOpenedFiles(), Array.Empty()); + } } } From 27e2d2f66115fdb77297a16d50a985746fe8882a Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Thu, 18 Aug 2022 14:18:52 -0400 Subject: [PATCH 058/327] Add artificial stack frame to represent contexts without one (#1898) * Add artifical stack frame to represent context When stepping into certain contexts like a param block's default value expression, the engine does not provide a call stack frame to represent it. In this scenario we want to create an artifical call stack frame to represent the context we've stepped into. * Add doc comments to new `PathUtils` members --- .../Services/DebugAdapter/DebugService.cs | 43 +++++++++++++++++-- .../Debugging/StackFrameDetails.cs | 6 +-- .../Utility/PathUtils.cs | 36 ++++++++++++++++ 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 906b9fe4d..0002245cb 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -994,10 +994,45 @@ await _remoteFileManager.FetchRemoteFileAsync( // Augment the top stack frame with details from the stop event if (invocationTypeScriptPositionProperty.GetValue(e.InvocationInfo) is IScriptExtent scriptExtent) { - stackFrameDetails[0].StartLineNumber = scriptExtent.StartLineNumber; - stackFrameDetails[0].EndLineNumber = scriptExtent.EndLineNumber; - stackFrameDetails[0].StartColumnNumber = scriptExtent.StartColumnNumber; - stackFrameDetails[0].EndColumnNumber = scriptExtent.EndColumnNumber; + StackFrameDetails targetFrame = stackFrameDetails[0]; + + // Certain context changes (like stepping into the default value expression + // of a parameter) do not create a call stack frame. In order to represent + // this context change we create a fake call stack frame. + if (!string.IsNullOrEmpty(scriptExtent.File) + && !PathUtils.IsPathEqual(scriptExtent.File, targetFrame.ScriptPath)) + { + await debugInfoHandle.WaitAsync().ConfigureAwait(false); + try + { + targetFrame = new StackFrameDetails + { + ScriptPath = scriptExtent.File, + // Just use the last frame's variables since we don't have a + // good way to get real values. + AutoVariables = targetFrame.AutoVariables, + CommandVariables = targetFrame.CommandVariables, + // Ideally we'd get a real value here but since there's no real + // call stack frame for this, we'd need to replicate a lot of + // engine code. + FunctionName = "", + }; + + StackFrameDetails[] newFrames = new StackFrameDetails[stackFrameDetails.Length + 1]; + newFrames[0] = targetFrame; + stackFrameDetails.CopyTo(newFrames, 1); + stackFrameDetails = newFrames; + } + finally + { + debugInfoHandle.Release(); + } + } + + targetFrame.StartLineNumber = scriptExtent.StartLineNumber; + targetFrame.EndLineNumber = scriptExtent.EndLineNumber; + targetFrame.StartColumnNumber = scriptExtent.StartColumnNumber; + targetFrame.EndColumnNumber = scriptExtent.EndColumnNumber; } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/StackFrameDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/StackFrameDetails.cs index 6a8704df8..c188f33e4 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/StackFrameDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/StackFrameDetails.cs @@ -31,7 +31,7 @@ internal class StackFrameDetails /// /// Gets the name of the function where the stack frame occurred. /// - public string FunctionName { get; private set; } + public string FunctionName { get; internal init; } /// /// Gets the start line number of the script where the stack frame occurred. @@ -62,12 +62,12 @@ internal class StackFrameDetails /// /// Gets or sets the VariableContainerDetails that contains the auto variables. /// - public VariableContainerDetails AutoVariables { get; private set; } + public VariableContainerDetails AutoVariables { get; internal init; } /// /// Gets or sets the VariableContainerDetails that contains the call stack frame variables. /// - public VariableContainerDetails CommandVariables { get; private set; } + public VariableContainerDetails CommandVariables { get; internal init; } #endregion diff --git a/src/PowerShellEditorServices/Utility/PathUtils.cs b/src/PowerShellEditorServices/Utility/PathUtils.cs index 568a92156..066bf5917 100644 --- a/src/PowerShellEditorServices/Utility/PathUtils.cs +++ b/src/PowerShellEditorServices/Utility/PathUtils.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.IO; using System.Management.Automation; using System.Runtime.InteropServices; @@ -29,6 +30,15 @@ internal static class PathUtils /// internal static readonly char AlternatePathSeparator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? '/' : '\\'; + /// + /// The value to be used when comparing paths. Will be + /// for case sensitive file systems and + /// in case insensitive file systems. + /// + internal static readonly StringComparison PathComparison = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + ? StringComparison.Ordinal + : StringComparison.OrdinalIgnoreCase; + /// /// Converts all alternate path separators to the current platform's main path separators. /// @@ -36,6 +46,32 @@ internal static class PathUtils /// The normalized path. public static string NormalizePathSeparators(string path) => string.IsNullOrWhiteSpace(path) ? path : path.Replace(AlternatePathSeparator, DefaultPathSeparator); + /// + /// Determines whether two specified strings represent the same path. + /// + /// The first path to compare, or . + /// The second path to compare, or . + /// + /// if the value of represents the same + /// path as the value of ; otherwise, . + /// + internal static bool IsPathEqual(string left, string right) + { + if (string.IsNullOrEmpty(left)) + { + return string.IsNullOrEmpty(right); + } + + if (string.IsNullOrEmpty(right)) + { + return false; + } + + left = Path.GetFullPath(left).TrimEnd(DefaultPathSeparator); + right = Path.GetFullPath(right).TrimEnd(DefaultPathSeparator); + return left.Equals(right, PathComparison); + } + /// /// Return the given path with all PowerShell globbing characters escaped, /// plus optionally the whitespace. From d1290328421bc6194d7220740ef44fd0b17ea11e Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 18 Aug 2022 11:23:14 -0700 Subject: [PATCH 059/327] Update CHANGELOG for `v3.5.0` --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 452f81d03..0559a31d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # PowerShell Editor Services Release History +## v3.5.0 +### Thursday, August 18, 2022 + +- 🐛 📁 [PowerShellEditorServices #1901](https://github.com/PowerShell/PowerShellEditorServices/pull/1902) - Fix file close in workspace service for Linux. (Thanks @fflaten!) +- ✨ 🐢 [PowerShellEditorServices #1892](https://github.com/PowerShell/PowerShellEditorServices/pull/1899) - Add symbols for Pester setup and teardown blocks. (Thanks @fflaten!) +- 🐛 🔍 [PowerShellEditorServices #1897](https://github.com/PowerShell/PowerShellEditorServices/pull/1898) - Add artificial stack frame to represent contexts without one. +- 🐛 🔍 [PowerShellEditorServices #1894](https://github.com/PowerShell/PowerShellEditorServices/pull/1894) - Fix stepping while watch expressions or interactive pipeline is running. +- ✨ 🐢 [PowerShellEditorServices #1891](https://github.com/PowerShell/PowerShellEditorServices/pull/1893) - Fix whitespace in Pester symbol and add test. (Thanks @fflaten!) +- 🐛 🙏 [PowerShellEditorServices #1887](https://github.com/PowerShell/PowerShellEditorServices/pull/1890) - Fix symbol highlight when hovering function name. (Thanks @fflaten!) + ## v3.4.10 ### Friday, August 12, 2022 From 78cf2a52e46eaf992f8b248a4a9319bc05bd7641 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 18 Aug 2022 11:23:14 -0700 Subject: [PATCH 060/327] Bump version to `v3.5.0` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index f3bae823a..93df7a6d5 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.4.10 + 3.5.0 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index ee4b20c69..33ee3c3f5 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.4.10' +ModuleVersion = '3.5.0' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 64a2681b33edec13df6ac372878d15784f20bb31 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Thu, 25 Aug 2022 13:43:43 -0700 Subject: [PATCH 061/327] Remove `null` markers to avoid `NullReferenceException` (#1907) --- .../Services/Analysis/AnalysisService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index aaa77fe1c..5b0d81875 100644 --- a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -235,7 +235,7 @@ public async Task>> Ge /// /// The file to clear markers in. /// A task that ends when all markers in the file have been cleared. - public void ClearMarkers(ScriptFile file) => PublishScriptDiagnostics(file, Array.Empty()); + public void ClearMarkers(ScriptFile file) => PublishScriptDiagnostics(file, new List()); /// /// Event subscription method to be run when PSES configuration has been updated. @@ -384,8 +384,11 @@ internal async Task DelayThenInvokeDiagnosticsAsync(ScriptFile[] filesToAnalyze, private void PublishScriptDiagnostics(ScriptFile scriptFile) => PublishScriptDiagnostics(scriptFile, scriptFile.DiagnosticMarkers); - private void PublishScriptDiagnostics(ScriptFile scriptFile, IReadOnlyList markers) + private void PublishScriptDiagnostics(ScriptFile scriptFile, List markers) { + // NOTE: Sometimes we have null markers for reasons we don't yet know, but we need to + // remove them. + _ = markers.RemoveAll(m => m is null); Diagnostic[] diagnostics = new Diagnostic[markers.Count]; CorrectionTableEntry fileCorrections = _mostRecentCorrectionsByFile.GetOrAdd( From fa14a9c3dfa0c9d8da77329fb67dc3c2ecf83e42 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 25 Aug 2022 13:46:55 -0700 Subject: [PATCH 062/327] Update CHANGELOG for `v3.5.1` --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0559a31d2..7d93fa897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # PowerShell Editor Services Release History +## v3.5.1 +### Thursday, August 25, 2022 + +- 🐛 ‍🕵️ [PowerShellEditorServices #1906](https://github.com/PowerShell/PowerShellEditorServices/pull/1907) - Remove `null` markers to avoid `NullReferenceException`. + ## v3.5.0 ### Thursday, August 18, 2022 From db80798f784cc9a26347b8f7749649bc5b5981cc Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 25 Aug 2022 13:46:56 -0700 Subject: [PATCH 063/327] Bump version to `v3.5.1` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 93df7a6d5..7c7cc1206 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.5.0 + 3.5.1 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 33ee3c3f5..98f10244a 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.5.0' +ModuleVersion = '3.5.1' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 62142eecbde56c35e55cec97de315f2770b9349f Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Wed, 31 Aug 2022 13:37:39 -0400 Subject: [PATCH 064/327] Add setting to control references code lens (#1900) * Add setting to control references code lens * Set `EnableReferencesCodeLens` default to true. Which, being the existing default behavior, fixes the tests. Co-authored-by: Andy Jordan <2226434+andschwa@users.noreply.github.com> --- .../CodeLens/ReferencesCodeLensProvider.cs | 4 ++- .../Services/Symbols/SymbolsService.cs | 31 ++++++++++++++----- .../Handlers/ConfigurationHandler.cs | 5 +-- .../Workspace/LanguageServerSettings.cs | 2 ++ 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index c55613178..02cff641d 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -31,11 +31,13 @@ internal class ReferencesCodeLensProvider : ICodeLensProvider private readonly SymbolsService _symbolsService; private readonly WorkspaceService _workspaceService; + public static string Id => nameof(ReferencesCodeLensProvider); + /// /// Specifies a unique identifier for the feature provider, typically a /// fully-qualified name like "Microsoft.PowerShell.EditorServices.MyProvider" /// - public string ProviderId => nameof(ReferencesCodeLensProvider); + public string ProviderId => Id; /// /// Construct a new ReferencesCodeLensProvider for a given EditorSession. diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 78e72f73d..29f3ceea3 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.CodeLenses; using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.Configuration; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; @@ -66,17 +67,15 @@ public SymbolsService( _workspaceService = workspaceService; _codeLensProviders = new ConcurrentDictionary(); - ICodeLensProvider[] codeLensProviders = new ICodeLensProvider[] + if (configurationService.CurrentSettings.EnableReferencesCodeLens) { - new ReferencesCodeLensProvider(_workspaceService, this), - new PesterCodeLensProvider(configurationService) - }; - - foreach (ICodeLensProvider codeLensProvider in codeLensProviders) - { - _codeLensProviders.TryAdd(codeLensProvider.ProviderId, codeLensProvider); + ReferencesCodeLensProvider referencesProvider = new(_workspaceService, this); + _codeLensProviders.TryAdd(referencesProvider.ProviderId, referencesProvider); } + PesterCodeLensProvider pesterProvider = new(configurationService); + _codeLensProviders.TryAdd(pesterProvider.ProviderId, pesterProvider); + _documentSymbolProviders = new ConcurrentDictionary(); IDocumentSymbolProvider[] documentSymbolProviders = new IDocumentSymbolProvider[] { @@ -671,5 +670,21 @@ public static FunctionDefinitionAst GetFunctionDefinitionAtLine( return functionDefinitionAst as FunctionDefinitionAst; } + + internal void OnConfigurationUpdated(object _, LanguageServerSettings e) + { + if (e.EnableReferencesCodeLens) + { + if (_codeLensProviders.ContainsKey(ReferencesCodeLensProvider.Id)) + { + return; + } + + TryRegisterCodeLensProvider(new ReferencesCodeLensProvider(_workspaceService, this)); + return; + } + + DeregisterCodeLensProvider(ReferencesCodeLensProvider.Id); + } } } diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index ca0361a89..ea7214a0a 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -24,13 +24,13 @@ internal class PsesConfigurationHandler : DidChangeConfigurationHandlerBase private readonly WorkspaceService _workspaceService; private readonly ConfigurationService _configurationService; private readonly ILanguageServerFacade _languageServer; - public PsesConfigurationHandler( ILoggerFactory factory, WorkspaceService workspaceService, AnalysisService analysisService, ConfigurationService configurationService, - ILanguageServerFacade languageServer) + ILanguageServerFacade languageServer, + SymbolsService symbolsService) { _logger = factory.CreateLogger(); _workspaceService = workspaceService; @@ -38,6 +38,7 @@ public PsesConfigurationHandler( _languageServer = languageServer; ConfigurationUpdated += analysisService.OnConfigurationUpdated; + ConfigurationUpdated += symbolsService.OnConfigurationUpdated; } public override async Task Handle(DidChangeConfigurationParams request, CancellationToken cancellationToken) diff --git a/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs index 9f59d87dc..8f16ed43a 100644 --- a/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs +++ b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs @@ -22,6 +22,7 @@ internal class LanguageServerSettings public CodeFoldingSettings CodeFolding { get; set; } public PesterSettings Pester { get; set; } public string Cwd { get; set; } + public bool EnableReferencesCodeLens { get; set; } = true; public LanguageServerSettings() { @@ -46,6 +47,7 @@ public void Update( CodeFolding.Update(settings.CodeFolding, logger); Pester.Update(settings.Pester, logger); Cwd = settings.Cwd; + EnableReferencesCodeLens = settings.EnableReferencesCodeLens; } } } From 60cf21b5b32df4140d09601dc225939a1575027f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 10:03:36 -0700 Subject: [PATCH 065/327] Bump Microsoft.NET.Test.Sdk from 17.3.0 to 17.3.1 (#1912) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.0 to 17.3.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.3.0...v17.3.1) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index f2f97497a..c5ed70b76 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 5cb80c5b7..080f61097 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From 889018ff2ceb3a1fdc6b24bc9e15352622ff6e88 Mon Sep 17 00:00:00 2001 From: Johanan Idicula Date: Thu, 1 Sep 2022 13:05:00 -0400 Subject: [PATCH 066/327] Update link to emacs-lsp lsp-pwsh plugin (#1909) Development has ceased in https://github.com/kiennq/lsp-powershell, and the README there now points to `lsp-pwsh.el` in the Emacs-LSP project. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e427c99d..3bd0d8276 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The functionality in PowerShell Editor Services is already available in the foll - [The VSCode PowerShell extension](https://github.com/PowerShell/vscode-powershell), also available in Azure Data Studio - [coc-powershell](https://github.com/yatli/coc-powershell), a vim/neovim PowerShell plugin - [The IntelliJ PowerShell plugin](https://github.com/ant-druha/intellij-powershell) -- [lsp-powershell](https://github.com/kiennq/lsp-powershell), an Emacs PowerShell plugin +- [lsp-pwsh](https://github.com/emacs-lsp/lsp-mode/blob/master/clients/lsp-pwsh.el), an Emacs PowerShell plugin ## Features From 9b0f08a5e953f056fc1da2d7b617e7c3b527ab1c Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 1 Sep 2022 10:11:21 -0700 Subject: [PATCH 067/327] Change CI build script to only build --- .vsts-ci/templates/ci-general.yml | 9 ++++++++- tools/azurePipelinesBuild.ps1 | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.vsts-ci/templates/ci-general.yml b/.vsts-ci/templates/ci-general.yml index e252fcf40..aec2aee4e 100644 --- a/.vsts-ci/templates/ci-general.yml +++ b/.vsts-ci/templates/ci-general.yml @@ -26,11 +26,18 @@ steps: performMultiLevelLookup: true - task: PowerShell@2 - displayName: Build and test + displayName: Build inputs: filePath: tools/azurePipelinesBuild.ps1 pwsh: ${{ parameters.pwsh }} +- task: PowerShell@2 + displayName: Test + inputs: + targetType: inline + script: Invoke-Build Test + pwsh: ${{ parameters.pwsh }} + - task: PublishTestResults@2 displayName: Publish test results inputs: diff --git a/tools/azurePipelinesBuild.ps1 b/tools/azurePipelinesBuild.ps1 index 13148fe0b..6ff04f284 100644 --- a/tools/azurePipelinesBuild.ps1 +++ b/tools/azurePipelinesBuild.ps1 @@ -20,4 +20,4 @@ Update-Help -Force -ErrorAction SilentlyContinue Install-Module -Name InvokeBuild -RequiredVersion 5.9.7 -Scope CurrentUser -Force Install-Module -Name platyPS -RequiredVersion 0.14.2 -Scope CurrentUser -Force -Invoke-Build -Configuration Release +Invoke-Build -Configuration Release Build From 5ddc6883a1c001483b2349856163b66091cc094f Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Thu, 1 Sep 2022 13:33:29 -0700 Subject: [PATCH 068/327] Add regression tests for F5 and F8 saving to history (#1914) This tests both the PowerShell history and the PSReadLine history via a stub, since we can't load PSReadLine itself in these tests. --- .../PowerShell/Console/ReadLineProvider.cs | 2 +- .../PowerShell/Host/PsesInternalHost.cs | 2 +- .../Debugging/DebugServiceTests.cs | 56 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs index 8b9a50312..be4b6d407 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs @@ -16,7 +16,7 @@ internal class ReadLineProvider : IReadLineProvider public ReadLineProvider(ILoggerFactory loggerFactory) => _logger = loggerFactory.CreateLogger(); - public IReadLine ReadLine { get; private set; } + public IReadLine ReadLine { get; internal set; } public void OverrideReadLine(IReadLine readLine) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index e1ee99345..5e9a84f3d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -59,7 +59,7 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns private readonly CancellationContext _cancellationContext; - private readonly ReadLineProvider _readLineProvider; + internal readonly ReadLineProvider _readLineProvider; private readonly Thread _pipelineThread; diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 02a5a23bc..43101db9a 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -13,6 +13,7 @@ using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test; @@ -22,6 +23,15 @@ namespace PowerShellEditorServices.Test.Debugging { + internal class TestReadLine : IReadLine + { + public List history = new(); + + public string ReadLine(CancellationToken cancellationToken) => ""; + + public void AddToHistory(string historyEntry) => history.Add(historyEntry); + } + [Trait("Category", "DebugService")] public class DebugServiceTests : IDisposable { @@ -32,6 +42,7 @@ public class DebugServiceTests : IDisposable private readonly WorkspaceService workspace; private readonly ScriptFile debugScriptFile; private readonly ScriptFile variableScriptFile; + private readonly TestReadLine testReadLine = new(); public DebugServiceTests() { @@ -39,6 +50,7 @@ public DebugServiceTests() // This is required for remote debugging, but we call it here to end up in the same // state as the usual startup path. psesHost.DebugContext.EnableDebugMode(); + psesHost._readLineProvider.ReadLine = testReadLine; breakpointService = new BreakpointService( NullLoggerFactory.Instance, @@ -558,6 +570,50 @@ await debugService.SetCommandBreakpointsAsync( Assert.Equal("\". $args[0]\"", myInvocationLine.ValueString); } + [Fact] + public async Task RecordsF5CommandInPowerShellHistory() + { + ConfigurationDoneHandler configurationDoneHandler = new( + NullLoggerFactory.Instance, null, debugService, null, null, psesHost, workspace, null, psesHost); + await configurationDoneHandler.LaunchScriptAsync(debugScriptFile.FilePath).ConfigureAwait(true); + + IReadOnlyList historyResult = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("(Get-History).CommandLine"), + CancellationToken.None).ConfigureAwait(true); + + // Check the PowerShell history + Assert.Single(historyResult); + Assert.Equal(". \"" + debugScriptFile.FilePath + "\"", historyResult[0]); + + // Check the stubbed PSReadLine history + Assert.Single(testReadLine.history); + Assert.Equal(". \"" + debugScriptFile.FilePath + "\"", testReadLine.history[0]); + } + + [Fact] + public async Task RecordsF8CommandInHistory() + { + const string script = "Write-Output Hello"; + EvaluateHandler evaluateHandler = new(psesHost); + EvaluateResponseBody evaluateResponseBody = await evaluateHandler.Handle( + new EvaluateRequestArguments { Expression = script, Context = "repl" }, + CancellationToken.None).ConfigureAwait(true); + // TODO: Right now this response is hard-coded, maybe it should change? + Assert.Equal("", evaluateResponseBody.Result); + + IReadOnlyList historyResult = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("(Get-History).CommandLine"), + CancellationToken.None).ConfigureAwait(true); + + // Check the PowerShell history + Assert.Single(historyResult); + Assert.Equal(script, historyResult[0]); + + // Check the stubbed PSReadLine history + Assert.Single(testReadLine.history); + Assert.Equal(script, testReadLine.history[0]); + } + [Fact] public async Task DebuggerVariableStringDisplaysCorrectly() { From 294f1c3219d08ee2eb893d8c76e16b09ef9ceb7d Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 2 Sep 2022 09:09:59 -0700 Subject: [PATCH 069/327] Add `/p:UseSharedCompilation=false` to workaround CodeQL bug --- PowerShellEditorServices.build.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 94b5d6207..afef1b8b5 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -161,15 +161,16 @@ Task SetupHelpForTests { } Task Build FindDotNet, CreateBuildInfo, { + # NOTE: We use /p:UseSharedCompilation=false to work around a bug with CodeQL. Exec { & dotnet restore $VerbosityArgs } - Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } - Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } + Exec { & dotnet publish /p:UseSharedCompilation=false $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } + Exec { & dotnet publish /p:UseSharedCompilation=false $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } if (-not $script:IsNix) { - Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } + Exec { & dotnet publish /p:UseSharedCompilation=false $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } } # Build PowerShellEditorServices.VSCode module - Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } + Exec { & dotnet publish /p:UseSharedCompilation=false $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } } Task Test TestServer, TestE2E From 244d97e0296e4b7fb0528347d4acea39bd1ecbda Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Tue, 13 Sep 2022 13:11:43 -0700 Subject: [PATCH 070/327] Created a nested PowerShell for the top-level root (#1918) So that events registered with the `OnIdle` engine event handler will always work. --- .../PowerShell/Host/PsesInternalHost.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 5e9a84f3d..499757faa 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -475,8 +475,18 @@ private void Run() (PowerShell pwsh, RunspaceInfo localRunspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession(); _mainRunspaceEngineIntrinsics = engineIntrinsics; _localComputerName = localRunspaceInfo.SessionDetails.ComputerName; - _runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo)); - PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal | PowerShellFrameType.Repl, localRunspaceInfo); + + // NOTE: In order to support running events registered to PowerShell's OnIdle + // handler, we have to have our top-level PowerShell instance be nested (otherwise + // we get a PSInvalidOperationException because pipelines cannot be run + // concurrently). Specifically this bug cropped up when a profile loaded code which + // registered (and subsequently ran) on the OnIdle handler since it was hitting the + // non-nested PowerShell instance. So now we just start with a nested instance. + // While the PowerShell object is nested, as a frame type, this is our top-level + // frame and therefore NOT nested in that sense. + PowerShell nestedPwsh = CreateNestedPowerShell(localRunspaceInfo); + _runspaceStack.Push(new RunspaceFrame(nestedPwsh.Runspace, localRunspaceInfo)); + PushPowerShellAndRunLoop(nestedPwsh, PowerShellFrameType.Normal | PowerShellFrameType.Repl, localRunspaceInfo); } catch (Exception e) { @@ -964,9 +974,7 @@ private static PowerShell CreateNestedPowerShell(RunspaceInfo currentRunspace) // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild // This means it throws due to the parent pipeline not running... // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead - PowerShell pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace); - pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - return pwsh; + return PowerShell.Create(RunspaceMode.CurrentRunspace); } private static PowerShell CreatePowerShellForRunspace(Runspace runspace) From 1d06b7c5533d1e501f6e378ce457695d3e47cd72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 14:09:05 -0700 Subject: [PATCH 071/327] Bump Serilog from 2.11.0 to 2.12.0 (#1919) Bumps [Serilog](https://github.com/serilog/serilog) from 2.11.0 to 2.12.0. - [Release notes](https://github.com/serilog/serilog/releases) - [Changelog](https://github.com/serilog/serilog/blob/dev/CHANGES.md) - [Commits](https://github.com/serilog/serilog/compare/v2.11.0...v2.12.0) --- updated-dependencies: - dependency-name: Serilog dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 0ee885a19..2668c3c35 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -31,7 +31,7 @@ - + From 7916a0cdbc8a87602dca688f6a02007c317f8155 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Thu, 15 Sep 2022 17:39:48 -0400 Subject: [PATCH 072/327] Overhaul workspace search for symbol references (#1917) Significantly reduce performance overhead of reference finding in large workspaces. * Dependent on PowerShell/vscode-powershell#4170 * Adds a reference cache to every ScriptFile * Workspace scan is performed only once on first request * An LSP file system watcher provides updates for files not changed via Did*TextDocument notifications * Adds a setting to only search "open documents" for references. This disables both the initial workspace scan and the file system watcher, relying only on Did*TextDocument notifications. As a stress test I opened up my profile directory (which has ~3k script files in total in the Modules directory) and created a file with a function definition on every line. I then tabbed to a different file, and then tabbed back to the new file. Before the changes, the references code lens took ~10 seconds to populate and my CPU spiked to ~50% usage. After the changes, they populated instantly and CPU spiked to ~2% usage. --- .../Server/PsesLanguageServer.cs | 1 + .../PowerShell/Utility/CommandHelpers.cs | 34 ++++ .../Services/Symbols/ReferenceTable.cs | 118 ++++++++++++ .../Services/Symbols/SymbolsService.cs | 172 ++++++++++++++---- .../Services/Symbols/Vistors/AstOperations.cs | 39 ++++ .../Handlers/DidChangeWatchedFilesHandler.cs | 117 ++++++++++++ .../Handlers/TextDocumentHandler.cs | 47 +++-- .../Services/TextDocument/ScriptFile.cs | 8 +- .../Workspace/LanguageServerSettings.cs | 2 + .../Services/Workspace/WorkspaceService.cs | 19 +- .../LanguageServerProtocolMessageTests.cs | 12 +- .../Language/SymbolsServiceTests.cs | 20 +- 12 files changed, 515 insertions(+), 74 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs create mode 100644 src/PowerShellEditorServices/Services/TextDocument/Handlers/DidChangeWatchedFilesHandler.cs diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 1f740cec6..9ebdac3fc 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -119,6 +119,7 @@ public async Task StartAsync() .WithHandler() .WithHandler() .WithHandler() + .WithHandler() // NOTE: The OnInitialize delegate gets run when we first receive the // _Initialize_ request: // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#initialize diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index 560a6524f..f5590a026 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Management.Automation; @@ -57,6 +58,39 @@ public record struct AliasMap( internal static readonly ConcurrentDictionary> s_cmdletToAliasCache = new(System.StringComparer.OrdinalIgnoreCase); internal static readonly ConcurrentDictionary s_aliasToCmdletCache = new(System.StringComparer.OrdinalIgnoreCase); + /// + /// Gets the actual command behind a fully module qualified command invocation, e.g. + /// Microsoft.PowerShell.Management\Get-ChildItem will return Get-ChildItem + /// + /// + /// The potentially module qualified command name at the site of invocation. + /// + /// + /// A reference that will contain the module name if the invocation is module qualified. + /// + /// The actual command name. + public static string StripModuleQualification(string invocationName, out ReadOnlyMemory moduleName) + { + int slashIndex = invocationName.LastIndexOfAny(new[] { '\\', '/' }); + if (slashIndex is -1) + { + moduleName = default; + return invocationName; + } + + // If '\' is the last character then it's probably not a module qualified command. + if (slashIndex == invocationName.Length - 1) + { + moduleName = default; + return invocationName; + } + + // Storing moduleName as ROMemory saves a string allocation in the common case where it + // is not needed. + moduleName = invocationName.AsMemory().Slice(0, slashIndex); + return invocationName.Substring(slashIndex + 1); + } + /// /// Gets the CommandInfo instance for a command with a particular name. /// diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs new file mode 100644 index 000000000..2c3242c83 --- /dev/null +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Concurrent; +using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using Microsoft.PowerShell.EditorServices.Services.Symbols; + +namespace Microsoft.PowerShell.EditorServices.Services; + +/// +/// Represents the symbols that are referenced and their locations within a single document. +/// +internal sealed class ReferenceTable +{ + private readonly ScriptFile _parent; + + private readonly ConcurrentDictionary> _symbolReferences = new(StringComparer.OrdinalIgnoreCase); + + private bool _isInited; + + public ReferenceTable(ScriptFile parent) => _parent = parent; + + /// + /// Clears the reference table causing it to rescan the source AST when queried. + /// + public void TagAsChanged() + { + _symbolReferences.Clear(); + _isInited = false; + } + + // Prefer checking if the dictionary has contents to determine if initialized. The field + // `_isInited` is to guard against rescanning files with no command references, but will + // generally be less reliable of a check. + private bool IsInitialized => !_symbolReferences.IsEmpty || _isInited; + + internal bool TryGetReferences(string command, out ConcurrentBag? references) + { + EnsureInitialized(); + return _symbolReferences.TryGetValue(command, out references); + } + + internal void EnsureInitialized() + { + if (IsInitialized) + { + return; + } + + _parent.ScriptAst.Visit(new ReferenceVisitor(this)); + } + + private void AddReference(string symbol, IScriptExtent extent) + { + _symbolReferences.AddOrUpdate( + symbol, + _ => new ConcurrentBag { extent }, + (_, existing) => + { + existing.Add(extent); + return existing; + }); + } + + private sealed class ReferenceVisitor : AstVisitor + { + private readonly ReferenceTable _references; + + public ReferenceVisitor(ReferenceTable references) => _references = references; + + public override AstVisitAction VisitCommand(CommandAst commandAst) + { + string? commandName = GetCommandName(commandAst); + if (string.IsNullOrEmpty(commandName)) + { + return AstVisitAction.Continue; + } + + _references.AddReference( + CommandHelpers.StripModuleQualification(commandName, out _), + commandAst.CommandElements[0].Extent); + + return AstVisitAction.Continue; + + static string? GetCommandName(CommandAst commandAst) + { + string commandName = commandAst.GetCommandName(); + if (!string.IsNullOrEmpty(commandName)) + { + return commandName; + } + + if (commandAst.CommandElements[0] is not ExpandableStringExpressionAst expandableStringExpressionAst) + { + return null; + } + + return AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null; + } + } + + public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) + { + // TODO: Consider tracking unscoped variable references only when they declared within + // the same function definition. + _references.AddReference( + $"${variableExpressionAst.VariablePath.UserPath}", + variableExpressionAst.Extent); + + return AstVisitAction.Continue; + } + } +} diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 29f3ceea3..f052520f0 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -41,6 +41,7 @@ internal class SymbolsService private readonly ConcurrentDictionary _codeLensProviders; private readonly ConcurrentDictionary _documentSymbolProviders; + private readonly ConfigurationService _configurationService; #endregion #region Constructors @@ -65,6 +66,7 @@ public SymbolsService( _runspaceContext = runspaceContext; _executionService = executionService; _workspaceService = workspaceService; + _configurationService = configurationService; _codeLensProviders = new ConcurrentDictionary(); if (configurationService.CurrentSettings.EnableReferencesCodeLens) @@ -177,8 +179,15 @@ public async Task> FindReferencesOfSymbol( _executionService, cancellationToken).ConfigureAwait(false); - Dictionary> cmdletToAliases = aliases.CmdletToAliases; - Dictionary aliasToCmdlets = aliases.AliasToCmdlets; + string targetName = foundSymbol.SymbolName; + if (foundSymbol.SymbolType is SymbolType.Function) + { + targetName = CommandHelpers.StripModuleQualification(targetName, out _); + if (aliases.AliasToCmdlets.TryGetValue(foundSymbol.SymbolName, out string aliasDefinition)) + { + targetName = aliasDefinition; + } + } // We want to look for references first in referenced files, hence we use ordered dictionary // TODO: File system case-sensitivity is based on filesystem not OS, but OS is a much cheaper heuristic @@ -191,52 +200,63 @@ public async Task> FindReferencesOfSymbol( fileMap[scriptFile.FilePath] = scriptFile; } - foreach (string filePath in workspace.EnumeratePSFiles()) + await ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false); + + List symbolReferences = new(); + + // Using a nested method here to get a bit more readability and to avoid roslynator + // asserting we should use a giant nested ternary here. + static string[] GetIdentifiers(string symbolName, SymbolType symbolType, CommandHelpers.AliasMap aliases) { - if (!fileMap.Contains(filePath)) + if (symbolType is not SymbolType.Function) { - // This async method is pretty dense with synchronous code - // so it's helpful to add some yields. - await Task.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - if (!workspace.TryGetFile(filePath, out ScriptFile scriptFile)) - { - // If we can't access the file for some reason, just ignore it - continue; - } + return new[] { symbolName }; + } - fileMap[filePath] = scriptFile; + if (!aliases.CmdletToAliases.TryGetValue(symbolName, out List foundAliasList)) + { + return new[] { symbolName }; } + + return foundAliasList.Prepend(symbolName) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); } - List symbolReferences = new(); - foreach (object fileName in fileMap.Keys) - { - ScriptFile file = (ScriptFile)fileMap[fileName]; + string[] allIdentifiers = GetIdentifiers(targetName, foundSymbol.SymbolType, aliases); - IEnumerable references = AstOperations.FindReferencesOfSymbol( - file.ScriptAst, - foundSymbol, - cmdletToAliases, - aliasToCmdlets); - - foreach (SymbolReference reference in references) + foreach (ScriptFile file in _workspaceService.GetOpenedFiles()) + { + foreach (string targetIdentifier in allIdentifiers) { - try + if (!file.References.TryGetReferences(targetIdentifier, out ConcurrentBag references)) { - reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber); + continue; } - catch (ArgumentOutOfRangeException e) + + foreach (IScriptExtent extent in references.OrderBy(e => e.StartOffset)) { - reference.SourceLine = string.Empty; - _logger.LogException("Found reference is out of range in script file", e); + SymbolReference reference = new( + SymbolType.Function, + foundSymbol.SymbolName, + extent); + + try + { + reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber); + } + catch (ArgumentOutOfRangeException e) + { + reference.SourceLine = string.Empty; + _logger.LogException("Found reference is out of range in script file", e); + } + reference.FilePath = file.FilePath; + symbolReferences.Add(reference); } - reference.FilePath = file.FilePath; - symbolReferences.Add(reference); - } - await Task.Yield(); - cancellationToken.ThrowIfCancellationRequested(); + await Task.Yield(); + cancellationToken.ThrowIfCancellationRequested(); + } } return symbolReferences; @@ -495,6 +515,59 @@ await CommandHelpers.GetCommandInfoAsync( return foundDefinition; } + private Task _workspaceScanCompleted; + + private async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = default) + { + if (_configurationService.CurrentSettings.AnalyzeOpenDocumentsOnly) + { + return; + } + + Task scanTask = _workspaceScanCompleted; + // It's not impossible for two scans to start at once but it should be exceedingly + // unlikely, and shouldn't break anything if it happens to. So we can save some + // lock time by accepting that possibility. + if (scanTask is null) + { + scanTask = Task.Run( + () => + { + foreach (string file in _workspaceService.EnumeratePSFiles()) + { + if (_workspaceService.TryGetFile(file, out ScriptFile scriptFile)) + { + scriptFile.References.EnsureInitialized(); + } + } + }, + CancellationToken.None); + + // Ignore the analyzer yelling that we're not awaiting this task, we'll get there. +#pragma warning disable CS4014 + Interlocked.CompareExchange(ref _workspaceScanCompleted, scanTask, null); +#pragma warning restore CS4014 + } + + // In the simple case where the task is already completed or the token we're given cannot + // be cancelled, do a simple await. + if (scanTask.IsCompleted || !cancellationToken.CanBeCanceled) + { + await scanTask.ConfigureAwait(false); + return; + } + + // If it's not yet done and we can be cancelled, create a new task to represent the + // cancellation. That way we can exit a request that relies on the scan without + // having to actually stop the work (and then request it again in a few seconds). + // + // TODO: There's a new API in net6 that lets you await a task with a cancellation token. + // we should #if that in if feasible. + TaskCompletionSource cancelled = new(); + cancellationToken.Register(() => cancelled.TrySetCanceled()); + await Task.WhenAny(scanTask, cancelled.Task).ConfigureAwait(false); + } + /// /// Gets a path from a dot-source symbol. /// @@ -673,6 +746,35 @@ public static FunctionDefinitionAst GetFunctionDefinitionAtLine( internal void OnConfigurationUpdated(object _, LanguageServerSettings e) { + if (e.AnalyzeOpenDocumentsOnly) + { + Task scanInProgress = _workspaceScanCompleted; + if (scanInProgress is not null) + { + // Wait until after the scan completes to close unopened files. + _ = scanInProgress.ContinueWith(_ => CloseUnopenedFiles(), TaskScheduler.Default); + } + else + { + CloseUnopenedFiles(); + } + + _workspaceScanCompleted = null; + + void CloseUnopenedFiles() + { + foreach (ScriptFile scriptFile in _workspaceService.GetOpenedFiles()) + { + if (scriptFile.IsOpen) + { + continue; + } + + _workspaceService.CloseFile(scriptFile); + } + } + } + if (e.EnableReferencesCodeLens) { if (_codeLensProviders.ContainsKey(ReferencesCodeLensProvider.Id)) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 841408ad5..e14b2051a 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -24,6 +24,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols internal static class AstOperations { private static readonly Func s_clonePositionWithNewOffset; + static AstOperations() { Type internalScriptPositionType = typeof(PSObject).GetTypeInfo().Assembly @@ -311,5 +312,43 @@ public static string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot return dotSourcedVisitor.DotSourcedFiles.ToArray(); } + + internal static bool TryGetInferredValue(ExpandableStringExpressionAst expandableStringExpressionAst, out string value) + { + // Currently we only support inferring the value of `$PSScriptRoot`. We could potentially + // expand this to parts of `$MyInvocation` and some basic constant folding. + if (string.IsNullOrEmpty(expandableStringExpressionAst.Extent.File)) + { + value = null; + return false; + } + + string psScriptRoot = System.IO.Path.GetDirectoryName(expandableStringExpressionAst.Extent.File); + if (string.IsNullOrEmpty(psScriptRoot)) + { + value = null; + return false; + } + + string path = expandableStringExpressionAst.Value; + foreach (ExpressionAst nestedExpression in expandableStringExpressionAst.NestedExpressions) + { + // If the string contains the variable $PSScriptRoot, we replace it with the corresponding value. + if (!(nestedExpression is VariableExpressionAst variableAst + && variableAst.VariablePath.UserPath.Equals("PSScriptRoot", StringComparison.OrdinalIgnoreCase))) + { + value = null; + return false; + } + + // TODO: This should use offsets from the extent rather than a blind replace. In + // practice it won't hurt anything because $ is not valid in paths, but if we expand + // this functionality, this will be problematic. + path = path.Replace(variableAst.ToString(), psScriptRoot); + } + + value = path; + return true; + } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DidChangeWatchedFilesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DidChangeWatchedFilesHandler.cs new file mode 100644 index 000000000..edbe9b0ad --- /dev/null +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DidChangeWatchedFilesHandler.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.FileSystemGlobbing; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Configuration; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; + +// Using an alias since this is conflicting with System.IO.FileSystemWatcher and ends up really finicky. +using OmniSharpFileSystemWatcher = OmniSharp.Extensions.LanguageServer.Protocol.Models.FileSystemWatcher; + +namespace Microsoft.PowerShell.EditorServices.Handlers; + +/// +/// Receives file change notifications from the client for any file in the workspace, including those +/// that are not considered opened by the client. This handler is used to allow us to scan the +/// workspace only once when the language server starts. +/// +internal class DidChangeWatchedFilesHandler : IDidChangeWatchedFilesHandler +{ + private readonly WorkspaceService _workspaceService; + + private readonly ConfigurationService _configurationService; + + public DidChangeWatchedFilesHandler( + WorkspaceService workspaceService, + ConfigurationService configurationService) + { + _workspaceService = workspaceService; + _configurationService = configurationService; + } + + public DidChangeWatchedFilesRegistrationOptions GetRegistrationOptions( + DidChangeWatchedFilesCapability capability, + ClientCapabilities clientCapabilities) + => new() + { + Watchers = new[] + { + new OmniSharpFileSystemWatcher() + { + GlobPattern = "**/*.{ps1,psm1}", + Kind = WatchKind.Create | WatchKind.Delete | WatchKind.Change, + }, + }, + }; + + public Task Handle(DidChangeWatchedFilesParams request, CancellationToken cancellationToken) + { + LanguageServerSettings currentSettings = _configurationService.CurrentSettings; + if (currentSettings.AnalyzeOpenDocumentsOnly) + { + return Task.FromResult(Unit.Value); + } + + // Honor `search.exclude` settings in the watcher. + Matcher matcher = new(); + matcher.AddExcludePatterns(_workspaceService.ExcludeFilesGlob); + foreach (FileEvent change in request.Changes) + { + if (matcher.Match(change.Uri.GetFileSystemPath()).HasMatches) + { + continue; + } + + if (!_workspaceService.TryGetFile(change.Uri, out ScriptFile scriptFile)) + { + continue; + } + + if (change.Type is FileChangeType.Created) + { + // We've already triggered adding the file to `OpenedFiles` via `TryGetFile`. + continue; + } + + if (change.Type is FileChangeType.Deleted) + { + _workspaceService.CloseFile(scriptFile); + continue; + } + + if (change.Type is FileChangeType.Changed) + { + // If the file is opened by the editor (rather than by us in the background), let + // DidChangeTextDocument handle changes. + if (scriptFile.IsOpen) + { + continue; + } + + string fileContents; + try + { + fileContents = WorkspaceService.ReadFileContents(change.Uri); + } + catch + { + continue; + } + + scriptFile.SetFileContents(fileContents); + scriptFile.References.TagAsChanged(); + } + } + + return Task.FromResult(Unit.Value); + } +} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs index 0f18d27cb..d9ece3db5 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs @@ -26,6 +26,8 @@ internal class PsesTextDocumentHandler : TextDocumentSyncHandlerBase private readonly WorkspaceService _workspaceService; private readonly RemoteFileManagerService _remoteFileManagerService; + private bool _isFileWatcherSupported; + public static TextDocumentSyncKind Change => TextDocumentSyncKind.Incremental; public PsesTextDocumentHandler( @@ -59,30 +61,34 @@ public override Task Handle(DidChangeTextDocumentParams notification, Canc return Unit.Task; } - protected override TextDocumentSyncRegistrationOptions CreateRegistrationOptions(SynchronizationCapability capability, ClientCapabilities clientCapabilities) => new() + protected override TextDocumentSyncRegistrationOptions CreateRegistrationOptions(SynchronizationCapability capability, ClientCapabilities clientCapabilities) { - DocumentSelector = LspUtils.PowerShellDocumentSelector, - Change = Change, - Save = new SaveOptions { IncludeText = true } - }; + _isFileWatcherSupported = clientCapabilities.Workspace.DidChangeWatchedFiles.IsSupported; + return new TextDocumentSyncRegistrationOptions() + { + DocumentSelector = LspUtils.PowerShellDocumentSelector, + Change = Change, + Save = new SaveOptions { IncludeText = true } + }; + } public override Task Handle(DidOpenTextDocumentParams notification, CancellationToken token) { + // We use a fake Uri because we only want to test the LanguageId here and not if the + // file ends in ps*1. + TextDocumentAttributes attributes = new(s_fakeUri, notification.TextDocument.LanguageId); + if (!LspUtils.PowerShellDocumentSelector.IsMatch(attributes)) + { + return Unit.Task; + } + ScriptFile openedFile = _workspaceService.GetFileBuffer( notification.TextDocument.Uri, notification.TextDocument.Text); - if (LspUtils.PowerShellDocumentSelector.IsMatch(new TextDocumentAttributes( - // We use a fake Uri because we only want to test the LanguageId here and not if the - // file ends in ps*1. - s_fakeUri, - notification.TextDocument.LanguageId))) - { - // Kick off script diagnostics if we got a PowerShell file without blocking the response - // TODO: Get all recently edited files in the workspace - _analysisService.StartScriptDiagnostics(new ScriptFile[] { openedFile }); - } + openedFile.IsOpen = true; + _analysisService.StartScriptDiagnostics(new ScriptFile[] { openedFile }); _logger.LogTrace("Finished opening document."); return Unit.Task; @@ -95,7 +101,16 @@ public override Task Handle(DidCloseTextDocumentParams notification, Cance if (fileToClose != null) { - _workspaceService.CloseFile(fileToClose); + fileToClose.IsOpen = false; + + // If the file watcher is supported, only close in-memory files when this + // notification is triggered. This lets us keep workspace files open so we can scan + // for references. When a file is deleted, the file watcher will close the file. + if (!_isFileWatcherSupported || fileToClose.IsInMemory) + { + _workspaceService.CloseFile(fileToClose); + } + _analysisService.ClearMarkers(fileToClose); } diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs index 449b78a13..c3e5ac303 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs @@ -119,6 +119,10 @@ public string[] ReferencedFiles private set; } + internal ReferenceTable References { get; } + + internal bool IsOpen { get; set; } + #endregion #region Constructors @@ -149,6 +153,7 @@ internal ScriptFile( // SetFileContents() calls ParseFileContents() which initializes the rest of the properties. SetFileContents(textReader.ReadToEnd()); + References = new ReferenceTable(this); } /// @@ -383,6 +388,7 @@ public void ApplyChange(FileChange fileChange) // Parse the script again to be up-to-date ParseFileContents(); + References.TagAsChanged(); } /// @@ -519,7 +525,7 @@ public BufferRange GetRangeBetweenOffsets(int startOffset, int endOffset) #region Private Methods - private void SetFileContents(string fileContents) + internal void SetFileContents(string fileContents) { // Split the file contents into lines and trim // any carriage returns from the strings. diff --git a/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs index 8f16ed43a..833b6a881 100644 --- a/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs +++ b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs @@ -23,6 +23,7 @@ internal class LanguageServerSettings public PesterSettings Pester { get; set; } public string Cwd { get; set; } public bool EnableReferencesCodeLens { get; set; } = true; + public bool AnalyzeOpenDocumentsOnly { get; set; } public LanguageServerSettings() { @@ -48,6 +49,7 @@ public void Update( Pester.Update(settings.Pester, logger); Cwd = settings.Cwd; EnableReferencesCodeLens = settings.EnableReferencesCodeLens; + AnalyzeOpenDocumentsOnly = settings.AnalyzeOpenDocumentsOnly; } } } diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs index b4e6a8b30..ee76bfd99 100644 --- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs @@ -127,8 +127,7 @@ public ScriptFile GetFile(DocumentUri documentUri) { // This method allows FileNotFoundException to bubble up // if the file isn't found. - using (FileStream fileStream = new(documentUri.GetFileSystemPath(), FileMode.Open, FileAccess.Read)) - using (StreamReader streamReader = new(fileStream, Encoding.UTF8)) + using (StreamReader streamReader = OpenStreamReader(documentUri)) { scriptFile = new ScriptFile( @@ -451,6 +450,22 @@ private void RecursivelyFindReferences( } } + internal static StreamReader OpenStreamReader(DocumentUri uri) + { + FileStream fileStream = new(uri.GetFileSystemPath(), FileMode.Open, FileAccess.Read); + // Default to UTF8 no BOM if a BOM is not present. Note that `Encoding.UTF8` is *with* + // BOM, so we call the ctor here to get the BOM-less version. + // + // TODO: Honor workspace encoding settings for the fallback. + return new StreamReader(fileStream, new UTF8Encoding(), detectEncodingFromByteOrderMarks: true); + } + + internal static string ReadFileContents(DocumentUri uri) + { + using StreamReader reader = OpenStreamReader(uri); + return reader.ReadToEnd(); + } + internal static bool IsPathInMemory(string filePath) { bool isInMemory = false; diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index bcfd49de9..df1a86912 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -478,17 +478,9 @@ function CanSendReferencesRequest { .Returning(CancellationToken.None).ConfigureAwait(true); Assert.Collection(locations, - location1 => + location => { - Range range = location1.Range; - Assert.Equal(1, range.Start.Line); - Assert.Equal(9, range.Start.Character); - Assert.Equal(1, range.End.Line); - Assert.Equal(33, range.End.Character); - }, - location2 => - { - Range range = location2.Range; + Range range = location.Range; Assert.Equal(5, range.Start.Line); Assert.Equal(0, range.Start.Character); Assert.Equal(5, range.End.Line); diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 34f1720ce..e7938c287 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -152,9 +152,9 @@ await psesHost.ExecutePSCommandAsync( public async Task FindsReferencesOnFunction() { List referencesResult = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); - Assert.Equal(3, referencesResult.Count); - Assert.Equal(1, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber); + Assert.Equal(2, referencesResult.Count); + Assert.Equal(3, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(2, referencesResult[0].ScriptRegion.StartColumnNumber); } [Fact] @@ -166,9 +166,9 @@ await psesHost.ExecutePSCommandAsync( 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); + Assert.Equal(3, referencesResult.Count); + Assert.Equal(3, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(2, referencesResult[0].ScriptRegion.StartColumnNumber); } [Fact] @@ -246,8 +246,8 @@ public async Task FindsReferencesOnCommandWithAlias() { List referencesResult = await GetReferences(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails).ConfigureAwait(true); Assert.Equal(4, referencesResult.Count); - Assert.Equal("gci", referencesResult[1].SymbolName); - Assert.Equal("dir", referencesResult[2].SymbolName); + Assert.Equal("Get-ChildItem", referencesResult[1].SymbolName); + Assert.Equal("Get-ChildItem", referencesResult[2].SymbolName); Assert.Equal("Get-ChildItem", referencesResult[referencesResult.Count - 1].SymbolName); } @@ -255,14 +255,14 @@ public async Task FindsReferencesOnCommandWithAlias() public async Task FindsReferencesOnFileWithReferencesFileB() { List referencesResult = await GetReferences(FindsReferencesOnFunctionMultiFileDotSourceFileB.SourceDetails).ConfigureAwait(true); - Assert.Equal(4, referencesResult.Count); + Assert.Equal(3, referencesResult.Count); } [Fact] public async Task FindsReferencesOnFileWithReferencesFileC() { List referencesResult = await GetReferences(FindsReferencesOnFunctionMultiFileDotSourceFileC.SourceDetails).ConfigureAwait(true); - Assert.Equal(4, referencesResult.Count); + Assert.Equal(3, referencesResult.Count); } [Fact] From 476f7243322bfbbd5732dcd8bed23da9422d5efa Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Tue, 27 Sep 2022 12:54:49 -0700 Subject: [PATCH 073/327] Fix Emacs unit test (#1922) The Eglot package updated and this is now a list. --- test/emacs-test.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/emacs-test.el b/test/emacs-test.el index 88b3d1a50..ce09d5ab5 100644 --- a/test/emacs-test.el +++ b/test/emacs-test.el @@ -58,7 +58,7 @@ (should (eglot-current-server)) (let ((lsp (eglot-current-server))) (should (string= (oref lsp project-nickname) "PowerShellEditorServices")) - (should (eq (oref lsp major-mode) 'powershell-mode)) + (should (member 'powershell-mode (oref lsp major-modes))) (should (string= (oref lsp language-id) "powershell"))) (sleep-for 5) ; TODO: Wait for "textDocument/publishDiagnostics" instead (flymake-start) From eed1e098851232138a82b49f7d1aa1aba5048a10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 20:18:25 +0000 Subject: [PATCH 074/327] Bump Microsoft.NET.Test.Sdk from 17.3.1 to 17.3.2 (#1920) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.1 to 17.3.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.3.1...v17.3.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index c5ed70b76..002dbeaa3 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 080f61097..3d186fd3d 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From e942b93c3d5490377b8b3f9816604fa6f3299499 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Tue, 27 Sep 2022 13:20:17 -0700 Subject: [PATCH 075/327] Update Andy's name (#1921) --- README.md | 2 +- test/emacs-test.el | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3bd0d8276..b63d4e0d9 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ contribute code, documentation, tests, or bug reports, please read our [Contribu - [Justin Grote](https://github.com/JustinGrote) - [@JustinWGrote](https://twitter.com/justinwgrote) - [Patrick Meinecke](https://github.com/SeeminglyScience) - [@SeeminglyScienc](http://twitter.com/SeeminglyScienc) -- [Andy Schwartzmeyer](https://github.com/andschwa) - [andschwa.com](https://andschwa.com/) +- [Andy Jordan](https://github.com/andschwa) - [andyleejordan.com](https://andyleejordan.com/) ### Emeriti diff --git a/test/emacs-test.el b/test/emacs-test.el index ce09d5ab5..9ed0dc330 100644 --- a/test/emacs-test.el +++ b/test/emacs-test.el @@ -3,7 +3,7 @@ ;; Copyright (c) Microsoft Corporation. ;; Licensed under the MIT License. -;; Author: Andy Schwartzmeyer +;; Author: Andy Jordan ;; Keywords: PowerShell, LSP ;;; Code: From 535132bf15d5035a86ad1c3f23870768b508b232 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 27 Sep 2022 13:26:52 -0700 Subject: [PATCH 076/327] Update CHANGELOG for `v3.5.2` --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d93fa897..8d15f5e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # PowerShell Editor Services Release History +## v3.5.2 +### Tuesday, September 27, 2022 + +- 🐛 🛫 [vscode-powershell #4048](https://github.com/PowerShell/PowerShellEditorServices/pull/1918) - Created a nested PowerShell for the top-level loop. +- #️⃣ 🙏 [PowerShellEditorServices #1917](https://github.com/PowerShell/PowerShellEditorServices/pull/1917) - Overhaul workspace search for symbol references. +- ✨ 🚨 [PowerShellEditorServices #1914](https://github.com/PowerShell/PowerShellEditorServices/pull/1914) - Add regression tests for F5 and F8 saving to history. +- ✨ 🙏 [PowerShellEditorServices #1900](https://github.com/PowerShell/PowerShellEditorServices/pull/1900) - Add setting to control references code lens. + ## v3.5.1 ### Thursday, August 25, 2022 From 04cee9c80d1365818796a82e058e47be6ddc7c2f Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 27 Sep 2022 13:26:52 -0700 Subject: [PATCH 077/327] Bump version to `v3.5.2` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 7c7cc1206..7cff64e64 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.5.1 + 3.5.2 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 98f10244a..ad1dcc232 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.5.1' +ModuleVersion = '3.5.2' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 69ceae67dd0ca9f1668734b7e89d189969d71e1a Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Wed, 28 Sep 2022 14:01:17 -0700 Subject: [PATCH 078/327] Re-implement indicator when running registered editor commands (#1924) --- .../Handlers/LaunchAndAttachHandler.cs | 2 +- .../Services/Extension/ExtensionService.cs | 53 ++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index c2ae8becb..8b5b4dee6 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -436,7 +436,7 @@ await _executionService.ExecutePSCommandAsync( _debugStateService.WaitingForAttach = true; Task nonAwaitedTask = _executionService .ExecutePSCommandAsync(debugRunspaceCmd, CancellationToken.None, PowerShellExecutionOptions.ImmediateInteractive) - .ContinueWith( OnExecutionCompletedAsync, TaskScheduler.Default); + .ContinueWith(OnExecutionCompletedAsync, TaskScheduler.Default); if (runspaceVersion.Version.Major >= 7) { diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index 028fe0f19..4405a4ce7 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -14,6 +14,37 @@ namespace Microsoft.PowerShell.EditorServices.Services.Extension { + /// Enumerates the possible execution results that can occur after + /// executing a command or script. + /// + internal enum ExecutionStatus + { + /// + /// Indicates that execution has not yet started. + /// + Pending, + + /// + /// Indicates that the command is executing. + /// + Running, + + /// + /// Indicates that execution has failed. + /// + Failed, + + /// + /// Indicates that execution was aborted by the user. + /// + Aborted, + + /// + /// Indicates that execution completed successfully. + /// + Completed + } + /// /// Provides a high-level service which enables PowerShell scripts /// and modules to extend the behavior of the host editor. @@ -123,6 +154,7 @@ internal Task InitializeAsync() /// The command being invoked was not registered. public Task InvokeCommandAsync(string commandName, EditorContext editorContext, CancellationToken cancellationToken) { + _languageServer?.SendNotification("powerShell/executionStatusChanged", ExecutionStatus.Pending); if (editorCommands.TryGetValue(commandName, out EditorCommand editorCommand)) { PSCommand executeCommand = new PSCommand() @@ -131,6 +163,7 @@ public Task InvokeCommandAsync(string commandName, EditorContext editorContext, .AddParameter("ArgumentList", new object[] { editorContext }); // This API is used for editor command execution so it requires the foreground. + _languageServer?.SendNotification("powerShell/executionStatusChanged", ExecutionStatus.Running); return ExecutionService.ExecutePSCommandAsync( executeCommand, cancellationToken, @@ -140,9 +173,27 @@ public Task InvokeCommandAsync(string commandName, EditorContext editorContext, WriteOutputToHost = !editorCommand.SuppressOutput, AddToHistory = !editorCommand.SuppressOutput, ThrowOnError = false, - }); + }).ContinueWith((Task executeTask) => + { + ExecutionStatus status = ExecutionStatus.Failed; + if (executeTask.IsCompleted) + { + status = ExecutionStatus.Completed; + } + else if (executeTask.IsCanceled) + { + status = ExecutionStatus.Aborted; + } + else if (executeTask.IsFaulted) + { + status = ExecutionStatus.Failed; + } + + _languageServer?.SendNotification("powerShell/executionStatusChanged", status); + }, TaskScheduler.Default); } + _languageServer?.SendNotification("powerShell/executionStatusChanged", ExecutionStatus.Failed); throw new KeyNotFoundException($"Editor command not found: '{commandName}'"); } From 2aab7c30d865921f210795c35fe279e7e5f73274 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 28 Sep 2022 14:22:35 -0700 Subject: [PATCH 079/327] Update CHANGELOG for `v3.5.3` --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d15f5e5c..49c19acd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # PowerShell Editor Services Release History +## v3.5.3 +### Wednesday, September 28, 2022 + +- ✨ 🙏 [PowerShellEditorServices #1924](https://github.com/PowerShell/PowerShellEditorServices/pull/1924) - Re-implement indicator when running registered editor commands. + ## v3.5.2 ### Tuesday, September 27, 2022 From 12633627f9769ef4665776094438f1c7945030d2 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 28 Sep 2022 14:22:35 -0700 Subject: [PATCH 080/327] Bump version to `v3.5.3` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 7cff64e64..f7a3a051f 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.5.2 + 3.5.3 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index ad1dcc232..7dab90709 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.5.2' +ModuleVersion = '3.5.3' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From bbdf98db30af3dd423bb2d6ae4d425d13fb01d5b Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Fri, 30 Sep 2022 00:47:14 +0100 Subject: [PATCH 081/327] Upgrade to PSSA 1.21.0 (#1916) --- modules.json | 2 +- .../Services/Workspace/LanguageServerSettings.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules.json b/modules.json index 1053a9e13..488d74b74 100644 --- a/modules.json +++ b/modules.json @@ -1,6 +1,6 @@ { "PSScriptAnalyzer": { - "Version": "1.20.0" + "Version": "1.21.0" }, "Plaster": { "Version": "1.1.3" diff --git a/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs index 833b6a881..e417d7b92 100644 --- a/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs +++ b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs @@ -191,6 +191,7 @@ public CodeFormattingSettings(CodeFormattingSettings codeFormattingSettings) public bool AddWhitespaceAroundPipe { get; set; } public bool AutoCorrectAliases { get; set; } + public bool AvoidSemicolonsAsLineTerminators { get; set; } public bool UseConstantStrings { get; set; } public CodeFormattingPreset Preset { get; set; } public bool OpenBraceOnSameLine { get; set; } @@ -312,6 +313,12 @@ private Hashtable GetCustomPSSASettingsHashtable(int tabSize, bool insertSpaces) { "Enable", UseConstantStrings } } }, + { + "PSAvoidSemicolonsAsLineTerminators", + new Hashtable { + { "Enable", AvoidSemicolonsAsLineTerminators } + } + }, }; if (AutoCorrectAliases) From b0afd8c8df2f6f1fcd0acc092c80ca8c000eb921 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 30 Sep 2022 13:14:55 -0700 Subject: [PATCH 082/327] Generalize the execution busy status to all PowerShell tasks (#1928) We generalized the "execution busy status" from just editor commands to any running PowerShell task. --- .../Services/Extension/ExtensionService.cs | 53 +----------- .../Execution/SynchronousPowerShellTask.cs | 11 ++- .../PowerShell/Host/PsesInternalHost.cs | 81 ++++++++++++++----- 3 files changed, 72 insertions(+), 73 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index 4405a4ce7..028fe0f19 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -14,37 +14,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.Extension { - /// Enumerates the possible execution results that can occur after - /// executing a command or script. - /// - internal enum ExecutionStatus - { - /// - /// Indicates that execution has not yet started. - /// - Pending, - - /// - /// Indicates that the command is executing. - /// - Running, - - /// - /// Indicates that execution has failed. - /// - Failed, - - /// - /// Indicates that execution was aborted by the user. - /// - Aborted, - - /// - /// Indicates that execution completed successfully. - /// - Completed - } - /// /// Provides a high-level service which enables PowerShell scripts /// and modules to extend the behavior of the host editor. @@ -154,7 +123,6 @@ internal Task InitializeAsync() /// The command being invoked was not registered. public Task InvokeCommandAsync(string commandName, EditorContext editorContext, CancellationToken cancellationToken) { - _languageServer?.SendNotification("powerShell/executionStatusChanged", ExecutionStatus.Pending); if (editorCommands.TryGetValue(commandName, out EditorCommand editorCommand)) { PSCommand executeCommand = new PSCommand() @@ -163,7 +131,6 @@ public Task InvokeCommandAsync(string commandName, EditorContext editorContext, .AddParameter("ArgumentList", new object[] { editorContext }); // This API is used for editor command execution so it requires the foreground. - _languageServer?.SendNotification("powerShell/executionStatusChanged", ExecutionStatus.Running); return ExecutionService.ExecutePSCommandAsync( executeCommand, cancellationToken, @@ -173,27 +140,9 @@ public Task InvokeCommandAsync(string commandName, EditorContext editorContext, WriteOutputToHost = !editorCommand.SuppressOutput, AddToHistory = !editorCommand.SuppressOutput, ThrowOnError = false, - }).ContinueWith((Task executeTask) => - { - ExecutionStatus status = ExecutionStatus.Failed; - if (executeTask.IsCompleted) - { - status = ExecutionStatus.Completed; - } - else if (executeTask.IsCanceled) - { - status = ExecutionStatus.Aborted; - } - else if (executeTask.IsFaulted) - { - status = ExecutionStatus.Failed; - } - - _languageServer?.SendNotification("powerShell/executionStatusChanged", status); - }, TaskScheduler.Default); + }); } - _languageServer?.SendNotification("powerShell/executionStatusChanged", ExecutionStatus.Failed); throw new KeyNotFoundException($"Editor command not found: '{commandName}'"); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 2fdefbfdc..5256f5ad9 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -16,7 +16,14 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { - internal class SynchronousPowerShellTask : SynchronousTask> + internal interface ISynchronousPowerShellTask + { + PowerShellExecutionOptions PowerShellExecutionOptions { get; } + + void MaybeAddToHistory(); + } + + internal class SynchronousPowerShellTask : SynchronousTask>, ISynchronousPowerShellTask { private static readonly PowerShellExecutionOptions s_defaultPowerShellExecutionOptions = new(); @@ -353,7 +360,7 @@ private void CancelNormalExecution() } } - internal void MaybeAddToHistory() + public void MaybeAddToHistory() { // Do not add PSES internal commands to history. Also exclude input that came from the // REPL (e.g. PSReadLine) as it handles history itself in that scenario. diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 499757faa..12f746baf 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -295,6 +295,8 @@ public void SetExit() internal void ForceSetExit() => _shouldExit = true; + private void SetBusy(bool busy) => _languageServer?.SendNotification("powerShell/executionBusyStatus", busy); + private bool CancelForegroundAndPrepend(ISynchronousTask task, bool isIdle = false) { // NOTE: This causes foreground tasks to act like they have `ExecutionPriority.Next`. @@ -313,9 +315,9 @@ private bool CancelForegroundAndPrepend(ISynchronousTask task, bool isIdle = fal _skipNextPrompt = true; - if (task is SynchronousPowerShellTask psTask) + if (task is ISynchronousPowerShellTask t) { - psTask.MaybeAddToHistory(); + t.MaybeAddToHistory(); } using (_taskQueue.BlockConsumers()) @@ -334,6 +336,32 @@ private bool CancelForegroundAndPrepend(ISynchronousTask task, bool isIdle = fal return true; } + // This handles executing the task while also notifying the client that the pipeline is + // currently busy with a PowerShell task. The extension indicates this with a spinner. + private void ExecuteTaskSynchronously(ISynchronousTask task, CancellationToken cancellationToken) + { + // TODO: Simplify this logic. + bool busy = false; + if (task is ISynchronousPowerShellTask t + && (t.PowerShellExecutionOptions.AddToHistory + || t.PowerShellExecutionOptions.FromRepl)) + { + busy = true; + SetBusy(true); + } + try + { + task.ExecuteSynchronously(cancellationToken); + } + finally + { + if (busy) + { + SetBusy(false); + } + } + } + public Task InvokeTaskOnPipelineThreadAsync(SynchronousTask task) { if (CancelForegroundAndPrepend(task)) @@ -769,8 +797,13 @@ private void RunExecutionLoop(bool isForDebug = false) { try { - task.ExecuteSynchronously(cancellationScope.CancellationToken); + ExecuteTaskSynchronously(task, cancellationScope.CancellationToken); } + // Our flaky extension command test seems to be such because sometimes another + // task gets queued, and since it runs in the foreground it cancels that task. + // Interactively, this happens in the first loop (with DoOneRepl) which catches + // the cancellation exception, but when under test that is a no-op, so it + // happens in this second loop. Hence we need to catch it here too. catch (OperationCanceledException e) { _logger.LogDebug(e, "Task {Task} was canceled!", task); @@ -935,19 +968,27 @@ private string InvokeReadLine(CancellationToken cancellationToken) } } + // TODO: Should we actually be directly invoking input versus queueing it as a task like everything else? private void InvokeInput(string input, CancellationToken cancellationToken) { - PSCommand command = new PSCommand().AddScript(input, useLocalScope: false); - InvokePSCommand( - command, - new PowerShellExecutionOptions - { - AddToHistory = true, - ThrowOnError = false, - WriteOutputToHost = true, - FromRepl = true, - }, - cancellationToken); + SetBusy(true); + try + { + InvokePSCommand( + new PSCommand().AddScript(input, useLocalScope: false), + new PowerShellExecutionOptions + { + AddToHistory = true, + ThrowOnError = false, + WriteOutputToHost = true, + FromRepl = true, + }, + cancellationToken); + } + finally + { + SetBusy(false); + } } private void AddRunspaceEventHandlers(Runspace runspace) @@ -1076,16 +1117,18 @@ private void OnPowerShellIdle(CancellationToken idleCancellationToken) while (!cancellationScope.CancellationToken.IsCancellationRequested && _taskQueue.TryTake(out ISynchronousTask task)) { + // Tasks which require the foreground cannot run under this idle handler, so the + // current foreground tasks gets canceled, the new task gets prepended, and this + // handler returns. if (CancelForegroundAndPrepend(task, isIdle: true)) { return; } - // If we're executing a task, we don't need to run an extra pipeline later for events - // TODO: This may not be a PowerShell task, so ideally we can differentiate that here. - // For now it's mostly true and an easy assumption to make. - runPipelineForEventProcessing = false; - task.ExecuteSynchronously(cancellationScope.CancellationToken); + // If we're executing a PowerShell task, we don't need to run an extra pipeline + // later for events. + runPipelineForEventProcessing = task is not ISynchronousPowerShellTask; + ExecuteTaskSynchronously(task, cancellationScope.CancellationToken); } } From b1d54552e1916d3b6a27df9247a7868b6b36d38d Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 30 Sep 2022 13:20:37 -0700 Subject: [PATCH 083/327] Update CHANGELOG for `v3.5.4` --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49c19acd1..ad1df8b74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # PowerShell Editor Services Release History +## v3.5.4 +### Friday, September 30, 2022 + +- ✨ 🚂 [PowerShellEditorServices #1928](https://github.com/PowerShell/PowerShellEditorServices/pull/1928) - Generalize the execution busy status to all PowerShell tasks. +- ✨ ‍🕵️ [PowerShellEditorServices #1916](https://github.com/PowerShell/PowerShellEditorServices/pull/1916) - Upgrade PSScriptAnalyzer to 1.21.0. (Thanks @bergmeister!) + ## v3.5.3 ### Wednesday, September 28, 2022 From 40fb5157a6f76f1403da1bbc35800d5a5658013f Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 30 Sep 2022 13:20:37 -0700 Subject: [PATCH 084/327] Bump version to `v3.5.4` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index f7a3a051f..1839d2440 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.5.3 + 3.5.4 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 7dab90709..9b87e5fd2 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.5.3' +ModuleVersion = '3.5.4' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From a80e4225d4acd9ecbcebf113243d8ee1d7eaaf60 Mon Sep 17 00:00:00 2001 From: Nicholas Jackson Date: Wed, 12 Oct 2022 15:56:39 -0700 Subject: [PATCH 085/327] Fix broken links (#1931) Fix broken links to non-existent powershell.github.io pages and replace them with links to .md files in the repository --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b63d4e0d9..74f58de09 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The functionality in PowerShell Editor Services is already available in the foll - Statement completions (IntelliSense) - Real-time semantic analysis of scripts using PowerShell Script Analyzer - The Debugging Service simplifies interaction with the PowerShell debugger (breakpoints, variables, call stack, etc.) -- The [$psEditor API](http://powershell.github.io/PowerShellEditorServices/guide/extensions.html) enables scripting of the host editor +- The [$psEditor API](https://github.com/PowerShell/PowerShellEditorServices/blob/main/docs/guide/extensions.md) enables scripting of the host editor - A full, Extension Terminal experience for interactive development and debugging ## Usage @@ -153,7 +153,7 @@ Open the PowerShellEditorServices folder that you cloned locally and press ## Contributions Welcome We would love to incorporate community contributions into this project. If you would like to -contribute code, documentation, tests, or bug reports, please read our [Contribution Guide](http://powershell.github.io/PowerShellEditorServices/CONTRIBUTING.html) to learn more. +contribute code, documentation, tests, or bug reports, please read our [Contribution Guide](https://github.com/PowerShell/PowerShellEditorServices/blob/main/CONTRIBUTING.md) to learn more. ## Maintainers From 9416a8ae9c7df5a2539731388753e558193c37fb Mon Sep 17 00:00:00 2001 From: Nicholas Jackson Date: Fri, 14 Oct 2022 10:59:48 -0700 Subject: [PATCH 086/327] Add example of starting PSES in a script to readme (#1932) # Problems Solved: - Executing pwsh with the original command (top, the old one is left in there) causes shells to hang. Not script or automation friendly. - The arguments list is also long enough to scroll off the screen on most devices. Spacing them out over an array that gets joined into a string is a screen-friendly alternative. - However, keeping the old one is good for users who just want a quick copy and paste one-liner. --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 74f58de09..9ddbd3aa1 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,30 @@ pwsh -NoLogo -NoProfile -Command "$PSES_BUNDLE_PATH/PowerShellEditorServices/Sta > - `$PSES_BUNDLE_PATH` is the root of the PowerShellEditorServices.zip downloaded from the GitHub releases. > - `$SESSION_TEMP_PATH` is the folder path that you'll use for this specific editor session. +If you are trying to automate the service in PowerShell, You can also run it under `Start-Process` to prevent hanging your script. It also gives you access to Process/PID automation features like `$process.Close()` or `$process.Kill()` + +```powershell +$command = @( + "$PSES_BUNDLE_PATH/PowerShellEditorServices/Start-EditorServices.ps1", + "-BundledModulesPath $PSES_BUNDLE_PATH", + "-LogPath $SESSION_TEMP_PATH/logs.log", + "-SessionDetailsPath $SESSION_TEMP_PATH/session.json", + "-FeatureFlags @()", + "-AdditionalModules @()", + "-HostName 'My Client'", + "-HostProfileId 'myclient'", + "-HostVersion 1.0.0", + "-LogLevel Normal" +)-join " " + +$pwsh_arguments = "-NoLogo -NoProfile -Command $command" +$process = Start-Process pwsh -ArgumentList $arguments -PassThru + +... + +$process.Close(); #$process.Kill(); +``` + Once the command is run, PowerShell Editor Services will wait until the client connects to the Named Pipe. The `session.json` will contain the paths of the Named Pipes that you will connect to. From 1b7dd3f23928145b1f86834434f833600898c9c4 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 21 Oct 2022 11:15:37 -0700 Subject: [PATCH 087/327] Add assertion to build that bits are built in release configuration I expect this to fail because unfortunately the bug is that our tests are invoked after the build, and not in release configuration, which means they build in debug configuration and overwrite the prior built bits, screwing everything up! --- .vsts-ci/templates/ci-general.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.vsts-ci/templates/ci-general.yml b/.vsts-ci/templates/ci-general.yml index aec2aee4e..830c671e0 100644 --- a/.vsts-ci/templates/ci-general.yml +++ b/.vsts-ci/templates/ci-general.yml @@ -45,6 +45,18 @@ steps: testResultsFiles: '**/*.trx' condition: succeededOrFailed() +- task: PowerShell@2 + displayName: Assert PowerShellEditorServices release configuration + inputs: + targetType: inline + script: | + $assembly = [Reflection.Assembly]::LoadFile("$(Build.SourcesDirectory)/module/PowerShellEditorServices.VSCode/bin/Microsoft.PowerShell.EditorServices.VSCode.dll") + if ($assembly.GetCustomAttributes([System.Diagnostics.DebuggableAttribute], $true).IsJITOptimizerDisabled) { + Write-Host "##vso[task.LogIssue type=error;] PowerShell Editor Services bits were not built in release configuration!" + exit 1 + } + pwsh: ${{ parameters.pwsh }} + # NOTE: We zip the artifacts because they're ~20 MB compressed, but ~300 MB raw, # and we have limited pipeline artifact storage space. - task: ArchiveFiles@2 From 5d6423898a85935a81ce4eba481c0e94b10d3e1e Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 21 Oct 2022 13:09:05 -0700 Subject: [PATCH 088/327] Invoke tests in release configuration --- .vsts-ci/templates/ci-general.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vsts-ci/templates/ci-general.yml b/.vsts-ci/templates/ci-general.yml index 830c671e0..33d09a746 100644 --- a/.vsts-ci/templates/ci-general.yml +++ b/.vsts-ci/templates/ci-general.yml @@ -35,7 +35,7 @@ steps: displayName: Test inputs: targetType: inline - script: Invoke-Build Test + script: Invoke-Build Test -Configuration Release pwsh: ${{ parameters.pwsh }} - task: PublishTestResults@2 From adac9e59e335b28b00546cb1e9f2d92bc71bfec8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Oct 2022 22:12:10 +0000 Subject: [PATCH 089/327] Bump Microsoft.PowerShell.SDK from 7.2.6 to 7.2.7 (#1934) Bumps [Microsoft.PowerShell.SDK](https://github.com/PowerShell/PowerShell) from 7.2.6 to 7.2.7. - [Release notes](https://github.com/PowerShell/PowerShell/releases) - [Commits](https://github.com/PowerShell/PowerShell/compare/v7.2.6...v7.2.7) --- updated-dependencies: - dependency-name: Microsoft.PowerShell.SDK dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 3d186fd3d..d2f21eecc 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -19,12 +19,12 @@ - + - + From 90840cc04f942de5925f2015c2ab02ecab96a279 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Oct 2022 13:21:46 -0700 Subject: [PATCH 090/327] Add regression test for `OnIdle` handler --- .../PowerShell/Host/PsesInternalHost.cs | 2 +- .../Test.PowerShellEditorServices_profile.ps1 | 2 + .../PsesHostFactory.cs | 4 +- .../Session/PsesInternalHostTests.cs | 130 +++++++++++++----- 4 files changed, 102 insertions(+), 36 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 12f746baf..f9b8902e2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -1085,7 +1085,7 @@ private Runspace CreateInitialRunspace(InitialSessionState initialSessionState) } // NOTE: This token is received from PSReadLine, and it _is_ the ReadKey cancellation token! - private void OnPowerShellIdle(CancellationToken idleCancellationToken) + internal void OnPowerShellIdle(CancellationToken idleCancellationToken) { IReadOnlyList eventSubscribers = _mainRunspaceEngineIntrinsics.Events.Subscribers; diff --git a/test/PowerShellEditorServices.Test.Shared/Profile/Test.PowerShellEditorServices_profile.ps1 b/test/PowerShellEditorServices.Test.Shared/Profile/Test.PowerShellEditorServices_profile.ps1 index 925cd85b4..b08caccca 100644 --- a/test/PowerShellEditorServices.Test.Shared/Profile/Test.PowerShellEditorServices_profile.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Profile/Test.PowerShellEditorServices_profile.ps1 @@ -5,3 +5,5 @@ function Assert-ProfileLoaded { return $true } + +Register-EngineEvent -SourceIdentifier PowerShell.OnIdle -MaxTriggerCount 1 -Action { $global:handledInProfile = $true } diff --git a/test/PowerShellEditorServices.Test/PsesHostFactory.cs b/test/PowerShellEditorServices.Test/PsesHostFactory.cs index 3f7ab95b9..8d535b260 100644 --- a/test/PowerShellEditorServices.Test/PsesHostFactory.cs +++ b/test/PowerShellEditorServices.Test/PsesHostFactory.cs @@ -28,7 +28,7 @@ internal static class PsesHostFactory public static readonly string BundledModulePath = Path.GetFullPath(TestUtilities.NormalizePath("../../../../../module")); - public static PsesInternalHost Create(ILoggerFactory loggerFactory) + public static PsesInternalHost Create(ILoggerFactory loggerFactory, bool loadProfiles = false) { // We intentionally use `CreateDefault2()` as it loads `Microsoft.PowerShell.Core` only, // which is a more minimal and therefore safer state. @@ -62,7 +62,7 @@ public static PsesInternalHost Create(ILoggerFactory loggerFactory) PsesInternalHost psesHost = new(loggerFactory, null, testHostDetails); // NOTE: Because this is used by constructors it can't use await. - if (psesHost.TryStartAsync(new HostStartOptions { LoadProfiles = false }, CancellationToken.None).GetAwaiter().GetResult()) + if (psesHost.TryStartAsync(new HostStartOptions { LoadProfiles = loadProfiles }, CancellationToken.None).GetAwaiter().GetResult()) { return psesHost; } diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index d66c46938..d1fefad79 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -111,39 +111,6 @@ public async Task CanCancelExecutionWithMethod() Assert.True(executeTask.IsCanceled); } - [Fact] - public async Task CanResolveAndLoadProfilesForHostId() - { - // Load the profiles for the test host name - await psesHost.LoadHostProfilesAsync(CancellationToken.None).ConfigureAwait(true); - - // Ensure that the $PROFILE variable is a string with the value of CurrentUserCurrentHost. - IReadOnlyList profileVariable = await psesHost.ExecutePSCommandAsync( - new PSCommand().AddScript("$PROFILE"), - CancellationToken.None).ConfigureAwait(true); - - Assert.Collection(profileVariable, - (p) => Assert.Equal(PsesHostFactory.TestProfilePaths.CurrentUserCurrentHost, p)); - - // Ensure that all the profile paths are set in the correct note properties. - IReadOnlyList profileProperties = await psesHost.ExecutePSCommandAsync( - new PSCommand().AddScript("$PROFILE | Get-Member -Type NoteProperty"), - CancellationToken.None).ConfigureAwait(true); - - Assert.Collection(profileProperties, - (p) => Assert.Equal($"string AllUsersAllHosts={PsesHostFactory.TestProfilePaths.AllUsersAllHosts}", p, ignoreCase: true), - (p) => Assert.Equal($"string AllUsersCurrentHost={PsesHostFactory.TestProfilePaths.AllUsersCurrentHost}", p, ignoreCase: true), - (p) => Assert.Equal($"string CurrentUserAllHosts={PsesHostFactory.TestProfilePaths.CurrentUserAllHosts}", p, ignoreCase: true), - (p) => Assert.Equal($"string CurrentUserCurrentHost={PsesHostFactory.TestProfilePaths.CurrentUserCurrentHost}", p, ignoreCase: true)); - - // Ensure that the profile was loaded. The profile also checks that $PROFILE was defined. - IReadOnlyList profileLoaded = await psesHost.ExecutePSCommandAsync( - new PSCommand().AddScript("Assert-ProfileLoaded"), - CancellationToken.None).ConfigureAwait(true); - - Assert.Collection(profileLoaded, Assert.True); - } - [Fact] public async Task CanHandleNoProfiles() { @@ -202,6 +169,35 @@ public async Task CanHandleUndefinedPrompt() Assert.Equal(PsesInternalHost.DefaultPrompt, prompt); } + [Fact] + public async Task CanRunOnIdleTask() + { + IReadOnlyList task = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("$handled = $false; Register-EngineEvent -SourceIdentifier PowerShell.OnIdle -MaxTriggerCount 1 -Action { $global:handled = $true }"), + CancellationToken.None).ConfigureAwait(true); + + IReadOnlyList handled = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("$handled"), + CancellationToken.None).ConfigureAwait(true); + + Assert.Collection(handled, (p) => Assert.False(p)); + + await psesHost.ExecuteDelegateAsync( + nameof(psesHost.OnPowerShellIdle), + executionOptions: null, + (_, _) => psesHost.OnPowerShellIdle(CancellationToken.None), + CancellationToken.None).ConfigureAwait(true); + + // TODO: Why is this racy? + Thread.Sleep(2000); + + handled = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("$handled"), + CancellationToken.None).ConfigureAwait(true); + + Assert.Collection(handled, (p) => Assert.True(p)); + } + [Fact] public async Task CanLoadPSReadLine() { @@ -240,4 +236,72 @@ public async Task CanHandleBadInitialWorkingDirectory(string path) Assert.Collection(getLocation, (d) => Assert.Equal(cwd, d, ignoreCase: true)); } } + + [Trait("Category", "PsesInternalHost")] + public class PsesInternalHostWithProfileTests : IDisposable + { + private readonly PsesInternalHost psesHost; + + public PsesInternalHostWithProfileTests() => psesHost = PsesHostFactory.Create(NullLoggerFactory.Instance, loadProfiles: true); + + public void Dispose() + { +#pragma warning disable VSTHRD002 + psesHost.StopAsync().Wait(); +#pragma warning restore VSTHRD002 + GC.SuppressFinalize(this); + } + + [Fact] + public async Task CanResolveAndLoadProfilesForHostId() + { + // Ensure that the $PROFILE variable is a string with the value of CurrentUserCurrentHost. + IReadOnlyList profileVariable = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("$PROFILE"), + CancellationToken.None).ConfigureAwait(true); + + Assert.Collection(profileVariable, + (p) => Assert.Equal(PsesHostFactory.TestProfilePaths.CurrentUserCurrentHost, p)); + + // Ensure that all the profile paths are set in the correct note properties. + IReadOnlyList profileProperties = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("$PROFILE | Get-Member -Type NoteProperty"), + CancellationToken.None).ConfigureAwait(true); + + Assert.Collection(profileProperties, + (p) => Assert.Equal($"string AllUsersAllHosts={PsesHostFactory.TestProfilePaths.AllUsersAllHosts}", p, ignoreCase: true), + (p) => Assert.Equal($"string AllUsersCurrentHost={PsesHostFactory.TestProfilePaths.AllUsersCurrentHost}", p, ignoreCase: true), + (p) => Assert.Equal($"string CurrentUserAllHosts={PsesHostFactory.TestProfilePaths.CurrentUserAllHosts}", p, ignoreCase: true), + (p) => Assert.Equal($"string CurrentUserCurrentHost={PsesHostFactory.TestProfilePaths.CurrentUserCurrentHost}", p, ignoreCase: true)); + + // Ensure that the profile was loaded. The profile also checks that $PROFILE was defined. + IReadOnlyList profileLoaded = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("Assert-ProfileLoaded"), + CancellationToken.None).ConfigureAwait(true); + + Assert.Collection(profileLoaded, Assert.True); + } + + // This test specifically relies on a handler registered in the test profile, and on the + // test host loading the profiles during startup, that way the pipeline timing is + // consistent. + [Fact] + public async Task CanRunOnIdleInProfileTask() + { + await psesHost.ExecuteDelegateAsync( + nameof(psesHost.OnPowerShellIdle), + executionOptions: null, + (_, _) => psesHost.OnPowerShellIdle(CancellationToken.None), + CancellationToken.None).ConfigureAwait(true); + + // TODO: Why is this racy? + Thread.Sleep(2000); + + IReadOnlyList handled = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("$handledInProfile"), + CancellationToken.None).ConfigureAwait(true); + + Assert.Collection(handled, (p) => Assert.True(p)); + } + } } From bef1b2e2f19d31775e4f1c2eba81ef9e58239f45 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Oct 2022 13:48:44 -0700 Subject: [PATCH 091/327] Revert "Created a nested PowerShell for the top-level root (#1918)" This reverts commit 244d97e0296e4b7fb0528347d4acea39bd1ecbda. --- .../PowerShell/Host/PsesInternalHost.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index f9b8902e2..e1282e6cb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -503,18 +503,8 @@ private void Run() (PowerShell pwsh, RunspaceInfo localRunspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession(); _mainRunspaceEngineIntrinsics = engineIntrinsics; _localComputerName = localRunspaceInfo.SessionDetails.ComputerName; - - // NOTE: In order to support running events registered to PowerShell's OnIdle - // handler, we have to have our top-level PowerShell instance be nested (otherwise - // we get a PSInvalidOperationException because pipelines cannot be run - // concurrently). Specifically this bug cropped up when a profile loaded code which - // registered (and subsequently ran) on the OnIdle handler since it was hitting the - // non-nested PowerShell instance. So now we just start with a nested instance. - // While the PowerShell object is nested, as a frame type, this is our top-level - // frame and therefore NOT nested in that sense. - PowerShell nestedPwsh = CreateNestedPowerShell(localRunspaceInfo); - _runspaceStack.Push(new RunspaceFrame(nestedPwsh.Runspace, localRunspaceInfo)); - PushPowerShellAndRunLoop(nestedPwsh, PowerShellFrameType.Normal | PowerShellFrameType.Repl, localRunspaceInfo); + _runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo)); + PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal | PowerShellFrameType.Repl, localRunspaceInfo); } catch (Exception e) { @@ -1015,7 +1005,9 @@ private static PowerShell CreateNestedPowerShell(RunspaceInfo currentRunspace) // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild // This means it throws due to the parent pipeline not running... // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead - return PowerShell.Create(RunspaceMode.CurrentRunspace); + PowerShell pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace); + pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + return pwsh; } private static PowerShell CreatePowerShellForRunspace(Runspace runspace) From 5df82eb8a8a8bd19c9deddb322b8531f6bebc422 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Mon, 24 Oct 2022 16:13:21 -0700 Subject: [PATCH 092/327] Catch exceptions within completion handler (#1935) We'll still see the same error in the log, but as a warning and not as an exception bubbled up over LSP that causes some clients, like Kate, to crash. We can't do anything about PowerShell's completion failing. --- .../Handlers/CompletionHandler.cs | 29 ++++++++++------ .../LSPTestsFixures.cs | 16 +++++---- .../LanguageServerProtocolMessageTests.cs | 33 +++++++++++++++++++ 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs index 284d11a96..a8144d360 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs @@ -69,16 +69,25 @@ public override async Task Handle(CompletionParams request, Canc int cursorColumn = request.Position.Character + 1; ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); - (bool isIncomplete, IReadOnlyList completionResults) = await GetCompletionsInFileAsync( - scriptFile, - cursorLine, - cursorColumn, - cancellationToken).ConfigureAwait(false); - - // Treat completions trigged by space as incomplete so that `gci ` - // and then typing `-` doesn't just filter the list of parameter values - // (typically files) returned by the space completion - return new CompletionList(completionResults, isIncomplete || request?.Context?.TriggerCharacter is " "); + try + { + (bool isIncomplete, IReadOnlyList completionResults) = await GetCompletionsInFileAsync( + scriptFile, + cursorLine, + cursorColumn, + cancellationToken).ConfigureAwait(false); + + // Treat completions triggered by space as incomplete so that `gci ` + // and then typing `-` doesn't just filter the list of parameter values + // (typically files) returned by the space completion + return new CompletionList(completionResults, isIncomplete || request?.Context?.TriggerCharacter is " "); + } + // We can't do anything about completions failing. + catch (Exception e) + { + _logger.LogWarning(e, "Exception occurred while running handling completion request"); + return new CompletionList(isIncomplete: true); + } } // Handler for "completionItem/resolve". In VSCode this is fired when a completion item is highlighted in the completion list. diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs index c31333d89..e58d1e550 100644 --- a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs +++ b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs @@ -33,8 +33,9 @@ public class LSPTestsFixture : IAsyncLifetime private const bool IsDebugAdapterTests = false; public ILanguageClient PsesLanguageClient { get; private set; } - public List Diagnostics { get; set; } - internal List TelemetryEvents { get; set; } + public List Messages = new(); + public List Diagnostics = new(); + internal List TelemetryEvents = new(); public ITestOutputHelper Output { get; set; } protected PsesStdioProcess _psesProcess; @@ -46,9 +47,7 @@ public async Task InitializeAsync() _psesProcess = new PsesStdioProcess(factory, IsDebugAdapterTests); await _psesProcess.Start().ConfigureAwait(false); - Diagnostics = new List(); - TelemetryEvents = new List(); - DirectoryInfo testdir = + DirectoryInfo testDir = Directory.CreateDirectory(Path.Combine(s_binDir, Path.GetRandomFileName())); PsesLanguageClient = LanguageClient.PreInit(options => @@ -56,10 +55,13 @@ public async Task InitializeAsync() options .WithInput(_psesProcess.OutputStream) .WithOutput(_psesProcess.InputStream) - .WithWorkspaceFolder(DocumentUri.FromFileSystemPath(testdir.FullName), "testdir") + .WithWorkspaceFolder(DocumentUri.FromFileSystemPath(testDir.FullName), "testdir") .WithInitializationOptions(new { EnableProfileLoading = false }) .OnPublishDiagnostics(diagnosticParams => Diagnostics.AddRange(diagnosticParams.Diagnostics.Where(d => d != null))) - .OnLogMessage(logMessageParams => Output?.WriteLine($"{logMessageParams.Type}: {logMessageParams.Message}")) + .OnLogMessage(logMessageParams => { + Output?.WriteLine($"{logMessageParams.Type}: {logMessageParams.Message}"); + Messages.Add(logMessageParams); + }) .OnTelemetryEvent(telemetryEventParams => TelemetryEvents.Add( new PsesTelemetryEvent { diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index df1a86912..0ca4bb2e3 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -38,6 +38,7 @@ public class LanguageServerProtocolMessageTests : IClassFixture Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); private readonly ILanguageClient PsesLanguageClient; + private readonly List Messages; private readonly List Diagnostics; private readonly List TelemetryEvents; private readonly string PwshExe; @@ -46,6 +47,8 @@ public LanguageServerProtocolMessageTests(ITestOutputHelper output, LSPTestsFixt { data.Output = output; PsesLanguageClient = data.PsesLanguageClient; + Messages = data.Messages; + Messages.Clear(); Diagnostics = data.Diagnostics; Diagnostics.Clear(); TelemetryEvents = data.TelemetryEvents; @@ -957,6 +960,36 @@ public async Task CanSendCompletionAndCompletionResolveRequestAsync() Assert.Contains("Writes customized output to a host", updatedCompletionItem.Documentation.String); } + // Regression test for https://github.com/PowerShell/PowerShellEditorServices/issues/1926 + [SkippableFact] + public async Task CanRequestCompletionsAndHandleExceptions() + { + Skip.If(PsesStdioProcess.IsWindowsPowerShell, "This is a temporary bug in PowerShell 7, the fix is making its way upstream."); + string filePath = NewTestFile(@" +@() | ForEach-Object { + if ($false) { + return + } + + @{key=$} + }"); + + Messages.Clear(); // On some systems there's a warning message about configuration items too. + CompletionList completionItems = await PsesLanguageClient.TextDocument.RequestCompletion( + new CompletionParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = DocumentUri.FromFileSystemPath(filePath) + }, + Position = new Position(line: 6, character: 11) + }); + + Assert.Empty(completionItems); + LogMessageParams message = Assert.Single(Messages); + Assert.Contains("Exception occurred while running handling completion request", message.Message); + } + [SkippableFact(Skip = "Completion for Expand-SlowArchive is flaky.")] public async Task CanSendCompletionResolveWithModulePrefixRequestAsync() { From 8380673c7bd1e6230b4c0240672a28498f7f699a Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 25 Oct 2022 12:14:27 -0700 Subject: [PATCH 093/327] Update CHANGELOG for `v3.6.0` --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad1df8b74..b3bf4cebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # PowerShell Editor Services Release History +## v3.6.0 +### Tuesday, October 25, 2022 + +- 🐛 🚂 [vscode-powershell #4219](https://github.com/PowerShell/PowerShellEditorServices/pull/1936) - Fix regression around `OnIdle` handler. +- 🐛 🧠 [PowerShellEditorServices #1926](https://github.com/PowerShell/PowerShellEditorServices/pull/1935) - Catch exceptions within completion handler. +- 🐛 👷 [vscode-powershell #4218](https://github.com/PowerShell/PowerShellEditorServices/pull/1933) - Fix release build. +- ✨ 📖 [PowerShellEditorServices #1932](https://github.com/PowerShell/PowerShellEditorServices/pull/1932) - Add example of starting PSES in a script to readme. (Thanks @smartguy1196!) +- #️⃣ 🙏 [PowerShellEditorServices #1931](https://github.com/PowerShell/PowerShellEditorServices/pull/1931) - Fix broken links in readme. (Thanks @smartguy1196!) + ## v3.5.4 ### Friday, September 30, 2022 From b5172502d8e8b5149510c494d19a1399c7a44eb0 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 25 Oct 2022 12:14:28 -0700 Subject: [PATCH 094/327] Bump version to `v3.6.0` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 1839d2440..125e8c7a0 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.5.4 + 3.6.0 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 9b87e5fd2..5e0f7ca8a 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.5.4' +ModuleVersion = '3.6.0' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 3602eec8cc2ab016b4b720263368cbecde77cc7d Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Thu, 27 Oct 2022 13:40:51 -0700 Subject: [PATCH 095/327] Remove `/p:UseSharedCompilation=false` injection (#1938) This was introduced in f234a8e to workaround an upstream bug in `dotnet`. That issue has since been fixed and released (so this flag is auto-injected), hence we can remove it. --- PowerShellEditorServices.build.ps1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index afef1b8b5..94b5d6207 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -161,16 +161,15 @@ Task SetupHelpForTests { } Task Build FindDotNet, CreateBuildInfo, { - # NOTE: We use /p:UseSharedCompilation=false to work around a bug with CodeQL. Exec { & dotnet restore $VerbosityArgs } - Exec { & dotnet publish /p:UseSharedCompilation=false $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } - Exec { & dotnet publish /p:UseSharedCompilation=false $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } if (-not $script:IsNix) { - Exec { & dotnet publish /p:UseSharedCompilation=false $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } } # Build PowerShellEditorServices.VSCode module - Exec { & dotnet publish /p:UseSharedCompilation=false $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } + Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } } Task Test TestServer, TestE2E From ed655ae481c0d4a3b7ee7c6626b3c9e6d5010c5e Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Wed, 2 Nov 2022 13:09:46 -0700 Subject: [PATCH 096/327] Wrap script paths with single instead of double quotes (#1940) In order to support funny path names with dollar signs. Also this is what PowerShell does by default (e.g. through tab-completion). --- .../Commands/StartEditorServicesCommand.cs | 2 +- .../DebugAdapter/Handlers/ConfigurationDoneHandler.cs | 2 +- .../DebugAdapterProtocolMessageTests.cs | 2 +- .../Debugging/DebugServiceTests.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 2774dfa0f..51e2d1b26 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -36,7 +36,7 @@ public sealed class StartEditorServicesCommand : PSCmdlet public StartEditorServicesCommand() { - //Sets the distribution channel to "PSES" so starts can be distinguished in PS7+ telemetry + // Sets the distribution channel to "PSES" so starts can be distinguished in PS7+ telemetry Environment.SetEnvironmentVariable("POWERSHELL_DISTRIBUTION_CHANNEL", "PSES"); _disposableResources = new List(); _loggerUnsubscribers = new List(); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 14bd5389e..eac0d664a 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -112,7 +112,7 @@ internal async Task LaunchScriptAsync(string scriptToLaunch) { // For a saved file we just execute its path (after escaping it). command = PSCommandHelpers.BuildDotSourceCommandWithArguments( - string.Concat('"', scriptToLaunch, '"'), _debugStateService?.Arguments); + string.Concat("'", scriptToLaunch, "'"), _debugStateService?.Arguments); } else // It's a URI to an untitled script, or a raw script. { diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 553024856..a270c694b 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -190,7 +190,7 @@ public async Task UsesDotSourceOperatorAndQuotesAsync() ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments()).ConfigureAwait(true); Assert.NotNull(configDoneResponse); Assert.Collection(await GetLog().ConfigureAwait(true), - (i) => Assert.StartsWith(". \"", i)); + (i) => Assert.StartsWith(". '", i)); } [Fact] diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 43101db9a..29b5dd7ec 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -583,11 +583,11 @@ public async Task RecordsF5CommandInPowerShellHistory() // Check the PowerShell history Assert.Single(historyResult); - Assert.Equal(". \"" + debugScriptFile.FilePath + "\"", historyResult[0]); + Assert.Equal(". '" + debugScriptFile.FilePath + "'", historyResult[0]); // Check the stubbed PSReadLine history Assert.Single(testReadLine.history); - Assert.Equal(". \"" + debugScriptFile.FilePath + "\"", testReadLine.history[0]); + Assert.Equal(". '" + debugScriptFile.FilePath + "'", testReadLine.history[0]); } [Fact] From f7b8fe7d22650934adbb4b225a8265a5f30ed801 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 4 Nov 2022 14:26:44 -0700 Subject: [PATCH 097/327] Manually update `Newtonsoft.Json` (#1947) --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 2668c3c35..23aafec93 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -28,6 +28,8 @@ + + From 7277b2d6e44d602690408f096d864a34fdbdbbde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 13:17:28 -0800 Subject: [PATCH 098/327] Bump Microsoft.Extensions.Logging from 6.0.0 to 7.0.0 (#1948) Bumps [Microsoft.Extensions.Logging](https://github.com/dotnet/runtime) from 6.0.0 to 7.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/commits) --- updated-dependencies: - dependency-name: Microsoft.Extensions.Logging dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 2 +- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 23aafec93..1f0c12d17 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -27,7 +27,7 @@ - + diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 002dbeaa3..bf308c7c8 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -7,7 +7,7 @@ - + From acdde6fa04611f037031ed6274c707ceb83a3e16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Nov 2022 16:40:12 +0000 Subject: [PATCH 099/327] Bump OmniSharp.Extensions.LanguageServer from 0.19.5 to 0.19.6 Bumps [OmniSharp.Extensions.LanguageServer](https://github.com/OmniSharp/csharp-language-server-protocol) from 0.19.5 to 0.19.6. - [Release notes](https://github.com/OmniSharp/csharp-language-server-protocol/releases) - [Changelog](https://github.com/OmniSharp/csharp-language-server-protocol/blob/master/GitReleaseManager.yaml) - [Commits](https://github.com/OmniSharp/csharp-language-server-protocol/compare/v0.19.5...v0.19.6) --- updated-dependencies: - dependency-name: OmniSharp.Extensions.LanguageServer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 1f0c12d17..7bc023577 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -30,7 +30,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index d2f21eecc..c545db2f4 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -36,7 +36,7 @@ - + From 2fadbcf8f28a6939f9bdfd5d67f8cc07e1312fab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Nov 2022 16:40:26 +0000 Subject: [PATCH 100/327] Bump OmniSharp.Extensions.DebugAdapter.Server from 0.19.5 to 0.19.6 Bumps [OmniSharp.Extensions.DebugAdapter.Server](https://github.com/OmniSharp/csharp-language-server-protocol) from 0.19.5 to 0.19.6. - [Release notes](https://github.com/OmniSharp/csharp-language-server-protocol/releases) - [Changelog](https://github.com/OmniSharp/csharp-language-server-protocol/blob/master/GitReleaseManager.yaml) - [Commits](https://github.com/OmniSharp/csharp-language-server-protocol/compare/v0.19.5...v0.19.6) --- updated-dependencies: - dependency-name: OmniSharp.Extensions.DebugAdapter.Server dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 7bc023577..911b42d55 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -31,7 +31,7 @@ - + From 0571d39e5332959c52ee57395493c9885d5c3237 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Nov 2022 16:40:16 +0000 Subject: [PATCH 101/327] Bump OmniSharp.Extensions.LanguageClient from 0.19.5 to 0.19.6 Bumps [OmniSharp.Extensions.LanguageClient](https://github.com/OmniSharp/csharp-language-server-protocol) from 0.19.5 to 0.19.6. - [Release notes](https://github.com/OmniSharp/csharp-language-server-protocol/releases) - [Changelog](https://github.com/OmniSharp/csharp-language-server-protocol/blob/master/GitReleaseManager.yaml) - [Commits](https://github.com/OmniSharp/csharp-language-server-protocol/compare/v0.19.5...v0.19.6) --- updated-dependencies: - dependency-name: OmniSharp.Extensions.LanguageClient dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index bf308c7c8..c2886ab90 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -10,7 +10,7 @@ - + From 19cdeb8cb68b9cbe231d26f9674b509d5d8ee736 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Nov 2022 16:40:22 +0000 Subject: [PATCH 102/327] Bump OmniSharp.Extensions.DebugAdapter.Client from 0.19.5 to 0.19.6 Bumps [OmniSharp.Extensions.DebugAdapter.Client](https://github.com/OmniSharp/csharp-language-server-protocol) from 0.19.5 to 0.19.6. - [Release notes](https://github.com/OmniSharp/csharp-language-server-protocol/releases) - [Changelog](https://github.com/OmniSharp/csharp-language-server-protocol/blob/master/GitReleaseManager.yaml) - [Commits](https://github.com/OmniSharp/csharp-language-server-protocol/compare/v0.19.5...v0.19.6) --- updated-dependencies: - dependency-name: OmniSharp.Extensions.DebugAdapter.Client dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index c2886ab90..339603c00 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -11,7 +11,7 @@ - + From e1817dedb6cab77d71672ebb2669a8f97c857c0c Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 7 Nov 2022 13:31:44 -0800 Subject: [PATCH 103/327] Skip tests broken by OmniSharp update Apps with the `netcore6.0` target framework are broken due to its `init` and `record` workarounds not working after their .NET 6 update. Pending a fix for this, we're disabling the tests which fail to use of `record` objects. --- .../Language/CompletionHandlerTests.cs | 17 ++++++++++++----- .../Services/Symbols/PSScriptAnalyzerTests.cs | 7 +++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs index d47afa38e..c7cb1765b 100644 --- a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs @@ -53,17 +53,19 @@ private Task GetCompletionResultsAsync(ScriptRegion scriptReg CancellationToken.None); } - [Fact] + [SkippableFact] public async Task CompletesCommandInFile() { + Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteCommandInFile.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); Assert.Equal(CompleteCommandInFile.ExpectedCompletion, actual); } - [Fact] + [SkippableFact] public async Task CompletesCommandFromModule() { + Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteCommandFromModule.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); // NOTE: The tooltip varies across PowerShell and OS versions, so we ignore it. @@ -74,6 +76,7 @@ public async Task CompletesCommandFromModule() [SkippableFact] public async Task CompletesTypeName() { + Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); Skip.If(VersionUtils.PSEdition == "Desktop", "Windows PowerShell has trouble with this test right now."); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteTypeName.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); @@ -95,23 +98,26 @@ public async Task CompletesTypeName() [SkippableFact] public async Task CompletesNamespace() { + Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); Skip.If(VersionUtils.PSEdition == "Desktop", "Windows PowerShell has trouble with this test right now."); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteNamespace.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); Assert.Equal(CompleteNamespace.ExpectedCompletion, actual); } - [Fact] + [SkippableFact] public async Task CompletesVariableInFile() { + Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteVariableInFile.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); Assert.Equal(CompleteVariableInFile.ExpectedCompletion, actual); } - [Fact] + [SkippableFact] public async Task CompletesAttributeValue() { + Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteAttributeValue.SourceDetails).ConfigureAwait(true); // NOTE: Since the completions come through un-ordered from PowerShell, their SortText // (which has an index prepended from the original order) will mis-match our assumed @@ -122,9 +128,10 @@ public async Task CompletesAttributeValue() actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion3)); } - [Fact] + [SkippableFact] public async Task CompletesFilePath() { + Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteFilePath.SourceDetails).ConfigureAwait(true); Assert.NotEmpty(results); CompletionItem actual = results.First(); diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs index ca3f3ae04..3f9a7ecb7 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs @@ -8,6 +8,7 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test; +using Microsoft.PowerShell.EditorServices.Utility; using Xunit; namespace PowerShellEditorServices.Test.Services.Symbols @@ -65,9 +66,10 @@ public async Task CanLoadPSScriptAnalyzerAsync() }); } - [Fact] + [SkippableFact] public async Task DoesNotDuplicateScriptMarkersAsync() { + Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); ScriptFile scriptFile = workspaceService.GetFileBuffer("untitled:Untitled-1", script); ScriptFile[] scriptFiles = { scriptFile }; @@ -83,9 +85,10 @@ await analysisService Assert.Single(scriptFile.DiagnosticMarkers); } - [Fact] + [SkippableFact] public async Task DoesNotClearParseErrorsAsync() { + Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); // Causing a missing closing } parser error ScriptFile scriptFile = workspaceService.GetFileBuffer("untitled:Untitled-2", script.TrimEnd('}')); ScriptFile[] scriptFiles = { scriptFile }; From 32f683246d4263507a66ab5ee8b77db748777e9f Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 7 Nov 2022 13:42:34 -0800 Subject: [PATCH 104/327] Remove superfluous build flags These have all since become the defaults. --- PowerShellEditorServices.Common.props | 2 -- .../PowerShellEditorServices.Test.Shared.csproj | 4 ---- .../PowerShellEditorServices.Test.csproj | 5 ----- 3 files changed, 11 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 125e8c7a0..d255d4511 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -17,7 +17,5 @@ true - - true diff --git a/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj b/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj index d617352a9..d1d09e43a 100644 --- a/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj +++ b/test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj @@ -8,10 +8,6 @@ - - true - true - <_Parameter1>Microsoft.PowerShell.EditorServices.Test diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index c545db2f4..9dd63bccd 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -7,11 +7,6 @@ x64 - - true - true - - From a58de283ad2d010d096f74d8765e1c09e79def84 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Tue, 8 Nov 2022 12:56:53 -0800 Subject: [PATCH 105/327] Revert "Bump Microsoft.Extensions.Logging from 6.0.0 to 7.0.0 (#1948)" (#1949) This reverts commit 7277b2d6e44d602690408f096d864a34fdbdbbde. It's not compatible with `netcoreapp3.1`. --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 2 +- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 911b42d55..c7e9f761e 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -27,7 +27,7 @@ - + diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 339603c00..21dddb35d 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -7,7 +7,7 @@ - + From 01594b25d8c4d24a0babba7372f0aa64e7c1e6f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Nov 2022 21:21:06 +0000 Subject: [PATCH 106/327] Bump Microsoft.NET.Test.Sdk from 17.3.2 to 17.4.0 (#1951) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.2 to 17.4.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.3.2...v17.4.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 21dddb35d..5f7618193 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 9dd63bccd..2e91ad038 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -27,7 +27,7 @@ - + From d79c817b19db7ce37a65ce3263b4a2dd01f72150 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 7 Nov 2022 15:14:09 -0800 Subject: [PATCH 107/327] Update CHANGELOG for `v3.6.1` --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3bf4cebe..37c107a65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # PowerShell Editor Services Release History +## v3.6.1 +### Monday, November 07, 2022 + +- #️⃣ 🙏 [PowerShellEditorServices #1947](https://github.com/PowerShell/PowerShellEditorServices/pull/1947) - Manually update `Newtonsoft.Json`. +- 🐛 🚂 [vscode-powershell #4175](https://github.com/PowerShell/PowerShellEditorServices/pull/1946) - Bump OmniSharp to `v0.19.6`. +- 🐛 🔍 [vscode-powershell #4238](https://github.com/PowerShell/PowerShellEditorServices/pull/1940) - Wrap script paths with single instead of double quotes. + ## v3.6.0 ### Tuesday, October 25, 2022 From 79d1be0a44731046be4c27227e59a422607b66a8 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 7 Nov 2022 15:14:10 -0800 Subject: [PATCH 108/327] Bump version to `v3.6.1` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index d255d4511..900995133 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.6.0 + 3.6.1 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 5e0f7ca8a..da7f88fc6 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.6.0' +ModuleVersion = '3.6.1' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 6ef2c21e95567ade4ba5e95e9073e4c62223feeb Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 9 Nov 2022 15:35:24 -0800 Subject: [PATCH 109/327] Update build script to use `Invoke-BuildItem` instead of `Exec` Since that's now a built-in alias. --- PowerShellEditorServices.build.ps1 | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 94b5d6207..fa577bc2c 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -73,16 +73,16 @@ Task FindDotNet { } Task BinClean { - Remove-Item $PSScriptRoot\.tmp -Recurse -Force -ErrorAction Ignore - Remove-Item $PSScriptRoot\module\PowerShellEditorServices\bin -Recurse -Force -ErrorAction Ignore - Remove-Item $PSScriptRoot\module\PowerShellEditorServices.VSCode\bin -Recurse -Force -ErrorAction Ignore + Remove-BuildItem $PSScriptRoot\.tmp + Remove-BuildItem $PSScriptRoot\module\PowerShellEditorServices\bin + Remove-BuildItem $PSScriptRoot\module\PowerShellEditorServices.VSCode\bin } Task Clean FindDotNet, BinClean, { - Exec { & dotnet clean $VerbosityArgs } - Get-ChildItem -Recurse $PSScriptRoot\src\*.nupkg | Remove-Item -Force -ErrorAction Ignore - Get-ChildItem $PSScriptRoot\PowerShellEditorServices*.zip | Remove-Item -Force -ErrorAction Ignore - Get-ChildItem $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US\*-help.xml | Remove-Item -Force -ErrorAction Ignore + Invoke-BuildExec { & dotnet clean $VerbosityArgs } + Get-ChildItem -Recurse $PSScriptRoot\src\*.nupkg | Remove-BuildItem + Get-ChildItem $PSScriptRoot\PowerShellEditorServices*.zip | Remove-BuildItem + Get-ChildItem $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US\*-help.xml | Remove-BuildItem # Remove bundled component modules $moduleJsonPath = "$PSScriptRoot\modules.json" @@ -90,7 +90,7 @@ Task Clean FindDotNet, BinClean, { Get-Content -Raw $moduleJsonPath | ConvertFrom-Json | ForEach-Object { $_.PSObject.Properties.Name } | - ForEach-Object { Remove-Item -Path "$PSScriptRoot/module/$_" -Recurse -Force -ErrorAction Ignore } + ForEach-Object { Remove-BuildItem -Path "$PSScriptRoot/module/$_" } } } @@ -161,15 +161,15 @@ Task SetupHelpForTests { } Task Build FindDotNet, CreateBuildInfo, { - Exec { & dotnet restore $VerbosityArgs } - Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } - Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } + Invoke-BuildExec { & dotnet restore $VerbosityArgs } + Invoke-BuildExec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } + Invoke-BuildExec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } if (-not $script:IsNix) { - Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } + Invoke-BuildExec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } } # Build PowerShellEditorServices.VSCode module - Exec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } + Invoke-BuildExec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } } Task Test TestServer, TestE2E @@ -184,17 +184,17 @@ Task TestServerWinPS -If ($PSVersionTable.PSEdition -eq "Desktop") Build, SetupH # that is debuggable! If architecture is added, the assembly path gets an # additional folder, necesstiating fixes to find the commands definition # file and test files. - Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.Desktop } + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.Desktop } } Task TestServerPS7 -If ($PSVersionTable.PSEdition -eq "Core" -and -not $script:IsAppleM1 -and -not $script:IsArm64) Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ - Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } } Task TestServerPS72 -If ($PSVersionTable.PSEdition -eq "Core") Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ - Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } } Task TestE2E Build, SetupHelpForTests, { @@ -202,7 +202,7 @@ Task TestE2E Build, SetupHelpForTests, { $env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" } $NetRuntime = if ($IsAppleM1 -or $script:IsArm64) { $script:NetRuntime.PS72 } else { $script:NetRuntime.PS7 } - Exec { & dotnet $script:dotnetTestArgs $NetRuntime } + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $NetRuntime } if (!$script:IsNix) { if (-not [Security.Principal.WindowsIdentity]::GetCurrent().Owner.IsWellKnown("BuiltInAdministratorsSid")) { @@ -213,7 +213,7 @@ Task TestE2E Build, SetupHelpForTests, { try { Write-Host "Running end-to-end tests in Constrained Language Mode." [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine); - Exec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } } finally { [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine); } From 7c8494ac7d0c2bbd7f11be07d103e745cae83158 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 9 Nov 2022 16:16:31 -0800 Subject: [PATCH 110/327] Add PowerShell 7.3 with `net7.0` to test matrix --- .vsts-ci/templates/ci-general.yml | 9 ++++++++- PowerShellEditorServices.build.ps1 | 13 +++++++++---- global.json | 2 +- .../PowerShellEditorServices.Test.csproj | 19 +++++++++++++------ 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/.vsts-ci/templates/ci-general.yml b/.vsts-ci/templates/ci-general.yml index 33d09a746..2278dc2eb 100644 --- a/.vsts-ci/templates/ci-general.yml +++ b/.vsts-ci/templates/ci-general.yml @@ -12,9 +12,16 @@ steps: pwsh: ${{ parameters.pwsh }} - task: UseDotNet@2 - displayName: Install .NET 6.0.x SDK + displayName: Install .NET 7.0.x SDK inputs: packageType: sdk + version: 7.0.x + performMultiLevelLookup: true + +- task: UseDotNet@2 + displayName: Install .NET 6.0.x runtime + inputs: + packageType: runtime version: 6.0.x performMultiLevelLookup: true diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index fa577bc2c..2af0db8b9 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -41,6 +41,7 @@ $script:PsesCommonProps = [xml](Get-Content -Raw "$PSScriptRoot/PowerShellEditor $script:NetRuntime = @{ PS7 = 'netcoreapp3.1' PS72 = 'net6.0' + PS73 = 'net7.0' Desktop = 'net462' Standard = 'netstandard2.0' } @@ -174,7 +175,7 @@ Task Build FindDotNet, CreateBuildInfo, { Task Test TestServer, TestE2E -Task TestServer TestServerWinPS, TestServerPS7, TestServerPS72 +Task TestServer TestServerWinPS, TestServerPS7, TestServerPS72, TestServerPS73 # NOTE: While these can run under `pwsh.exe` we only want them to run under # `powershell.exe` so that the CI time isn't doubled. @@ -197,12 +198,16 @@ Task TestServerPS72 -If ($PSVersionTable.PSEdition -eq "Core") Build, SetupHelpF Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } } +Task TestServerPS73 -If ($PSVersionTable.PSEdition -eq "Core") Build, SetupHelpForTests, { + Set-Location .\test\PowerShellEditorServices.Test\ + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS73 } +} + Task TestE2E Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test.E2E\ $env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" } - $NetRuntime = if ($IsAppleM1 -or $script:IsArm64) { $script:NetRuntime.PS72 } else { $script:NetRuntime.PS7 } - Invoke-BuildExec { & dotnet $script:dotnetTestArgs $NetRuntime } + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } if (!$script:IsNix) { if (-not [Security.Principal.WindowsIdentity]::GetCurrent().Owner.IsWellKnown("BuiltInAdministratorsSid")) { @@ -213,7 +218,7 @@ Task TestE2E Build, SetupHelpForTests, { try { Write-Host "Running end-to-end tests in Constrained Language Mode." [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine); - Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } } finally { [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine); } diff --git a/global.json b/global.json index 124d3fe9b..b46f32ad3 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0", + "version": "7.0.100", "rollForward": "latestFeature", "allowPrerelease": true } diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 2e91ad038..26323a8d6 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -2,7 +2,7 @@ - net6.0;netcoreapp3.1;net462 + net7.0;net6.0;netcoreapp3.1;net462 Microsoft.PowerShell.EditorServices.Test x64 @@ -12,20 +12,30 @@ - + + + + + + - + + + + $(DefineConstants);CoreCLR + + @@ -48,7 +58,4 @@ - - $(DefineConstants);CoreCLR - From 79264e11a8930cc108df359964e62a315ffa589f Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 9 Nov 2022 16:17:48 -0800 Subject: [PATCH 111/327] Fix error CS8347, usage of `usingNamespaces` exceeds its lifetime Caught by .NET 7.0 compiler. --- src/PowerShellEditorServices/Utility/FormatUtils.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Utility/FormatUtils.cs b/src/PowerShellEditorServices/Utility/FormatUtils.cs index 9863d7b78..b3bc791dd 100644 --- a/src/PowerShellEditorServices/Utility/FormatUtils.cs +++ b/src/PowerShellEditorServices/Utility/FormatUtils.cs @@ -23,6 +23,8 @@ internal static class FormatUtils private const string Static = "static "; + private static HashSet? usingNamespaces; + /// /// Space, new line, carriage return and tab. /// @@ -164,9 +166,9 @@ private static string GetMethodDocumentation(ReadOnlySpan toolTip, out Mar { kind = MarkupKind.Markdown; StringBuilder text = new(); - HashSet? usingNamespaces = null; while (true) { + usingNamespaces = null; toolTip = toolTip.TrimStart(s_whiteSpace.Span); toolTip = ProcessMethod(toolTip, text, ref usingNamespaces); if (toolTip.IsEmpty) From a3b1423f6fd32c02bac9b91dffc1ecfc8dfcd627 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 10 Nov 2022 10:15:50 -0800 Subject: [PATCH 112/327] Fix error CA2213, field `_underlyingStream` was not being disposed --- .../Processes/LoggingStream.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs b/test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs index 1a0151ec1..9a29452b0 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs @@ -16,6 +16,15 @@ internal class LoggingStream : Stream public LoggingStream(Stream underlyingStream) => _underlyingStream = underlyingStream; + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + _underlyingStream.Dispose(); + } + } + public override bool CanRead => _underlyingStream.CanRead; public override bool CanSeek => _underlyingStream.CanSeek; From 8f31af31be67480d558a587def41720c176d196f Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 10 Nov 2022 10:21:15 -0800 Subject: [PATCH 113/327] Add `setup-dotnet` action to GitHub workflows --- .github/workflows/codeql-analysis.yml | 3 +++ .github/workflows/emacs-test.yml | 3 +++ .github/workflows/vim-test.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fa898e2bb..fcc259678 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -29,6 +29,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Install dotnet + uses: actions/setup-dotnet@v3 + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/.github/workflows/emacs-test.yml b/.github/workflows/emacs-test.yml index 4a62a3ae7..ddbcb7887 100644 --- a/.github/workflows/emacs-test.yml +++ b/.github/workflows/emacs-test.yml @@ -17,6 +17,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Install dotnet + uses: actions/setup-dotnet@v3 + - name: Build PSES shell: pwsh run: tools/azurePipelinesBuild.ps1 diff --git a/.github/workflows/vim-test.yml b/.github/workflows/vim-test.yml index 5dcf11f88..387a98e00 100644 --- a/.github/workflows/vim-test.yml +++ b/.github/workflows/vim-test.yml @@ -17,6 +17,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Install dotnet + uses: actions/setup-dotnet@v3 + - name: Build PSES shell: pwsh run: tools/azurePipelinesBuild.ps1 From d2e6e7143531c4c6016615d24752499b459e80f8 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 18 Nov 2022 15:06:20 -0800 Subject: [PATCH 114/327] Escape single quotes when launching a script by path (#1957) Since we surround it with single quotes. Resolves https://github.com/PowerShell/vscode-powershell/issues/4269 with tests. --- .../Handlers/ConfigurationDoneHandler.cs | 4 +- .../Utility/PSCommandExtensions.cs | 2 + ...t].ps1 => Debug' W&ith $Params [Test].ps1} | 0 .../Debugging/DebugServiceTests.cs | 99 ++++++++++--------- 4 files changed, 57 insertions(+), 48 deletions(-) rename test/PowerShellEditorServices.Test.Shared/Debugging/{Debug W&ith Params [Test].ps1 => Debug' W&ith $Params [Test].ps1} (100%) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index eac0d664a..df8165319 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Management.Automation; @@ -112,7 +112,7 @@ internal async Task LaunchScriptAsync(string scriptToLaunch) { // For a saved file we just execute its path (after escaping it). command = PSCommandHelpers.BuildDotSourceCommandWithArguments( - string.Concat("'", scriptToLaunch, "'"), _debugStateService?.Arguments); + PSCommandHelpers.EscapeScriptFilePath(scriptToLaunch), _debugStateService?.Arguments); } else // It's a URI to an untitled script, or a raw script. { diff --git a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs index 154710003..6a9390af1 100644 --- a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs +++ b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs @@ -126,6 +126,8 @@ private static StringBuilder AddCommandText(this StringBuilder sb, Command comma return sb; } + public static string EscapeScriptFilePath(string f) => string.Concat("'", f.Replace("'", "''"), "'"); + public static PSCommand BuildDotSourceCommandWithArguments(string command, IEnumerable arguments) { string args = string.Join(" ", arguments ?? Array.Empty()); diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/Debug W&ith Params [Test].ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/Debug' W&ith $Params [Test].ps1 similarity index 100% rename from test/PowerShellEditorServices.Test.Shared/Debugging/Debug W&ith Params [Test].ps1 rename to test/PowerShellEditorServices.Test.Shared/Debugging/Debug' W&ith $Params [Test].ps1 diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 29b5dd7ec..9362772d2 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -41,6 +41,7 @@ public class DebugServiceTests : IDisposable private readonly BlockingCollection debuggerStoppedQueue = new(); private readonly WorkspaceService workspace; private readonly ScriptFile debugScriptFile; + private readonly ScriptFile oddPathScriptFile; private readonly ScriptFile variableScriptFile; private readonly TestReadLine testReadLine = new(); @@ -68,9 +69,10 @@ public DebugServiceTests() debugService.DebuggerStopped += OnDebuggerStopped; - // Load the test debug files + // Load the test debug files. workspace = new WorkspaceService(NullLoggerFactory.Instance); debugScriptFile = GetDebugScript("DebugTest.ps1"); + oddPathScriptFile = GetDebugScript("Debug' W&ith $Params [Test].ps1"); variableScriptFile = GetDebugScript("VariableTest.ps1"); } @@ -105,16 +107,16 @@ private VariableDetailsBase[] GetVariables(string scopeName) return debugService.GetVariables(scope.Id); } - private Task ExecutePowerShellCommand(string command, params string[] args) + private Task ExecuteScriptFileAsync(string scriptFilePath, params string[] args) { return psesHost.ExecutePSCommandAsync( - PSCommandHelpers.BuildDotSourceCommandWithArguments(string.Concat('"', command, '"'), args), + PSCommandHelpers.BuildDotSourceCommandWithArguments(PSCommandHelpers.EscapeScriptFilePath(scriptFilePath), args), CancellationToken.None); } - private Task ExecuteDebugFile() => ExecutePowerShellCommand(debugScriptFile.FilePath); + private Task ExecuteDebugFileAsync() => ExecuteScriptFileAsync(debugScriptFile.FilePath); - private Task ExecuteVariableScriptFile() => ExecutePowerShellCommand(variableScriptFile.FilePath); + private Task ExecuteVariableScriptFileAsync() => ExecuteScriptFileAsync(variableScriptFile.FilePath); private void AssertDebuggerPaused() { @@ -201,27 +203,22 @@ await debugService.SetCommandBreakpointsAsync( [MemberData(nameof(DebuggerAcceptsScriptArgsTestData))] public async Task DebuggerAcceptsScriptArgs(string[] args) { - // The path is intentionally odd (some escaped chars but not all) because we are testing - // the internal path escaping mechanism - it should escape certain chars ([, ] and space) but - // it should not escape already escaped chars. - ScriptFile debugWithParamsFile = GetDebugScript("Debug W&ith Params [Test].ps1"); - BreakpointDetails[] breakpoints = await debugService.SetLineBreakpointsAsync( - debugWithParamsFile, - new[] { BreakpointDetails.Create(debugWithParamsFile.FilePath, 3) }).ConfigureAwait(true); + oddPathScriptFile, + new[] { BreakpointDetails.Create(oddPathScriptFile.FilePath, 3) }).ConfigureAwait(true); Assert.Single(breakpoints); Assert.Collection(breakpoints, (breakpoint) => { // TODO: The drive letter becomes lower cased on Windows for some reason. - Assert.Equal(debugWithParamsFile.FilePath, breakpoint.Source, ignoreCase: true); + Assert.Equal(oddPathScriptFile.FilePath, breakpoint.Source, ignoreCase: true); Assert.Equal(3, breakpoint.LineNumber); Assert.True(breakpoint.Verified); }); - Task _ = ExecutePowerShellCommand(debugWithParamsFile.FilePath, args); + Task _ = ExecuteScriptFileAsync(oddPathScriptFile.FilePath, args); - AssertDebuggerStopped(debugWithParamsFile.FilePath, 3); + AssertDebuggerStopped(oddPathScriptFile.FilePath, 3); VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); @@ -273,8 +270,7 @@ public async Task DebuggerSetsAndClearsFunctionBreakpoints() breakpoints = await debugService.SetCommandBreakpointsAsync( new[] { CommandBreakpointDetails.Create("Get-Host") }).ConfigureAwait(true); - Assert.Single(breakpoints); - Assert.Equal("Get-Host", breakpoints[0].Name); + Assert.Equal("Get-Host", Assert.Single(breakpoints).Name); breakpoints = await debugService.SetCommandBreakpointsAsync( Array.Empty()).ConfigureAwait(true); @@ -288,7 +284,7 @@ public async Task DebuggerStopsOnFunctionBreakpoints() CommandBreakpointDetails[] breakpoints = await debugService.SetCommandBreakpointsAsync( new[] { CommandBreakpointDetails.Create("Write-Host") }).ConfigureAwait(true); - Task _ = ExecuteDebugFile(); + Task _ = ExecuteDebugFileAsync(); AssertDebuggerStopped(debugScriptFile.FilePath, 6); VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); @@ -355,7 +351,7 @@ await debugService.SetLineBreakpointsAsync( BreakpointDetails.Create(debugScriptFile.FilePath, 7) }).ConfigureAwait(true); - Task _ = ExecuteDebugFile(); + Task _ = ExecuteDebugFileAsync(); AssertDebuggerStopped(debugScriptFile.FilePath, 5); debugService.Continue(); AssertDebuggerStopped(debugScriptFile.FilePath, 7); @@ -373,7 +369,7 @@ await debugService.SetLineBreakpointsAsync( BreakpointDetails.Create(debugScriptFile.FilePath, 7, null, $"$i -eq {breakpointValue1} -or $i -eq {breakpointValue2}"), }).ConfigureAwait(true); - Task _ = ExecuteDebugFile(); + Task _ = ExecuteDebugFileAsync(); AssertDebuggerStopped(debugScriptFile.FilePath, 7); VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); @@ -409,7 +405,7 @@ await debugService.SetLineBreakpointsAsync( BreakpointDetails.Create(debugScriptFile.FilePath, 6, null, null, $"{hitCount}"), }).ConfigureAwait(true); - Task _ = ExecuteDebugFile(); + Task _ = ExecuteDebugFileAsync(); AssertDebuggerStopped(debugScriptFile.FilePath, 6); VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); @@ -430,7 +426,7 @@ await debugService.SetLineBreakpointsAsync( debugScriptFile, new[] { BreakpointDetails.Create(debugScriptFile.FilePath, 6, null, "$i % 2 -eq 0", $"{hitCount}") }).ConfigureAwait(true); - Task _ = ExecuteDebugFile(); + Task _ = ExecuteDebugFileAsync(); AssertDebuggerStopped(debugScriptFile.FilePath, 6); VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); @@ -498,7 +494,7 @@ public async Task DebuggerBreaksWhenRequested() { IReadOnlyList confirmedBreakpoints = await GetConfirmedBreakpoints(debugScriptFile).ConfigureAwait(true); Assert.Equal(0, confirmedBreakpoints.Count); - Task _ = ExecuteDebugFile(); + Task _ = ExecuteDebugFileAsync(); // NOTE: This must be run on a separate thread so the async event handlers can fire. await Task.Run(() => debugService.Break()).ConfigureAwait(true); AssertDebuggerPaused(); @@ -507,7 +503,7 @@ public async Task DebuggerBreaksWhenRequested() [Fact] public async Task DebuggerRunsCommandsWhileStopped() { - Task _ = ExecuteDebugFile(); + Task _ = ExecuteDebugFileAsync(); // NOTE: This must be run on a separate thread so the async event handlers can fire. await Task.Run(() => debugService.Break()).ConfigureAwait(true); AssertDebuggerPaused(); @@ -529,7 +525,7 @@ await debugService.SetCommandBreakpointsAsync( new[] { CommandBreakpointDetails.Create("Write-Host") }).ConfigureAwait(true); ScriptFile testScript = GetDebugScript("PSDebugContextTest.ps1"); - Task _ = ExecutePowerShellCommand(testScript.FilePath); + Task _ = ExecuteScriptFileAsync(testScript.FilePath); AssertDebuggerStopped(testScript.FilePath, 11); VariableDetails prompt = await debugService.EvaluateExpressionAsync("prompt", false).ConfigureAwait(true); @@ -582,12 +578,10 @@ public async Task RecordsF5CommandInPowerShellHistory() CancellationToken.None).ConfigureAwait(true); // Check the PowerShell history - Assert.Single(historyResult); - Assert.Equal(". '" + debugScriptFile.FilePath + "'", historyResult[0]); + Assert.Equal(". '" + debugScriptFile.FilePath + "'", Assert.Single(historyResult)); // Check the stubbed PSReadLine history - Assert.Single(testReadLine.history); - Assert.Equal(". '" + debugScriptFile.FilePath + "'", testReadLine.history[0]); + Assert.Equal(". '" + debugScriptFile.FilePath + "'", Assert.Single(testReadLine.history)); } [Fact] @@ -606,12 +600,25 @@ public async Task RecordsF8CommandInHistory() CancellationToken.None).ConfigureAwait(true); // Check the PowerShell history - Assert.Single(historyResult); - Assert.Equal(script, historyResult[0]); + Assert.Equal(script, Assert.Single(historyResult)); // Check the stubbed PSReadLine history - Assert.Single(testReadLine.history); - Assert.Equal(script, testReadLine.history[0]); + Assert.Equal(script, Assert.Single(testReadLine.history)); + } + + [Fact] + public async Task OddFilePathsLaunchCorrectly() + { + ConfigurationDoneHandler configurationDoneHandler = new( + NullLoggerFactory.Instance, null, debugService, null, null, psesHost, workspace, null, psesHost); + await configurationDoneHandler.LaunchScriptAsync(oddPathScriptFile.FilePath).ConfigureAwait(true); + + IReadOnlyList historyResult = await psesHost.ExecutePSCommandAsync( + new PSCommand().AddScript("(Get-History).CommandLine"), + CancellationToken.None).ConfigureAwait(true); + + // Check the PowerShell history + Assert.Equal(". " + PSCommandHelpers.EscapeScriptFilePath(oddPathScriptFile.FilePath), Assert.Single(historyResult)); } [Fact] @@ -621,7 +628,7 @@ await debugService.SetLineBreakpointsAsync( variableScriptFile, new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 8) }).ConfigureAwait(true); - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); @@ -639,7 +646,7 @@ await debugService.SetLineBreakpointsAsync( variableScriptFile, new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 21) }).ConfigureAwait(true); - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); @@ -689,7 +696,7 @@ await debugService.SetLineBreakpointsAsync( variableScriptFile, new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 14) }).ConfigureAwait(true); - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); VariableScope[] scopes = debugService.GetVariableScopes(0); @@ -743,7 +750,7 @@ await debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 14) }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); VariableScope[] scopes = debugService.GetVariableScopes(0); @@ -799,7 +806,7 @@ await debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 15) }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); @@ -819,7 +826,7 @@ await debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 11) }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); @@ -852,7 +859,7 @@ await debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 16) }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); @@ -872,7 +879,7 @@ await debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 17) }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); @@ -898,7 +905,7 @@ public async Task DebuggerEnumerableShowsRawView() await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(commandBreakpointDetails: breakpoint); VariableDetailsBase simpleArrayVar = Array.Find( @@ -935,7 +942,7 @@ public async Task DebuggerDictionaryShowsRawView() await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(commandBreakpointDetails: breakpoint); VariableDetailsBase simpleDictionaryVar = Array.Find( @@ -971,7 +978,7 @@ public async Task DebuggerDerivedDictionaryPropertyInRawView() await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(commandBreakpointDetails: breakpoint); VariableDetailsBase sortedDictionaryVar = Array.Find( @@ -1000,7 +1007,7 @@ await debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 18) }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); @@ -1029,7 +1036,7 @@ await debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 19) }).ConfigureAwait(true); // Execute the script and wait for the breakpoint to be hit - Task _ = ExecuteVariableScriptFile(); + Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); From 5e9aeb2e0d3a6c4ca378e377b8ab6446cc9d90c0 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Mon, 21 Nov 2022 13:01:18 -0800 Subject: [PATCH 115/327] Enable VS Code's shell integration (#1958) It seems to work, but needs more testing. --- .../Server/PsesLanguageServer.cs | 3 +- .../PowerShell/Host/HostStartOptions.cs | 4 +- .../PowerShell/Host/PsesInternalHost.cs | 113 ++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 9ebdac3fc..05099b36b 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -152,7 +152,8 @@ public async Task StartAsync() LoadProfiles = initializationOptions?.GetValue("enableProfileLoading")?.Value() ?? true, // TODO: Consider deprecating the setting which sets this and // instead use WorkspacePath exclusively. - InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value() ?? workspaceService.WorkspacePath + InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value() ?? workspaceService.WorkspacePath, + ShellIntegrationEnabled = initializationOptions?.GetValue("shellIntegrationEnabled")?.Value() ?? false }; _psesHost = languageServer.Services.GetService(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs index 2a1fdfd2f..f483b76b4 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs @@ -8,5 +8,7 @@ internal struct HostStartOptions public bool LoadProfiles { get; set; } public string InitialWorkingDirectory { get; set; } - } + + public bool ShellIntegrationEnabled { get; set; } +} } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index e1282e6cb..19b873e19 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -77,6 +77,8 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns private string _localComputerName; + private bool _shellIntegrationEnabled; + private ConsoleKeyInfo? _lastKey; private bool _skipNextPrompt; @@ -254,6 +256,18 @@ public async Task TryStartAsync(HostStartOptions startOptions, Cancellatio _logger.LogDebug("Profiles loaded!"); } + if (startOptions.ShellIntegrationEnabled) + { + _logger.LogDebug("Enabling shell integration..."); + _shellIntegrationEnabled = true; + await EnableShellIntegrationAsync(cancellationToken).ConfigureAwait(false); + _logger.LogDebug("Shell integration enabled!"); + } + else + { + _logger.LogDebug("Shell integration not enabled!"); + } + if (startOptions.InitialWorkingDirectory is not null) { _logger.LogDebug($"Setting InitialWorkingDirectory to {startOptions.InitialWorkingDirectory}..."); @@ -487,6 +501,96 @@ internal Task LoadHostProfilesAsync(CancellationToken cancellationToken) cancellationToken); } + private Task EnableShellIntegrationAsync(CancellationToken cancellationToken) + { + // Imported on 11/17/22 from + // https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 + // with quotes escaped, `__VSCodeOriginalPSConsoleHostReadLine` removed (as it's done + // in our own ReadLine function), and `[Console]::Write` replaced with `Write-Host`. + // TODO: We can probably clean some of this up. + const string shellIntegrationScript = @" +# Prevent installing more than once per session +if (Test-Path variable:global:__VSCodeOriginalPrompt) { + return; +} + +# Disable shell integration when the language mode is restricted +if ($ExecutionContext.SessionState.LanguageMode -ne ""FullLanguage"") { + return; +} + +$Global:__VSCodeOriginalPrompt = $function:Prompt + +$Global:__LastHistoryId = -1 + + +function Global:Prompt() { + $FakeCode = [int]!$global:? + $LastHistoryEntry = Get-History -Count 1 + # Skip finishing the command if the first command has not yet started + if ($Global:__LastHistoryId -ne -1) { + if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) { + # Don't provide a command line or exit code if there was no history entry (eg. ctrl+c, enter on no command) + $Result = ""`e]633;E`a"" + $Result += ""`e]633;D`a"" + } else { + # Command finished command line + # OSC 633 ; A ; ST + $Result = ""`e]633;E;"" + # Sanitize the command line to ensure it can get transferred to the terminal and can be parsed + # correctly. This isn't entirely safe but good for most cases, it's important for the Pt parameter + # to only be composed of _printable_ characters as per the spec. + if ($LastHistoryEntry.CommandLine) { + $CommandLine = $LastHistoryEntry.CommandLine + } else { + $CommandLine = """" + } + $Result += $CommandLine.Replace(""\"", ""\\"").Replace(""`n"", ""\x0a"").Replace("";"", ""\x3b"") + $Result += ""`a"" + # Command finished exit code + # OSC 633 ; D [; ] ST + $Result += ""`e]633;D;$FakeCode`a"" + } + } + # Prompt started + # OSC 633 ; A ST + $Result += ""`e]633;A`a"" + # Current working directory + # OSC 633 ; = ST + $Result += if($pwd.Provider.Name -eq 'FileSystem'){""`e]633;P;Cwd=$($pwd.ProviderPath)`a""} + # Before running the original prompt, put $? back to what it was: + if ($FakeCode -ne 0) { Write-Error ""failure"" -ea ignore } + # Run the original prompt + $Result += $Global:__VSCodeOriginalPrompt.Invoke() + # Write command started + $Result += ""`e]633;B`a"" + $Global:__LastHistoryId = $LastHistoryEntry.Id + return $Result +} + +# Set IsWindows property +Write-Host -NoNewLine ""`e]633;P;IsWindows=$($IsWindows)`a"" + +# Set always on key handlers which map to default VS Code keybindings +function Set-MappedKeyHandler { + param ([string[]] $Chord, [string[]]$Sequence) + $Handler = $(Get-PSReadLineKeyHandler -Chord $Chord | Select-Object -First 1) + if ($Handler) { + Set-PSReadLineKeyHandler -Chord $Sequence -Function $Handler.Function + } +} +function Set-MappedKeyHandlers { + Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a' + Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b' + Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c' + Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d' +} +Set-MappedKeyHandlers + "; + + return ExecutePSCommandAsync(new PSCommand().AddScript(shellIntegrationScript), cancellationToken); + } + public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cancellationToken) { return Directory.Exists(path) @@ -962,8 +1066,17 @@ private string InvokeReadLine(CancellationToken cancellationToken) private void InvokeInput(string input, CancellationToken cancellationToken) { SetBusy(true); + try { + // For VS Code's shell integration feature, this replaces their + // PSConsoleHostReadLine function wrapper, as that global function is not available + // to users of PSES, since we already wrap ReadLine ourselves. + if (_shellIntegrationEnabled) + { + System.Console.Write("\x1b]633;C\a"); + } + InvokePSCommand( new PSCommand().AddScript(input, useLocalScope: false), new PowerShellExecutionOptions From 5d7b7bc7fd913b348d86284b8790b47101039aff Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 21 Nov 2022 13:55:48 -0800 Subject: [PATCH 116/327] Update CHANGELOG for `v3.6.2` --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37c107a65..6da10cd3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # PowerShell Editor Services Release History +## v3.6.2 +### Monday, November 21, 2022 + +- ✨ 📟 [vscode-powershell #3901](https://github.com/PowerShell/PowerShellEditorServices/pull/1958) - Enable VS Code's shell integration. +- 🐛 🔍 [vscode-powershell #4269](https://github.com/PowerShell/PowerShellEditorServices/pull/1957) - Escape single quotes when launching a script by path. +- ✨ 🚨 [PowerShellEditorServices #1955](https://github.com/PowerShell/PowerShellEditorServices/pull/1955) - Add PowerShell 7.3 to test matrix. + ## v3.6.1 ### Monday, November 07, 2022 From 6cadc7484a6d2b14ed1e9bb59df7b22fab362803 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 21 Nov 2022 13:55:48 -0800 Subject: [PATCH 117/327] Bump version to `v3.6.2` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 900995133..bec39b71d 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.6.1 + 3.6.2 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index da7f88fc6..ba3790fbe 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.6.1' +ModuleVersion = '3.6.2' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From ab8bfa35ac1475e2468845ad7c8a7b3f9b548c56 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Wed, 23 Nov 2022 15:36:22 -0800 Subject: [PATCH 118/327] Replace `e with $([char]0x1b) (#1961) This allows shell integration to work with Windows PowerShell, as the only problem seemed to be use of `e, which was an escape character added only to PowerShell Core, but the `[char]` equivalent works fine. --- .../Services/PowerShell/Host/PsesInternalHost.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 19b873e19..cf83dd34b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -531,12 +531,12 @@ private Task EnableShellIntegrationAsync(CancellationToken cancellationToken) if ($Global:__LastHistoryId -ne -1) { if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) { # Don't provide a command line or exit code if there was no history entry (eg. ctrl+c, enter on no command) - $Result = ""`e]633;E`a"" - $Result += ""`e]633;D`a"" + $Result = ""$([char]0x1b)]633;E`a"" + $Result += ""$([char]0x1b)]633;D`a"" } else { # Command finished command line # OSC 633 ; A ; ST - $Result = ""`e]633;E;"" + $Result = ""$([char]0x1b)]633;E;"" # Sanitize the command line to ensure it can get transferred to the terminal and can be parsed # correctly. This isn't entirely safe but good for most cases, it's important for the Pt parameter # to only be composed of _printable_ characters as per the spec. @@ -549,27 +549,27 @@ private Task EnableShellIntegrationAsync(CancellationToken cancellationToken) $Result += ""`a"" # Command finished exit code # OSC 633 ; D [; ] ST - $Result += ""`e]633;D;$FakeCode`a"" + $Result += ""$([char]0x1b)]633;D;$FakeCode`a"" } } # Prompt started # OSC 633 ; A ST - $Result += ""`e]633;A`a"" + $Result += ""$([char]0x1b)]633;A`a"" # Current working directory # OSC 633 ; = ST - $Result += if($pwd.Provider.Name -eq 'FileSystem'){""`e]633;P;Cwd=$($pwd.ProviderPath)`a""} + $Result += if($pwd.Provider.Name -eq 'FileSystem'){""$([char]0x1b)]633;P;Cwd=$($pwd.ProviderPath)`a""} # Before running the original prompt, put $? back to what it was: if ($FakeCode -ne 0) { Write-Error ""failure"" -ea ignore } # Run the original prompt $Result += $Global:__VSCodeOriginalPrompt.Invoke() # Write command started - $Result += ""`e]633;B`a"" + $Result += ""$([char]0x1b)]633;B`a"" $Global:__LastHistoryId = $LastHistoryEntry.Id return $Result } # Set IsWindows property -Write-Host -NoNewLine ""`e]633;P;IsWindows=$($IsWindows)`a"" +Write-Host -NoNewLine ""$([char]0x1b)]633;P;IsWindows=$($IsWindows)`a"" # Set always on key handlers which map to default VS Code keybindings function Set-MappedKeyHandler { From 5ce62d890c5bc09faebf2a260b91f3f5fdf4f708 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 28 Nov 2022 13:29:43 -0800 Subject: [PATCH 119/327] Revert manual pin of Newtonsoft.Json --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index c7e9f761e..6d2b5e3d5 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -28,8 +28,8 @@ - - + + From 142b7f495a9631dce5703e1c7e04e5d99aa36eb0 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 28 Nov 2022 13:59:33 -0800 Subject: [PATCH 120/327] Use the `LspSerializer` in `CodeActions` and `SendTelemetry` --- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 4 +++- .../Services/CodeLens/ReferencesCodeLensProvider.cs | 6 +++--- .../Services/TextDocument/Handlers/CodeActionHandler.cs | 8 +++++++- .../Services/Workspace/WorkspaceService.cs | 1 + 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 0e79946ef..6aca82e04 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -89,6 +89,8 @@ public async Task StartAsync() { // Start the host if not already started, and enable debug mode (required // for remote debugging). + // + // TODO: We might need to fill in HostStartOptions here. _startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), cancellationToken).ConfigureAwait(false); _psesHost.DebugContext.EnableDebugMode(); diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index 02cff641d..eb50ce2d5 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -154,9 +154,9 @@ public async Task ResolveCodeLens( Title = GetReferenceCountHeader(referenceLocations.Length), Arguments = JArray.FromObject(new object[] { - scriptFile.DocumentUri, - codeLens.Range.Start, - referenceLocations + scriptFile.DocumentUri, + codeLens.Range.Start, + referenceLocations }, LspSerializer.Instance.JsonSerializer) } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs index 8ea7eca70..2681baa19 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -12,6 +13,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -131,7 +133,11 @@ public override async Task Handle(CodeActionParams { Title = title, Name = "PowerShell.ShowCodeActionDocumentation", - Arguments = Newtonsoft.Json.Linq.JArray.FromObject(new[] { diagnostic.Code?.String }) + Arguments = JArray.FromObject(new object[] + { + diagnostic.Code?.String + }, + LspSerializer.Instance.JsonSerializer) } }); } diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs index ee76bfd99..0d9a614e4 100644 --- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs @@ -506,6 +506,7 @@ internal static bool IsPathInMemory(string filePath) internal string ResolveRelativeScriptPath(string baseFilePath, string relativePath) { + // TODO: Sometimes the `baseFilePath` (even when its `WorkspacePath`) is null. string combinedPath = null; Exception resolveException = null; From f790119ebb9e8b6a3130c27a8fc0fecb9b11d721 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 29 Nov 2022 11:42:58 -0800 Subject: [PATCH 121/327] Update CHANGELOG for `v3.6.3` --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6da10cd3e..6f17f0ac8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # PowerShell Editor Services Release History +## v3.6.3 +### Tuesday, November 29, 2022 + +- 🐛 🙏 [PowerShellEditorServices #1962](https://github.com/PowerShell/PowerShellEditorServices/pull/1962) - Revert manual pin of Newtonsoft.Json. +- 🐛 📟 [vscode-powershell #4279](https://github.com/PowerShell/PowerShellEditorServices/pull/1961) - Replace backtick-e with `$([char]0x1b)`. + ## v3.6.2 ### Monday, November 21, 2022 From adad35851c8a518a38953fda4f97a87e31fc6238 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 29 Nov 2022 11:42:58 -0800 Subject: [PATCH 122/327] Bump version to `v3.6.3` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index bec39b71d..847ed6576 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.6.2 + 3.6.3 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index ba3790fbe..2cd88bf78 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.6.2' +ModuleVersion = '3.6.3' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From ee53878ff7465f6b0b024cea873f8bf8574b6ead Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 2 Dec 2022 11:21:19 -0800 Subject: [PATCH 123/327] Remove unnecessary `PowerShellProcessArchitecture` (#1964) This simply wasn't being used and was overly complicated. The only time we want the architecture is when queried via LSP so that the VS Code client can determine which installer to download for its auto-update feature. If we can, we should deduplicate version logic in the loader and the `VersionUtils` class. --- .../EditorServicesLoader.cs | 1 + .../Context/PowerShellVersionDetails.cs | 73 ++----------------- .../PowerShell/Handlers/GetVersionHandler.cs | 25 +------ .../PowerShell/Handlers/IGetVersionHandler.cs | 29 ++------ .../Utility/VersionUtils.cs | 24 ++++++ 5 files changed, 36 insertions(+), 116 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index 924619283..2f9b8f8a6 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -354,6 +354,7 @@ private void LogOperatingSystemDetails() "); } + // TODO: Deduplicate this with VersionUtils. private static string GetOSArchitecture() { #if CoreCLR diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs index 4bbdd47e1..e84f235b9 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs @@ -11,34 +11,11 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { using System.Management.Automation; - /// - /// Defines the possible enumeration values for the PowerShell process architecture. - /// - internal enum PowerShellProcessArchitecture - { - /// - /// The processor architecture is unknown or wasn't accessible. - /// - Unknown, - - /// - /// The processor architecture is 32-bit. - /// - X86, - - /// - /// The processor architecture is 64-bit. - /// - X64 - } - /// /// Provides details about the version of the PowerShell runtime. /// internal class PowerShellVersionDetails { - #region Properties - /// /// Gets the version of the PowerShell runtime. /// @@ -55,40 +32,26 @@ internal class PowerShellVersionDetails /// public string Edition { get; } - /// - /// Gets the architecture of the PowerShell process. - /// - public PowerShellProcessArchitecture Architecture { get; } - - #endregion - - #region Constructors - /// /// Creates an instance of the PowerShellVersionDetails class. /// /// The version of the PowerShell runtime. /// A string representation of the PowerShell version. /// The string representation of the PowerShell edition. - /// The processor architecture. public PowerShellVersionDetails( Version version, string versionString, - string editionString, - PowerShellProcessArchitecture architecture) + string editionString) { Version = version; VersionString = versionString; Edition = editionString; - Architecture = architecture; } - #endregion - - #region Public Methods - /// - /// Gets the PowerShell version details for the given runspace. + /// Gets the PowerShell version details for the given runspace. This doesn't use + /// VersionUtils because we may be remoting, and therefore want the remote runspace's + /// version, not the local process. /// /// An ILogger implementation used for writing log messages. /// The PowerShell instance for which to get the version. @@ -98,7 +61,6 @@ public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerSh Version powerShellVersion = new(5, 0); string versionString = null; string powerShellEdition = "Desktop"; - PowerShellProcessArchitecture architecture = PowerShellProcessArchitecture.Unknown; try { @@ -129,25 +91,6 @@ public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerSh } versionString = psVersionTable["GitCommitId"] is string gitCommitId ? gitCommitId : powerShellVersion.ToString(); - - PSCommand procArchCommand = new PSCommand().AddScript("$env:PROCESSOR_ARCHITECTURE", useLocalScope: true); - - string arch = pwsh - .AddScript("$env:PROCESSOR_ARCHITECTURE", useLocalScope: true) - .InvokeAndClear() - .FirstOrDefault(); - - if (arch != null) - { - if (string.Equals(arch, "AMD64", StringComparison.CurrentCultureIgnoreCase)) - { - architecture = PowerShellProcessArchitecture.X64; - } - else if (string.Equals(arch, "x86", StringComparison.CurrentCultureIgnoreCase)) - { - architecture = PowerShellProcessArchitecture.X86; - } - } } } catch (Exception ex) @@ -156,13 +99,7 @@ public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerSh "Failed to look up PowerShell version, defaulting to version 5.\r\n\r\n" + ex.ToString()); } - return new PowerShellVersionDetails( - powerShellVersion, - versionString, - powerShellEdition, - architecture); + return new PowerShellVersionDetails(powerShellVersion, versionString, powerShellEdition); } - - #endregion } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index b500b7c1e..2490c8ad8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Threading; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Services.PowerShell; @@ -13,35 +12,13 @@ internal class GetVersionHandler : IGetVersionHandler { public async Task Handle(GetVersionParams request, CancellationToken cancellationToken) { - PowerShellProcessArchitecture architecture = PowerShellProcessArchitecture.Unknown; - // This should be changed to using a .NET call sometime in the future... but it's just for logging purposes. - string arch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); - if (arch != null) - { - if (string.Equals(arch, "AMD64", StringComparison.CurrentCultureIgnoreCase)) - { - architecture = PowerShellProcessArchitecture.X64; - } - else if (string.Equals(arch, "x86", StringComparison.CurrentCultureIgnoreCase)) - { - architecture = PowerShellProcessArchitecture.X86; - } - } - return new PowerShellVersion { Version = VersionUtils.PSVersionString, Edition = VersionUtils.PSEdition, DisplayVersion = VersionUtils.PSVersion.ToString(2), - Architecture = architecture.ToString() + Architecture = VersionUtils.Architecture }; } - - private enum PowerShellProcessArchitecture - { - Unknown, - X86, - X64 - } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs index 66e66eab6..c2b4977bd 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using MediatR; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using OmniSharp.Extensions.JsonRpc; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell @@ -12,29 +11,11 @@ internal interface IGetVersionHandler : IJsonRpcRequestHandler { } - internal class PowerShellVersion + internal record PowerShellVersion { - public string Version { get; set; } - public string DisplayVersion { get; set; } - public string Edition { get; set; } - public string Architecture { get; set; } - - public PowerShellVersion() - { - } - - public PowerShellVersion(PowerShellVersionDetails versionDetails) - { - Version = versionDetails.VersionString; - DisplayVersion = $"{versionDetails.Version.Major}.{versionDetails.Version.Minor}"; - Edition = versionDetails.Edition; - - Architecture = versionDetails.Architecture switch - { - PowerShellProcessArchitecture.X64 => "x64", - PowerShellProcessArchitecture.X86 => "x86", - _ => "Architecture Unknown", - }; - } + public string Version { get; init; } + public string DisplayVersion { get; init; } + public string Edition { get; init; } + public string Architecture { get; init; } } } diff --git a/src/PowerShellEditorServices/Utility/VersionUtils.cs b/src/PowerShellEditorServices/Utility/VersionUtils.cs index a814b73a6..287b7277e 100644 --- a/src/PowerShellEditorServices/Utility/VersionUtils.cs +++ b/src/PowerShellEditorServices/Utility/VersionUtils.cs @@ -61,6 +61,11 @@ internal static class VersionUtils /// True if we are running on Linux, false otherwise. /// public static bool IsLinux { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + /// + /// The .NET Architecture as a string. + /// + public static string Architecture { get; } = PowerShellReflectionUtils.GetOSArchitecture(); } internal static class PowerShellReflectionUtils @@ -96,5 +101,24 @@ internal static class PowerShellReflectionUtils public static string PSVersionString { get; } = s_psCurrentVersionProperty != null ? s_psCurrentVersionProperty.GetValue(null).ToString() : s_psVersionProperty.GetValue(null).ToString(); + + public static string GetOSArchitecture() + { +#if CoreCLR + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { + return RuntimeInformation.OSArchitecture.ToString(); + } +#endif + // If on win7 (version 6.1.x), avoid System.Runtime.InteropServices.RuntimeInformation + if (Environment.OSVersion.Version < new Version(6, 2)) + { + return Environment.Is64BitProcess + ? "X64" + : "X86"; + } + + return RuntimeInformation.OSArchitecture.ToString(); + } } } From f617fb4ef6e7656e249b08e63f2c959f2ab14b9a Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 7 Nov 2022 15:19:35 -0800 Subject: [PATCH 124/327] Revert "Skip tests broken by OmniSharp update" This reverts commit e1817dedb6cab77d71672ebb2669a8f97c857c0c. --- .../Language/CompletionHandlerTests.cs | 17 +++++------------ .../Services/Symbols/PSScriptAnalyzerTests.cs | 7 ++----- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs index c7cb1765b..d47afa38e 100644 --- a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs @@ -53,19 +53,17 @@ private Task GetCompletionResultsAsync(ScriptRegion scriptReg CancellationToken.None); } - [SkippableFact] + [Fact] public async Task CompletesCommandInFile() { - Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteCommandInFile.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); Assert.Equal(CompleteCommandInFile.ExpectedCompletion, actual); } - [SkippableFact] + [Fact] public async Task CompletesCommandFromModule() { - Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteCommandFromModule.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); // NOTE: The tooltip varies across PowerShell and OS versions, so we ignore it. @@ -76,7 +74,6 @@ public async Task CompletesCommandFromModule() [SkippableFact] public async Task CompletesTypeName() { - Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); Skip.If(VersionUtils.PSEdition == "Desktop", "Windows PowerShell has trouble with this test right now."); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteTypeName.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); @@ -98,26 +95,23 @@ public async Task CompletesTypeName() [SkippableFact] public async Task CompletesNamespace() { - Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); Skip.If(VersionUtils.PSEdition == "Desktop", "Windows PowerShell has trouble with this test right now."); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteNamespace.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); Assert.Equal(CompleteNamespace.ExpectedCompletion, actual); } - [SkippableFact] + [Fact] public async Task CompletesVariableInFile() { - Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteVariableInFile.SourceDetails).ConfigureAwait(true); CompletionItem actual = Assert.Single(results); Assert.Equal(CompleteVariableInFile.ExpectedCompletion, actual); } - [SkippableFact] + [Fact] public async Task CompletesAttributeValue() { - Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteAttributeValue.SourceDetails).ConfigureAwait(true); // NOTE: Since the completions come through un-ordered from PowerShell, their SortText // (which has an index prepended from the original order) will mis-match our assumed @@ -128,10 +122,9 @@ public async Task CompletesAttributeValue() actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion3)); } - [SkippableFact] + [Fact] public async Task CompletesFilePath() { - Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); (_, IEnumerable results) = await GetCompletionResultsAsync(CompleteFilePath.SourceDetails).ConfigureAwait(true); Assert.NotEmpty(results); CompletionItem actual = results.First(); diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs index 3f9a7ecb7..ca3f3ae04 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs @@ -8,7 +8,6 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test; -using Microsoft.PowerShell.EditorServices.Utility; using Xunit; namespace PowerShellEditorServices.Test.Services.Symbols @@ -66,10 +65,9 @@ public async Task CanLoadPSScriptAnalyzerAsync() }); } - [SkippableFact] + [Fact] public async Task DoesNotDuplicateScriptMarkersAsync() { - Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); ScriptFile scriptFile = workspaceService.GetFileBuffer("untitled:Untitled-1", script); ScriptFile[] scriptFiles = { scriptFile }; @@ -85,10 +83,9 @@ await analysisService Assert.Single(scriptFile.DiagnosticMarkers); } - [SkippableFact] + [Fact] public async Task DoesNotClearParseErrorsAsync() { - Skip.If(VersionUtils.PSEdition == "Core", "OmniSharp records are broken!"); // Causing a missing closing } parser error ScriptFile scriptFile = workspaceService.GetFileBuffer("untitled:Untitled-2", script.TrimEnd('}')); ScriptFile[] scriptFiles = { scriptFile }; From f7517b38e61e2f499c3e8a24e719c3b7cc0f5427 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 7 Nov 2022 17:10:31 -0800 Subject: [PATCH 125/327] Add `-LocalOmniSharp` build flag To build against project at `../csharp-language-server-protocol`. --- PowerShellEditorServices.build.ps1 | 30 ++++++++++++------- .../PowerShellEditorServices.Hosting.csproj | 2 +- .../PowerShellEditorServices.csproj | 16 ++++++++++ .../PowerShellEditorServices.Test.E2E.csproj | 17 +++++++++-- .../PowerShellEditorServices.Test.csproj | 1 - 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 2af0db8b9..136a0547b 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -5,27 +5,36 @@ param( [ValidateSet("Debug", "Release")] [string]$Configuration = "Debug", + [switch]$LocalOmniSharp, + [string]$PsesSubmodulePath = "$PSScriptRoot/module", [string]$ModulesJsonPath = "$PSScriptRoot/modules.json", [string]$DefaultModuleRepository = "PSGallery", - [string[]]$VerbosityArgs = @("--verbosity", "quiet", "--nologo"), + [string]$Verbosity = "quiet", # See: https://docs.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests [string]$TestFilter = '', # See: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test - [string[]]$TestArgs = $VerbosityArgs + @("--logger", "console;verbosity=normal", "--logger", "trx") + [string[]]$TestArgs = @("--logger", "console;verbosity=normal", "--logger", "trx") ) #Requires -Modules @{ModuleName="InvokeBuild"; ModuleVersion="5.0.0"} #Requires -Modules @{ModuleName="platyPS"; ModuleVersion="0.14.0"} -$script:dotnetTestArgs = @( - "test" - $TestArgs +$script:dotnetBuildArgs = @( + "--verbosity" + $Verbosity + "--nologo" + "-c" + $Configuration + if ($LocalOmniSharp) { "-property:LocalOmniSharp=true" } +) + +$script:dotnetTestArgs = @("test") + $script:dotnetBuildArgs + $TestArgs + @( if ($TestFilter) { "--filter", $TestFilter } "--framework" ) @@ -80,7 +89,7 @@ Task BinClean { } Task Clean FindDotNet, BinClean, { - Invoke-BuildExec { & dotnet clean $VerbosityArgs } + Invoke-BuildExec { & dotnet clean --verbosity $Verbosity } Get-ChildItem -Recurse $PSScriptRoot\src\*.nupkg | Remove-BuildItem Get-ChildItem $PSScriptRoot\PowerShellEditorServices*.zip | Remove-BuildItem Get-ChildItem $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US\*-help.xml | Remove-BuildItem @@ -162,15 +171,14 @@ Task SetupHelpForTests { } Task Build FindDotNet, CreateBuildInfo, { - Invoke-BuildExec { & dotnet restore $VerbosityArgs } - Invoke-BuildExec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } - Invoke-BuildExec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } + Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } + Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } if (-not $script:IsNix) { - Invoke-BuildExec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } + Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } } # Build PowerShellEditorServices.VSCode module - Invoke-BuildExec { & dotnet publish $VerbosityArgs -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } + Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } } Task Test TestServer, TestE2E diff --git a/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj b/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj index c5aa05dd4..2a172f2fc 100644 --- a/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj +++ b/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 6d2b5e3d5..69d938f59 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -42,6 +42,22 @@ + + + + + + + + + + + + + + + + diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 5f7618193..07f04bb99 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -10,13 +10,26 @@ - - + + + + + + + + + + + + + + + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 26323a8d6..9a32254b9 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -41,7 +41,6 @@ - From aa63263df2327a2cfa7cf898a272c4eaed514b0c Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 8 Nov 2022 16:09:53 -0800 Subject: [PATCH 126/327] Fix our `IsExternalInit` workaround We were also missing the conditional type forward. --- .../IsExternalInit.cs | 19 +++++++++++++++++++ .../Utility/IsExternalInit.cs | 15 --------------- 2 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 src/PowerShellEditorServices/IsExternalInit.cs delete mode 100644 src/PowerShellEditorServices/Utility/IsExternalInit.cs diff --git a/src/PowerShellEditorServices/IsExternalInit.cs b/src/PowerShellEditorServices/IsExternalInit.cs new file mode 100644 index 000000000..c2a9d8275 --- /dev/null +++ b/src/PowerShellEditorServices/IsExternalInit.cs @@ -0,0 +1,19 @@ +#pragma warning disable IDE0073 +#if NET5_0_OR_GREATER +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] +#else + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} +#endif diff --git a/src/PowerShellEditorServices/Utility/IsExternalInit.cs b/src/PowerShellEditorServices/Utility/IsExternalInit.cs deleted file mode 100644 index 1f0f8f07c..000000000 --- a/src/PowerShellEditorServices/Utility/IsExternalInit.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.ComponentModel; - -namespace System.Runtime.CompilerServices -{ - /// - /// This type must be defined to use init property accessors, - /// but is not in .NET Standard 2.0. - /// So instead we define the type in our own code. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal class IsExternalInit { } -} From 4ddb93f642554a00620caf64ed98db060eea67b0 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 2 Dec 2022 13:51:16 -0800 Subject: [PATCH 127/327] Bump OmniSharp to v0.19.7 --- .../PowerShellEditorServices.csproj | 10 ++++------ .../PowerShellEditorServices.Test.E2E.csproj | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 69d938f59..2e06e66e3 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -28,10 +28,8 @@ - - - - + + @@ -51,8 +49,8 @@ - - + + diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 07f04bb99..a20809070 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -24,8 +24,8 @@ - - + + From c6eb624eae2c3fbba94cd2ee384a4b6273a3d4b0 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Tue, 6 Dec 2022 13:37:03 -0800 Subject: [PATCH 128/327] Fix duplicate package reference from bad merge (#1966) --- PowerShellEditorServices.build.ps1 | 6 +++--- .../PowerShellEditorServices.csproj | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 136a0547b..02664bc13 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -90,9 +90,9 @@ Task BinClean { Task Clean FindDotNet, BinClean, { Invoke-BuildExec { & dotnet clean --verbosity $Verbosity } - Get-ChildItem -Recurse $PSScriptRoot\src\*.nupkg | Remove-BuildItem - Get-ChildItem $PSScriptRoot\PowerShellEditorServices*.zip | Remove-BuildItem - Get-ChildItem $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US\*-help.xml | Remove-BuildItem + Remove-BuildItem $PSScriptRoot\src\*.nupkg + Remove-BuildItem $PSScriptRoot\PowerShellEditorServices*.zip + Remove-BuildItem $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US\*-help.xml # Remove bundled component modules $moduleJsonPath = "$PSScriptRoot\modules.json" diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 2e06e66e3..30831daf5 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -28,8 +28,6 @@ - - From 551cf8bba663647bca2cf35d8ce963daed353efa Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Tue, 6 Dec 2022 13:43:26 -0800 Subject: [PATCH 129/327] Send `GitCommitId` over `GetVersionHandler` (#1965) So the client can skip updates for daily and dev builds. Also clean up a little more, the commit ID was not actually being used in the rest of the codebase, and the `DisplayVersion` was dumb. --- .../Context/PowerShellVersionDetails.cs | 14 +------------ .../PowerShell/Handlers/GetVersionHandler.cs | 2 +- .../PowerShell/Handlers/IGetVersionHandler.cs | 2 +- .../Utility/VersionUtils.cs | 20 +++++++++++++++++-- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs index e84f235b9..ec5b8711f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs @@ -21,12 +21,6 @@ internal class PowerShellVersionDetails /// public Version Version { get; } - /// - /// Gets the full version string, either the ToString of the Version - /// property or the GitCommitId for open-source PowerShell releases. - /// - public string VersionString { get; } - /// /// Gets the PowerShell edition (generally Desktop or Core). /// @@ -36,15 +30,12 @@ internal class PowerShellVersionDetails /// Creates an instance of the PowerShellVersionDetails class. /// /// The version of the PowerShell runtime. - /// A string representation of the PowerShell version. /// The string representation of the PowerShell edition. public PowerShellVersionDetails( Version version, - string versionString, string editionString) { Version = version; - VersionString = versionString; Edition = editionString; } @@ -59,7 +50,6 @@ public PowerShellVersionDetails( public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerShell pwsh) { Version powerShellVersion = new(5, 0); - string versionString = null; string powerShellEdition = "Desktop"; try @@ -89,8 +79,6 @@ public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerSh // Expected version string format is 6.0.0-alpha so build a simpler version from that powerShellVersion = new Version(version.ToString().Split('-')[0]); } - - versionString = psVersionTable["GitCommitId"] is string gitCommitId ? gitCommitId : powerShellVersion.ToString(); } } catch (Exception ex) @@ -99,7 +87,7 @@ public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerSh "Failed to look up PowerShell version, defaulting to version 5.\r\n\r\n" + ex.ToString()); } - return new PowerShellVersionDetails(powerShellVersion, versionString, powerShellEdition); + return new PowerShellVersionDetails(powerShellVersion, powerShellEdition); } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index 2490c8ad8..025f92f5a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -16,7 +16,7 @@ public async Task Handle(GetVersionParams request, Cancellati { Version = VersionUtils.PSVersionString, Edition = VersionUtils.PSEdition, - DisplayVersion = VersionUtils.PSVersion.ToString(2), + Commit = VersionUtils.GitCommitId, Architecture = VersionUtils.Architecture }; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs index c2b4977bd..ac82d85af 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs @@ -14,8 +14,8 @@ internal class GetVersionParams : IRequest { } internal record PowerShellVersion { public string Version { get; init; } - public string DisplayVersion { get; init; } public string Edition { get; init; } + public string Commit { get; init; } public string Architecture { get; init; } } } diff --git a/src/PowerShellEditorServices/Utility/VersionUtils.cs b/src/PowerShellEditorServices/Utility/VersionUtils.cs index 287b7277e..f130039ee 100644 --- a/src/PowerShellEditorServices/Utility/VersionUtils.cs +++ b/src/PowerShellEditorServices/Utility/VersionUtils.cs @@ -27,6 +27,11 @@ internal static class VersionUtils /// public static string PSEdition { get; } = PowerShellReflectionUtils.PSEdition; + /// + /// Gets the GitCommitId of PowerShell being used. + /// + public static string GitCommitId { get; } = PowerShellReflectionUtils.GitCommitId; + /// /// Gets the string of the PSVersion including prerelease tags if it applies. /// @@ -84,8 +89,12 @@ internal static class PowerShellReflectionUtils private static readonly PropertyInfo s_psEditionProperty = s_psVersionInfoType .GetProperty("PSEdition", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + // This property is a string, existing only in PowerShell Core. + private static readonly FieldInfo s_psGitCommitIdProperty = s_psVersionInfoType + .GetField("GitCommitId", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + /// - /// Get's the Version of PowerShell being used. Note: this will get rid of the SemVer 2.0 suffix because apparently + /// Gets the Version of PowerShell being used. NOTE: this will get rid of the SemVer 2.0 suffix because apparently /// that property is added as a note property and it is not there when we reflect. /// public static Version PSVersion { get; } = s_psVersionProperty.GetValue(null) as Version; @@ -95,12 +104,19 @@ internal static class PowerShellReflectionUtils /// public static string PSEdition { get; } = s_psEditionProperty.GetValue(null) as string; + /// + /// Gets the GitCommitId or at most x.y.z from the PSVersion, making Windows PowerShell conform to SemVer. + /// + public static string GitCommitId { get; } = s_psGitCommitIdProperty != null + ? s_psGitCommitIdProperty.GetValue(null).ToString() + : PSVersion.ToString(3); + /// /// Gets the stringified version of PowerShell including prerelease tags if it applies. /// public static string PSVersionString { get; } = s_psCurrentVersionProperty != null ? s_psCurrentVersionProperty.GetValue(null).ToString() - : s_psVersionProperty.GetValue(null).ToString(); + : PSVersion.ToString(3); public static string GetOSArchitecture() { From 8e17b728ffec45868e1018cf09c36e83aab8b412 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Tue, 6 Dec 2022 14:01:01 -0800 Subject: [PATCH 130/327] Drop support for end-of-life PowerShell 7.0 (#1967) Which more importantly means we can drop `netcoreapp3.1`. --- .vsts-ci/templates/ci-general.yml | 7 ------- PowerShellEditorServices.build.ps1 | 20 ++++--------------- .../PowerShellEditorServices.Hosting.csproj | 4 ++-- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 9 ++------- 5 files changed, 9 insertions(+), 33 deletions(-) diff --git a/.vsts-ci/templates/ci-general.yml b/.vsts-ci/templates/ci-general.yml index 2278dc2eb..a4a8a04d5 100644 --- a/.vsts-ci/templates/ci-general.yml +++ b/.vsts-ci/templates/ci-general.yml @@ -25,13 +25,6 @@ steps: version: 6.0.x performMultiLevelLookup: true -- task: UseDotNet@2 - displayName: Install .NET 3.1.x runtime - inputs: - packageType: runtime - version: 3.1.x - performMultiLevelLookup: true - - task: PowerShell@2 displayName: Build inputs: diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 02664bc13..34a1e7590 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -48,14 +48,13 @@ $script:BuildInfoPath = [System.IO.Path]::Combine($PSScriptRoot, "src", "PowerSh $script:PsesCommonProps = [xml](Get-Content -Raw "$PSScriptRoot/PowerShellEditorServices.Common.props") $script:NetRuntime = @{ - PS7 = 'netcoreapp3.1' PS72 = 'net6.0' PS73 = 'net7.0' Desktop = 'net462' Standard = 'netstandard2.0' } -$script:HostCoreOutput = "$PSScriptRoot/src/PowerShellEditorServices.Hosting/bin/$Configuration/$($script:NetRuntime.PS7)/publish" +$script:HostCoreOutput = "$PSScriptRoot/src/PowerShellEditorServices.Hosting/bin/$Configuration/$($script:NetRuntime.PS72)/publish" $script:HostDeskOutput = "$PSScriptRoot/src/PowerShellEditorServices.Hosting/bin/$Configuration/$($script:NetRuntime.Desktop)/publish" $script:PsesOutput = "$PSScriptRoot/src/PowerShellEditorServices/bin/$Configuration/$($script:NetRuntime.Standard)/publish" $script:VSCodeOutput = "$PSScriptRoot/src/PowerShellEditorServices.VSCode/bin/$Configuration/$($script:NetRuntime.Standard)/publish" @@ -72,13 +71,6 @@ Task FindDotNet { $existingVersion, $null = (dotnet --version) -split '-' Assert ([Version]$existingVersion -ge [Version]("6.0")) ".NET SDK 6.0 or higher is required, please update it: https://aka.ms/dotnet-cli" - # Anywhere other than on a Mac with an M1 processor, we additionally - # need the .NET 3.1 runtime for our netcoreapp3.1 framework. - if (-not $script:IsAppleM1 -and -not $script:IsArm64) { - $runtimes = dotnet --list-runtimes - Assert ($runtimes -match "Microsoft.NETCore.App 3.1") ".NET Runtime 3.1 required but not found!" - } - Write-Host "Using dotnet v$(dotnet --version) at path $((Get-Command dotnet).Source)" -ForegroundColor Green } @@ -172,7 +164,8 @@ Task SetupHelpForTests { Task Build FindDotNet, CreateBuildInfo, { Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f $script:NetRuntime.Standard } - Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS7 } + Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.PS72 } + if (-not $script:IsNix) { Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs .\src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj -f $script:NetRuntime.Desktop } } @@ -183,7 +176,7 @@ Task Build FindDotNet, CreateBuildInfo, { Task Test TestServer, TestE2E -Task TestServer TestServerWinPS, TestServerPS7, TestServerPS72, TestServerPS73 +Task TestServer TestServerWinPS, TestServerPS72, TestServerPS73 # NOTE: While these can run under `pwsh.exe` we only want them to run under # `powershell.exe` so that the CI time isn't doubled. @@ -196,11 +189,6 @@ Task TestServerWinPS -If ($PSVersionTable.PSEdition -eq "Desktop") Build, SetupH Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.Desktop } } -Task TestServerPS7 -If ($PSVersionTable.PSEdition -eq "Core" -and -not $script:IsAppleM1 -and -not $script:IsArm64) Build, SetupHelpForTests, { - Set-Location .\test\PowerShellEditorServices.Test\ - Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS7 } -} - Task TestServerPS72 -If ($PSVersionTable.PSEdition -eq "Core") Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test\ Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } diff --git a/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj b/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj index 2a172f2fc..118b25d73 100644 --- a/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj +++ b/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj @@ -2,11 +2,11 @@ - netcoreapp3.1;net462 + net6.0;net462 Microsoft.PowerShell.EditorServices.Hosting - + $(DefineConstants);CoreCLR diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index a20809070..1a0b3798c 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -2,7 +2,7 @@ - net6.0;netcoreapp3.1 + net7.0 false diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 9a32254b9..1b60e09a4 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -2,7 +2,7 @@ - net7.0;net6.0;netcoreapp3.1;net462 + net7.0;net6.0;net462 Microsoft.PowerShell.EditorServices.Test x64 @@ -17,16 +17,11 @@ - + - - - - - From 5af67285da5106202da34a0fd445f329eaa4df76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 14:28:52 -0800 Subject: [PATCH 131/327] Bump Microsoft.Extensions.Logging from 6.0.0 to 7.0.0 (#1952) Bumps [Microsoft.Extensions.Logging](https://github.com/dotnet/runtime) from 6.0.0 to 7.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v6.0.0...v7.0.0) --- updated-dependencies: - dependency-name: Microsoft.Extensions.Logging dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 2 +- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 30831daf5..a7f09ff2e 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -27,7 +27,7 @@ - + diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 1a0b3798c..8d4fb50c2 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -7,7 +7,7 @@ - + From e7738e2247d0ae55fe21b450df57f383823277dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 22:42:50 +0000 Subject: [PATCH 132/327] Bump Microsoft.Extensions.FileSystemGlobbing from 6.0.0 to 7.0.0 (#1950) Bumps [Microsoft.Extensions.FileSystemGlobbing](https://github.com/dotnet/runtime) from 6.0.0 to 7.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v6.0.0...v7.0.0) --- updated-dependencies: - dependency-name: Microsoft.Extensions.FileSystemGlobbing dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index a7f09ff2e..9eefce9de 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -26,7 +26,7 @@ - + From ac36aaa8849fb9710d16f479f6b84a70867d5d19 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 6 Dec 2022 16:00:23 -0800 Subject: [PATCH 133/327] Update CHANGELOG for `v3.7.0` --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f17f0ac8..f9abcdcf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # PowerShell Editor Services Release History +## v3.7.0 +### Tuesday, December 06, 2022 + +- ✨ 👷 [PowerShellEditorServices #1879](https://github.com/PowerShell/PowerShellEditorServices/pull/1967) - Drop support for end-of-life PowerShell 7.0. +- ✨ 🛫 [PowerShellEditorServices #1965](https://github.com/PowerShell/PowerShellEditorServices/pull/1965) - Send `GitCommitId` over `GetVersionHandler`. +- 🐛 🛫 [PowerShellEditorServices #1964](https://github.com/PowerShell/PowerShellEditorServices/pull/1964) - Remove unnecessary `PowerShellProcessArchitecture`. +- 🐛 🚂 [PowerShellEditorServices #1953](https://github.com/PowerShell/PowerShellEditorServices/pull/1953) - Fix `IsExternalInit` bug, re-enable tests, and update OmniSharp to v0.19.7. + ## v3.6.3 ### Tuesday, November 29, 2022 From 07a42148a00b32762e266ed8afb8450a852d0147 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 6 Dec 2022 16:00:23 -0800 Subject: [PATCH 134/327] Bump version to `v3.7.0` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 847ed6576..b9037f765 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.6.3 + 3.7.0 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 2cd88bf78..012556e69 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.6.3' +ModuleVersion = '3.7.0' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From e28d8df4d96bebfab51a3efa778b019c9a4dd37f Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 9 Dec 2022 11:39:54 -0800 Subject: [PATCH 135/327] Fix `ShowHelpHandler` by running with `RequiresForeground` (#1970) This was broken after the rewrite because we'd forgotten to specify that it needs the foreground (which implies too that it must run immediately). Now the script actually executes, either showing the help in the Extension Terminal (or...I guess nowhere for clients without it) or opening the URL it found. This should still be rewritten properly to return the help over LSP and do something better with it, but at least it's no longer broken. --- .../Services/PowerShell/Handlers/ShowHelpHandler.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs index d2f84b01b..ea18de73c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs @@ -67,7 +67,15 @@ public async Task Handle(ShowHelpParams request, CancellationToken cancell // TODO: Rather than print the help in the console, we should send the string back // to VSCode to display in a help pop-up (or similar) - await _executionService.ExecutePSCommandAsync(checkHelpPSCommand, cancellationToken, new PowerShellExecutionOptions { WriteOutputToHost = true, ThrowOnError = false }).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync( + checkHelpPSCommand, + cancellationToken, + new PowerShellExecutionOptions + { + RequiresForeground = true, + WriteOutputToHost = true, + ThrowOnError = false + }).ConfigureAwait(false); return Unit.Value; } } From 839a252a5cf76ca617929715e8308419bbd718ad Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 12 Dec 2022 11:21:31 -0800 Subject: [PATCH 136/327] Update CHANGELOG for `v3.7.1` --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9abcdcf2..1013ac24a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # PowerShell Editor Services Release History +## v3.7.1 +### Monday, December 12, 2022 + +- 🐛 🚂 [vscode-powershell #4212](https://github.com/PowerShell/PowerShellEditorServices/pull/1970) - Fix `ShowHelpHandler` by running with `RequiresForeground`. + ## v3.7.0 ### Tuesday, December 06, 2022 From 48ee3a2e3212dffd0116fed5ffede4d42b939f96 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 12 Dec 2022 11:21:32 -0800 Subject: [PATCH 137/327] Bump version to `v3.7.1` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index b9037f765..08852f3f6 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.7.0 + 3.7.1 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 012556e69..7b5af0d63 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.7.0' +ModuleVersion = '3.7.1' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From ca83e0e001266a86091690476865d4516f960d86 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Wed, 14 Dec 2022 16:37:18 -0800 Subject: [PATCH 138/327] Update `Microsoft.PowerShell.SDK` with workaround (#1973) --- .../PowerShellEditorServices.Test.csproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 1b60e09a4..1fcd8250d 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -14,7 +14,9 @@ - + + + From 3845d5fd3cc3ab3d1d8409e27228b9e8656dac79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 14:06:22 -0800 Subject: [PATCH 139/327] Bump Microsoft.NET.Test.Sdk from 17.4.0 to 17.4.1 (#1974) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.4.0 to 17.4.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.4.0...v17.4.1) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 8d4fb50c2..044051e74 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 1fcd8250d..3e5c8fbee 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -34,7 +34,7 @@ - + From 243f2ea4de5cf86d4025b08216cb13e98d7b52da Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 16 Dec 2022 16:51:36 -0800 Subject: [PATCH 140/327] Small suggested cleanups while browsing --- .../Commands/StartEditorServicesCommand.cs | 5 +++-- .../DebugAdapter/Debugging/VariableDetails.cs | 15 ++++----------- .../Debugging/VariableDetailsBase.cs | 1 - .../Handlers/DebugEvaluateHandler.cs | 16 +++++----------- .../Debugging/PowerShellDebugContext.cs | 2 +- .../Debugging/DebugServiceTests.cs | 8 ++++---- .../Session/PsesInternalHostTests.cs | 6 +++--- 7 files changed, 20 insertions(+), 33 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 51e2d1b26..743b6aac9 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -15,7 +15,6 @@ #if DEBUG using System.Diagnostics; using System.Threading; - using Debugger = System.Diagnostics.Debugger; #endif @@ -197,9 +196,11 @@ protected override void BeginProcessing() #if DEBUG if (WaitForDebugger) { + // NOTE: Ignore the suggestion to use Environment.ProcessId as it doesn't work for + // .NET 4.6.2 (for Windows PowerShell), and this won't be caught in CI. + Console.WriteLine($"Waiting for debugger to attach, PID: {Process.GetCurrentProcess().Id}"); while (!Debugger.IsAttached) { - Console.WriteLine($"PID: {Process.GetCurrentProcess().Id}"); Thread.Sleep(1000); } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index 96a6b2b99..df8d93e83 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -98,16 +98,11 @@ public VariableDetails(string name, object value) /// If this variable instance is expandable, this method returns the /// details of its children. Otherwise it returns an empty array. /// - /// public override VariableDetailsBase[] GetChildren(ILogger logger) { if (IsExpandable) { - if (cachedChildren == null) - { - cachedChildren = GetChildren(ValueObject, logger); - } - + cachedChildren ??= GetChildren(ValueObject, logger); return cachedChildren; } @@ -131,9 +126,7 @@ private static bool GetIsExpandable(object valueObject) valueObject = psobject.BaseObject; } - Type valueType = - valueObject?.GetType(); - + Type valueType = valueObject?.GetType(); TypeInfo valueTypeInfo = valueType.GetTypeInfo(); return @@ -379,7 +372,7 @@ protected static void AddDotNetProperties(object obj, List chil #endregion - private struct UnableToRetrievePropertyMessage + private readonly struct UnableToRetrievePropertyMessage { public UnableToRetrievePropertyMessage(string message) => Message = message; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs index 51e613f2c..ea4f14a56 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs @@ -51,7 +51,6 @@ internal abstract class VariableDetailsBase /// If this variable instance is expandable, this method returns the /// details of its children. Otherwise it returns an empty array. /// - /// public abstract VariableDetailsBase[] GetChildren(ILogger logger); } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs index 056f2f778..23c6ee0b4 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -64,21 +64,15 @@ await _executionService.ExecutePSCommandAsync( result = _debugService.GetVariableFromExpression(request.Expression); // If the expression is not a naked variable reference, then evaluate the expression. - if (result == null) - { - result = - await _debugService.EvaluateExpressionAsync( - request.Expression, - isFromRepl).ConfigureAwait(false); - } + result ??= await _debugService.EvaluateExpressionAsync( + request.Expression, + isFromRepl).ConfigureAwait(false); } if (result != null) { valueString = result.ValueString; - variableId = - result.IsExpandable ? - result.Id : 0; + variableId = result.IsExpandable ? result.Id : 0; } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 236403e5a..1d45c435d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -146,7 +146,7 @@ public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction) // TODO: We need to assign cancellation tokens to each frame, because the current // logic results in a deadlock here when we try to cancel the scopes...which // includes ourself (hence running it in a separate thread). - _ = Task.Run(() => _psesHost.UnwindCallStack()); + _ = Task.Run(_psesHost.UnwindCallStack); return; } diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 9362772d2..f80b21eb9 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -496,7 +496,7 @@ public async Task DebuggerBreaksWhenRequested() Assert.Equal(0, confirmedBreakpoints.Count); Task _ = ExecuteDebugFileAsync(); // NOTE: This must be run on a separate thread so the async event handlers can fire. - await Task.Run(() => debugService.Break()).ConfigureAwait(true); + await Task.Run(debugService.Break).ConfigureAwait(true); AssertDebuggerPaused(); } @@ -505,7 +505,7 @@ public async Task DebuggerRunsCommandsWhileStopped() { Task _ = ExecuteDebugFileAsync(); // NOTE: This must be run on a separate thread so the async event handlers can fire. - await Task.Run(() => debugService.Break()).ConfigureAwait(true); + await Task.Run(debugService.Break).ConfigureAwait(true); AssertDebuggerPaused(); // Try running a command from outside the pipeline thread @@ -723,7 +723,7 @@ await debugService.SetLineBreakpointsAsync( // The above just tests that the debug service returns the correct new value string. // Let's step the debugger and make sure the values got set to the new values. - await Task.Run(() => debugService.StepOver()).ConfigureAwait(true); + await Task.Run(debugService.StepOver).ConfigureAwait(true); AssertDebuggerStopped(variableScriptFile.FilePath); // Test set of a local string variable (not strongly typed) @@ -779,7 +779,7 @@ await debugService.SetLineBreakpointsAsync( // The above just tests that the debug service returns the correct new value string. // Let's step the debugger and make sure the values got set to the new values. - await Task.Run(() => debugService.StepOver()).ConfigureAwait(true); + await Task.Run(debugService.StepOver).ConfigureAwait(true); AssertDebuggerStopped(variableScriptFile.FilePath); // Test set of a local string variable (not strongly typed but force conversion) diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index d1fefad79..76f303681 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -180,7 +180,7 @@ public async Task CanRunOnIdleTask() new PSCommand().AddScript("$handled"), CancellationToken.None).ConfigureAwait(true); - Assert.Collection(handled, (p) => Assert.False(p)); + Assert.Collection(handled, Assert.False); await psesHost.ExecuteDelegateAsync( nameof(psesHost.OnPowerShellIdle), @@ -195,7 +195,7 @@ await psesHost.ExecuteDelegateAsync( new PSCommand().AddScript("$handled"), CancellationToken.None).ConfigureAwait(true); - Assert.Collection(handled, (p) => Assert.True(p)); + Assert.Collection(handled, Assert.True); } [Fact] @@ -301,7 +301,7 @@ await psesHost.ExecuteDelegateAsync( new PSCommand().AddScript("$handledInProfile"), CancellationToken.None).ConfigureAwait(true); - Assert.Collection(handled, (p) => Assert.True(p)); + Assert.Collection(handled, Assert.True); } } } From e438e766977517437846f26f72a6d231eafc5d6e Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 16 Dec 2022 17:34:36 -0800 Subject: [PATCH 141/327] Expand variables on pipeline thread So that expansions of embedded PowerShell code succeed. --- .../Services/DebugAdapter/DebugService.cs | 25 ++-- .../DebugAdapter/Debugging/VariableDetails.cs | 132 +++++++++--------- .../Handlers/DebugEvaluateHandler.cs | 9 +- .../DebugAdapter/Handlers/VariablesHandler.cs | 8 +- .../Debugging/DebugServiceTests.cs | 95 ++++++------- 5 files changed, 137 insertions(+), 132 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 0002245cb..4c9ef8735 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -241,11 +241,12 @@ public async Task SetCommandBreakpointsAsync( /// that is identified by the given referenced ID. /// /// + /// /// An array of VariableDetails instances which describe the requested variables. - public VariableDetailsBase[] GetVariables(int variableReferenceId) + public async Task GetVariables(int variableReferenceId, CancellationToken cancellationToken) { VariableDetailsBase[] childVariables; - debugInfoHandle.Wait(); + await debugInfoHandle.WaitAsync(cancellationToken).ConfigureAwait(false); try { if ((variableReferenceId < 0) || (variableReferenceId >= variables.Count)) @@ -257,7 +258,12 @@ public VariableDetailsBase[] GetVariables(int variableReferenceId) VariableDetailsBase parentVariable = variables[variableReferenceId]; if (parentVariable.IsExpandable) { - childVariables = parentVariable.GetChildren(_logger); + // We execute this on the pipeline thread so the expansion of child variables works. + childVariables = await _executionService.ExecuteDelegateAsync( + $"Getting children of variable ${parentVariable.Name}", + new ExecutionOptions { Priority = ExecutionPriority.Next }, + (_, _) => parentVariable.GetChildren(_logger), cancellationToken).ConfigureAwait(false); + foreach (VariableDetailsBase child in childVariables) { // Only add child if it hasn't already been added. @@ -287,8 +293,9 @@ public VariableDetailsBase[] GetVariables(int variableReferenceId) /// walk the cached variable data for the specified stack frame. /// /// The variable expression string to evaluate. + /// /// A VariableDetailsBase object containing the result. - public VariableDetailsBase GetVariableFromExpression(string variableExpression) + public async Task GetVariableFromExpression(string variableExpression, CancellationToken cancellationToken) { // NOTE: From a watch we will get passed expressions that are not naked variables references. // Probably the right way to do this would be to examine the AST of the expr before calling @@ -302,7 +309,7 @@ public VariableDetailsBase GetVariableFromExpression(string variableExpression) IEnumerable variableList; // Ensure debug info isn't currently being built. - debugInfoHandle.Wait(); + await debugInfoHandle.WaitAsync(cancellationToken).ConfigureAwait(false); try { variableList = variables; @@ -331,7 +338,7 @@ public VariableDetailsBase GetVariableFromExpression(string variableExpression) if (resolvedVariable?.IsExpandable == true) { // Continue by searching in this variable's children. - variableList = GetVariables(resolvedVariable.Id); + variableList = await GetVariables(resolvedVariable.Id, cancellationToken).ConfigureAwait(false); } } @@ -491,10 +498,12 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str /// /// If true, writes the expression result as host output rather than returning the results. /// In this case, the return value of this function will be null. + /// /// A VariableDetails object containing the result. public async Task EvaluateExpressionAsync( string expressionString, - bool writeResultAsOutput) + bool writeResultAsOutput, + CancellationToken cancellationToken) { PSCommand command = new PSCommand().AddScript(expressionString); IReadOnlyList results; @@ -502,7 +511,7 @@ public async Task EvaluateExpressionAsync( { results = await _executionService.ExecutePSCommandAsync( command, - CancellationToken.None, + cancellationToken, new PowerShellExecutionOptions { WriteOutputToHost = writeResultAsOutput, ThrowOnError = !writeResultAsOutput }).ConfigureAwait(false); } catch (Exception e) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index df8d93e83..457c43c7c 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -229,7 +229,7 @@ private static string InsertDimensionSize(string value, int dimensionSize) return value + ": " + dimensionSize; } - private VariableDetails[] GetChildren(object obj, ILogger logger) + private static VariableDetails[] GetChildren(object obj, ILogger logger) { List childVariables = new(); @@ -238,86 +238,82 @@ private VariableDetails[] GetChildren(object obj, ILogger logger) return childVariables.ToArray(); } - try - { - PSObject psObject = obj as PSObject; + // NOTE: Variable expansion now takes place on the pipeline thread as an async delegate, + // so expansion of children that cause PowerShell script code to execute should + // generally work. However, we might need more error handling. + PSObject psObject = obj as PSObject; - if ((psObject != null) && - (psObject.TypeNames[0] == typeof(PSCustomObject).ToString())) + if ((psObject != null) && + (psObject.TypeNames[0] == typeof(PSCustomObject).ToString())) + { + // PowerShell PSCustomObject's properties are completely defined by the ETS type system. + logger.LogDebug("PSObject was a PSCustomObject"); + childVariables.AddRange( + psObject + .Properties + .Select(p => new VariableDetails(p))); + } + else + { + // If a PSObject other than a PSCustomObject, unwrap it. + if (psObject != null) { - // PowerShell PSCustomObject's properties are completely defined by the ETS type system. + // First add the PSObject's ETS properties + logger.LogDebug("PSObject was something else, first getting ETS properties"); childVariables.AddRange( psObject .Properties + // Here we check the object's MemberType against the `Properties` + // bit-mask to determine if this is a property. Hence the selection + // will only include properties. + .Where(p => (PSMemberTypes.Properties & p.MemberType) is not 0) .Select(p => new VariableDetails(p))); + + obj = psObject.BaseObject; } - else + + // We're in the realm of regular, unwrapped .NET objects + if (obj is IDictionary dictionary) { - // If a PSObject other than a PSCustomObject, unwrap it. - if (psObject != null) + logger.LogDebug("PSObject was an IDictionary"); + // Buckle up kids, this is a bit weird. We could not use the LINQ + // operator OfType. Even though R# will squiggle the + // "foreach" keyword below and offer to convert to a LINQ-expression - DON'T DO IT! + // The reason is that LINQ extension methods work with objects of type + // IEnumerable. Objects of type Dictionary<,>, respond to iteration via + // IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic + // dictionaries like HashTable return DictionaryEntry objects. + // It turns out that iteration via C#'s foreach loop, operates on the variable's + // type which in this case is IDictionary. IDictionary was designed to always + // return DictionaryEntry objects upon iteration and the Dictionary<,> implementation + // honors that when the object is reinterpreted as an IDictionary object. + // FYI, a test case for this is to open $PSBoundParameters when debugging a + // function that defines parameters and has been passed parameters. + // If you open the $PSBoundParameters variable node in this scenario and see nothing, + // this code is broken. + foreach (DictionaryEntry entry in dictionary) { - // First add the PSObject's ETS properties - childVariables.AddRange( - psObject - .Properties - // Here we check the object's MemberType against the `Properties` - // bit-mask to determine if this is a property. Hence the selection - // will only include properties. - .Where(p => (PSMemberTypes.Properties & p.MemberType) is not 0) - .Select(p => new VariableDetails(p))); - - obj = psObject.BaseObject; + childVariables.Add( + new VariableDetails( + "[" + entry.Key + "]", + entry)); } - - // We're in the realm of regular, unwrapped .NET objects - if (obj is IDictionary dictionary) - { - // Buckle up kids, this is a bit weird. We could not use the LINQ - // operator OfType. Even though R# will squiggle the - // "foreach" keyword below and offer to convert to a LINQ-expression - DON'T DO IT! - // The reason is that LINQ extension methods work with objects of type - // IEnumerable. Objects of type Dictionary<,>, respond to iteration via - // IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic - // dictionaries like HashTable return DictionaryEntry objects. - // It turns out that iteration via C#'s foreach loop, operates on the variable's - // type which in this case is IDictionary. IDictionary was designed to always - // return DictionaryEntry objects upon iteration and the Dictionary<,> implementation - // honors that when the object is reinterpreted as an IDictionary object. - // FYI, a test case for this is to open $PSBoundParameters when debugging a - // function that defines parameters and has been passed parameters. - // If you open the $PSBoundParameters variable node in this scenario and see nothing, - // this code is broken. - foreach (DictionaryEntry entry in dictionary) - { - childVariables.Add( - new VariableDetails( - "[" + entry.Key + "]", - entry)); - } - } - else if (obj is IEnumerable enumerable and not string) + } + else if (obj is IEnumerable enumerable and not string) + { + logger.LogDebug("PSObject was an IEnumerable"); + int i = 0; + foreach (object item in enumerable) { - int i = 0; - foreach (object item in enumerable) - { - childVariables.Add( - new VariableDetails( - "[" + i++ + "]", - item)); - } + childVariables.Add( + new VariableDetails( + "[" + i++ + "]", + item)); } - - AddDotNetProperties(obj, childVariables); } - } - catch (GetValueInvocationException ex) - { - // This exception occurs when accessing the value of a - // variable causes a script to be executed. Right now - // we aren't loading children on the pipeline thread so - // this causes an exception to be raised. In this case, - // just return an empty list of children. - logger.LogWarning($"Failed to get properties of variable {Name}, value invocation was attempted: {ex.Message}"); + + logger.LogDebug("Adding .NET properties to PSObject"); + AddDotNetProperties(obj, childVariables); } return childVariables.ToArray(); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs index 23c6ee0b4..2c6c47364 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -49,7 +49,7 @@ public async Task Handle(EvaluateRequestArguments request, { await _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), - CancellationToken.None, + cancellationToken, new PowerShellExecutionOptions { WriteOutputToHost = true, ThrowOnError = false, AddToHistory = true }).HandleErrorsAsync(_logger).ConfigureAwait(false); } else @@ -61,12 +61,13 @@ await _executionService.ExecutePSCommandAsync( if (_debugContext.IsStopped) { // First check to see if the watch expression refers to a naked variable reference. - result = _debugService.GetVariableFromExpression(request.Expression); + result = await _debugService.GetVariableFromExpression(request.Expression, cancellationToken).ConfigureAwait(false); // If the expression is not a naked variable reference, then evaluate the expression. result ??= await _debugService.EvaluateExpressionAsync( request.Expression, - isFromRepl).ConfigureAwait(false); + isFromRepl, + cancellationToken).ConfigureAwait(false); } if (result != null) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs index a4f5259ed..58fb4d5ef 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs @@ -18,11 +18,9 @@ internal class VariablesHandler : IVariablesHandler public VariablesHandler(DebugService debugService) => _debugService = debugService; - public Task Handle(VariablesArguments request, CancellationToken cancellationToken) + public async Task Handle(VariablesArguments request, CancellationToken cancellationToken) { - VariableDetailsBase[] variables = - _debugService.GetVariables( - (int)request.VariablesReference); + VariableDetailsBase[] variables = await _debugService.GetVariables((int)request.VariablesReference, cancellationToken).ConfigureAwait(false); VariablesResponse variablesResponse = null; @@ -41,7 +39,7 @@ public Task Handle(VariablesArguments request, CancellationTo // TODO: This shouldn't be so broad } - return Task.FromResult(variablesResponse); + return variablesResponse; } } } diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index f80b21eb9..1db6fe890 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -99,12 +99,12 @@ public void Dispose() private ScriptFile GetDebugScript(string fileName) => workspace.GetFile(TestUtilities.GetSharedPath(Path.Combine("Debugging", fileName))); - private VariableDetailsBase[] GetVariables(string scopeName) + private Task GetVariables(string scopeName) { VariableScope scope = Array.Find( debugService.GetVariableScopes(0), s => s.Name == scopeName); - return debugService.GetVariables(scope.Id); + return debugService.GetVariables(scope.Id, CancellationToken.None); } private Task ExecuteScriptFileAsync(string scriptFilePath, params string[] args) @@ -185,7 +185,7 @@ await debugService.SetCommandBreakpointsAsync( // NOTE: This assertion will fail if any error occurs. Notably this happens in testing // when the assembly path changes and the commands definition file can't be found. - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.GlobalScopeName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.GlobalScopeName).ConfigureAwait(true); VariableDetailsBase var = Array.Find(variables, v => v.Name == "$Error"); Assert.NotNull(var); Assert.True(var.IsExpandable); @@ -220,7 +220,7 @@ public async Task DebuggerAcceptsScriptArgs(string[] args) AssertDebuggerStopped(oddPathScriptFile.FilePath, 3); - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); VariableDetailsBase var = Array.Find(variables, v => v.Name == "$Param1"); Assert.NotNull(var); @@ -231,7 +231,7 @@ public async Task DebuggerAcceptsScriptArgs(string[] args) Assert.NotNull(var); Assert.True(var.IsExpandable); - VariableDetailsBase[] childVars = debugService.GetVariables(var.Id); + VariableDetailsBase[] childVars = await debugService.GetVariables(var.Id, CancellationToken.None).ConfigureAwait(true); // 2 variables plus "Raw View" Assert.Equal(3, childVars.Length); Assert.Equal("\"Bar\"", childVars[0].ValueString); @@ -244,12 +244,12 @@ public async Task DebuggerAcceptsScriptArgs(string[] args) // NOTE: $args are no longer found in AutoVariables but CommandVariables instead. StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); - variables = debugService.GetVariables(stackFrames[0].CommandVariables.Id); + variables = await debugService.GetVariables(stackFrames[0].CommandVariables.Id, CancellationToken.None).ConfigureAwait(true); var = Array.Find(variables, v => v.Name == "$args"); Assert.NotNull(var); Assert.True(var.IsExpandable); - childVars = debugService.GetVariables(var.Id); + childVars = await debugService.GetVariables(var.Id, CancellationToken.None).ConfigureAwait(true); Assert.Equal(2, childVars.Length); Assert.Equal("\"Extra1\"", childVars[0].ValueString); } @@ -287,7 +287,7 @@ public async Task DebuggerStopsOnFunctionBreakpoints() Task _ = ExecuteDebugFileAsync(); AssertDebuggerStopped(debugScriptFile.FilePath, 6); - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); // Verify the function breakpoint broke at Write-Host and $i is 1 VariableDetailsBase i = Array.Find(variables, v => v.Name == "$i"); @@ -299,7 +299,7 @@ public async Task DebuggerStopsOnFunctionBreakpoints() debugService.Continue(); AssertDebuggerStopped(debugScriptFile.FilePath, 6); - variables = GetVariables(VariableContainerDetails.LocalScopeName); + variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); // Verify the function breakpoint broke at Write-Host and $i is 1 i = Array.Find(variables, v => v.Name == "$i"); @@ -372,7 +372,7 @@ await debugService.SetLineBreakpointsAsync( Task _ = ExecuteDebugFileAsync(); AssertDebuggerStopped(debugScriptFile.FilePath, 7); - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); // Verify the breakpoint only broke at the condition ie. $i -eq breakpointValue1 VariableDetailsBase i = Array.Find(variables, v => v.Name == "$i"); @@ -385,7 +385,7 @@ await debugService.SetLineBreakpointsAsync( debugService.Continue(); AssertDebuggerStopped(debugScriptFile.FilePath, 7); - variables = GetVariables(VariableContainerDetails.LocalScopeName); + variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); // Verify the breakpoint only broke at the condition ie. $i -eq breakpointValue1 i = Array.Find(variables, v => v.Name == "$i"); @@ -408,7 +408,7 @@ await debugService.SetLineBreakpointsAsync( Task _ = ExecuteDebugFileAsync(); AssertDebuggerStopped(debugScriptFile.FilePath, 6); - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); // Verify the breakpoint only broke at the condition ie. $i -eq breakpointValue1 VariableDetailsBase i = Array.Find(variables, v => v.Name == "$i"); @@ -429,7 +429,7 @@ await debugService.SetLineBreakpointsAsync( Task _ = ExecuteDebugFileAsync(); AssertDebuggerStopped(debugScriptFile.FilePath, 6); - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); // Verify the breakpoint only broke at the condition ie. $i -eq breakpointValue1 VariableDetailsBase i = Array.Find(variables, v => v.Name == "$i"); @@ -528,7 +528,7 @@ await debugService.SetCommandBreakpointsAsync( Task _ = ExecuteScriptFileAsync(testScript.FilePath); AssertDebuggerStopped(testScript.FilePath, 11); - VariableDetails prompt = await debugService.EvaluateExpressionAsync("prompt", false).ConfigureAwait(true); + VariableDetails prompt = await debugService.EvaluateExpressionAsync("prompt", false, CancellationToken.None).ConfigureAwait(true); Assert.Equal("\"True > \"", prompt.ValueString); } @@ -553,7 +553,7 @@ await debugService.SetCommandBreakpointsAsync( Task _ = configurationDoneHandler.LaunchScriptAsync(scriptPath); AssertDebuggerStopped(scriptPath, 1); - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.CommandVariablesName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.CommandVariablesName).ConfigureAwait(true); VariableDetailsBase myInvocation = Array.Find(variables, v => v.Name == "$MyInvocation"); Assert.NotNull(myInvocation); Assert.True(myInvocation.IsExpandable); @@ -561,7 +561,7 @@ await debugService.SetCommandBreakpointsAsync( // Here we're asserting that our hacky workaround to support breakpoints in untitled // scripts is working, namely that we're actually dot-sourcing our first argument, which // should be a cached script block. See the `LaunchScriptAsync` for more info. - VariableDetailsBase[] myInvocationChildren = debugService.GetVariables(myInvocation.Id); + VariableDetailsBase[] myInvocationChildren = await debugService.GetVariables(myInvocation.Id, CancellationToken.None).ConfigureAwait(true); VariableDetailsBase myInvocationLine = Array.Find(myInvocationChildren, v => v.Name == "Line"); Assert.Equal("\". $args[0]\"", myInvocationLine.ValueString); } @@ -631,7 +631,7 @@ await debugService.SetLineBreakpointsAsync( Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); VariableDetailsBase var = Array.Find(variables, v => v.Name == "$strVar"); Assert.NotNull(var); @@ -649,7 +649,7 @@ await debugService.SetLineBreakpointsAsync( Task _ = ExecuteVariableScriptFileAsync(); AssertDebuggerStopped(variableScriptFile.FilePath); - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); // TODO: Add checks for correct value strings as well VariableDetailsBase strVar = Array.Find(variables, v => v.Name == "$strVar"); @@ -660,7 +660,7 @@ await debugService.SetLineBreakpointsAsync( Assert.NotNull(objVar); Assert.True(objVar.IsExpandable); - VariableDetailsBase[] objChildren = debugService.GetVariables(objVar.Id); + VariableDetailsBase[] objChildren = await debugService.GetVariables(objVar.Id, CancellationToken.None).ConfigureAwait(true); // Two variables plus "Raw View" Assert.Equal(3, objChildren.Length); @@ -668,14 +668,14 @@ await debugService.SetLineBreakpointsAsync( Assert.NotNull(arrVar); Assert.True(arrVar.IsExpandable); - VariableDetailsBase[] arrChildren = debugService.GetVariables(arrVar.Id); + VariableDetailsBase[] arrChildren = await debugService.GetVariables(arrVar.Id, CancellationToken.None).ConfigureAwait(true); Assert.Equal(5, arrChildren.Length); VariableDetailsBase classVar = Array.Find(variables, v => v.Name == "$classVar"); Assert.NotNull(classVar); Assert.True(classVar.IsExpandable); - VariableDetailsBase[] classChildren = debugService.GetVariables(classVar.Id); + VariableDetailsBase[] classChildren = await debugService.GetVariables(classVar.Id, CancellationToken.None).ConfigureAwait(true); Assert.Equal(2, classChildren.Length); VariableDetailsBase trueVar = Array.Find(variables, v => v.Name == "$trueVar"); @@ -700,7 +700,7 @@ await debugService.SetLineBreakpointsAsync( AssertDebuggerStopped(variableScriptFile.FilePath); VariableScope[] scopes = debugService.GetVariableScopes(0); - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); // Test set of a local string variable (not strongly typed) const string newStrValue = "\"Goodbye\""; @@ -727,17 +727,17 @@ await debugService.SetLineBreakpointsAsync( AssertDebuggerStopped(variableScriptFile.FilePath); // Test set of a local string variable (not strongly typed) - variables = GetVariables(VariableContainerDetails.LocalScopeName); + variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); VariableDetailsBase strVar = Array.Find(variables, v => v.Name == "$strVar"); Assert.Equal(newStrValue, strVar.ValueString); // Test set of script scope int variable (not strongly typed) - variables = GetVariables(VariableContainerDetails.ScriptScopeName); + variables = await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true); VariableDetailsBase intVar = Array.Find(variables, v => v.Name == "$scriptInt"); Assert.Equal(newIntValue, intVar.ValueString); // Test set of global scope int variable (not strongly typed) - variables = GetVariables(VariableContainerDetails.GlobalScopeName); + variables = await GetVariables(VariableContainerDetails.GlobalScopeName).ConfigureAwait(true); VariableDetailsBase intGlobalVar = Array.Find(variables, v => v.Name == "$MaximumHistoryCount"); Assert.Equal(newGlobalIntValue, intGlobalVar.ValueString); } @@ -754,7 +754,7 @@ await debugService.SetLineBreakpointsAsync( AssertDebuggerStopped(variableScriptFile.FilePath); VariableScope[] scopes = debugService.GetVariableScopes(0); - VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName); + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); // Test set of a local string variable (not strongly typed but force conversion) const string newStrValue = "\"False\""; @@ -783,17 +783,17 @@ await debugService.SetLineBreakpointsAsync( AssertDebuggerStopped(variableScriptFile.FilePath); // Test set of a local string variable (not strongly typed but force conversion) - variables = GetVariables(VariableContainerDetails.LocalScopeName); + variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); VariableDetailsBase strVar = Array.Find(variables, v => v.Name == "$strVar2"); Assert.Equal(newStrValue, strVar.ValueString); // Test set of script scope bool variable (strongly typed) - variables = GetVariables(VariableContainerDetails.ScriptScopeName); + variables = await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true); VariableDetailsBase boolVar = Array.Find(variables, v => v.Name == "$scriptBool"); Assert.Equal(newBoolValue, boolVar.ValueString); // Test set of global scope ActionPreference variable (strongly typed) - variables = GetVariables(VariableContainerDetails.GlobalScopeName); + variables = await GetVariables(VariableContainerDetails.GlobalScopeName).ConfigureAwait(true); VariableDetailsBase globalVar = Array.Find(variables, v => v.Name == "$VerbosePreference"); Assert.Equal(newGlobalValue, globalVar.ValueString); } @@ -810,7 +810,7 @@ await debugService.SetLineBreakpointsAsync( AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); - VariableDetailsBase[] variables = debugService.GetVariables(stackFrames[0].AutoVariables.Id); + VariableDetailsBase[] variables = await debugService.GetVariables(stackFrames[0].AutoVariables.Id, CancellationToken.None).ConfigureAwait(true); VariableDetailsBase var = Array.Find(variables, v => v.Name == "$enumVar"); Assert.NotNull(var); @@ -830,14 +830,14 @@ await debugService.SetLineBreakpointsAsync( AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); - VariableDetailsBase[] variables = debugService.GetVariables(stackFrames[0].AutoVariables.Id); + VariableDetailsBase[] variables = await debugService.GetVariables(stackFrames[0].AutoVariables.Id, CancellationToken.None).ConfigureAwait(true); VariableDetailsBase var = Array.Find(variables, v => v.Name == "$assocArrVar"); Assert.NotNull(var); Assert.Equal("[Hashtable: 2]", var.ValueString); Assert.True(var.IsExpandable); - VariableDetailsBase[] childVars = debugService.GetVariables(var.Id); + VariableDetailsBase[] childVars = await debugService.GetVariables(var.Id, CancellationToken.None).ConfigureAwait(true); // 2 variables plus "Raw View" Assert.Equal(3, childVars.Length); @@ -863,7 +863,7 @@ await debugService.SetLineBreakpointsAsync( AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); - VariableDetailsBase[] variables = debugService.GetVariables(stackFrames[0].AutoVariables.Id); + VariableDetailsBase[] variables = await debugService.GetVariables(stackFrames[0].AutoVariables.Id, CancellationToken.None).ConfigureAwait(true); VariableDetailsBase nullStringVar = Array.Find(variables, v => v.Name == "$nullString"); Assert.NotNull(nullStringVar); @@ -883,19 +883,20 @@ await debugService.SetLineBreakpointsAsync( AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); - VariableDetailsBase[] variables = debugService.GetVariables(stackFrames[0].AutoVariables.Id); + VariableDetailsBase[] variables = await debugService.GetVariables(stackFrames[0].AutoVariables.Id, CancellationToken.None).ConfigureAwait(true); VariableDetailsBase psObjVar = Array.Find(variables, v => v.Name == "$psObjVar"); Assert.NotNull(psObjVar); Assert.True("@{Age=75; Name=John}".Equals(psObjVar.ValueString) || "@{Name=John; Age=75}".Equals(psObjVar.ValueString)); Assert.True(psObjVar.IsExpandable); - IDictionary childVars = debugService.GetVariables(psObjVar.Id).ToDictionary(v => v.Name, v => v.ValueString); - Assert.Equal(2, childVars.Count); - Assert.Contains("Age", childVars.Keys); - Assert.Contains("Name", childVars.Keys); - Assert.Equal("75", childVars["Age"]); - Assert.Equal("\"John\"", childVars["Name"]); + VariableDetailsBase[] childVars = await debugService.GetVariables(psObjVar.Id, CancellationToken.None).ConfigureAwait(true); + IDictionary dictionary = childVars.ToDictionary(v => v.Name, v => v.ValueString); + Assert.Equal(2, dictionary.Count); + Assert.Contains("Age", dictionary.Keys); + Assert.Contains("Name", dictionary.Keys); + Assert.Equal("75", dictionary["Age"]); + Assert.Equal("\"John\"", dictionary["Name"]); } [Fact] @@ -909,7 +910,7 @@ public async Task DebuggerEnumerableShowsRawView() AssertDebuggerStopped(commandBreakpointDetails: breakpoint); VariableDetailsBase simpleArrayVar = Array.Find( - GetVariables(VariableContainerDetails.ScriptScopeName), + await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true), v => v.Name == "$simpleArray"); Assert.NotNull(simpleArrayVar); VariableDetailsBase rawDetailsView = Array.Find( @@ -946,7 +947,7 @@ public async Task DebuggerDictionaryShowsRawView() AssertDebuggerStopped(commandBreakpointDetails: breakpoint); VariableDetailsBase simpleDictionaryVar = Array.Find( - GetVariables(VariableContainerDetails.ScriptScopeName), + await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true), v => v.Name == "$simpleDictionary"); Assert.NotNull(simpleDictionaryVar); VariableDetailsBase rawDetailsView = Array.Find( @@ -982,7 +983,7 @@ public async Task DebuggerDerivedDictionaryPropertyInRawView() AssertDebuggerStopped(commandBreakpointDetails: breakpoint); VariableDetailsBase sortedDictionaryVar = Array.Find( - GetVariables(VariableContainerDetails.ScriptScopeName), + await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true), v => v.Name == "$sortedDictionary"); Assert.NotNull(sortedDictionaryVar); VariableDetailsBase[] simpleDictionaryChildren = sortedDictionaryVar.GetChildren(NullLogger.Instance); @@ -1011,14 +1012,14 @@ await debugService.SetLineBreakpointsAsync( AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); - VariableDetailsBase[] variables = debugService.GetVariables(stackFrames[0].AutoVariables.Id); + VariableDetailsBase[] variables = await debugService.GetVariables(stackFrames[0].AutoVariables.Id, CancellationToken.None).ConfigureAwait(true); VariableDetailsBase var = Array.Find(variables, v => v.Name == "$psCustomObjVar"); Assert.NotNull(var); Assert.Equal("@{Name=Paul; Age=73}", var.ValueString); Assert.True(var.IsExpandable); - VariableDetailsBase[] childVars = debugService.GetVariables(var.Id); + VariableDetailsBase[] childVars = await debugService.GetVariables(var.Id, CancellationToken.None).ConfigureAwait(true); Assert.Equal(2, childVars.Length); Assert.Equal("Name", childVars[0].Name); Assert.Equal("\"Paul\"", childVars[0].ValueString); @@ -1040,14 +1041,14 @@ await debugService.SetLineBreakpointsAsync( AssertDebuggerStopped(variableScriptFile.FilePath); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); - VariableDetailsBase[] variables = debugService.GetVariables(stackFrames[0].AutoVariables.Id); + VariableDetailsBase[] variables = await debugService.GetVariables(stackFrames[0].AutoVariables.Id, CancellationToken.None).ConfigureAwait(true); VariableDetailsBase var = Array.Find(variables, v => v.Name == "$procVar"); Assert.NotNull(var); Assert.StartsWith("System.Diagnostics.Process", var.ValueString); Assert.True(var.IsExpandable); - VariableDetailsBase[] childVars = debugService.GetVariables(var.Id); + VariableDetailsBase[] childVars = await debugService.GetVariables(var.Id, CancellationToken.None).ConfigureAwait(true); Assert.Equal(53, childVars.Length); } } From 89fb98f267b9b6293d0e30b75380cbf2043b9498 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 19 Dec 2022 11:40:34 -0800 Subject: [PATCH 142/327] Guard expansion of `PSPropertyInfo.Value` Which fixes our test that gets the properties of a process object, since the value for `ExitCode` fails (as the process hasn't ended), we now handle that a bit more gracefully and can get all 130 properties. This also seemed to fix the overall bug where lots of expected properties failed to show up. While investigating, I also found many properties duplicated, seemingly due to being retrieved both off the PSObject and off the .NET object, so now when we do the latter we check (by name) that it hasn't already been added. --- .../DebugAdapter/Debugging/VariableDetails.cs | 42 +++-- .../Debugging/GetChildItemTest.ps1 | 2 + .../Debugging/DebugServiceTests.cs | 159 ++++++++++++++---- 3 files changed, 155 insertions(+), 48 deletions(-) create mode 100644 test/PowerShellEditorServices.Test.Shared/Debugging/GetChildItemTest.ps1 diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs index 457c43c7c..f67230af5 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs @@ -68,7 +68,7 @@ public VariableDetails(PSObject psVariableObject) /// The PSPropertyInfo instance from which variable details will be obtained. /// public VariableDetails(PSPropertyInfo psProperty) - : this(psProperty.Name, psProperty.Value) + : this(psProperty.Name, SafeGetValue(psProperty)) { } @@ -113,6 +113,20 @@ public override VariableDetailsBase[] GetChildren(ILogger logger) #region Private Methods + private static object SafeGetValue(PSPropertyInfo psProperty) + { + try + { + return psProperty.Value; + } + catch (GetValueInvocationException ex) + { + // Sometimes we can't get the value, like ExitCode, for reasons beyond our control, + // so just return the message from the exception that arises. + return new UnableToRetrievePropertyMessage { Name = psProperty.Name, Message = ex.Message }; + } + } + private static bool GetIsExpandable(object valueObject) { if (valueObject == null) @@ -331,9 +345,8 @@ protected static void AddDotNetProperties(object obj, List chil return; } - PropertyInfo[] properties = objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance); - - foreach (PropertyInfo property in properties) + // Search all the public instance properties and add those missing. + foreach (PropertyInfo property in objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { // Don't display indexer properties, it causes an exception anyway. if (property.GetIndexParameters().Length > 0) @@ -343,10 +356,11 @@ protected static void AddDotNetProperties(object obj, List chil try { - childVariables.Add( - new VariableDetails( - property.Name, - property.GetValue(obj))); + // Only add unique properties because we may have already added some. + if (!childVariables.Exists(p => p.Name == property.Name)) + { + childVariables.Add(new VariableDetails(property.Name, property.GetValue(obj))); + } } catch (Exception ex) { @@ -360,21 +374,19 @@ protected static void AddDotNetProperties(object obj, List chil childVariables.Add( new VariableDetails( property.Name, - new UnableToRetrievePropertyMessage( - "Error retrieving property - " + ex.GetType().Name))); + new UnableToRetrievePropertyMessage { Name = property.Name, Message = ex.Message })); } } } #endregion - private readonly struct UnableToRetrievePropertyMessage + private record UnableToRetrievePropertyMessage { - public UnableToRetrievePropertyMessage(string message) => Message = message; - - public string Message { get; } + public string Name { get; init; } + public string Message { get; init; } - public override string ToString() => "<" + Message + ">"; + public override string ToString() => $"Error retrieving property '${Name}': ${Message}"; } } diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/GetChildItemTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/GetChildItemTest.ps1 new file mode 100644 index 000000000..bab9ef52c --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Debugging/GetChildItemTest.ps1 @@ -0,0 +1,2 @@ +$file = Get-ChildItem -Path "." | Select-Object -First 1 +Write-Host "Debug over" diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 1db6fe890..c65b6a040 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -920,20 +920,40 @@ await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true Assert.Empty(rawDetailsView.Type); Assert.Empty(rawDetailsView.ValueString); VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance); - Assert.Equal(7, rawViewChildren.Length); - Assert.Equal("Length", rawViewChildren[0].Name); - Assert.Equal("4", rawViewChildren[0].ValueString); - Assert.Equal("LongLength", rawViewChildren[1].Name); - Assert.Equal("4", rawViewChildren[1].ValueString); - Assert.Equal("Rank", rawViewChildren[2].Name); - Assert.Equal("1", rawViewChildren[2].ValueString); - Assert.Equal("SyncRoot", rawViewChildren[3].Name); - Assert.Equal("IsReadOnly", rawViewChildren[4].Name); - Assert.Equal("$false", rawViewChildren[4].ValueString); - Assert.Equal("IsFixedSize", rawViewChildren[5].Name); - Assert.Equal("$true", rawViewChildren[5].ValueString); - Assert.Equal("IsSynchronized", rawViewChildren[6].Name); - Assert.Equal("$false", rawViewChildren[6].ValueString); + Assert.Collection(rawViewChildren, + (i) => + { + Assert.Equal("Length", i.Name); + Assert.Equal("4", i.ValueString); + }, + (i) => + { + Assert.Equal("LongLength", i.Name); + Assert.Equal("4", i.ValueString); + }, + (i) => + { + Assert.Equal("Rank", i.Name); + Assert.Equal("1", i.ValueString); + }, + (i) => + { + Assert.Equal("SyncRoot", i.Name); + Assert.True(i.IsExpandable); + }, + (i) => + { + Assert.Equal("IsReadOnly", i.Name); + Assert.Equal("$false", i.ValueString); + }, (i) => + { + Assert.Equal("IsFixedSize", i.Name); + Assert.Equal("$true", i.ValueString); + }, (i) => + { + Assert.Equal("IsSynchronized", i.Name); + Assert.Equal("$false", i.ValueString); + }); } [Fact] @@ -956,20 +976,47 @@ await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true Assert.NotNull(rawDetailsView); Assert.Empty(rawDetailsView.Type); Assert.Empty(rawDetailsView.ValueString); - VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance); - Assert.Equal(7, rawViewChildren.Length); - Assert.Equal("IsReadOnly", rawViewChildren[0].Name); - Assert.Equal("$false", rawViewChildren[0].ValueString); - Assert.Equal("IsFixedSize", rawViewChildren[1].Name); - Assert.Equal("$false", rawViewChildren[1].ValueString); - Assert.Equal("IsSynchronized", rawViewChildren[2].Name); - Assert.Equal("$false", rawViewChildren[2].ValueString); - Assert.Equal("Keys", rawViewChildren[3].Name); - Assert.Equal("Values", rawViewChildren[4].Name); - Assert.Equal("[ValueCollection: 4]", rawViewChildren[4].ValueString); - Assert.Equal("SyncRoot", rawViewChildren[5].Name); - Assert.Equal("Count", rawViewChildren[6].Name); - Assert.Equal("4", rawViewChildren[6].ValueString); + VariableDetailsBase[] rawDetailsChildren = rawDetailsView.GetChildren(NullLogger.Instance); + Assert.Collection(rawDetailsChildren, + (i) => + { + Assert.Equal("IsReadOnly", i.Name); + Assert.Equal("$false", i.ValueString); + }, + (i) => + { + Assert.Equal("IsFixedSize", i.Name); + Assert.Equal("$false", i.ValueString); + }, + (i) => + { + Assert.Equal("IsSynchronized", i.Name); + Assert.Equal("$false", i.ValueString); + }, + (i) => + { + Assert.Equal("Keys", i.Name); + Assert.Equal("[KeyCollection: 4]", i.ValueString); + }, + (i) => + { + Assert.Equal("Values", i.Name); + Assert.Equal("[ValueCollection: 4]", i.ValueString); + }, + (i) => + { + Assert.Equal("SyncRoot", i.Name); +#if CoreCLR + Assert.Equal("[Hashtable: 4]", i.ValueString); +#else + Assert.Equal("[Object]", i.ValueString); +#endif + }, + (i) => + { + Assert.Equal("Count", i.Name); + Assert.Equal("4", i.ValueString); + }); } [Fact] @@ -996,8 +1043,28 @@ await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true Assert.Empty(rawDetailsView.Type); Assert.Empty(rawDetailsView.ValueString); VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance); - Assert.Equal(4, rawViewChildren.Length); - Assert.NotNull(Array.Find(rawViewChildren, v => v.Name == "Comparer")); + Assert.Collection(rawViewChildren, + (i) => + { + Assert.Equal("Count", i.Name); + Assert.Equal("4", i.ValueString); + }, + (i) => + { + Assert.Equal("Comparer", i.Name); + Assert.Equal("[GenericComparer`1]", i.ValueString); + }, + (i) => + { + Assert.Equal("Keys", i.Name); + Assert.Equal("[KeyCollection: 4]", i.ValueString); + }, + (i) => + { + Assert.Equal("Values", i.Name); + Assert.Equal("[ValueCollection: 4]", i.ValueString); + } + ); } [Fact] @@ -1029,8 +1096,8 @@ await debugService.SetLineBreakpointsAsync( // Verifies fix for issue #86, $proc = Get-Process foo displays just the ETS property set // and not all process properties. - [Fact(Skip = "Length of child vars is wrong now")] - public async Task DebuggerVariableProcessObjDisplaysCorrectly() + [Fact] + public async Task DebuggerVariableProcessObjectDisplaysCorrectly() { await debugService.SetLineBreakpointsAsync( variableScriptFile, @@ -1049,7 +1116,33 @@ await debugService.SetLineBreakpointsAsync( Assert.True(var.IsExpandable); VariableDetailsBase[] childVars = await debugService.GetVariables(var.Id, CancellationToken.None).ConfigureAwait(true); - Assert.Equal(53, childVars.Length); + Assert.Contains(childVars, i => i.Name is "Name"); + Assert.Contains(childVars, i => i.Name is "Handles"); +#if CoreCLR + Assert.Contains(childVars, i => i.Name is "CommandLine"); + Assert.Contains(childVars, i => i.Name is "ExitCode"); + Assert.Contains(childVars, i => i.Name is "HasExited" && i.ValueString is "$false"); +#endif + Assert.Contains(childVars, i => i.Name is "Id"); + } + + [Fact] + public async Task DebuggerVariableFileObjectDisplaysCorrectly() + { + await debugService.SetCommandBreakpointsAsync( + new[] { CommandBreakpointDetails.Create("Write-Host") }).ConfigureAwait(true); + + ScriptFile testScript = GetDebugScript("GetChildItemTest.ps1"); + Task _ = ExecuteScriptFileAsync(testScript.FilePath); + AssertDebuggerStopped(testScript.FilePath, 2); + + VariableDetailsBase[] variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); + VariableDetailsBase var = Array.Find(variables, v => v.Name == "$file"); + VariableDetailsBase[] childVars = await debugService.GetVariables(var.Id, CancellationToken.None).ConfigureAwait(true); + Assert.Contains(childVars, i => i.Name is "PSPath"); + Assert.Contains(childVars, i => i.Name is "PSProvider" && i.ValueString is "Microsoft.PowerShell.Core\\FileSystem"); + Assert.Contains(childVars, i => i.Name is "Exists" && i.ValueString is "$true"); + Assert.Contains(childVars, i => i.Name is "LastAccessTime"); } } } From 874e1b82d805c4365eef476210aef000a2871ab2 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Mon, 19 Dec 2022 14:26:19 -0800 Subject: [PATCH 143/327] Add test for custom `ToString` implementations This now works fine since the expansion takes place on the pipeline thread. --- .../Debugging/VariableTest.ps1 | 13 ++++++++++ .../Debugging/DebugServiceTests.cs | 25 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 index e8c23d9a0..2dc513746 100644 --- a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 @@ -51,3 +51,16 @@ $sortedDictionary['blue'] = 'red' # This is a dummy function that the test will use to stop and evaluate the debug environment function __BreakDebuggerDerivedDictionaryPropertyInRawView{}; __BreakDebuggerDerivedDictionaryPropertyInRawView + +class CustomToString { + [String]$String = 'Hello' + [String]ToString() { + return $this.String.ToUpper() + } +} +$SCRIPT:CustomToStrings = 1..1000 | ForEach-Object { + [CustomToString]::new() +} + +# This is a dummy function that the test will use to stop and evaluate the debug environment +function __BreakDebuggerToStringShouldMarshallToPipeline{}; __BreakDebuggerToStringShouldMarshallToPipeline diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index c65b6a040..7a5025b85 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -1144,5 +1144,30 @@ await debugService.SetCommandBreakpointsAsync( Assert.Contains(childVars, i => i.Name is "Exists" && i.ValueString is "$true"); Assert.Contains(childVars, i => i.Name is "LastAccessTime"); } + + // Verifies Issue #1686 + [Fact] + public async Task DebuggerToStringShouldMarshallToPipeline() + { + CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create("__BreakDebuggerToStringShouldMarshallToPipeline"); + await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true); + + // Execute the script and wait for the breakpoint to be hit + Task _ = ExecuteVariableScriptFileAsync(); + AssertDebuggerStopped(commandBreakpointDetails: breakpoint); + + VariableDetailsBase[] vars = await GetVariables(VariableContainerDetails.ScriptScopeName).ConfigureAwait(true); + VariableDetailsBase customToStrings = Array.Find(vars, i => i.Name is "$CustomToStrings"); + Assert.True(customToStrings.IsExpandable); + Assert.Equal("[System.Object[]]", customToStrings.Type); + VariableDetailsBase[] childVars = await debugService.GetVariables(customToStrings.Id, CancellationToken.None).ConfigureAwait(true); + // Check everything but the last variable (which is "Raw View") + Assert.Equal(1001, childVars.Length); // 1000 custom variables plus "Raw View" + Assert.All(childVars.Take(childVars.Length - 1), i => + { + Assert.Equal("HELLO", i.ValueString); + Assert.Equal("[CustomToString]", i.Type); + }); + } } } From f6b1ffa103766c92d43c9c2eef8fffaa8558da75 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 20 Dec 2022 11:05:57 -0800 Subject: [PATCH 144/327] Run `debugService.Continue()` in the thread pool So that it doesn't deadlock with the pipeline thread. --- .../Debugging/DebugServiceTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 7a5025b85..fa1ed7be8 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -177,7 +177,7 @@ await debugService.SetCommandBreakpointsAsync( new PSCommand().AddScript("Get-Random -SetSeed 42 -Maximum 100"), CancellationToken.None); AssertDebuggerStopped("", 1); - debugService.Continue(); + await Task.Run(debugService.Continue).ConfigureAwait(true); Assert.Equal(17, (await executeTask.ConfigureAwait(true))[0]); StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true); @@ -296,7 +296,7 @@ public async Task DebuggerStopsOnFunctionBreakpoints() Assert.Equal("1", i.ValueString); // The function breakpoint should fire the next time through the loop. - debugService.Continue(); + await Task.Run(debugService.Continue).ConfigureAwait(true); AssertDebuggerStopped(debugScriptFile.FilePath, 6); variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); @@ -353,7 +353,7 @@ await debugService.SetLineBreakpointsAsync( Task _ = ExecuteDebugFileAsync(); AssertDebuggerStopped(debugScriptFile.FilePath, 5); - debugService.Continue(); + await Task.Run(debugService.Continue).ConfigureAwait(true); AssertDebuggerStopped(debugScriptFile.FilePath, 7); } @@ -382,7 +382,7 @@ await debugService.SetLineBreakpointsAsync( // The conditional breakpoint should not fire again, until the value of // i reaches breakpointValue2. - debugService.Continue(); + await Task.Run(debugService.Continue).ConfigureAwait(true); AssertDebuggerStopped(debugScriptFile.FilePath, 7); variables = await GetVariables(VariableContainerDetails.LocalScopeName).ConfigureAwait(true); From 685532c1aaaf250a8c7e5e9f6968a3de1ece15cd Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 20 Dec 2022 12:22:55 -0800 Subject: [PATCH 145/327] Update CHANGELOG for `v3.7.2` --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1013ac24a..f1b343d59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # PowerShell Editor Services Release History +## v3.7.2 +### Tuesday, December 20, 2022 + +- 🐛 🔍 [vscode-powershell #4174](https://github.com/PowerShell/PowerShellEditorServices/pull/1975) - Fix several bugs in the debugger. +- #️⃣ 🙏 [PowerShellEditorServices #1973](https://github.com/PowerShell/PowerShellEditorServices/pull/1973) - Update `Microsoft.PowerShell.SDK` with workaround. + ## v3.7.1 ### Monday, December 12, 2022 From ecb6d96a4400d67627d129eb91acc9c2b54dd714 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 20 Dec 2022 12:22:55 -0800 Subject: [PATCH 146/327] Bump version to `v3.7.2` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 08852f3f6..da08bb8ba 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.7.1 + 3.7.2 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 7b5af0d63..74f167d11 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.7.1' +ModuleVersion = '3.7.2' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 4517101911ee2a8f83d40422d46257bf167ef153 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Tue, 3 Jan 2023 13:34:09 -0500 Subject: [PATCH 147/327] Use an empty array instead of `null` (#1980) Fixes #1959 The code on the client expects DefaultChoices to never be `null`. If you send `null`, it tries to access the property `length` on what would be an array. --- src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs b/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs index 4b50e6e41..a62131d2e 100644 --- a/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs +++ b/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs @@ -168,7 +168,7 @@ public async Task PromptSelectionAsync(string message, IReadOnlyList -1 ? new[] { defaultChoiceIndex } : null, + DefaultChoices = defaultChoiceIndex > -1 ? new[] { defaultChoiceIndex } : Array.Empty(), }) .Returning(CancellationToken.None) .ConfigureAwait(false); From c1733a17b36d489db5a9245804621fa7a54ed81f Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Tue, 3 Jan 2023 13:50:52 -0500 Subject: [PATCH 148/327] Make `Set-ScriptExtent` not slow (#1981) When I first wrote this many moons ago I didn't know most things. Like that you can just edit in reverse and side step all positioning issues... This gets rid of the awful "wait for document update" logic and just does it properly. --- .../Commands/Public/Set-ScriptExtent.ps1 | 61 ++++++------------- 1 file changed, 19 insertions(+), 42 deletions(-) diff --git a/module/PowerShellEditorServices/Commands/Public/Set-ScriptExtent.ps1 b/module/PowerShellEditorServices/Commands/Public/Set-ScriptExtent.ps1 index d5bf9dbd1..5f263097f 100644 --- a/module/PowerShellEditorServices/Commands/Public/Set-ScriptExtent.ps1 +++ b/module/PowerShellEditorServices/Commands/Public/Set-ScriptExtent.ps1 @@ -5,27 +5,29 @@ function Set-ScriptExtent { <# .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml #> - [CmdletBinding(PositionalBinding=$false, DefaultParameterSetName='__AllParameterSets')] + [CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = '__AllParameterSets')] param( - [Parameter(Position=0, Mandatory)] - [psobject] - $Text, + [Parameter(Position = 0, Mandatory)] + [psobject] $Text, - [Parameter(Mandatory, ParameterSetName='AsString')] + [Parameter(Mandatory, ParameterSetName = 'AsString')] [switch] $AsString, - [Parameter(Mandatory, ParameterSetName='AsArray')] - [switch] - $AsArray, + [Parameter(Mandatory, ParameterSetName = 'AsArray')] + [switch] $AsArray, [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] - [System.Management.Automation.Language.IScriptExtent] - $Extent = (Find-Ast -AtCursor).Extent + [System.Management.Automation.Language.IScriptExtent] $Extent = (Find-Ast -AtCursor).Extent ) begin { $fileContext = $psEditor.GetEditorContext().CurrentFile - $extentList = [System.Collections.Generic.List[[Microsoft.PowerShell.EditorServices.Extensions.FileScriptExtent, Microsoft.PowerShell.EditorServices]]]::new() + $descendingComparer = [System.Collections.Generic.Comparer[int]]::Create{ + param($x, $y) return $y.CompareTo($x) + } + + $extentList = [System.Collections.Generic.SortedList[int, System.Management.Automation.Language.IScriptExtent]]::new( + $descendingComparer) } process { if ($Extent -isnot [Microsoft.PowerShell.EditorServices.Extensions.FileScriptExtent, Microsoft.PowerShell.EditorServices]) { @@ -34,11 +36,11 @@ function Set-ScriptExtent { $Extent.StartOffset, $Extent.EndOffset) } - $extentList.Add($Extent) + + $extentList.Add($Extent.StartOffset, $Extent) } - # Currently this kills the pipeline because we need to keep track and edit all extents for position tracking. - # TODO: Consider queueing changes in a static property and adding a PassThru switch. end { + $needsIndentFix = $false switch ($PSCmdlet.ParameterSetName) { # Insert text as a single string expression. AsString { @@ -55,41 +57,16 @@ function Set-ScriptExtent { } } - foreach ($aExtent in $extentList) { + foreach ($kvp in $extentList.GetEnumerator()) { + $aExtent = $kvp.Value $aText = $Text if ($needsIndentFix) { - # I'd rather let PSSA handle this when there are more formatting options. $indentOffset = ' ' * ($aExtent.StartColumnNumber - 1) - $aText = $aText -split '\r?\n' ` - -join ([Environment]::NewLine + $indentOffset) + $aText = $aText -split '\r?\n' -join ([Environment]::NewLine + $indentOffset) } - $differenceOffset = $aText.Length - $aExtent.Text.Length - $scriptText = $fileContext.GetText() $fileContext.InsertText($aText, $aExtent) - - $newText = $scriptText.Remove($aExtent.StartOffset, $aExtent.Text.Length).Insert($aExtent.StartOffset, $aText) - - $timeoutLoop = 0 - while ($fileContext.GetText() -ne $newText) { - Start-Sleep -Milliseconds 30 - $timeoutLoop++ - - if ($timeoutLoop -gt 20) { - $PSCmdlet.WriteDebug(('Timed out waiting for change at range {0}, {1}' -f $aExtent.StartOffset, - $aExtent.EndOffset)) - break - } - } - - if ($differenceOffset) { - $extentList.ForEach({ - if ($args[0].StartOffset -ge $aExtent.EndOffset) { - $args[0].AddOffset($differenceOffset) - } - }) - } } } } From d27fe8c5391b858b4ce4397a3b9c5853d584c764 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Wed, 4 Jan 2023 11:19:38 -0800 Subject: [PATCH 149/327] Update VS Code shell integration script (#1982) --- .../PowerShell/Host/PsesInternalHost.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index cf83dd34b..5a6c8fb31 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -503,11 +503,10 @@ internal Task LoadHostProfilesAsync(CancellationToken cancellationToken) private Task EnableShellIntegrationAsync(CancellationToken cancellationToken) { - // Imported on 11/17/22 from + // Imported on 01/03/23 from // https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 // with quotes escaped, `__VSCodeOriginalPSConsoleHostReadLine` removed (as it's done // in our own ReadLine function), and `[Console]::Write` replaced with `Write-Host`. - // TODO: We can probably clean some of this up. const string shellIntegrationScript = @" # Prevent installing more than once per session if (Test-Path variable:global:__VSCodeOriginalPrompt) { @@ -523,8 +522,21 @@ private Task EnableShellIntegrationAsync(CancellationToken cancellationToken) $Global:__LastHistoryId = -1 +function Global:__VSCode-Escape-Value([string]$value) { + # NOTE: In PowerShell v6.1+, this can be written `$value -replace '…', { … }` instead of `[regex]::Replace`. + # Replace any non-alphanumeric characters. + [regex]::Replace($value, '[\\\n;]', { param($match) + # Encode the (ascii) matches as `\x` + -Join ( + [System.Text.Encoding]::UTF8.GetBytes($match.Value) | ForEach-Object { '\x{0:x2}' -f $_ } + ) + }) +} function Global:Prompt() { + # NOTE: We disable strict mode for the scope of this function because it unhelpfully throws an + # error when $LastHistoryEntry is null, and is not otherwise useful. + Set-StrictMode -Off $FakeCode = [int]!$global:? $LastHistoryEntry = Get-History -Count 1 # Skip finishing the command if the first command has not yet started @@ -545,7 +557,7 @@ private Task EnableShellIntegrationAsync(CancellationToken cancellationToken) } else { $CommandLine = """" } - $Result += $CommandLine.Replace(""\"", ""\\"").Replace(""`n"", ""\x0a"").Replace("";"", ""\x3b"") + $Result += $(__VSCode-Escape-Value $CommandLine) $Result += ""`a"" # Command finished exit code # OSC 633 ; D [; ] ST @@ -557,9 +569,11 @@ private Task EnableShellIntegrationAsync(CancellationToken cancellationToken) $Result += ""$([char]0x1b)]633;A`a"" # Current working directory # OSC 633 ; = ST - $Result += if($pwd.Provider.Name -eq 'FileSystem'){""$([char]0x1b)]633;P;Cwd=$($pwd.ProviderPath)`a""} + $Result += if($pwd.Provider.Name -eq 'FileSystem'){""$([char]0x1b)]633;P;Cwd=$(__VSCode-Escape-Value $pwd.ProviderPath)`a""} # Before running the original prompt, put $? back to what it was: - if ($FakeCode -ne 0) { Write-Error ""failure"" -ea ignore } + if ($FakeCode -ne 0) { + Write-Error ""failure"" -ea ignore + } # Run the original prompt $Result += $Global:__VSCodeOriginalPrompt.Invoke() # Write command started @@ -579,12 +593,14 @@ function Set-MappedKeyHandler { Set-PSReadLineKeyHandler -Chord $Sequence -Function $Handler.Function } } + function Set-MappedKeyHandlers { Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a' Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b' Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c' Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d' } + Set-MappedKeyHandlers "; From 745c8c0e5bb7451b5ae5a37dc5705dfff2952e83 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 4 Jan 2023 12:02:49 -0800 Subject: [PATCH 150/327] Update CHANGELOG for `v3.7.3` --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1b343d59..e826a0e3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # PowerShell Editor Services Release History +## v3.7.3 +### Wednesday, January 04, 2023 + +- 🐛 🚂 [vscode-powershell #4343](https://github.com/PowerShell/PowerShellEditorServices/pull/1982) - Update VS Code shell integration script. +- ✨ 🙏 [PowerShellEditorServices #1981](https://github.com/PowerShell/PowerShellEditorServices/pull/1981) - Make `Set-ScriptExtent` not slow. +- 🐛 🙏 [PowerShellEditorServices #1959](https://github.com/PowerShell/PowerShellEditorServices/pull/1980) - Use an empty array instead of `null`. + ## v3.7.2 ### Tuesday, December 20, 2022 From aba3b89eaa29100bc53ff888131de5667b5f0981 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 4 Jan 2023 12:02:49 -0800 Subject: [PATCH 151/327] Bump version to `v3.7.3` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index da08bb8ba..ca8404bd4 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.7.2 + 3.7.3 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 74f167d11..9fc35546b 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.7.2' +ModuleVersion = '3.7.3' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 39389829be5530d7ebb2fc3d18c547e696ab3e79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 21:39:36 +0000 Subject: [PATCH 152/327] Bump Microsoft.PowerShell.SDK from 7.3.1 to 7.3.2 (#1986) Bumps [Microsoft.PowerShell.SDK](https://github.com/PowerShell/PowerShell) from 7.3.1 to 7.3.2. - [Release notes](https://github.com/PowerShell/PowerShell/releases) - [Commits](https://github.com/PowerShell/PowerShell/compare/v7.3.1...v7.3.2) --- updated-dependencies: - dependency-name: Microsoft.PowerShell.SDK dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.csproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 3e5c8fbee..98c6e28f7 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -14,14 +14,12 @@ - - - + - + From 7270bed76cf34e0cc2545517e3bebf514b1ab91a Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sat, 13 Aug 2022 21:47:26 +0000 Subject: [PATCH 153/327] add symbols for classes --- .../Services/Symbols/SymbolType.cs | 27 +++- .../Symbols/Vistors/FindSymbolsVisitor.cs | 138 +++++++++++++++++- .../Handlers/DocumentSymbolHandler.cs | 16 +- .../Handlers/WorkspaceSymbolsHandler.cs | 25 +++- 4 files changed, 195 insertions(+), 11 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index 02778b106..207038107 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -41,6 +41,31 @@ internal enum SymbolType /// /// The symbol is a hashtable key /// - HashtableKey + HashtableKey, + + /// + /// The symbol is a class + /// + Class, + + /// + /// The symbol is a enum + /// + Enum, + + /// + /// The symbol is a class property + /// + Property, + + /// + /// The symbol is a class method + /// + Method, + + /// + /// The symbol is a class constructor + /// + Constructor } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs index 970acb4e7..4d2d3a076 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs @@ -7,12 +7,9 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// - /// The visitor used to find all the symbols (function and class defs) in the AST. + /// The visitor used to find all the symbols (variables, functions and class defs etc) in the AST. /// - /// - /// Requires PowerShell v3 or higher - /// - internal class FindSymbolsVisitor : AstVisitor + internal class FindSymbolsVisitor : AstVisitor2 { public List SymbolReferences { get; } @@ -26,6 +23,12 @@ internal class FindSymbolsVisitor : AstVisitor /// or a decision to continue if it wasn't found public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. + // This will not exclude nested functions as they have ScriptBlockAst as parent + if (functionDefinitionAst.Parent is FunctionMemberAst) { + return AstVisitAction.Continue; + } + IScriptExtent nameExtent = new ScriptExtent() { Text = functionDefinitionAst.Name, @@ -49,7 +52,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun } /// - /// Checks to see if this variable expression is the symbol we are looking for. + /// Checks to see if this variable expression is the symbol we are looking for. /// /// A VariableExpressionAst object in the script's AST /// A decision to stop searching if the right symbol was found, @@ -80,6 +83,129 @@ private static bool IsAssignedAtScriptScope(VariableExpressionAst variableExpres parent = parent.Parent; return parent is null || parent.Parent is null || parent.Parent.Parent is null; } + + /// + /// Adds class and AST to symbol reference list + /// + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + IScriptExtent nameExtent = new ScriptExtent() + { + Text = typeDefinitionAst.Name, + StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, + EndLineNumber = typeDefinitionAst.Extent.EndLineNumber, + StartColumnNumber = typeDefinitionAst.Extent.StartColumnNumber, + EndColumnNumber = typeDefinitionAst.Extent.EndColumnNumber, + File = typeDefinitionAst.Extent.File + }; + + SymbolType symbolType = + typeDefinitionAst.IsEnum ? + SymbolType.Enum : SymbolType.Class; + + SymbolReferences.Add( + new SymbolReference( + symbolType, + nameExtent)); + + return AstVisitAction.Continue; + } + + /// + /// Adds class method and constructor AST to symbol reference list + /// + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + IScriptExtent nameExtent = new ScriptExtent() + { + Text = GetMethodOverloadName(functionMemberAst), + StartLineNumber = functionMemberAst.Extent.StartLineNumber, + EndLineNumber = functionMemberAst.Extent.EndLineNumber, + StartColumnNumber = functionMemberAst.Extent.StartColumnNumber, + EndColumnNumber = functionMemberAst.Extent.EndColumnNumber, + File = functionMemberAst.Extent.File + }; + + SymbolType symbolType = + functionMemberAst.IsConstructor ? + SymbolType.Constructor : SymbolType.Method; + + SymbolReferences.Add( + new SymbolReference( + symbolType, + nameExtent)); + + return AstVisitAction.Continue; + } + + /// + /// Gets the method or constructor name with parameters for current overload. + /// + /// A FunctionMemberAst object in the script's AST + /// Function member name with parameter types and names + private static string GetMethodOverloadName(FunctionMemberAst functionMemberAst) { + if (functionMemberAst.Parameters.Count > 0) + { + List parameters = new(functionMemberAst.Parameters.Count); + foreach (ParameterAst param in functionMemberAst.Parameters) + { + parameters.Add(param.Extent.Text); + } + + string paramString = string.Join(", ", parameters); + return string.Concat(functionMemberAst.Name, "(", paramString, ")"); + } + else + { + return string.Concat(functionMemberAst.Name, "()"); + } + } + + /// + /// Adds class property AST to symbol reference list + /// + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + IScriptExtent nameExtent = new ScriptExtent() + { + Text = propertyMemberAst.Name, + StartLineNumber = propertyMemberAst.Extent.StartLineNumber, + EndLineNumber = propertyMemberAst.Extent.EndLineNumber, + StartColumnNumber = propertyMemberAst.Extent.StartColumnNumber, + EndColumnNumber = propertyMemberAst.Extent.EndColumnNumber, + File = propertyMemberAst.Extent.File + }; + + SymbolReferences.Add( + new SymbolReference( + SymbolType.Property, + nameExtent)); + + return AstVisitAction.Continue; + } + + /// + /// Adds DSC configuration AST to symbol reference list + /// + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) + { + IScriptExtent nameExtent = new ScriptExtent() + { + Text = configurationDefinitionAst.InstanceName.Extent.Text, + StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, + EndLineNumber = configurationDefinitionAst.Extent.EndLineNumber, + StartColumnNumber = configurationDefinitionAst.Extent.StartColumnNumber, + EndColumnNumber = configurationDefinitionAst.Extent.EndColumnNumber, + File = configurationDefinitionAst.Extent.File + }; + + SymbolReferences.Add( + new SymbolReference( + SymbolType.Configuration, + nameExtent)); + + return AstVisitAction.Continue; + } } /// diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index f0973a7f0..01d544f93 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -128,7 +128,12 @@ private static SymbolKind GetSymbolKind(SymbolType symbolType) { return symbolType switch { - SymbolType.Configuration or SymbolType.Function or SymbolType.Workflow => SymbolKind.Function, + SymbolType.Function or SymbolType.Configuration or SymbolType.Workflow => SymbolKind.Function, + SymbolType.Enum => SymbolKind.Enum, + SymbolType.Class => SymbolKind.Class, + SymbolType.Constructor => SymbolKind.Constructor, + SymbolType.Method => SymbolKind.Method, + SymbolType.Property => SymbolKind.Property, _ => SymbolKind.Variable, }; } @@ -137,8 +142,15 @@ private static string GetDecoratedSymbolName(ISymbolReference symbolReference) { string name = symbolReference.SymbolName; - if (symbolReference.SymbolType is SymbolType.Configuration or + // Append { } for symbols with scriptblock + // Constructors and Methods have overloaded names already + if (symbolReference.SymbolType is SymbolType.Function or + SymbolType.Enum or + SymbolType.Class or + SymbolType.Constructor or + SymbolType.Method or + SymbolType.Configuration or SymbolType.Workflow) { name += " { }"; diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 8a9aaa815..3fa830f99 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -72,7 +72,7 @@ public override async Task> Handle(WorkspaceSymbolP symbols.Add(new SymbolInformation { ContainerName = containerName, - Kind = foundOccurrence.SymbolType == SymbolType.Variable ? SymbolKind.Variable : SymbolKind.Function, + Kind = GetSymbolKind(foundOccurrence.SymbolType), Location = location, Name = GetDecoratedSymbolName(foundOccurrence) }); @@ -107,8 +107,15 @@ private static string GetDecoratedSymbolName(SymbolReference symbolReference) { string name = symbolReference.SymbolName; - if (symbolReference.SymbolType is SymbolType.Configuration or + // Append { } for symbols with scriptblock + // Constructors and Methods have overloaded names already + if (symbolReference.SymbolType is SymbolType.Function or + SymbolType.Enum or + SymbolType.Class or + SymbolType.Constructor or + SymbolType.Method or + SymbolType.Configuration or SymbolType.Workflow) { name += " { }"; @@ -117,6 +124,20 @@ SymbolType.Function or return name; } + private static SymbolKind GetSymbolKind(SymbolType symbolType) + { + return symbolType switch + { + SymbolType.Function or SymbolType.Configuration or SymbolType.Workflow => SymbolKind.Function, + SymbolType.Enum => SymbolKind.Enum, + SymbolType.Class => SymbolKind.Class, + SymbolType.Constructor => SymbolKind.Constructor, + SymbolType.Method => SymbolKind.Method, + SymbolType.Property => SymbolKind.Property, + _ => SymbolKind.Variable, + }; + } + #endregion } } From 69a66bb1c572b826d1be8c71e6ca75217b0fe149 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sat, 13 Aug 2022 22:07:33 +0000 Subject: [PATCH 154/327] remove code used for supporting PSv3 and v4 --- .../Symbols/ScriptDocumentSymbolProvider.cs | 11 --- .../Services/Symbols/Vistors/AstOperations.cs | 12 --- .../Symbols/Vistors/FindSymbolsVisitor2.cs | 74 ------------------- 3 files changed, 97 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs diff --git a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs index 92b33c8de..0b40a5cc1 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs @@ -33,17 +33,6 @@ IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( /// A collection of SymbolReference objects public static IEnumerable FindSymbolsInDocument(Ast scriptAst) { - // TODO: Restore this when we figure out how to support multiple - // PS versions in the new PSES-as-a-module world (issue #276) - // if (powerShellVersion >= new Version(5,0)) - // { - //#if PowerShell v5 - // FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2(); - // scriptAst.Visit(findSymbolsVisitor); - // symbolReferences = findSymbolsVisitor.SymbolReferences; - //#endif - // } - // else FindSymbolsVisitor findSymbolsVisitor = new(); scriptAst.Visit(findSymbolsVisitor); return findSymbolsVisitor.SymbolReferences; diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index e14b2051a..6334b48b1 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -228,18 +228,6 @@ public static SymbolReference FindDefinitionOfSymbol( /// A collection of SymbolReference objects public static IEnumerable FindSymbolsInDocument(Ast scriptAst) { - // TODO: Restore this when we figure out how to support multiple - // PS versions in the new PSES-as-a-module world (issue #276) - // if (powerShellVersion >= new Version(5,0)) - // { - //#if PowerShell v5 - // FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2(); - // scriptAst.Visit(findSymbolsVisitor); - // symbolReferences = findSymbolsVisitor.SymbolReferences; - //#endif - // } - // else - FindSymbolsVisitor findSymbolsVisitor = new(); scriptAst.Visit(findSymbolsVisitor); return findSymbolsVisitor.SymbolReferences; diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs deleted file mode 100644 index 15f5e49db..000000000 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.Symbols -{ - // TODO: Restore this when we figure out how to support multiple - // PS versions in the new PSES-as-a-module world (issue #276) - - ///// - ///// The visitor used to find all the symbols (function and class defs) in the AST. - ///// - ///// - ///// Requires PowerShell v5 or higher - ///// - ///// - //internal class FindSymbolsVisitor2 : AstVisitor2 - //{ - // private FindSymbolsVisitor findSymbolsVisitor; - - // public List SymbolReferences - // { - // get - // { - // return this.findSymbolsVisitor.SymbolReferences; - // } - // } - - // public FindSymbolsVisitor2() - // { - // this.findSymbolsVisitor = new FindSymbolsVisitor(); - // } - - // /// - // /// Adds each function definition as a - // /// - // /// A functionDefinitionAst object in the script's AST - // /// A decision to stop searching if the right symbol was found, - // /// or a decision to continue if it wasn't found - // public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - // { - // return this.findSymbolsVisitor.VisitFunctionDefinition(functionDefinitionAst); - // } - - // /// - // /// Checks to see if this variable expression is the symbol we are looking for. - // /// - // /// A VariableExpressionAst object in the script's AST - // /// A decision to stop searching if the right symbol was found, - // /// or a decision to continue if it wasn't found - // public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - // { - // return this.findSymbolsVisitor.VisitVariableExpression(variableExpressionAst); - // } - - // public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) - // { - // IScriptExtent nameExtent = new ScriptExtent() - // { - // Text = configurationDefinitionAst.InstanceName.Extent.Text, - // StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, - // EndLineNumber = configurationDefinitionAst.Extent.EndLineNumber, - // StartColumnNumber = configurationDefinitionAst.Extent.StartColumnNumber, - // EndColumnNumber = configurationDefinitionAst.Extent.EndColumnNumber - // }; - - // this.findSymbolsVisitor.SymbolReferences.Add( - // new SymbolReference( - // SymbolType.Configuration, - // nameExtent)); - - // return AstVisitAction.Continue; - // } - //} -} From e3845e8089cba9d9e345c1129a6bcbbaa0a2626b Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sun, 14 Aug 2022 16:04:35 +0000 Subject: [PATCH 155/327] support hover --- .../Services/Symbols/SymbolDetails.cs | 26 ++++ .../Symbols/Vistors/FindSymbolVisitor.cs | 143 +++++++++++++++++- 2 files changed, 167 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index 80a1dd8b4..fa19e0289 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -82,6 +82,32 @@ await CommandHelpers.GetCommandSynopsisAsync( symbolDetails.DisplayString = symbolReference.SymbolName; return symbolDetails; + case SymbolType.Class: + symbolDetails.DisplayString = "class " + symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Enum: + symbolDetails.DisplayString = "enum " + symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Constructor: + // TODO: constructor Class(parameters) + symbolDetails.DisplayString = "constructor " + symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Method: + // TODO: method ReturnType Class.MethodName(parameters) + symbolDetails.DisplayString = "method " + symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Property: + symbolDetails.DisplayString = "(property) " + symbolReference.SymbolName; + return symbolDetails; + + case SymbolType.Configuration: + symbolDetails.DisplayString = "configuration " + symbolReference.SymbolName; + return symbolDetails; + default: return symbolDetails; } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index bf2520a3c..adb1d6b48 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -9,7 +9,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols /// /// The visitor used to find the symbol at a specific location in the AST /// - internal class FindSymbolVisitor : AstVisitor + internal class FindSymbolVisitor : AstVisitor2 { private readonly int lineNumber; private readonly int columnNumber; @@ -116,7 +116,7 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command } /// - /// Checks to see if this variable expression is the symbol we are looking for. + /// Checks to see if this variable expression is the symbol we are looking for. /// /// A VariableExpressionAst object in the script's AST /// A decision to stop searching if the right symbol was found, @@ -147,5 +147,144 @@ private bool IsPositionInExtent(IScriptExtent extent) extent.StartColumnNumber <= columnNumber && extent.EndColumnNumber >= columnNumber; } + + /// + /// Checks to see if this function member is the symbol we are looking for. + /// + /// A FunctionMemberAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + // Show only method/ctor name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + functionMemberAst.Extent.StartColumnNumber + + functionMemberAst.Extent.Text.IndexOf(functionMemberAst.Name); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = functionMemberAst.Name, + StartLineNumber = functionMemberAst.Extent.StartLineNumber, + EndLineNumber = functionMemberAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + functionMemberAst.Name.Length, + File = functionMemberAst.Extent.File + }; + + if (IsPositionInExtent(nameExtent)) + { + SymbolType symbolType = + functionMemberAst.IsConstructor ? + SymbolType.Constructor : SymbolType.Method; + + FoundSymbolReference = + new SymbolReference( + symbolType, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Checks to see if this type definition is the symbol we are looking for. + /// + /// A TypeDefinitionAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + // Show only type name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + typeDefinitionAst.Extent.StartColumnNumber + + typeDefinitionAst.Extent.Text.IndexOf(typeDefinitionAst.Name); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = typeDefinitionAst.Name, + StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, + EndLineNumber = typeDefinitionAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + typeDefinitionAst.Name.Length, + File = typeDefinitionAst.Extent.File + }; + + if (IsPositionInExtent(nameExtent)) + { + SymbolType symbolType = + typeDefinitionAst.IsEnum ? + SymbolType.Enum : SymbolType.Class; + + FoundSymbolReference = + new SymbolReference( + symbolType, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Checks to see if this configuration definition is the symbol we are looking for. + /// + /// A ConfigurationDefinitionAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) + { + string configurationName = configurationDefinitionAst.InstanceName.Extent.Text; + + // Show only configuration name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + configurationDefinitionAst.Extent.StartColumnNumber + + configurationDefinitionAst.Extent.Text.IndexOf(configurationName); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = configurationName, + StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, + EndLineNumber = configurationDefinitionAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + configurationName.Length, + File = configurationDefinitionAst.Extent.File + }; + + if (IsPositionInExtent(nameExtent)) + { + FoundSymbolReference = + new SymbolReference( + SymbolType.Configuration, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Checks to see if this variable expression is the symbol we are looking for. + /// + /// A VariableExpressionAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + if (IsPositionInExtent(propertyMemberAst.Extent)) + { + FoundSymbolReference = + new SymbolReference( + SymbolType.Property, + propertyMemberAst.Extent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } } } From b1187b976f0e9c79aee85a5ca77eb71a7b928fe4 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Tue, 16 Aug 2022 20:14:58 +0000 Subject: [PATCH 156/327] add type reference symboltype --- .../Services/Symbols/SymbolDetails.cs | 4 ++++ .../Services/Symbols/SymbolType.cs | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index fa19e0289..fc85acd30 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -90,6 +90,10 @@ await CommandHelpers.GetCommandSynopsisAsync( symbolDetails.DisplayString = "enum " + symbolReference.SymbolName; return symbolDetails; + case SymbolType.Type: + symbolDetails.DisplayString = "type " + symbolReference.SymbolName; + return symbolDetails; + case SymbolType.Constructor: // TODO: constructor Class(parameters) symbolDetails.DisplayString = "constructor " + symbolReference.SymbolName; diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index 207038107..84756661c 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -66,6 +66,11 @@ internal enum SymbolType /// /// The symbol is a class constructor /// - Constructor + Constructor, + + /// + /// The symbol is a type reference + /// + Type, } } From 7cf22f0e3dcf255b987834d14ef871c79a641e9e Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Tue, 16 Aug 2022 20:20:54 +0000 Subject: [PATCH 157/327] support references and definitions --- .../Symbols/Vistors/FindDeclarationVisitor.cs | 153 +++++++++++++++- .../Symbols/Vistors/FindReferencesVisitor.cs | 163 +++++++++++++++++- .../Symbols/Vistors/FindSymbolVisitor.cs | 68 ++++++++ 3 files changed, 381 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs index 5c3071451..60d82eeac 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs @@ -9,7 +9,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols /// /// The visitor used to find the definition of a symbol /// - internal class FindDeclarationVisitor : AstVisitor + internal class FindDeclarationVisitor : AstVisitor2 { private readonly SymbolReference symbolRef; private readonly string variableName; @@ -68,6 +68,157 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return base.VisitFunctionDefinition(functionDefinitionAst); } + /// + /// Decides if the current type definition is the right definition + /// for the symbol being searched for. The definition of the symbol will be a of type + /// SymbolType.Enum or SymbolType.Class and have the same name as the symbol + /// + /// A TypeDefinitionAst in the script's AST + /// A decision to stop searching if the right TypeDefinitionAst was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + SymbolType symbolType = + typeDefinitionAst.IsEnum ? + SymbolType.Enum : SymbolType.Class; + + if ((symbolRef.SymbolType is SymbolType.Type || symbolRef.SymbolType.Equals(symbolType)) && + typeDefinitionAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // Show only type name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + typeDefinitionAst.Extent.StartColumnNumber + + typeDefinitionAst.Extent.Text.IndexOf(typeDefinitionAst.Name); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = typeDefinitionAst.Name, + StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, + EndLineNumber = typeDefinitionAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + typeDefinitionAst.Name.Length, + File = typeDefinitionAst.Extent.File + }; + + FoundDeclaration = + new SymbolReference( + symbolType, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Decides if the current function member is the right definition + /// for the symbol being searched for. The definition of the symbol will be a of type + /// SymbolType.Constructor or SymbolType.Method and have the same name as the symbol + /// + /// A FunctionMemberAst in the script's AST + /// A decision to stop searching if the right FunctionMemberAst was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + SymbolType symbolType = + functionMemberAst.IsConstructor ? + SymbolType.Constructor : SymbolType.Method; + + if (symbolRef.SymbolType.Equals(symbolType) && + functionMemberAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // Show only method/ctor name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + functionMemberAst.Extent.StartColumnNumber + + functionMemberAst.Extent.Text.IndexOf(functionMemberAst.Name); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = functionMemberAst.Name, + StartLineNumber = functionMemberAst.Extent.StartLineNumber, + EndLineNumber = functionMemberAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + functionMemberAst.Name.Length, + File = functionMemberAst.Extent.File + }; + + FoundDeclaration = + new SymbolReference( + symbolType, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Decides if the current property member is the right definition + /// for the symbol being searched for. The definition of the symbol will be a of type + /// SymbolType.Property and have the same name as the symbol + /// + /// A PropertyMemberAst in the script's AST + /// A decision to stop searching if the right PropertyMemberAst was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + if (symbolRef.SymbolType.Equals(SymbolType.Property) && + propertyMemberAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + FoundDeclaration = + new SymbolReference( + SymbolType.Property, + propertyMemberAst.Extent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + /// + /// Decides if the current configuration definition is the right definition + /// for the symbol being searched for. The definition of the symbol will be a of type + /// SymbolType.Configuration and have the same name as the symbol + /// + /// A ConfigurationDefinitionAst in the script's AST + /// A decision to stop searching if the right ConfigurationDefinitionAst was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) + { + string configurationName = configurationDefinitionAst.InstanceName.Extent.Text; + + if (symbolRef.SymbolType.Equals(SymbolType.Configuration) && + configurationName.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // Show only configuration name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + configurationDefinitionAst.Extent.StartColumnNumber + + configurationDefinitionAst.Extent.Text.IndexOf(configurationName); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = configurationName, + StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, + EndLineNumber = configurationDefinitionAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + configurationName.Length, + File = configurationDefinitionAst.Extent.File + }; + + FoundDeclaration = + new SymbolReference( + SymbolType.Configuration, + nameExtent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + /// /// Check if the left hand side of an assignmentStatementAst is a VariableExpressionAst /// with the same name as that of symbolRef. diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs index 44b64c8f5..bbae9ccaf 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols /// /// The visitor used to find the references of a symbol in a script's AST /// - internal class FindReferencesVisitor : AstVisitor + internal class FindReferencesVisitor : AstVisitor2 { private readonly SymbolReference _symbolRef; private readonly IDictionary> _cmdletToAliasDictionary; @@ -168,5 +168,164 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var } return AstVisitAction.Continue; } + + /// + /// Decides if the current type definition is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Class or SymbolType.Enum and have the same name as the symbol + /// + /// A TypeDefinitionAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + SymbolType symbolType = + typeDefinitionAst.IsEnum ? + SymbolType.Enum : SymbolType.Class; + + if ((_symbolRef.SymbolType is SymbolType.Type || _symbolRef.SymbolType.Equals(symbolType)) && + typeDefinitionAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // Show only type name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + typeDefinitionAst.Extent.StartColumnNumber + + typeDefinitionAst.Extent.Text.IndexOf(typeDefinitionAst.Name); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = typeDefinitionAst.Name, + StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, + EndLineNumber = typeDefinitionAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + typeDefinitionAst.Name.Length, + File = typeDefinitionAst.Extent.File + }; + + FoundReferences.Add(new SymbolReference(symbolType, nameExtent)); + } + return AstVisitAction.Continue; + } + + /// + /// Decides if the current type expression is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Type and have the same name as the symbol + /// + /// A TypeExpressionAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) + { + // We don't know if we're looking at a class or enum, but name is likely unique + if (IsTypeSymbol(_symbolRef.SymbolType) && + typeExpressionAst.TypeName.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + FoundReferences.Add(new SymbolReference(SymbolType.Type, typeExpressionAst.Extent)); + } + return AstVisitAction.Continue; + } + + /// + /// Decides if the current type constraint is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Type and have the same name as the symbol + /// + /// A TypeConstraintAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) + { + // We don't know if we're looking at a class or enum, but name is likely unique + if (IsTypeSymbol(_symbolRef.SymbolType) && + typeConstraintAst.TypeName.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + FoundReferences.Add(new SymbolReference(SymbolType.Type, typeConstraintAst.Extent)); + } + return AstVisitAction.Continue; + } + + /// + /// Decides if the current function member is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Constructor or SymbolType.Method and have the same name as the symbol + /// + /// A FunctionMemberAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + SymbolType symbolType = + functionMemberAst.IsConstructor ? + SymbolType.Constructor : SymbolType.Method; + + if (_symbolRef.SymbolType.Equals(symbolType) && + functionMemberAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // Show only method/ctor name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + functionMemberAst.Extent.StartColumnNumber + + functionMemberAst.Extent.Text.IndexOf(functionMemberAst.Name); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = functionMemberAst.Name, + StartLineNumber = functionMemberAst.Extent.StartLineNumber, + EndLineNumber = functionMemberAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + functionMemberAst.Name.Length, + File = functionMemberAst.Extent.File + }; + + FoundReferences.Add(new SymbolReference(symbolType, nameExtent)); + } + return AstVisitAction.Continue; + } + + /// + /// Decides if the current property member is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Property and have the same name as the symbol + /// + /// A PropertyMemberAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + if (_symbolRef.SymbolType.Equals(SymbolType.Property) && + propertyMemberAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + FoundReferences.Add(new SymbolReference(SymbolType.Property, propertyMemberAst.Extent)); + } + return AstVisitAction.Continue; + } + + /// + /// Decides if the current configuration definition is a reference of the symbol being searched for. + /// A reference of the symbol will be a of type SymbolType.Configuration and have the same name as the symbol + /// + /// A ConfigurationDefinitionAst in the script's AST + /// A visit action that continues the search for references + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) + { + string configurationName = configurationDefinitionAst.InstanceName.Extent.Text; + + if (_symbolRef.SymbolType.Equals(SymbolType.Configuration) && + configurationName.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + { + // Show only configuration name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + configurationDefinitionAst.Extent.StartColumnNumber + + configurationDefinitionAst.Extent.Text.IndexOf(configurationName); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = configurationName, + StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, + EndLineNumber = configurationDefinitionAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + configurationName.Length, + File = configurationDefinitionAst.Extent.File + }; + + FoundReferences.Add(new SymbolReference(SymbolType.Configuration, nameExtent)); + } + return AstVisitAction.Continue; + } + + /// + /// Tests if symbol type is a type (class/enum) definition or type reference. + /// + private static bool IsTypeSymbol(SymbolType symbolType) + => symbolType is SymbolType.Class or SymbolType.Enum or SymbolType.Type; } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index adb1d6b48..f40b44fd1 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -228,6 +228,74 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit return AstVisitAction.Continue; } + /// + /// Checks to see if this type expression is the symbol we are looking for. + /// + /// A TypeExpressionAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) + { + // Show only type name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + typeExpressionAst.Extent.StartColumnNumber + + typeExpressionAst.Extent.Text.IndexOf(typeExpressionAst.TypeName.Name); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = typeExpressionAst.TypeName.Name, + StartLineNumber = typeExpressionAst.Extent.StartLineNumber, + EndLineNumber = typeExpressionAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + typeExpressionAst.TypeName.Name.Length, + File = typeExpressionAst.Extent.File + }; + + if (IsPositionInExtent(nameExtent)) + { + FoundSymbolReference = + new SymbolReference( + SymbolType.Type, + nameExtent); + return AstVisitAction.StopVisit; + } + return AstVisitAction.Continue; + } + + /// + /// Checks to see if this type constraint is the symbol we are looking for. + /// + /// A TypeConstraintAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) + { + // Show only type name. Offset by StartColumn to include indentation etc. + int startColumnNumber = + typeConstraintAst.Extent.StartColumnNumber + + typeConstraintAst.Extent.Text.IndexOf(typeConstraintAst.TypeName.Name); + + IScriptExtent nameExtent = new ScriptExtent() + { + Text = typeConstraintAst.TypeName.Name, + StartLineNumber = typeConstraintAst.Extent.StartLineNumber, + EndLineNumber = typeConstraintAst.Extent.StartLineNumber, + StartColumnNumber = startColumnNumber, + EndColumnNumber = startColumnNumber + typeConstraintAst.TypeName.Name.Length, + File = typeConstraintAst.Extent.File + }; + + if (IsPositionInExtent(nameExtent)) + { + FoundSymbolReference = + new SymbolReference( + SymbolType.Type, + nameExtent); + return AstVisitAction.StopVisit; + } + return AstVisitAction.Continue; + } + /// /// Checks to see if this configuration definition is the symbol we are looking for. /// From 13adfc312005fb40a598ab1b9b854414bc37c327 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Tue, 16 Aug 2022 20:51:45 +0000 Subject: [PATCH 158/327] add codelens for class and enum --- .../CodeLens/ReferencesCodeLensProvider.cs | 9 +++-- .../Services/Symbols/SymbolsService.cs | 6 ++-- .../Services/Symbols/Vistors/AstOperations.cs | 6 ++-- .../Symbols/Vistors/FindSymbolVisitor.cs | 33 ++++++++++++------- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index eb50ce2d5..5f9b6d958 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -57,14 +57,17 @@ public ReferencesCodeLensProvider(WorkspaceService workspaceService, SymbolsServ /// /// The PowerShell script file to get code lenses for. /// - /// An array of CodeLenses describing all functions in the given script file. + /// An array of CodeLenses describing all functions, classes and enums in the given script file. public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) { List acc = new(); foreach (SymbolReference sym in _symbolProvider.ProvideDocumentSymbols(scriptFile)) { cancellationToken.ThrowIfCancellationRequested(); - if (sym.SymbolType == SymbolType.Function) + if (sym.SymbolType is + SymbolType.Function or + SymbolType.Class or + SymbolType.Enum) { acc.Add(new CodeLens { @@ -96,7 +99,7 @@ public async Task ResolveCodeLens( ScriptFile[] references = _workspaceService.ExpandScriptReferences( scriptFile); - SymbolReference foundSymbol = SymbolsService.FindFunctionDefinitionAtLocation( + SymbolReference foundSymbol = SymbolsService.FindSymbolDefinitionAtLocation( scriptFile, codeLens.Range.Start.Line + 1, codeLens.Range.Start.Character + 1); diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index f052520f0..336495bd8 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -288,7 +288,7 @@ public static IReadOnlyList FindOccurrencesInFile( } /// - /// Finds a function definition in the script given a file location + /// Finds a function, class or enum definition in the script given a file location /// /// The details and contents of a open script file /// The line number of the cursor for the given script @@ -296,7 +296,7 @@ public static IReadOnlyList FindOccurrencesInFile( /// A SymbolReference of the symbol found at the given location /// or null if there is no symbol at that location /// - public static SymbolReference FindFunctionDefinitionAtLocation( + public static SymbolReference FindSymbolDefinitionAtLocation( ScriptFile scriptFile, int lineNumber, int columnNumber) @@ -306,7 +306,7 @@ public static SymbolReference FindFunctionDefinitionAtLocation( scriptFile.ScriptAst, lineNumber, columnNumber, - includeFunctionDefinitions: true); + includeDefinitions: true); if (symbolReference != null) { diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 6334b48b1..6fcbd62c9 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -147,19 +147,19 @@ await executionService.ExecuteDelegateAsync( /// The abstract syntax tree of the given script /// The line number of the cursor for the given script /// The column number of the cursor for the given script - /// Includes full function definition ranges in the search. + /// Includes full symbol definition ranges in the search. /// SymbolReference of found symbol public static SymbolReference FindSymbolAtPosition( Ast scriptAst, int lineNumber, int columnNumber, - bool includeFunctionDefinitions = false) + bool includeDefinitions = false) { FindSymbolVisitor symbolVisitor = new( lineNumber, columnNumber, - includeFunctionDefinitions); + includeDefinitions); scriptAst.Visit(symbolVisitor); diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index f40b44fd1..41cd2be00 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -13,18 +13,18 @@ internal class FindSymbolVisitor : AstVisitor2 { private readonly int lineNumber; private readonly int columnNumber; - private readonly bool includeFunctionDefinitions; + private readonly bool includeDefinitions; public SymbolReference FoundSymbolReference { get; private set; } public FindSymbolVisitor( int lineNumber, int columnNumber, - bool includeFunctionDefinitions) + bool includeDefinitions) { this.lineNumber = lineNumber; this.columnNumber = columnNumber; - this.includeFunctionDefinitions = includeFunctionDefinitions; + this.includeDefinitions = includeDefinitions; } /// @@ -63,7 +63,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun int endLineNumber = functionDefinitionAst.Extent.EndLineNumber; int endColumnNumber = functionDefinitionAst.Extent.EndColumnNumber; - if (!includeFunctionDefinitions) + if (!includeDefinitions) { // We only want the function name (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst); @@ -196,18 +196,29 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem /// or a decision to continue if it wasn't found public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { - // Show only type name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - typeDefinitionAst.Extent.StartColumnNumber + - typeDefinitionAst.Extent.Text.IndexOf(typeDefinitionAst.Name); + int startLineNumber = typeDefinitionAst.Extent.StartLineNumber; + int startColumnNumber = typeDefinitionAst.Extent.StartColumnNumber; + int endLineNumber = typeDefinitionAst.Extent.EndLineNumber; + int endColumnNumber = typeDefinitionAst.Extent.EndColumnNumber; + + if (!includeDefinitions) + { + // We only want the function name + startColumnNumber = + typeDefinitionAst.Extent.StartColumnNumber + + typeDefinitionAst.Extent.Text.IndexOf(typeDefinitionAst.Name); + startLineNumber = typeDefinitionAst.Extent.StartLineNumber; + endColumnNumber = startColumnNumber + typeDefinitionAst.Name.Length; + endLineNumber = typeDefinitionAst.Extent.StartLineNumber; + } IScriptExtent nameExtent = new ScriptExtent() { Text = typeDefinitionAst.Name, - StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, - EndLineNumber = typeDefinitionAst.Extent.StartLineNumber, + StartLineNumber = startLineNumber, + EndLineNumber = endLineNumber, StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + typeDefinitionAst.Name.Length, + EndColumnNumber = endColumnNumber, File = typeDefinitionAst.Extent.File }; From f2be365763042dd89c23eecab5d80cae0ed10f93 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Tue, 16 Aug 2022 21:38:09 +0000 Subject: [PATCH 159/327] skip function ast visit for FunctionMemberAst --- .../Services/Symbols/Vistors/FindDeclarationVisitor.cs | 7 +++++++ .../Services/Symbols/Vistors/FindReferencesVisitor.cs | 7 +++++++ .../Services/Symbols/Vistors/FindSymbolVisitor.cs | 7 +++++++ .../Services/Symbols/Vistors/FindSymbolsVisitor.cs | 3 ++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs index 60d82eeac..aa587003d 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs @@ -36,6 +36,13 @@ public FindDeclarationVisitor(SymbolReference symbolRef) /// or a decision to continue if it wasn't found public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. + // This will not exclude nested functions as they have ScriptBlockAst as parent + if (functionDefinitionAst.Parent is FunctionMemberAst) + { + return AstVisitAction.Continue; + } + // Get the start column number of the function name, // instead of the the start column of 'function' and create new extent for the functionName int startColumnNumber = diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs index bbae9ccaf..7438482ce 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs @@ -117,6 +117,13 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) /// A visit action that continues the search for references public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. + // This will not exclude nested functions as they have ScriptBlockAst as parent + if (functionDefinitionAst.Parent is FunctionMemberAst) + { + return AstVisitAction.Continue; + } + (int startColumnNumber, int startLineNumber) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst); IScriptExtent nameExtent = new ScriptExtent() diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index 41cd2be00..71a773c0e 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -58,6 +58,13 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) /// or a decision to continue if it wasn't found public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. + // This will not exclude nested functions as they have ScriptBlockAst as parent + if (functionDefinitionAst.Parent is FunctionMemberAst) + { + return AstVisitAction.Continue; + } + int startLineNumber = functionDefinitionAst.Extent.StartLineNumber; int startColumnNumber = functionDefinitionAst.Extent.StartColumnNumber; int endLineNumber = functionDefinitionAst.Extent.EndLineNumber; diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs index 4d2d3a076..d7fe87db1 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs @@ -25,7 +25,8 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun { // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. // This will not exclude nested functions as they have ScriptBlockAst as parent - if (functionDefinitionAst.Parent is FunctionMemberAst) { + if (functionDefinitionAst.Parent is FunctionMemberAst) + { return AstVisitAction.Continue; } From 1d1b5cc0c846947cb939866915164a09c8a8b0a2 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Wed, 17 Aug 2022 19:05:10 +0000 Subject: [PATCH 160/327] drop reference-support for ConfigurationDefinition --- .../Symbols/Vistors/FindDeclarationVisitor.cs | 41 ------------------- .../Symbols/Vistors/FindReferencesVisitor.cs | 33 --------------- 2 files changed, 74 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs index aa587003d..18aafef21 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs @@ -185,47 +185,6 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem return AstVisitAction.Continue; } - /// - /// Decides if the current configuration definition is the right definition - /// for the symbol being searched for. The definition of the symbol will be a of type - /// SymbolType.Configuration and have the same name as the symbol - /// - /// A ConfigurationDefinitionAst in the script's AST - /// A decision to stop searching if the right ConfigurationDefinitionAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) - { - string configurationName = configurationDefinitionAst.InstanceName.Extent.Text; - - if (symbolRef.SymbolType.Equals(SymbolType.Configuration) && - configurationName.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - // Show only configuration name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - configurationDefinitionAst.Extent.StartColumnNumber + - configurationDefinitionAst.Extent.Text.IndexOf(configurationName); - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = configurationName, - StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, - EndLineNumber = configurationDefinitionAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + configurationName.Length, - File = configurationDefinitionAst.Extent.File - }; - - FoundDeclaration = - new SymbolReference( - SymbolType.Configuration, - nameExtent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - /// /// Check if the left hand side of an assignmentStatementAst is a VariableExpressionAst /// with the same name as that of symbolRef. diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs index 7438482ce..858ee8479 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs @@ -296,39 +296,6 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem return AstVisitAction.Continue; } - /// - /// Decides if the current configuration definition is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Configuration and have the same name as the symbol - /// - /// A ConfigurationDefinitionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) - { - string configurationName = configurationDefinitionAst.InstanceName.Extent.Text; - - if (_symbolRef.SymbolType.Equals(SymbolType.Configuration) && - configurationName.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - // Show only configuration name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - configurationDefinitionAst.Extent.StartColumnNumber + - configurationDefinitionAst.Extent.Text.IndexOf(configurationName); - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = configurationName, - StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, - EndLineNumber = configurationDefinitionAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + configurationName.Length, - File = configurationDefinitionAst.Extent.File - }; - - FoundReferences.Add(new SymbolReference(SymbolType.Configuration, nameExtent)); - } - return AstVisitAction.Continue; - } - /// /// Tests if symbol type is a type (class/enum) definition or type reference. /// From 50e8a6c3c2fc1fe35984e00f3f3279aff9b741ba Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Wed, 17 Aug 2022 19:07:04 +0000 Subject: [PATCH 161/327] update document symbols test --- .../Symbols/DSCFile.ps1 | 4 ++ .../Symbols/FindSymbolsInDSCFile.cs | 21 ++++++++ .../Symbols/MultipleSymbols.ps1 | 18 +++++-- .../Language/SymbolsServiceTests.cs | 51 ++++++++++++++++--- 4 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 test/PowerShellEditorServices.Test.Shared/Symbols/DSCFile.ps1 create mode 100644 test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInDSCFile.cs diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/DSCFile.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/DSCFile.ps1 new file mode 100644 index 000000000..defec6863 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/DSCFile.ps1 @@ -0,0 +1,4 @@ +# This file represents a script with a DSC configuration +configuration AConfiguration { + Node "TEST-PC" {} +} diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInDSCFile.cs b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInDSCFile.cs new file mode 100644 index 000000000..6e3d45ff2 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInDSCFile.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Symbols +{ + public static class FindSymbolsInDSCFile + { + public static readonly ScriptRegion SourceDetails = + new( + file: TestUtilities.NormalizePath("Symbols/DSCFile.ps1"), + text: string.Empty, + startLineNumber: 0, + startColumnNumber: 0, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 index f234fed03..db53a6c1a 100644 --- a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 @@ -22,10 +22,22 @@ function AnAdvancedFunction { workflow AWorkflow {} -Configuration AConfiguration { - Node "TEST-PC" {} +class AClass { + [string]$AProperty + + AClass([string]$AParameter) { + + } + + [void]AMethod([string]$param1, [int]$param2, $param3) { + + } +} + +enum AEnum { + AValue = 0 } AFunction 1..3 | AFilter -AnAdvancedFunction \ No newline at end of file +AnAdvancedFunction diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index e7938c287..b6552c0fb 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Management.Automation; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; @@ -32,6 +33,7 @@ public class SymbolsServiceTests : IDisposable private readonly PsesInternalHost psesHost; private readonly WorkspaceService workspace; private readonly SymbolsService symbolsService; + private static readonly bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); public SymbolsServiceTests() { @@ -287,6 +289,11 @@ public void FindsSymbolsInFile() Assert.Equal(4, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Function)); Assert.Equal(3, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable)); Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Workflow)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); + Assert.Equal(2, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Property)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); SymbolReference firstFunctionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Function); Assert.Equal("AFunction", firstFunctionSymbol.SymbolName); @@ -303,12 +310,44 @@ public void FindsSymbolsInFile() Assert.Equal(23, firstWorkflowSymbol.ScriptRegion.StartLineNumber); Assert.Equal(1, firstWorkflowSymbol.ScriptRegion.StartColumnNumber); - // TODO: Bring this back when we can use AstVisitor2 again (#276) - //Assert.Equal(1, symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Configuration).Count()); - //SymbolReference firstConfigurationSymbol = symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Configuration).First(); - //Assert.Equal("AConfiguration", firstConfigurationSymbol.SymbolName); - //Assert.Equal(25, firstConfigurationSymbol.ScriptRegion.StartLineNumber); - //Assert.Equal(1, firstConfigurationSymbol.ScriptRegion.StartColumnNumber); + SymbolReference firstClassSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Class); + Assert.Equal("AClass", firstClassSymbol.SymbolName); + Assert.Equal(25, firstClassSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstClassSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstPropertySymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Property); + Assert.Equal("AProperty", firstPropertySymbol.SymbolName); + Assert.Equal(26, firstPropertySymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstPropertySymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstConstructorSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Constructor); + Assert.Equal("AClass([string]$AParameter)", firstConstructorSymbol.SymbolName); + Assert.Equal(28, firstConstructorSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstConstructorSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstMethodSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Method); + Assert.Equal("AMethod([string]$param1, [int]$param2, $param3)", firstMethodSymbol.SymbolName); + Assert.Equal(32, firstMethodSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstMethodSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstEnumSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Enum); + Assert.Equal("AEnum", firstEnumSymbol.SymbolName); + Assert.Equal(37, firstEnumSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstEnumSymbol.ScriptRegion.StartColumnNumber); + } + + [SkippableFact] + public void FindsSymbolsInDSCFile() + { + Skip.If(!s_isWindows, "DSC only works properly on Windows."); + + List symbolsResult = FindSymbolsInFile(FindSymbolsInDSCFile.SourceDetails); + + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Configuration)); + SymbolReference firstConfigurationSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Configuration); + Assert.Equal("AConfiguration", firstConfigurationSymbol.SymbolName); + Assert.Equal(2, firstConfigurationSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstConfigurationSymbol.ScriptRegion.StartColumnNumber); } [Fact] From 4c3a4ab2e7d4eb184a76406f569f1cf3b3a4f024 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Wed, 17 Aug 2022 23:24:56 +0000 Subject: [PATCH 162/327] refactor and cleanup --- .../Symbols/Vistors/FindDeclarationVisitor.cs | 60 ++------ .../Symbols/Vistors/FindReferencesVisitor.cs | 66 +++------ .../Symbols/Vistors/FindSymbolVisitor.cs | 128 ++++++------------ .../Symbols/Vistors/FindSymbolsVisitor.cs | 95 ++++++------- .../Utility/VisitorUtils.cs | 120 +++++++++++++++- 5 files changed, 230 insertions(+), 239 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs index 18aafef21..71fd99905 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs @@ -3,6 +3,7 @@ using System; using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { @@ -43,27 +44,15 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.Continue; } - // Get the start column number of the function name, - // instead of the the start column of 'function' and create new extent for the functionName - int startColumnNumber = - functionDefinitionAst.Extent.Text.IndexOf( - functionDefinitionAst.Name, StringComparison.OrdinalIgnoreCase) + 1; - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = functionDefinitionAst.Name, - StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndLineNumber = functionDefinitionAst.Extent.StartLineNumber, - EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length, - 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.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + functionDefinitionAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { + // Get the start column number of the function name, + // instead of the the start column of 'function' and create new extent for the functionName + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); + FoundDeclaration = new SymbolReference( SymbolType.Function, @@ -92,20 +81,8 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit if ((symbolRef.SymbolType is SymbolType.Type || symbolRef.SymbolType.Equals(symbolType)) && typeDefinitionAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { - // Show only type name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - typeDefinitionAst.Extent.StartColumnNumber + - typeDefinitionAst.Extent.Text.IndexOf(typeDefinitionAst.Name); - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = typeDefinitionAst.Name, - StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, - EndLineNumber = typeDefinitionAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + typeDefinitionAst.Name.Length, - File = typeDefinitionAst.Extent.File - }; + // We only want the type name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); FoundDeclaration = new SymbolReference( @@ -135,20 +112,8 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem if (symbolRef.SymbolType.Equals(symbolType) && functionMemberAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { - // Show only method/ctor name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - functionMemberAst.Extent.StartColumnNumber + - functionMemberAst.Extent.Text.IndexOf(functionMemberAst.Name); - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = functionMemberAst.Name, - StartLineNumber = functionMemberAst.Extent.StartLineNumber, - EndLineNumber = functionMemberAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + functionMemberAst.Name.Length, - File = functionMemberAst.Extent.File - }; + // We only want the method/ctor name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst); FoundDeclaration = new SymbolReference( @@ -174,10 +139,13 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem if (symbolRef.SymbolType.Equals(SymbolType.Property) && propertyMemberAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { + // We only want the property name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst); + FoundDeclaration = new SymbolReference( SymbolType.Property, - propertyMemberAst.Extent); + nameExtent); return AstVisitAction.StopVisit; } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs index 858ee8479..81fca6331 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -113,7 +113,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) /// Decides if the current function definition is a reference of the symbol being searched for. /// A reference of the symbol will be a of type SymbolType.Function and have the same name as the symbol /// - /// A functionDefinitionAst in the script's AST + /// A FunctionDefinitionAst in the script's AST /// A visit action that continues the search for references public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { @@ -124,31 +124,21 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.Continue; } - (int startColumnNumber, int startLineNumber) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst); - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = functionDefinitionAst.Name, - StartLineNumber = startLineNumber, - EndLineNumber = startLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length, - File = functionDefinitionAst.Extent.File - }; - if (_symbolRef.SymbolType.Equals(SymbolType.Function) && - nameExtent.Text.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + functionDefinitionAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { + // We only want the function name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); FoundReferences.Add(new SymbolReference(SymbolType.Function, nameExtent)); } return base.VisitFunctionDefinition(functionDefinitionAst); } /// - /// Decides if the current function definition is a reference of the symbol being searched for. + /// Decides if the current command parameter is a reference of the symbol being searched for. /// A reference of the symbol will be a of type SymbolType.Parameter and have the same name as the symbol /// - /// A commandParameterAst in the script's AST + /// A CommandParameterAst in the script's AST /// A visit action that continues the search for references public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) { @@ -161,10 +151,10 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command } /// - /// Decides if the current function definition is a reference of the symbol being searched for. + /// Decides if the current variable expression is a reference of the symbol being searched for. /// A reference of the symbol will be a of type SymbolType.Variable and have the same name as the symbol /// - /// A variableExpressionAst in the script's AST + /// A VariableExpressionAst in the script's AST /// A visit action that continues the search for references public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { @@ -191,21 +181,8 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit if ((_symbolRef.SymbolType is SymbolType.Type || _symbolRef.SymbolType.Equals(symbolType)) && typeDefinitionAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { - // Show only type name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - typeDefinitionAst.Extent.StartColumnNumber + - typeDefinitionAst.Extent.Text.IndexOf(typeDefinitionAst.Name); - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = typeDefinitionAst.Name, - StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, - EndLineNumber = typeDefinitionAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + typeDefinitionAst.Name.Length, - File = typeDefinitionAst.Extent.File - }; - + // We only want the type name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); FoundReferences.Add(new SymbolReference(symbolType, nameExtent)); } return AstVisitAction.Continue; @@ -260,21 +237,8 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem if (_symbolRef.SymbolType.Equals(symbolType) && functionMemberAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { - // Show only method/ctor name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - functionMemberAst.Extent.StartColumnNumber + - functionMemberAst.Extent.Text.IndexOf(functionMemberAst.Name); - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = functionMemberAst.Name, - StartLineNumber = functionMemberAst.Extent.StartLineNumber, - EndLineNumber = functionMemberAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + functionMemberAst.Name.Length, - File = functionMemberAst.Extent.File - }; - + // We only want the method/ctor name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst); FoundReferences.Add(new SymbolReference(symbolType, nameExtent)); } return AstVisitAction.Continue; @@ -291,7 +255,9 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem if (_symbolRef.SymbolType.Equals(SymbolType.Property) && propertyMemberAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { - FoundReferences.Add(new SymbolReference(SymbolType.Property, propertyMemberAst.Extent)); + // We only want the property name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst); + FoundReferences.Add(new SymbolReference(SymbolType.Property, nameExtent)); } return AstVisitAction.Continue; } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index 71a773c0e..697a92041 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -65,30 +65,25 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.Continue; } - int startLineNumber = functionDefinitionAst.Extent.StartLineNumber; - int startColumnNumber = functionDefinitionAst.Extent.StartColumnNumber; - int endLineNumber = functionDefinitionAst.Extent.EndLineNumber; - int endColumnNumber = functionDefinitionAst.Extent.EndColumnNumber; + IScriptExtent nameExtent; - if (!includeDefinitions) + if (includeDefinitions) { - // We only want the function name - (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst); - startLineNumber = startLine; - startColumnNumber = startColumn; - endLineNumber = startLine; - endColumnNumber = startColumn + functionDefinitionAst.Name.Length; + nameExtent = new ScriptExtent() + { + Text = functionDefinitionAst.Name, + StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, + EndLineNumber = functionDefinitionAst.Extent.EndLineNumber, + StartColumnNumber = functionDefinitionAst.Extent.StartColumnNumber, + EndColumnNumber = functionDefinitionAst.Extent.EndColumnNumber, + File = functionDefinitionAst.Extent.File + }; } - - IScriptExtent nameExtent = new ScriptExtent() + else { - Text = functionDefinitionAst.Name, - StartLineNumber = startLineNumber, - EndLineNumber = endLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = endColumnNumber, - File = functionDefinitionAst.Extent.File - }; + // We only want the function name + nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); + } if (IsPositionInExtent(nameExtent)) { @@ -163,20 +158,8 @@ private bool IsPositionInExtent(IScriptExtent extent) /// or a decision to continue if it wasn't found public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) { - // Show only method/ctor name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - functionMemberAst.Extent.StartColumnNumber + - functionMemberAst.Extent.Text.IndexOf(functionMemberAst.Name); - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = functionMemberAst.Name, - StartLineNumber = functionMemberAst.Extent.StartLineNumber, - EndLineNumber = functionMemberAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + functionMemberAst.Name.Length, - File = functionMemberAst.Extent.File - }; + // We only want the method/ctor name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst); if (IsPositionInExtent(nameExtent)) { @@ -203,31 +186,25 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem /// or a decision to continue if it wasn't found public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { - int startLineNumber = typeDefinitionAst.Extent.StartLineNumber; - int startColumnNumber = typeDefinitionAst.Extent.StartColumnNumber; - int endLineNumber = typeDefinitionAst.Extent.EndLineNumber; - int endColumnNumber = typeDefinitionAst.Extent.EndColumnNumber; + IScriptExtent nameExtent; - if (!includeDefinitions) + if (includeDefinitions) { - // We only want the function name - startColumnNumber = - typeDefinitionAst.Extent.StartColumnNumber + - typeDefinitionAst.Extent.Text.IndexOf(typeDefinitionAst.Name); - startLineNumber = typeDefinitionAst.Extent.StartLineNumber; - endColumnNumber = startColumnNumber + typeDefinitionAst.Name.Length; - endLineNumber = typeDefinitionAst.Extent.StartLineNumber; + nameExtent = new ScriptExtent() + { + Text = typeDefinitionAst.Name, + StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, + EndLineNumber = typeDefinitionAst.Extent.EndLineNumber, + StartColumnNumber = typeDefinitionAst.Extent.StartColumnNumber, + EndColumnNumber = typeDefinitionAst.Extent.EndColumnNumber, + File = typeDefinitionAst.Extent.File + }; } - - IScriptExtent nameExtent = new ScriptExtent() + else { - Text = typeDefinitionAst.Name, - StartLineNumber = startLineNumber, - EndLineNumber = endLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = endColumnNumber, - File = typeDefinitionAst.Extent.File - }; + // We only want the type name + nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); + } if (IsPositionInExtent(nameExtent)) { @@ -254,10 +231,8 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit /// or a decision to continue if it wasn't found public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) { - // Show only type name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - typeExpressionAst.Extent.StartColumnNumber + - typeExpressionAst.Extent.Text.IndexOf(typeExpressionAst.TypeName.Name); + // Show only type name (skip leading '['). Offset by StartColumn to include indentation etc. + int startColumnNumber = typeExpressionAst.Extent.StartColumnNumber + 1; IScriptExtent nameExtent = new ScriptExtent() { @@ -288,10 +263,8 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress /// or a decision to continue if it wasn't found public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { - // Show only type name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - typeConstraintAst.Extent.StartColumnNumber + - typeConstraintAst.Extent.Text.IndexOf(typeConstraintAst.TypeName.Name); + // Show only type name (skip leading '['). Offset by StartColumn to include indentation etc. + int startColumnNumber = typeConstraintAst.Extent.StartColumnNumber + 1; IScriptExtent nameExtent = new ScriptExtent() { @@ -322,22 +295,8 @@ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstra /// or a decision to continue if it wasn't found public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { - string configurationName = configurationDefinitionAst.InstanceName.Extent.Text; - - // Show only configuration name. Offset by StartColumn to include indentation etc. - int startColumnNumber = - configurationDefinitionAst.Extent.StartColumnNumber + - configurationDefinitionAst.Extent.Text.IndexOf(configurationName); - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = configurationName, - StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, - EndLineNumber = configurationDefinitionAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + configurationName.Length, - File = configurationDefinitionAst.Extent.File - }; + // We only want the configuration name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(configurationDefinitionAst); if (IsPositionInExtent(nameExtent)) { @@ -353,19 +312,22 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit } /// - /// Checks to see if this variable expression is the symbol we are looking for. + /// Checks to see if this property member is the symbol we are looking for. /// - /// A VariableExpressionAst object in the script's AST + /// A PropertyMemberAst object in the script's AST /// A decision to stop searching if the right symbol was found, /// or a decision to continue if it wasn't found public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { - if (IsPositionInExtent(propertyMemberAst.Extent)) + // We only want the property name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst); + + if (IsPositionInExtent(nameExtent)) { FoundSymbolReference = new SymbolReference( SymbolType.Property, - propertyMemberAst.Extent); + nameExtent); return AstVisitAction.StopVisit; } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs index d7fe87db1..b66b6f265 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs @@ -16,11 +16,10 @@ internal class FindSymbolsVisitor : AstVisitor2 public FindSymbolsVisitor() => SymbolReferences = new List(); /// - /// Adds each function definition as a + /// Adds each function definition to symbol reference list /// - /// A functionDefinitionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found + /// A FunctionDefinitionAst in the script's AST + /// A visit action that continues the search for references public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. @@ -30,15 +29,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.Continue; } - IScriptExtent nameExtent = new ScriptExtent() - { - Text = functionDefinitionAst.Name, - StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, - EndLineNumber = functionDefinitionAst.Extent.EndLineNumber, - StartColumnNumber = functionDefinitionAst.Extent.StartColumnNumber, - EndColumnNumber = functionDefinitionAst.Extent.EndColumnNumber, - File = functionDefinitionAst.Extent.File - }; + IScriptExtent nameExtent = GetNewExtent(functionDefinitionAst, functionDefinitionAst.Name); SymbolType symbolType = functionDefinitionAst.IsWorkflow ? @@ -53,11 +44,10 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun } /// - /// Checks to see if this variable expression is the symbol we are looking for. + /// Adds each script scoped variable assignment to symbol reference list /// - /// A VariableExpressionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found + /// A VariableExpressionAst in the script's AST + /// A visit action that continues the search for references public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { if (!IsAssignedAtScriptScope(variableExpressionAst)) @@ -86,19 +76,13 @@ private static bool IsAssignedAtScriptScope(VariableExpressionAst variableExpres } /// - /// Adds class and AST to symbol reference list + /// Adds class and enum AST to symbol reference list /// + /// A TypeDefinitionAst in the script's AST + /// A visit action that continues the search for references public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { - IScriptExtent nameExtent = new ScriptExtent() - { - Text = typeDefinitionAst.Name, - StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, - EndLineNumber = typeDefinitionAst.Extent.EndLineNumber, - StartColumnNumber = typeDefinitionAst.Extent.StartColumnNumber, - EndColumnNumber = typeDefinitionAst.Extent.EndColumnNumber, - File = typeDefinitionAst.Extent.File - }; + IScriptExtent nameExtent = GetNewExtent(typeDefinitionAst, typeDefinitionAst.Name); SymbolType symbolType = typeDefinitionAst.IsEnum ? @@ -115,17 +99,11 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit /// /// Adds class method and constructor AST to symbol reference list /// + /// A FunctionMemberAst in the script's AST + /// A visit action that continues the search for references public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) { - IScriptExtent nameExtent = new ScriptExtent() - { - Text = GetMethodOverloadName(functionMemberAst), - StartLineNumber = functionMemberAst.Extent.StartLineNumber, - EndLineNumber = functionMemberAst.Extent.EndLineNumber, - StartColumnNumber = functionMemberAst.Extent.StartColumnNumber, - EndColumnNumber = functionMemberAst.Extent.EndColumnNumber, - File = functionMemberAst.Extent.File - }; + IScriptExtent nameExtent = GetNewExtent(functionMemberAst, GetMethodOverloadName(functionMemberAst)); SymbolType symbolType = functionMemberAst.IsConstructor ? @@ -144,7 +122,8 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem /// /// A FunctionMemberAst object in the script's AST /// Function member name with parameter types and names - private static string GetMethodOverloadName(FunctionMemberAst functionMemberAst) { + private static string GetMethodOverloadName(FunctionMemberAst functionMemberAst) + { if (functionMemberAst.Parameters.Count > 0) { List parameters = new(functionMemberAst.Parameters.Count); @@ -165,17 +144,11 @@ private static string GetMethodOverloadName(FunctionMemberAst functionMemberAst) /// /// Adds class property AST to symbol reference list /// + /// A PropertyMemberAst in the script's AST + /// A visit action that continues the search for references public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { - IScriptExtent nameExtent = new ScriptExtent() - { - Text = propertyMemberAst.Name, - StartLineNumber = propertyMemberAst.Extent.StartLineNumber, - EndLineNumber = propertyMemberAst.Extent.EndLineNumber, - StartColumnNumber = propertyMemberAst.Extent.StartColumnNumber, - EndColumnNumber = propertyMemberAst.Extent.EndColumnNumber, - File = propertyMemberAst.Extent.File - }; + IScriptExtent nameExtent = GetNewExtent(propertyMemberAst, propertyMemberAst.Name); SymbolReferences.Add( new SymbolReference( @@ -188,17 +161,11 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem /// /// Adds DSC configuration AST to symbol reference list /// + /// A ConfigurationDefinitionAst in the script's AST + /// A visit action that continues the search for references public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { - IScriptExtent nameExtent = new ScriptExtent() - { - Text = configurationDefinitionAst.InstanceName.Extent.Text, - StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber, - EndLineNumber = configurationDefinitionAst.Extent.EndLineNumber, - StartColumnNumber = configurationDefinitionAst.Extent.StartColumnNumber, - EndColumnNumber = configurationDefinitionAst.Extent.EndColumnNumber, - File = configurationDefinitionAst.Extent.File - }; + IScriptExtent nameExtent = GetNewExtent(configurationDefinitionAst, configurationDefinitionAst.InstanceName.Extent.Text); SymbolReferences.Add( new SymbolReference( @@ -207,6 +174,22 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit return AstVisitAction.Continue; } + + /// + /// Gets a new ScriptExtent for a given Ast with same range but modified Text + /// + private static ScriptExtent GetNewExtent(Ast ast, string text) + { + return new ScriptExtent() + { + Text = text, + StartLineNumber = ast.Extent.StartLineNumber, + EndLineNumber = ast.Extent.EndLineNumber, + StartColumnNumber = ast.Extent.StartColumnNumber, + EndColumnNumber = ast.Extent.EndColumnNumber, + File = ast.Extent.File + }; + } } /// @@ -227,6 +210,8 @@ internal class FindHashtableSymbolsVisitor : AstVisitor /// /// Adds keys in the input hashtable to the symbol reference /// + /// A HashtableAst in the script's AST + /// A visit action that continues the search for references public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) { if (hashtableAst.KeyValuePairs == null) diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index 861d04acc..3456c1920 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Management.Automation.Language; +using PSESSymbols = Microsoft.PowerShell.EditorServices.Services.Symbols; namespace Microsoft.PowerShell.EditorServices.Utility { @@ -11,15 +12,16 @@ namespace Microsoft.PowerShell.EditorServices.Utility internal static class VisitorUtils { /// - /// Calculates the start line and column of the actual function name in a function definition AST. + /// Calculates the start line and column of the actual symbol name in a AST. /// - /// A FunctionDefinitionAst object in the script's AST - /// A tuple with start column and line for the function name - internal static (int startColumn, int startLine) GetNameStartColumnAndLineNumbersFromAst(FunctionDefinitionAst ast) + /// An Ast object in the script's AST + /// An offset specifying where to begin searching in the first line of the AST's extent text + /// A tuple with start column and line of the symbol name + private static (int startColumn, int startLine) GetNameStartColumnAndLineNumbersFromAst(Ast ast, int firstLineColumnOffset) { int startColumnNumber = ast.Extent.StartColumnNumber; int startLineNumber = ast.Extent.StartLineNumber; - int astOffset = ast.IsFilter ? "filter".Length : ast.IsWorkflow ? "workflow".Length : "function".Length; + int astOffset = firstLineColumnOffset; string astText = ast.Extent.Text; // The line offset represents the offset on the line that we're on where as // astOffset is the offset on the entire text of the AST. @@ -47,5 +49,113 @@ internal static (int startColumn, int startLine) GetNameStartColumnAndLineNumber return (startColumnNumber + lineOffset, startLineNumber); } + + /// + /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable) + /// + /// A FunctionDefinitionAst in the script's AST + /// A ScriptExtent with for the symbol name only + internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionDefinitionAst functionDefinitionAst) + { + int astOffset = functionDefinitionAst.IsFilter ? "filter".Length : functionDefinitionAst.IsWorkflow ? "workflow".Length : "function".Length; + (int startColumn, int startLine) = GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst, astOffset); + + return new PSESSymbols.ScriptExtent() + { + Text = functionDefinitionAst.Name, + StartLineNumber = startLine, + EndLineNumber = startLine, + StartColumnNumber = startColumn, + EndColumnNumber = startColumn + functionDefinitionAst.Name.Length, + File = functionDefinitionAst.Extent.File + }; + } + + /// + /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable) + /// + /// A TypeDefinitionAst in the script's AST + /// A ScriptExtent with for the symbol name only + internal static PSESSymbols.ScriptExtent GetNameExtent(TypeDefinitionAst typeDefinitionAst) + { + int astOffset = typeDefinitionAst.IsEnum ? "enum".Length : "class".Length; + (int startColumn, int startLine) = GetNameStartColumnAndLineNumbersFromAst(typeDefinitionAst, astOffset); + + return new PSESSymbols.ScriptExtent() + { + Text = typeDefinitionAst.Name, + StartLineNumber = startLine, + EndLineNumber = startLine, + StartColumnNumber = startColumn, + EndColumnNumber = startColumn + typeDefinitionAst.Name.Length, + File = typeDefinitionAst.Extent.File + }; + } + + /// + /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable) + /// + /// A FunctionMemberAst in the script's AST + /// A ScriptExtent with for the symbol name only + internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functionMemberAst) + { + // offset by [type] if return type is specified + int astOffset = functionMemberAst.ReturnType?.Extent.Text.Length ?? 0; + (int startColumn, int startLine) = GetNameStartColumnAndLineNumbersFromAst(functionMemberAst, astOffset); + + return new PSESSymbols.ScriptExtent() + { + Text = functionMemberAst.Name, + StartLineNumber = startLine, + EndLineNumber = startLine, + StartColumnNumber = startColumn, + EndColumnNumber = startColumn + functionMemberAst.Name.Length, + File = functionMemberAst.Extent.File + }; + } + + /// + /// Gets a new ScriptExtent for a given Ast for the property name only + /// + /// A PropertyMemberAst in the script's AST + /// A ScriptExtent with for the symbol name only + internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propertyMemberAst) + { + // offset by [type] if type is specified + int astOffset = propertyMemberAst.PropertyType?.Extent.Text.Length ?? 0; + (int startColumn, int startLine) = GetNameStartColumnAndLineNumbersFromAst(propertyMemberAst, astOffset); + + return new PSESSymbols.ScriptExtent() + { + Text = propertyMemberAst.Name, + StartLineNumber = startLine, + EndLineNumber = startLine, + StartColumnNumber = startColumn, + EndColumnNumber = startColumn + propertyMemberAst.Name.Length + 1, + File = propertyMemberAst.Extent.File + }; + } + + /// + /// Gets a new ScriptExtent for a given Ast for the configuration instance name only + /// + /// A ConfigurationDefinitionAst in the script's AST + /// A ScriptExtent with for the symbol name only + internal static PSESSymbols.ScriptExtent GetNameExtent(ConfigurationDefinitionAst configurationDefinitionAst) + { + string configurationName = configurationDefinitionAst.InstanceName.Extent.Text; + const int astOffset = 13; // "configuration".Length + (int startColumn, int startLine) = GetNameStartColumnAndLineNumbersFromAst(configurationDefinitionAst, astOffset); + + return new PSESSymbols.ScriptExtent() + { + Text = configurationName, + StartLineNumber = startLine, + EndLineNumber = startLine, + StartColumnNumber = startColumn, + EndColumnNumber = startColumn + configurationName.Length, + File = configurationDefinitionAst.Extent.File + }; + } } } From ad58bcd83282a1ff3a50063c56cddc6dcc8a972f Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sat, 20 Aug 2022 02:50:17 +0000 Subject: [PATCH 163/327] fix Contains extension method for same line extent --- .../Services/Symbols/Vistors/FindSymbolVisitor.cs | 10 ++++++---- src/PowerShellEditorServices/Utility/Extensions.cs | 9 ++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index 697a92041..8269eb2d9 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Management.Automation.Language; @@ -85,7 +85,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); } - if (IsPositionInExtent(nameExtent)) + if (nameExtent.Contains(lineNumber, columnNumber)) { FoundSymbolReference = new SymbolReference( @@ -139,7 +139,9 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var } /// - /// Is the position of the given location is in the ast's extent + /// Is the position of the given location is in the ast's extent. + /// Only works with single-line extents like name extents. + /// Use extension for definition extents. /// /// The script extent of the element /// True if the given position is in the range of the element's extent @@ -206,7 +208,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); } - if (IsPositionInExtent(nameExtent)) + if (nameExtent.Contains(lineNumber, columnNumber)) { SymbolType symbolType = typeDefinitionAst.IsEnum ? diff --git a/src/PowerShellEditorServices/Utility/Extensions.cs b/src/PowerShellEditorServices/Utility/Extensions.cs index c280f1b14..22148e8b3 100644 --- a/src/PowerShellEditorServices/Utility/Extensions.cs +++ b/src/PowerShellEditorServices/Utility/Extensions.cs @@ -135,7 +135,14 @@ public static bool Contains(this IScriptExtent scriptExtent, int line, int colum if (scriptExtent.StartLineNumber == line) { - return scriptExtent.StartColumnNumber <= column; + if (scriptExtent.StartLineNumber == scriptExtent.EndLineNumber) + { + return scriptExtent.StartColumnNumber <= column && scriptExtent.EndColumnNumber >= column; + } + else + { + return scriptExtent.StartColumnNumber <= column; + } } if (scriptExtent.EndLineNumber == line) From 12b1366dc90071d801bba82a03f20f76a90a1e9c Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sat, 20 Aug 2022 02:54:35 +0000 Subject: [PATCH 164/327] add EnumMember, signature and fix detection --- .../Services/Symbols/SymbolDetails.cs | 11 +- .../Services/Symbols/SymbolType.cs | 5 + .../Services/Symbols/SymbolsService.cs | 3 +- .../Services/Symbols/Vistors/AstOperations.cs | 7 +- .../Symbols/Vistors/FindDeclarationVisitor.cs | 12 +- .../Symbols/Vistors/FindReferencesVisitor.cs | 13 +- .../Symbols/Vistors/FindSymbolVisitor.cs | 24 ++- .../Symbols/Vistors/FindSymbolsVisitor.cs | 53 ++---- .../Handlers/DocumentSymbolHandler.cs | 1 + .../Utility/VisitorUtils.cs | 179 +++++++++++++++--- 10 files changed, 222 insertions(+), 86 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index fc85acd30..84d0cef29 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -95,17 +95,10 @@ await CommandHelpers.GetCommandSynopsisAsync( return symbolDetails; case SymbolType.Constructor: - // TODO: constructor Class(parameters) - symbolDetails.DisplayString = "constructor " + symbolReference.SymbolName; - return symbolDetails; - case SymbolType.Method: - // TODO: method ReturnType Class.MethodName(parameters) - symbolDetails.DisplayString = "method " + symbolReference.SymbolName; - return symbolDetails; - + case SymbolType.EnumMember: case SymbolType.Property: - symbolDetails.DisplayString = "(property) " + symbolReference.SymbolName; + symbolDetails.DisplayString = symbolReference.SymbolName; return symbolDetails; case SymbolType.Configuration: diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index 84756661c..7f5850f45 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -53,6 +53,11 @@ internal enum SymbolType /// Enum, + /// + /// The symbol is a enum member/value + /// + EnumMember, + /// /// The symbol is a class property /// diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 336495bd8..2e22ac1dc 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -332,7 +332,8 @@ public Task FindSymbolDetailsAtLocationAsync( AstOperations.FindSymbolAtPosition( scriptFile.ScriptAst, lineNumber, - columnNumber); + columnNumber, + returnMemberSignature: true); if (symbolReference == null) { diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 6fcbd62c9..623fc02ba 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -148,18 +148,21 @@ await executionService.ExecuteDelegateAsync( /// The line number of the cursor for the given script /// The column number of the cursor for the given script /// Includes full symbol definition ranges in the search. + /// Includes return type and class in symbol name. /// SymbolReference of found symbol public static SymbolReference FindSymbolAtPosition( Ast scriptAst, int lineNumber, int columnNumber, - bool includeDefinitions = false) + bool includeDefinitions = false, + bool returnMemberSignature = false) { FindSymbolVisitor symbolVisitor = new( lineNumber, columnNumber, - includeDefinitions); + includeDefinitions, + returnMemberSignature); scriptAst.Visit(symbolVisitor); diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs index 71fd99905..e3d111923 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs @@ -110,7 +110,7 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem SymbolType.Constructor : SymbolType.Method; if (symbolRef.SymbolType.Equals(symbolType) && - functionMemberAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + VisitorUtils.GetMemberOverloadName(functionMemberAst).Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { // We only want the method/ctor name. Get start-location for name IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst); @@ -129,15 +129,19 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem /// /// Decides if the current property member is the right definition /// for the symbol being searched for. The definition of the symbol will be a of type - /// SymbolType.Property and have the same name as the symbol + /// SymbolType.Property or SymbolType.EnumMember and have the same name as the symbol /// /// A PropertyMemberAst in the script's AST /// A decision to stop searching if the right PropertyMemberAst was found, /// or a decision to continue if it wasn't found public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { - if (symbolRef.SymbolType.Equals(SymbolType.Property) && - propertyMemberAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + SymbolType symbolType = + propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? + SymbolType.EnumMember : SymbolType.Property; + + if (symbolRef.SymbolType.Equals(symbolType) && + VisitorUtils.GetMemberOverloadName(propertyMemberAst).Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { // We only want the property name. Get start-location for name IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst); diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs index 81fca6331..6bdab55bd 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs @@ -235,7 +235,7 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem SymbolType.Constructor : SymbolType.Method; if (_symbolRef.SymbolType.Equals(symbolType) && - functionMemberAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + VisitorUtils.GetMemberOverloadName(functionMemberAst).Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { // We only want the method/ctor name. Get start-location for name IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst); @@ -246,14 +246,19 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem /// /// Decides if the current property member is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Property and have the same name as the symbol + /// A reference of the symbol will be a of type SymbolType.Property or SymbolType.EnumMember + /// and have the same name as the symbol. /// /// A PropertyMemberAst in the script's AST /// A visit action that continues the search for references public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { - if (_symbolRef.SymbolType.Equals(SymbolType.Property) && - propertyMemberAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + SymbolType symbolType = + propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? + SymbolType.EnumMember : SymbolType.Property; + + if (_symbolRef.SymbolType.Equals(symbolType) && + VisitorUtils.GetMemberOverloadName(propertyMemberAst).Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { // We only want the property name. Get start-location for name IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst); diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index 8269eb2d9..5442fb681 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Management.Automation.Language; @@ -14,17 +14,20 @@ internal class FindSymbolVisitor : AstVisitor2 private readonly int lineNumber; private readonly int columnNumber; private readonly bool includeDefinitions; + private readonly bool returnMemberSignature; public SymbolReference FoundSymbolReference { get; private set; } public FindSymbolVisitor( int lineNumber, int columnNumber, - bool includeDefinitions) + bool includeDefinitions, + bool returnMemberSignature) { this.lineNumber = lineNumber; this.columnNumber = columnNumber; this.includeDefinitions = includeDefinitions; + this.returnMemberSignature = returnMemberSignature; } /// @@ -161,7 +164,7 @@ private bool IsPositionInExtent(IScriptExtent extent) public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) { // We only want the method/ctor name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, returnMemberSignature); if (IsPositionInExtent(nameExtent)) { @@ -265,8 +268,11 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress /// or a decision to continue if it wasn't found public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { - // Show only type name (skip leading '['). Offset by StartColumn to include indentation etc. - int startColumnNumber = typeConstraintAst.Extent.StartColumnNumber + 1; + // Show only type name (skip leading '[' if present). It's not present for inherited types + // Offset by StartColumn to include indentation etc. + int startColumnNumber = + typeConstraintAst.Extent.Text[0] == '[' ? + typeConstraintAst.Extent.StartColumnNumber + 1 : typeConstraintAst.Extent.StartColumnNumber; IScriptExtent nameExtent = new ScriptExtent() { @@ -322,13 +328,17 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { // We only want the property name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, returnMemberSignature); if (IsPositionInExtent(nameExtent)) { + SymbolType symbolType = + propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? + SymbolType.EnumMember : SymbolType.Property; + FoundSymbolReference = new SymbolReference( - SymbolType.Property, + symbolType, nameExtent); return AstVisitAction.StopVisit; diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs index b66b6f265..f2b78abe3 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Microsoft.PowerShell.EditorServices.Utility; using System.Collections.Generic; using System.Management.Automation.Language; @@ -29,7 +30,8 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.Continue; } - IScriptExtent nameExtent = GetNewExtent(functionDefinitionAst, functionDefinitionAst.Name); + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(functionDefinitionAst); + IScriptExtent nameExtent = GetNewExtent(functionDefinitionAst, functionDefinitionAst.Name, startLine, startColumn); SymbolType symbolType = functionDefinitionAst.IsWorkflow ? @@ -82,7 +84,8 @@ private static bool IsAssignedAtScriptScope(VariableExpressionAst variableExpres /// A visit action that continues the search for references public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { - IScriptExtent nameExtent = GetNewExtent(typeDefinitionAst, typeDefinitionAst.Name); + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(typeDefinitionAst); + IScriptExtent nameExtent = GetNewExtent(typeDefinitionAst, typeDefinitionAst.Name, startLine, startColumn); SymbolType symbolType = typeDefinitionAst.IsEnum ? @@ -103,7 +106,8 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit /// A visit action that continues the search for references public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) { - IScriptExtent nameExtent = GetNewExtent(functionMemberAst, GetMethodOverloadName(functionMemberAst)); + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(functionMemberAst); + IScriptExtent nameExtent = GetNewExtent(functionMemberAst, VisitorUtils.GetMemberOverloadName(functionMemberAst), startLine, startColumn); SymbolType symbolType = functionMemberAst.IsConstructor ? @@ -117,30 +121,6 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem return AstVisitAction.Continue; } - /// - /// Gets the method or constructor name with parameters for current overload. - /// - /// A FunctionMemberAst object in the script's AST - /// Function member name with parameter types and names - private static string GetMethodOverloadName(FunctionMemberAst functionMemberAst) - { - if (functionMemberAst.Parameters.Count > 0) - { - List parameters = new(functionMemberAst.Parameters.Count); - foreach (ParameterAst param in functionMemberAst.Parameters) - { - parameters.Add(param.Extent.Text); - } - - string paramString = string.Join(", ", parameters); - return string.Concat(functionMemberAst.Name, "(", paramString, ")"); - } - else - { - return string.Concat(functionMemberAst.Name, "()"); - } - } - /// /// Adds class property AST to symbol reference list /// @@ -148,11 +128,17 @@ private static string GetMethodOverloadName(FunctionMemberAst functionMemberAst) /// A visit action that continues the search for references public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { - IScriptExtent nameExtent = GetNewExtent(propertyMemberAst, propertyMemberAst.Name); + SymbolType symbolType = + propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? + SymbolType.EnumMember : SymbolType.Property; + + bool isEnumMember = symbolType.Equals(SymbolType.EnumMember); + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(propertyMemberAst, isEnumMember); + IScriptExtent nameExtent = GetNewExtent(propertyMemberAst, propertyMemberAst.Name, startLine, startColumn); SymbolReferences.Add( new SymbolReference( - SymbolType.Property, + symbolType, nameExtent)); return AstVisitAction.Continue; @@ -165,7 +151,8 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem /// A visit action that continues the search for references public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { - IScriptExtent nameExtent = GetNewExtent(configurationDefinitionAst, configurationDefinitionAst.InstanceName.Extent.Text); + (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(configurationDefinitionAst); + IScriptExtent nameExtent = GetNewExtent(configurationDefinitionAst, configurationDefinitionAst.InstanceName.Extent.Text, startLine, startColumn); SymbolReferences.Add( new SymbolReference( @@ -178,14 +165,14 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit /// /// Gets a new ScriptExtent for a given Ast with same range but modified Text /// - private static ScriptExtent GetNewExtent(Ast ast, string text) + private static ScriptExtent GetNewExtent(Ast ast, string text, int startLine, int startColumn) { return new ScriptExtent() { Text = text, - StartLineNumber = ast.Extent.StartLineNumber, + StartLineNumber = startLine, EndLineNumber = ast.Extent.EndLineNumber, - StartColumnNumber = ast.Extent.StartColumnNumber, + StartColumnNumber = startColumn, EndColumnNumber = ast.Extent.EndColumnNumber, File = ast.Extent.File }; diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index 01d544f93..5071cc745 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -134,6 +134,7 @@ private static SymbolKind GetSymbolKind(SymbolType symbolType) SymbolType.Constructor => SymbolKind.Constructor, SymbolType.Method => SymbolKind.Method, SymbolType.Property => SymbolKind.Property, + SymbolType.EnumMember => SymbolKind.EnumMember, _ => SymbolKind.Variable, }; } diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index 3456c1920..3c959f35a 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; +using System.Collections.Generic; using System.Management.Automation.Language; +using System.Text; using PSESSymbols = Microsoft.PowerShell.EditorServices.Services.Symbols; namespace Microsoft.PowerShell.EditorServices.Utility @@ -15,39 +18,94 @@ internal static class VisitorUtils /// Calculates the start line and column of the actual symbol name in a AST. /// /// An Ast object in the script's AST - /// An offset specifying where to begin searching in the first line of the AST's extent text + /// An int specifying start index of name in the AST's extent text /// A tuple with start column and line of the symbol name - private static (int startColumn, int startLine) GetNameStartColumnAndLineNumbersFromAst(Ast ast, int firstLineColumnOffset) + private static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(Ast ast, int nameStartIndex) { int startColumnNumber = ast.Extent.StartColumnNumber; int startLineNumber = ast.Extent.StartLineNumber; - int astOffset = firstLineColumnOffset; string astText = ast.Extent.Text; - // The line offset represents the offset on the line that we're on where as // astOffset is the offset on the entire text of the AST. - int lineOffset = astOffset; - for (; astOffset < astText.Length; astOffset++, lineOffset++) + for (int astOffset = 0; astOffset <= ast.Extent.Text.Length; astOffset++, startColumnNumber++) { if (astText[astOffset] == '\n') { // reset numbers since we are operating on a different line and increment the line number. startColumnNumber = 0; startLineNumber++; - lineOffset = 0; } else if (astText[astOffset] == '\r') { // Do nothing with carriage returns... we only look for line feeds since those // are used on every platform. } - else if (!char.IsWhiteSpace(astText[astOffset])) + else if (astOffset >= nameStartIndex && !char.IsWhiteSpace(astText[astOffset])) { // This is the start of the function name so we've found our start column and line number. break; } } - return (startColumnNumber + lineOffset, startLineNumber); + return (startColumnNumber, startLineNumber); + } + + /// + /// Calculates the start line and column of the actual function name in a function definition AST. + /// + /// A FunctionDefinitionAst object in the script's AST + /// A tuple with start column and line for the function name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(FunctionDefinitionAst functionDefinitionAst) + { + int startOffset = functionDefinitionAst.IsFilter ? "filter".Length : functionDefinitionAst.IsWorkflow ? "workflow".Length : "function".Length; + return GetNameStartColumnAndLineFromAst(functionDefinitionAst, startOffset); + } + + /// + /// Calculates the start line and column of the actual class/enum name in a type definition AST. + /// + /// A TypeDefinitionAst object in the script's AST + /// A tuple with start column and line for the type name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(TypeDefinitionAst typeDefinitionAst) + { + int startOffset = typeDefinitionAst.IsEnum ? "enum".Length : "class".Length; + return GetNameStartColumnAndLineFromAst(typeDefinitionAst, startOffset); + } + + /// + /// Calculates the start line and column of the actual method/constructor name in a function member AST. + /// + /// A FunctionMemberAst object in the script's AST + /// A tuple with start column and line for the method/constructor name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(FunctionMemberAst functionMemberAst) + { + // find name index to get offset even with attributes, static, hidden ++ + int nameStartIndex = functionMemberAst.Extent.Text.LastIndexOf(string.Concat(functionMemberAst.Name, '('), StringComparison.OrdinalIgnoreCase); + return GetNameStartColumnAndLineFromAst(functionMemberAst, nameStartIndex); + } + + /// + /// Calculates the start line and column of the actual property name in a property member AST. + /// + /// A PropertyMemberAst object in the script's AST + /// A bool indicating this is a enum member + /// A tuple with start column and line for the property name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(PropertyMemberAst propertyMemberAst, bool isEnumMember) + { + // find name index to get offset even with attributes, static, hidden ++ + string searchString = isEnumMember ? propertyMemberAst.Name : string.Concat('$', propertyMemberAst.Name); + int nameStartIndex = propertyMemberAst.Extent.Text.LastIndexOf(searchString, StringComparison.OrdinalIgnoreCase); + return GetNameStartColumnAndLineFromAst(propertyMemberAst, nameStartIndex); + } + + /// + /// Calculates the start line and column of the actual configuration name in a configuration definition AST. + /// + /// A ConfigurationDefinitionAst object in the script's AST + /// A tuple with start column and line for the configuration name + internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(ConfigurationDefinitionAst configurationDefinitionAst) + { + const int startOffset = 13; // "configuration".Length + return GetNameStartColumnAndLineFromAst(configurationDefinitionAst, startOffset); } /// @@ -57,8 +115,7 @@ private static (int startColumn, int startLine) GetNameStartColumnAndLineNumbers /// A ScriptExtent with for the symbol name only internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionDefinitionAst functionDefinitionAst) { - int astOffset = functionDefinitionAst.IsFilter ? "filter".Length : functionDefinitionAst.IsWorkflow ? "workflow".Length : "function".Length; - (int startColumn, int startLine) = GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst, astOffset); + (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(functionDefinitionAst); return new PSESSymbols.ScriptExtent() { @@ -78,8 +135,7 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionDefinitionAst fun /// A ScriptExtent with for the symbol name only internal static PSESSymbols.ScriptExtent GetNameExtent(TypeDefinitionAst typeDefinitionAst) { - int astOffset = typeDefinitionAst.IsEnum ? "enum".Length : "class".Length; - (int startColumn, int startLine) = GetNameStartColumnAndLineNumbersFromAst(typeDefinitionAst, astOffset); + (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(typeDefinitionAst); return new PSESSymbols.ScriptExtent() { @@ -96,16 +152,15 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(TypeDefinitionAst typeDef /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable) /// /// A FunctionMemberAst in the script's AST + /// A bool indicating if return type and class should be included /// A ScriptExtent with for the symbol name only - internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functionMemberAst) + internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functionMemberAst, bool includeSignature = false) { - // offset by [type] if return type is specified - int astOffset = functionMemberAst.ReturnType?.Extent.Text.Length ?? 0; - (int startColumn, int startLine) = GetNameStartColumnAndLineNumbersFromAst(functionMemberAst, astOffset); + (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(functionMemberAst); return new PSESSymbols.ScriptExtent() { - Text = functionMemberAst.Name, + Text = GetMemberOverloadName(functionMemberAst, includeSignature), StartLineNumber = startLine, EndLineNumber = startLine, StartColumnNumber = startColumn, @@ -118,20 +173,25 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functio /// Gets a new ScriptExtent for a given Ast for the property name only /// /// A PropertyMemberAst in the script's AST + /// A bool indicating if property-type and name of class/enum should be included /// A ScriptExtent with for the symbol name only - internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propertyMemberAst) + internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propertyMemberAst, bool includeSignature = false) { - // offset by [type] if type is specified - int astOffset = propertyMemberAst.PropertyType?.Extent.Text.Length ?? 0; - (int startColumn, int startLine) = GetNameStartColumnAndLineNumbersFromAst(propertyMemberAst, astOffset); + bool isEnumMember = propertyMemberAst.Parent is TypeDefinitionAst typeDef && typeDef.IsEnum; + (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(propertyMemberAst, isEnumMember); + + // +1 when class property to as start includes $ + int endColumnNumber = isEnumMember ? + startColumn + propertyMemberAst.Name.Length : + startColumn + propertyMemberAst.Name.Length + 1; return new PSESSymbols.ScriptExtent() { - Text = propertyMemberAst.Name, + Text = GetMemberOverloadName(propertyMemberAst, includeSignature), StartLineNumber = startLine, EndLineNumber = startLine, StartColumnNumber = startColumn, - EndColumnNumber = startColumn + propertyMemberAst.Name.Length + 1, + EndColumnNumber = endColumnNumber, File = propertyMemberAst.Extent.File }; } @@ -144,8 +204,7 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propert internal static PSESSymbols.ScriptExtent GetNameExtent(ConfigurationDefinitionAst configurationDefinitionAst) { string configurationName = configurationDefinitionAst.InstanceName.Extent.Text; - const int astOffset = 13; // "configuration".Length - (int startColumn, int startLine) = GetNameStartColumnAndLineNumbersFromAst(configurationDefinitionAst, astOffset); + (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(configurationDefinitionAst); return new PSESSymbols.ScriptExtent() { @@ -157,5 +216,73 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(ConfigurationDefinitionAs File = configurationDefinitionAst.Extent.File }; } + + /// + /// Gets the method or constructor name with parameters for current overload. + /// + /// A FunctionMemberAst object in the script's AST + /// A bool indicating if return type and class should be included + /// Function member name with return type (optional) and parameters + internal static string GetMemberOverloadName(FunctionMemberAst functionMemberAst, bool includeSignature = false) + { + StringBuilder sb = new(); + + // Prepend return type and class. Used for symbol details (hover) + if (includeSignature) + { + if (!functionMemberAst.IsConstructor) + { + sb.Append(functionMemberAst.ReturnType?.TypeName.Name ?? "void").Append(' '); + } + + if (functionMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsClass) + { + sb.Append(typeAst.Name).Append('.'); + } + } + + sb.Append(functionMemberAst.Name); + + // Add parameters + sb.Append('('); + if (functionMemberAst.Parameters.Count > 0) + { + List parameters = new(functionMemberAst.Parameters.Count); + foreach (ParameterAst param in functionMemberAst.Parameters) + { + parameters.Add(param.Extent.Text); + } + + sb.Append(string.Join(", ", parameters)); + } + sb.Append(')'); + + return sb.ToString(); + } + + /// + /// Gets the property name with type and class/enum. + /// + /// A PropertyMemberAst object in the script's AST + /// A bool indicating if property-type and name of class/enum should be included + /// Property name with type (optional) and class/enum + internal static string GetMemberOverloadName(PropertyMemberAst propertyMemberAst, bool includeSignature = false) + { + StringBuilder sb = new(); + + // Prepend return type and class. Used for symbol details (hover) + if (includeSignature && propertyMemberAst.Parent is TypeDefinitionAst typeAst) + { + if (!typeAst.IsEnum) + { + sb.Append(propertyMemberAst.PropertyType?.TypeName.Name ?? "object").Append(' '); + } + + sb.Append(typeAst.Name).Append('.'); + } + + sb.Append(propertyMemberAst.Name); + return sb.ToString(); + } } } From 884fecb7f95891b38d525e3487b970ab40c62f4d Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sun, 21 Aug 2022 15:00:58 +0000 Subject: [PATCH 165/327] update symbols test with name position --- .../LanguageServerProtocolMessageTests.cs | 6 +++--- .../Language/SymbolsServiceTests.cs | 20 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 0ca4bb2e3..5011d5245 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -442,7 +442,7 @@ await PsesLanguageClient Range range = symInfoOrDocSym.SymbolInformation.Location.Range; Assert.Equal(1, range.Start.Line); - Assert.Equal(0, range.Start.Character); + Assert.Equal(9, range.Start.Character); Assert.Equal(3, range.End.Line); Assert.Equal(1, range.End.Character); }); @@ -867,7 +867,7 @@ function CanSendReferencesCodeLensRequest { Range range = codeLens.Range; Assert.Equal(1, range.Start.Line); - Assert.Equal(0, range.Start.Character); + Assert.Equal(9, range.Start.Character); Assert.Equal(3, range.End.Line); Assert.Equal(1, range.End.Character); diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index b6552c0fb..27e0e8893 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -290,15 +290,16 @@ public void FindsSymbolsInFile() Assert.Equal(3, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable)); Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Workflow)); Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); - Assert.Equal(2, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Property)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); SymbolReference firstFunctionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Function); Assert.Equal("AFunction", firstFunctionSymbol.SymbolName); Assert.Equal(7, firstFunctionSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstFunctionSymbol.ScriptRegion.StartColumnNumber); + Assert.Equal(10, firstFunctionSymbol.ScriptRegion.StartColumnNumber); SymbolReference lastVariableSymbol = symbolsResult.Last(r => r.SymbolType == SymbolType.Variable); Assert.Equal("$Script:ScriptVar2", lastVariableSymbol.SymbolName); @@ -308,17 +309,17 @@ public void FindsSymbolsInFile() SymbolReference firstWorkflowSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Workflow); Assert.Equal("AWorkflow", firstWorkflowSymbol.SymbolName); Assert.Equal(23, firstWorkflowSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstWorkflowSymbol.ScriptRegion.StartColumnNumber); + Assert.Equal(10, firstWorkflowSymbol.ScriptRegion.StartColumnNumber); SymbolReference firstClassSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Class); Assert.Equal("AClass", firstClassSymbol.SymbolName); Assert.Equal(25, firstClassSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstClassSymbol.ScriptRegion.StartColumnNumber); + Assert.Equal(7, firstClassSymbol.ScriptRegion.StartColumnNumber); SymbolReference firstPropertySymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Property); Assert.Equal("AProperty", firstPropertySymbol.SymbolName); Assert.Equal(26, firstPropertySymbol.ScriptRegion.StartLineNumber); - Assert.Equal(5, firstPropertySymbol.ScriptRegion.StartColumnNumber); + Assert.Equal(13, firstPropertySymbol.ScriptRegion.StartColumnNumber); SymbolReference firstConstructorSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Constructor); Assert.Equal("AClass([string]$AParameter)", firstConstructorSymbol.SymbolName); @@ -328,12 +329,17 @@ public void FindsSymbolsInFile() SymbolReference firstMethodSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Method); Assert.Equal("AMethod([string]$param1, [int]$param2, $param3)", firstMethodSymbol.SymbolName); Assert.Equal(32, firstMethodSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(5, firstMethodSymbol.ScriptRegion.StartColumnNumber); + Assert.Equal(11, firstMethodSymbol.ScriptRegion.StartColumnNumber); SymbolReference firstEnumSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Enum); Assert.Equal("AEnum", firstEnumSymbol.SymbolName); Assert.Equal(37, firstEnumSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstEnumSymbol.ScriptRegion.StartColumnNumber); + Assert.Equal(6, firstEnumSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstEnumMemberSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.EnumMember); + Assert.Equal("AValue", firstEnumMemberSymbol.SymbolName); + Assert.Equal(38, firstEnumMemberSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstEnumMemberSymbol.ScriptRegion.StartColumnNumber); } [SkippableFact] From 109dbb012773c18aa3fdc95c76b71113a324eef3 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sun, 21 Aug 2022 16:47:18 +0000 Subject: [PATCH 166/327] check type for property and function members --- .../Services/Symbols/SymbolsService.cs | 2 +- .../Services/Symbols/Vistors/AstOperations.cs | 6 +-- .../Symbols/Vistors/FindDeclarationVisitor.cs | 8 ++-- .../Symbols/Vistors/FindReferencesVisitor.cs | 8 ++-- .../Symbols/Vistors/FindSymbolVisitor.cs | 10 ++--- .../Symbols/Vistors/FindSymbolsVisitor.cs | 2 +- .../Utility/VisitorUtils.cs | 44 ++++++++++--------- 7 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 2e22ac1dc..294cbb986 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -333,7 +333,7 @@ public Task FindSymbolDetailsAtLocationAsync( scriptFile.ScriptAst, lineNumber, columnNumber, - returnMemberSignature: true); + returnFullSignature: true); if (symbolReference == null) { diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 623fc02ba..e843128d2 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -148,21 +148,21 @@ await executionService.ExecuteDelegateAsync( /// The line number of the cursor for the given script /// The column number of the cursor for the given script /// Includes full symbol definition ranges in the search. - /// Includes return type and class in symbol name. + /// Includes return or property type in symbol name. /// SymbolReference of found symbol public static SymbolReference FindSymbolAtPosition( Ast scriptAst, int lineNumber, int columnNumber, bool includeDefinitions = false, - bool returnMemberSignature = false) + bool returnFullSignature = false) { FindSymbolVisitor symbolVisitor = new( lineNumber, columnNumber, includeDefinitions, - returnMemberSignature); + returnFullSignature); scriptAst.Visit(symbolVisitor); diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs index e3d111923..8c8e1842a 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs @@ -110,10 +110,10 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem SymbolType.Constructor : SymbolType.Method; if (symbolRef.SymbolType.Equals(symbolType) && - VisitorUtils.GetMemberOverloadName(functionMemberAst).Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + VisitorUtils.GetMemberOverloadName(functionMemberAst, true, false).Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { // We only want the method/ctor name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, false); FoundDeclaration = new SymbolReference( @@ -141,10 +141,10 @@ propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? SymbolType.EnumMember : SymbolType.Property; if (symbolRef.SymbolType.Equals(symbolType) && - VisitorUtils.GetMemberOverloadName(propertyMemberAst).Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + VisitorUtils.GetMemberOverloadName(propertyMemberAst, false).Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { // We only want the property name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); FoundDeclaration = new SymbolReference( diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs index 6bdab55bd..7f6014a2a 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs @@ -235,10 +235,10 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem SymbolType.Constructor : SymbolType.Method; if (_symbolRef.SymbolType.Equals(symbolType) && - VisitorUtils.GetMemberOverloadName(functionMemberAst).Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + VisitorUtils.GetMemberOverloadName(functionMemberAst, true, false).Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { // We only want the method/ctor name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, false); FoundReferences.Add(new SymbolReference(symbolType, nameExtent)); } return AstVisitAction.Continue; @@ -258,10 +258,10 @@ propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? SymbolType.EnumMember : SymbolType.Property; if (_symbolRef.SymbolType.Equals(symbolType) && - VisitorUtils.GetMemberOverloadName(propertyMemberAst).Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) + VisitorUtils.GetMemberOverloadName(propertyMemberAst, false).Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) { // We only want the property name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); FoundReferences.Add(new SymbolReference(SymbolType.Property, nameExtent)); } return AstVisitAction.Continue; diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index 5442fb681..b45ee03eb 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -14,7 +14,7 @@ internal class FindSymbolVisitor : AstVisitor2 private readonly int lineNumber; private readonly int columnNumber; private readonly bool includeDefinitions; - private readonly bool returnMemberSignature; + private readonly bool returnFullSignature; public SymbolReference FoundSymbolReference { get; private set; } @@ -22,12 +22,12 @@ public FindSymbolVisitor( int lineNumber, int columnNumber, bool includeDefinitions, - bool returnMemberSignature) + bool returnFullSignature) { this.lineNumber = lineNumber; this.columnNumber = columnNumber; this.includeDefinitions = includeDefinitions; - this.returnMemberSignature = returnMemberSignature; + this.returnFullSignature = returnFullSignature; } /// @@ -164,7 +164,7 @@ private bool IsPositionInExtent(IScriptExtent extent) public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) { // We only want the method/ctor name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, returnMemberSignature); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, returnFullSignature); if (IsPositionInExtent(nameExtent)) { @@ -328,7 +328,7 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { // We only want the property name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, returnMemberSignature); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, returnFullSignature); if (IsPositionInExtent(nameExtent)) { diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs index f2b78abe3..55c90b4fd 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs @@ -107,7 +107,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) { (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(functionMemberAst); - IScriptExtent nameExtent = GetNewExtent(functionMemberAst, VisitorUtils.GetMemberOverloadName(functionMemberAst), startLine, startColumn); + IScriptExtent nameExtent = GetNewExtent(functionMemberAst, VisitorUtils.GetMemberOverloadName(functionMemberAst, false, false), startLine, startColumn); SymbolType symbolType = functionMemberAst.IsConstructor ? diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index 3c959f35a..3fc3f7ea1 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -152,15 +152,16 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(TypeDefinitionAst typeDef /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable) /// /// A FunctionMemberAst in the script's AST - /// A bool indicating if return type and class should be included + /// A bool indicating if class/enum name should be prepended + /// A bool indicating if return type should be included for methods /// A ScriptExtent with for the symbol name only - internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functionMemberAst, bool includeSignature = false) + internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functionMemberAst, bool useQualifiedName = true, bool includeReturnType = false) { (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(functionMemberAst); return new PSESSymbols.ScriptExtent() { - Text = GetMemberOverloadName(functionMemberAst, includeSignature), + Text = GetMemberOverloadName(functionMemberAst, useQualifiedName, includeReturnType), StartLineNumber = startLine, EndLineNumber = startLine, StartColumnNumber = startColumn, @@ -173,9 +174,9 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functio /// Gets a new ScriptExtent for a given Ast for the property name only /// /// A PropertyMemberAst in the script's AST - /// A bool indicating if property-type and name of class/enum should be included + /// A bool indicating if type should be included for class property /// A ScriptExtent with for the symbol name only - internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propertyMemberAst, bool includeSignature = false) + internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propertyMemberAst, bool includePropertyType = false) { bool isEnumMember = propertyMemberAst.Parent is TypeDefinitionAst typeDef && typeDef.IsEnum; (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(propertyMemberAst, isEnumMember); @@ -187,7 +188,7 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propert return new PSESSymbols.ScriptExtent() { - Text = GetMemberOverloadName(propertyMemberAst, includeSignature), + Text = GetMemberOverloadName(propertyMemberAst, includePropertyType), StartLineNumber = startLine, EndLineNumber = startLine, StartColumnNumber = startColumn, @@ -221,24 +222,24 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(ConfigurationDefinitionAs /// Gets the method or constructor name with parameters for current overload. /// /// A FunctionMemberAst object in the script's AST - /// A bool indicating if return type and class should be included + /// A bool indicating if class/enum name should be prepended + /// A bool indicating if return type should be included for methods /// Function member name with return type (optional) and parameters - internal static string GetMemberOverloadName(FunctionMemberAst functionMemberAst, bool includeSignature = false) + internal static string GetMemberOverloadName(FunctionMemberAst functionMemberAst, + bool useQualifiedName = true, + bool includeReturnType = false) { StringBuilder sb = new(); // Prepend return type and class. Used for symbol details (hover) - if (includeSignature) + if (includeReturnType && !functionMemberAst.IsConstructor) { - if (!functionMemberAst.IsConstructor) - { - sb.Append(functionMemberAst.ReturnType?.TypeName.Name ?? "void").Append(' '); - } + sb.Append(functionMemberAst.ReturnType?.TypeName.Name ?? "void").Append(' '); + } - if (functionMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsClass) - { - sb.Append(typeAst.Name).Append('.'); - } + if (useQualifiedName && functionMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsClass) + { + sb.Append(typeAst.Name).Append('.'); } sb.Append(functionMemberAst.Name); @@ -264,16 +265,17 @@ internal static string GetMemberOverloadName(FunctionMemberAst functionMemberAst /// Gets the property name with type and class/enum. /// /// A PropertyMemberAst object in the script's AST - /// A bool indicating if property-type and name of class/enum should be included + /// A bool indicating if type should be included for class property /// Property name with type (optional) and class/enum - internal static string GetMemberOverloadName(PropertyMemberAst propertyMemberAst, bool includeSignature = false) + internal static string GetMemberOverloadName(PropertyMemberAst propertyMemberAst, + bool includePropertyType = false) { StringBuilder sb = new(); // Prepend return type and class. Used for symbol details (hover) - if (includeSignature && propertyMemberAst.Parent is TypeDefinitionAst typeAst) + if (propertyMemberAst.Parent is TypeDefinitionAst typeAst) { - if (!typeAst.IsEnum) + if (includePropertyType && !typeAst.IsEnum) { sb.Append(propertyMemberAst.PropertyType?.TypeName.Name ?? "object").Append(' '); } From e7769842962b76312347d831376688fedfabdbd3 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Thu, 25 Aug 2022 19:12:04 +0000 Subject: [PATCH 167/327] add symbolsservice tests --- .../Definition/FindsTypeSymbolsDefinition.cs | 90 ++++++ .../FindsOccurrencesOnTypeSymbols.cs | 90 ++++++ .../Occurrences/FindsOccurrencesOnVariable.cs | 20 ++ .../FindsReferencesOnTypeSymbols.cs | 90 ++++++ .../References/TypeAndClassesFile.ps1 | 46 +++ .../FindsDetailsForTypeSymbols.cs | 50 ++++ .../SymbolDetails/TypeSymbolDetails.ps1 | 23 ++ .../Language/SymbolsServiceTests.cs | 269 ++++++++++++++++++ 8 files changed, 678 insertions(+) create mode 100644 test/PowerShellEditorServices.Test.Shared/Definition/FindsTypeSymbolsDefinition.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnVariable.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 create mode 100644 test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypeSymbolsDefinition.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypeSymbolsDefinition.cs new file mode 100644 index 000000000..a0b5ae61c --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypeSymbolsDefinition.cs @@ -0,0 +1,90 @@ +// 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 FindsTypeSymbolsDefinitionData + { + public static readonly ScriptRegion ClassSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 8, + startColumnNumber: 14, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 39, + startColumnNumber: 10, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeExpressionSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 45, + startColumnNumber: 5, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeConstraintSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 25, + startColumnNumber: 24, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion ConstructorSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 9, + startColumnNumber: 14, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion MethodSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 19, + startColumnNumber: 25, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion PropertySourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 15, + startColumnNumber: 32, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumMemberSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 41, + startColumnNumber: 11, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs new file mode 100644 index 000000000..7c012bc7c --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Occurrences +{ + public static class FindsOccurrencesOnTypeSymbolsData + { + public static readonly ScriptRegion ClassSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 8, + startColumnNumber: 16, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 39, + startColumnNumber: 7, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeExpressionSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 34, + startColumnNumber: 16, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeConstraintSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 8, + startColumnNumber: 24, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion ConstructorSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 13, + startColumnNumber: 14, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion MethodSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 28, + startColumnNumber: 22, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion PropertySourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 15, + startColumnNumber: 18, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumMemberSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 40, + startColumnNumber: 6, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnVariable.cs b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnVariable.cs new file mode 100644 index 000000000..c01db0591 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnVariable.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.Occurrences +{ + public static class FindsOccurrencesOnVariableData + { + public static readonly ScriptRegion SourceDetails = new( + file: TestUtilities.NormalizePath("References/SimpleFile.ps1"), + text: string.Empty, + startLineNumber: 8, + startColumnNumber: 3, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs new file mode 100644 index 000000000..d9cbcf434 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.References +{ + public static class FindsReferencesOnTypeSymbolsData + { + public static readonly ScriptRegion ClassSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 8, + startColumnNumber: 12, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 39, + startColumnNumber: 8, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion ConstructorSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 9, + startColumnNumber: 8, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion MethodSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 19, + startColumnNumber: 20, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion PropertySourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 17, + startColumnNumber: 15, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion EnumMemberSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 41, + startColumnNumber: 8, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeExpressionSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 34, + startColumnNumber: 12, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion TypeConstraintSourceDetails = new( + file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), + text: string.Empty, + startLineNumber: 25, + startColumnNumber: 22, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 b/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 new file mode 100644 index 000000000..4fe54505d --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 @@ -0,0 +1,46 @@ +Get-ChildItem ./file1.ps1 +$myScriptVar = 123 + +class BaseClass { + +} + +class SuperClass : BaseClass { + SuperClass([string]$name) { + + } + + SuperClass() { } + + [string]$SomePropWithDefault = 'this is a default value' + + [int]$SomeProp + + [string]MyClassMethod([string]$param1, $param2, [int]$param3) { + $this.SomePropWithDefault = 'something happend' + return 'finished' + } + + [string] + MyClassMethod([MyEnum]$param1) { + return 'hello world' + } + [string]MyClassMethod() { + return 'hello world' + } +} + +New-Object SuperClass +$o = [SuperClass]::new() +$o.SomeProp +$o.MyClassMeth + + +enum MyEnum { + First + Second + Third +} + +[MyEnum]::First +'First' -is [MyEnum] diff --git a/test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs new file mode 100644 index 000000000..ff78a2c5a --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.SymbolDetails +{ + public static class FindsDetailsForTypeSymbolsData + { + public static readonly ScriptRegion EnumMemberSourceDetails = new( + file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), + text: string.Empty, + startLineNumber: 20, + startColumnNumber: 6, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion PropertySourceDetails = new( + file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), + text: string.Empty, + startLineNumber: 6, + startColumnNumber: 18, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion ConstructorSourceDetails = new( + file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), + text: string.Empty, + startLineNumber: 2, + startColumnNumber: 11, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + + public static readonly ScriptRegion MethodSourceDetails = new( + file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), + text: string.Empty, + startLineNumber: 10, + startColumnNumber: 20, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 b/test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 new file mode 100644 index 000000000..fd4a10a46 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 @@ -0,0 +1,23 @@ +class SuperClass { + SuperClass([string]$name) { + + } + + [string]$SomePropWithDefault = 'this is a default value' + + [int]$SomeProp + + [string]MyClassMethod([string]$param1, $param2, [int]$param3) { + $this.SomePropWithDefault = 'something happend' + return 'finished' + } +} + +New-Object SuperClass +$o = [SuperClass]::new() + +enum MyEnum { + First + Second + Third +} diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 27e0e8893..c3de81c8d 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -225,6 +225,15 @@ public async Task FindsReferencesOnVariable() Assert.Equal(13, referencesResult[referencesResult.Count - 1].ScriptRegion.StartColumnNumber); } + [Fact] + public void FindsOccurrencesOnVariable() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnVariableData.SourceDetails); + Assert.Equal(3, occurrencesResult.Count); + Assert.Equal(10, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + Assert.Equal(13, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartColumnNumber); + } + [Fact] public void FindsOccurrencesOnFunction() { @@ -253,6 +262,222 @@ public async Task FindsReferencesOnCommandWithAlias() Assert.Equal("Get-ChildItem", referencesResult[referencesResult.Count - 1].SymbolName); } + [Fact] + public async Task FindsClassDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.ClassSourceDetails).ConfigureAwait(true); + Assert.Equal(8, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(7, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("SuperClass", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnClass() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.ClassSourceDetails).ConfigureAwait(true); + Assert.Equal(2, referencesResult.Count); + Assert.Equal(8, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(7, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnClass() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ClassSourceDetails); + Assert.Equal(2, occurrencesResult.Count); + Assert.Equal("[SuperClass]", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(34, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsEnumDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumSourceDetails).ConfigureAwait(true); + Assert.Equal(39, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(6, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("MyEnum", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnEnum() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumSourceDetails).ConfigureAwait(true); + Assert.Equal(4, referencesResult.Count); + Assert.Equal(25, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(19, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnEnum() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumSourceDetails); + Assert.Equal(4, occurrencesResult.Count); + Assert.Equal("[MyEnum]", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(46, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsTypeExpressionDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeExpressionSourceDetails).ConfigureAwait(true); + Assert.Equal(39, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(6, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("MyEnum", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnTypeExpression() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeExpressionSourceDetails).ConfigureAwait(true); + Assert.Equal(2, referencesResult.Count); + Assert.Equal(8, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(7, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnTypeExpression() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.TypeExpressionSourceDetails); + Assert.Equal(2, occurrencesResult.Count); + Assert.Equal("SuperClass", occurrencesResult[0].SymbolName); + Assert.Equal(8, occurrencesResult[0].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsTypeConstraintDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeConstraintSourceDetails).ConfigureAwait(true); + Assert.Equal(39, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(6, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("MyEnum", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnTypeConstraint() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeConstraintSourceDetails).ConfigureAwait(true); + Assert.Equal(4, referencesResult.Count); + Assert.Equal(25, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(19, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnTypeConstraint() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.TypeConstraintSourceDetails); + Assert.Equal(2, occurrencesResult.Count); + Assert.Equal("BaseClass", occurrencesResult[0].SymbolName); + Assert.Equal(4, occurrencesResult[0].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsConstructorDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.ConstructorSourceDetails).ConfigureAwait(true); + Assert.Equal(9, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(5, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("SuperClass.SuperClass([string]$name)", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnConstructor() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.ConstructorSourceDetails).ConfigureAwait(true); + Assert.Single(referencesResult); + Assert.Equal(9, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(5, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnConstructor() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ConstructorSourceDetails); + Assert.Single(occurrencesResult); + Assert.Equal("SuperClass.SuperClass()", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(13, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsMethodDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.MethodSourceDetails).ConfigureAwait(true); + Assert.Equal(19, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(13, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("SuperClass.MyClassMethod([string]$param1, $param2, [int]$param3)", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnMethod() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.MethodSourceDetails).ConfigureAwait(true); + Assert.Single(referencesResult); + Assert.Equal(19, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(13, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnMethod() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.MethodSourceDetails); + Assert.Single(occurrencesResult); + Assert.Equal("SuperClass.MyClassMethod()", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(28, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsPropertyDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.PropertySourceDetails).ConfigureAwait(true); + Assert.Equal(15, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(13, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("SuperClass.SomePropWithDefault", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnProperty() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.PropertySourceDetails).ConfigureAwait(true); + Assert.Single(referencesResult); + Assert.Equal(17, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnProperty() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.PropertySourceDetails); + Assert.Equal(1, occurrencesResult.Count); + Assert.Equal("SuperClass.SomePropWithDefault", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(15, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + + [Fact] + public async Task FindsEnumMemberDefinition() + { + SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumMemberSourceDetails).ConfigureAwait(true); + Assert.Equal(41, definitionResult.ScriptRegion.StartLineNumber); + Assert.Equal(5, definitionResult.ScriptRegion.StartColumnNumber); + Assert.Equal("MyEnum.Second", definitionResult.SymbolName); + } + + [Fact] + public async Task FindsReferencesOnEnumMember() + { + List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails).ConfigureAwait(true); + Assert.Single(referencesResult); + Assert.Equal(41, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(5, referencesResult[0].ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsOccurrencesOnEnumMember() + { + IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumMemberSourceDetails); + Assert.Single(occurrencesResult); + Assert.Equal("MyEnum.First", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal(40, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + } + [Fact] public async Task FindsReferencesOnFileWithReferencesFileB() { @@ -279,6 +504,50 @@ public async Task FindsDetailsForBuiltInCommand() Assert.NotEqual("", symbolDetails.Documentation); } + [Fact] + public async Task FindsDetailsWithSignatureForEnumMember() + { + SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( + GetScriptFile(FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails), + FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails.StartLineNumber, + FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails.StartColumnNumber).ConfigureAwait(true); + + Assert.Equal("MyEnum.First", symbolDetails.DisplayString); + } + + [Fact] + public async Task FindsDetailsWithSignatureForProperty() + { + SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( + GetScriptFile(FindsDetailsForTypeSymbolsData.PropertySourceDetails), + FindsDetailsForTypeSymbolsData.PropertySourceDetails.StartLineNumber, + FindsDetailsForTypeSymbolsData.PropertySourceDetails.StartColumnNumber).ConfigureAwait(true); + + Assert.Equal("string SuperClass.SomePropWithDefault", symbolDetails.DisplayString); + } + + [Fact] + public async Task FindsDetailsWithSignatureForConstructor() + { + SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( + GetScriptFile(FindsDetailsForTypeSymbolsData.ConstructorSourceDetails), + FindsDetailsForTypeSymbolsData.ConstructorSourceDetails.StartLineNumber, + FindsDetailsForTypeSymbolsData.ConstructorSourceDetails.StartColumnNumber).ConfigureAwait(true); + + Assert.Equal("SuperClass.SuperClass([string]$name)", symbolDetails.DisplayString); + } + + [Fact] + public async Task FindsDetailsWithSignatureForMethod() + { + SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( + GetScriptFile(FindsDetailsForTypeSymbolsData.MethodSourceDetails), + FindsDetailsForTypeSymbolsData.MethodSourceDetails.StartLineNumber, + FindsDetailsForTypeSymbolsData.MethodSourceDetails.StartColumnNumber).ConfigureAwait(true); + + Assert.Equal("string SuperClass.MyClassMethod([string]$param1, $param2, [int]$param3)", symbolDetails.DisplayString); + } + [Fact] public void FindsSymbolsInFile() { From 4be7724326eab4439f506af6006e8a90e956e419 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Thu, 25 Aug 2022 19:12:50 +0000 Subject: [PATCH 168/327] add codelens tests --- .../LanguageServerProtocolMessageTests.cs | 106 +++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 5011d5245..d990ebe09 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -841,7 +841,7 @@ public async Task NoMessageIfPesterCodeLensDisabled() } [Fact] - public async Task CanSendReferencesCodeLensRequestAsync() + public async Task CanSendFunctionReferencesCodeLensRequestAsync() { string filePath = NewTestFile(@" function CanSendReferencesCodeLensRequest { @@ -878,6 +878,110 @@ function CanSendReferencesCodeLensRequest { Assert.Equal("1 reference", codeLensResolveResult.Command.Title); } + [Fact] + public async Task CanSendClassReferencesCodeLensRequestAsync() + { + string filePath = NewTestFile(@" +param( + [MyBaseClass]$enumValue +) + +class MyBaseClass { + +} + +class ChildClass : MyBaseClass, System.IDisposable { + +} + +$o = [MyBaseClass]::new() +$o -is [MyBaseClass] +"); + + CodeLensContainer codeLenses = await PsesLanguageClient + .SendRequest( + "textDocument/codeLens", + new CodeLensParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(filePath) + } + }) + .Returning(CancellationToken.None).ConfigureAwait(true); + + Assert.Collection(codeLenses, + codeLens => + { + Range range = codeLens.Range; + Assert.Equal(5, range.Start.Line); + Assert.Equal(6, range.Start.Character); + Assert.Equal(7, range.End.Line); + Assert.Equal(1, range.End.Character); + }, + codeLens => + { + Range range = codeLens.Range; + Assert.Equal(9, range.Start.Line); + Assert.Equal(6, range.Start.Character); + Assert.Equal(11, range.End.Line); + Assert.Equal(1, range.End.Character); + } + ); + + CodeLens baseClassCodeLens = codeLenses.First(); + CodeLens codeLensResolveResult = await PsesLanguageClient + .SendRequest("codeLens/resolve", baseClassCodeLens) + .Returning(CancellationToken.None).ConfigureAwait(true); + + Assert.Equal("4 references", codeLensResolveResult.Command.Title); + } + + [Fact] + public async Task CanSendEnumReferencesCodeLensRequestAsync() + { + string filePath = NewTestFile(@" +param( + [MyEnum]$enumValue +) + +enum MyEnum { + First = 1 + Second + Third +} + +[MyEnum]::First +'First' -is [MyEnum] +"); + + CodeLensContainer codeLenses = await PsesLanguageClient + .SendRequest( + "textDocument/codeLens", + new CodeLensParams + { + TextDocument = new TextDocumentIdentifier + { + Uri = new Uri(filePath) + } + }) + .Returning(CancellationToken.None).ConfigureAwait(true); + + CodeLens codeLens = Assert.Single(codeLenses); + + Range range = codeLens.Range; + Assert.Equal(5, range.Start.Line); + Assert.Equal(5, range.Start.Character); + Assert.Equal(9, range.End.Line); + Assert.Equal(1, range.End.Character); + + CodeLens codeLensResolveResult = await PsesLanguageClient + .SendRequest("codeLens/resolve", codeLens) + .Returning(CancellationToken.None).ConfigureAwait(true); + + Assert.Equal("3 references", codeLensResolveResult.Command.Title); + } + [SkippableFact] public async Task CanSendCodeActionRequestAsync() { From bca3de0abeab701499ffa463ac2d2e0e850f812e Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Thu, 25 Aug 2022 19:34:02 +0000 Subject: [PATCH 169/327] add symbol tests using newline after keyword --- .../Symbols/FindSymbolsInNewLineSymbolFile.cs | 21 ++++++++ .../Symbols/NewLineSymbols.ps1 | 28 ++++++++++ .../Language/SymbolsServiceTests.cs | 51 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInNewLineSymbolFile.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/Symbols/NewLineSymbols.ps1 diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInNewLineSymbolFile.cs b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInNewLineSymbolFile.cs new file mode 100644 index 000000000..0be43f8d1 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInNewLineSymbolFile.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Symbols +{ + public static class FindSymbolsInNewLineSymbolFile + { + public static readonly ScriptRegion SourceDetails = + new( + file: TestUtilities.NormalizePath("Symbols/NewLineSymbols.ps1"), + text: string.Empty, + startLineNumber: 0, + startColumnNumber: 0, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/NewLineSymbols.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/NewLineSymbols.ps1 new file mode 100644 index 000000000..5ca44f02a --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/NewLineSymbols.ps1 @@ -0,0 +1,28 @@ +function +returnTrue { + $true +} + +class +NewLineClass { + NewLineClass() { + + } + + static + hidden + [string] + $SomePropWithDefault = 'some value' + + static + hidden + [string] + MyClassMethod([MyNewLineEnum]$param1) { + return 'hello world $param1' + } +} + +enum +MyNewLineEnum { + First +} diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index c3de81c8d..26754f92b 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -611,6 +611,57 @@ public void FindsSymbolsInFile() Assert.Equal(5, firstEnumMemberSymbol.ScriptRegion.StartColumnNumber); } + [Fact] + public void FindsSymbolsWithNewLineInFile() + { + List symbolsResult = + FindSymbolsInFile( + FindSymbolsInNewLineSymbolFile.SourceDetails); + + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Function)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); + + SymbolReference firstFunctionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Function); + Assert.Equal("returnTrue", firstFunctionSymbol.SymbolName); + Assert.Equal(2, firstFunctionSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstFunctionSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstClassSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Class); + Assert.Equal("NewLineClass", firstClassSymbol.SymbolName); + Assert.Equal(7, firstClassSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstClassSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstConstructorSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Constructor); + Assert.Equal("NewLineClass()", firstConstructorSymbol.SymbolName); + Assert.Equal(8, firstConstructorSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstConstructorSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstPropertySymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Property); + Assert.Equal("SomePropWithDefault", firstPropertySymbol.SymbolName); + Assert.Equal(15, firstPropertySymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstPropertySymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstMethodSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Method); + Assert.Equal("MyClassMethod([MyNewLineEnum]$param1)", firstMethodSymbol.SymbolName); + Assert.Equal(20, firstMethodSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstMethodSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstEnumSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Enum); + Assert.Equal("MyNewLineEnum", firstEnumSymbol.SymbolName); + Assert.Equal(26, firstEnumSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstEnumSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstEnumMemberSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.EnumMember); + Assert.Equal("First", firstEnumMemberSymbol.SymbolName); + Assert.Equal(27, firstEnumMemberSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, firstEnumMemberSymbol.ScriptRegion.StartColumnNumber); + } + [SkippableFact] public void FindsSymbolsInDSCFile() { From da2ad177289050a1c66dc484bdb39a234247f57b Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Thu, 25 Aug 2022 19:45:19 +0000 Subject: [PATCH 170/327] fix DSC symbol test --- .../Language/SymbolsServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 26754f92b..0f30eccde 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -673,7 +673,7 @@ public void FindsSymbolsInDSCFile() SymbolReference firstConfigurationSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Configuration); Assert.Equal("AConfiguration", firstConfigurationSymbol.SymbolName); Assert.Equal(2, firstConfigurationSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstConfigurationSymbol.ScriptRegion.StartColumnNumber); + Assert.Equal(15, firstConfigurationSymbol.ScriptRegion.StartColumnNumber); } [Fact] From 98fbfbd32f319bc37d2afa12765dd58dcb171cd1 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 6 Jan 2023 18:55:25 -0800 Subject: [PATCH 171/327] Minor clean-ups --- .../CodeLens/ReferencesCodeLensProvider.cs | 1 - .../Services/Symbols/ReferenceTable.cs | 42 ++++++++++--------- .../Services/Symbols/SymbolsService.cs | 2 - .../Services/Symbols/Vistors/AstOperations.cs | 6 +-- .../Symbols/Vistors/FindReferencesVisitor.cs | 16 +++---- .../Handlers/ReferencesHandler.cs | 1 - .../Language/SymbolsServiceTests.cs | 7 +--- 7 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index 5f9b6d958..e1ae41c13 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -107,7 +107,6 @@ public async Task ResolveCodeLens( List referencesResult = await _symbolsService.FindReferencesOfSymbol( foundSymbol, references, - _workspaceService, cancellationToken).ConfigureAwait(false); Location[] referenceLocations; diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 2c3242c83..b2af70005 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -26,7 +26,7 @@ internal sealed class ReferenceTable public ReferenceTable(ScriptFile parent) => _parent = parent; /// - /// Clears the reference table causing it to rescan the source AST when queried. + /// Clears the reference table causing it to re-scan the source AST when queried. /// public void TagAsChanged() { @@ -34,9 +34,11 @@ public void TagAsChanged() _isInited = false; } - // Prefer checking if the dictionary has contents to determine if initialized. The field - // `_isInited` is to guard against rescanning files with no command references, but will - // generally be less reliable of a check. + /// + /// Prefer checking if the dictionary has contents to determine if initialized. The field + /// `_isInited` is to guard against re-scanning files with no command references, but will + /// generally be less reliable of a check. + /// private bool IsInitialized => !_symbolReferences.IsEmpty || _isInited; internal bool TryGetReferences(string command, out ConcurrentBag? references) @@ -73,6 +75,22 @@ private sealed class ReferenceVisitor : AstVisitor public ReferenceVisitor(ReferenceTable references) => _references = references; + private static string? GetCommandName(CommandAst commandAst) + { + string commandName = commandAst.GetCommandName(); + if (!string.IsNullOrEmpty(commandName)) + { + return commandName; + } + + if (commandAst.CommandElements[0] is not ExpandableStringExpressionAst expandableStringExpressionAst) + { + return null; + } + + return AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null; + } + public override AstVisitAction VisitCommand(CommandAst commandAst) { string? commandName = GetCommandName(commandAst); @@ -86,22 +104,6 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) commandAst.CommandElements[0].Extent); return AstVisitAction.Continue; - - static string? GetCommandName(CommandAst commandAst) - { - string commandName = commandAst.GetCommandName(); - if (!string.IsNullOrEmpty(commandName)) - { - return commandName; - } - - if (commandAst.CommandElements[0] is not ExpandableStringExpressionAst expandableStringExpressionAst) - { - return null; - } - - return AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null; - } } public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 294cbb986..1c464f23f 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -161,13 +161,11 @@ public static SymbolReference FindSymbolAtLocation( /// /// The symbol to find all references for /// An array of scriptFiles too search for references in - /// The workspace that will be searched for symbols /// /// FindReferencesResult public async Task> FindReferencesOfSymbol( SymbolReference foundSymbol, ScriptFile[] referencedFiles, - WorkspaceService workspace, CancellationToken cancellationToken = default) { if (foundSymbol == null) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index e843128d2..97ba26250 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -189,9 +189,9 @@ public static SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumbe /// /// The abstract syntax tree of the given script /// The symbol that we are looking for references of - /// Dictionary maping cmdlets to aliases for finding alias references - /// Dictionary maping aliases to cmdlets for finding alias references - /// + /// Dictionary mapping cmdlets to aliases for finding alias references + /// Dictionary mapping aliases to cmdlets for finding alias references + /// The found symbol references as an enumerable public static IEnumerable FindReferencesOfSymbol( Ast scriptAst, SymbolReference symbolReference, diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs index 7f6014a2a..11c297ee9 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -25,8 +25,8 @@ internal class FindReferencesVisitor : AstVisitor2 /// Constructor used when searching for aliases is needed /// /// The found symbolReference that other symbols are being compared to - /// Dictionary maping cmdlets to aliases for finding alias references - /// Dictionary maping aliases to cmdlets for finding alias references + /// Dictionary mapping cmdlets to aliases for finding alias references + /// Dictionary mapping aliases to cmdlets for finding alias references public FindReferencesVisitor( SymbolReference symbolReference, IDictionary> cmdletToAliasDictionary = default, @@ -49,11 +49,7 @@ public FindReferencesVisitor( // exists (if the symbol isn't an alias to a command) set symbolRefCommandName to an // empty string. aliasToCmdletDictionary.TryGetValue(symbolReference.ScriptRegion.Text, out _symbolRefCommandName); - - if (_symbolRefCommandName == null) - { - _symbolRefCommandName = string.Empty; - } + _symbolRefCommandName ??= string.Empty; } /// @@ -81,8 +77,8 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) // set aliases to an empty List _cmdletToAliasDictionary.TryGetValue(commandName, out List aliases); _aliasToCmdletDictionary.TryGetValue(commandName, out string command); - if (aliases == null) { aliases = new List(); } - if (command == null) { command = string.Empty; } + aliases ??= new List(); + command ??= string.Empty; // Check if the found symbol's name is the same as the commandAst's name OR // if the symbol's name is an alias for this commandAst's name (commandAst is a cmdlet) OR diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index d76aa7c66..a2c1f01ad 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -45,7 +45,6 @@ public override async Task Handle(ReferenceParams request, Ca await _symbolsService.FindReferencesOfSymbol( foundSymbol, _workspaceService.ExpandScriptReferences(scriptFile), - _workspaceService, cancellationToken).ConfigureAwait(false); List locations = new(); diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 0f30eccde..8bbe0211a 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -94,8 +94,7 @@ private Task> GetReferences(ScriptRegion scriptRegion) return symbolsService.FindReferencesOfSymbol( symbolReference, - workspace.ExpandScriptReferences(scriptFile), - workspace); + workspace.ExpandScriptReferences(scriptFile)); } private IReadOnlyList GetOccurrences(ScriptRegion scriptRegion) @@ -257,9 +256,7 @@ public async Task FindsReferencesOnCommandWithAlias() { List referencesResult = await GetReferences(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails).ConfigureAwait(true); Assert.Equal(4, referencesResult.Count); - Assert.Equal("Get-ChildItem", referencesResult[1].SymbolName); - Assert.Equal("Get-ChildItem", referencesResult[2].SymbolName); - Assert.Equal("Get-ChildItem", referencesResult[referencesResult.Count - 1].SymbolName); + Assert.All(referencesResult, (i) => Assert.Equal("Get-ChildItem", i.SymbolName)); } [Fact] From 3eebb455d425de66822a6192cee5d52b357852c0 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 10 Jan 2023 16:58:36 -0800 Subject: [PATCH 172/327] Remove deprecated (and confusing) `DocumentSymbolService.cs` file --- .../Extensions/Api/DocumentSymbolService.cs | 277 ------------------ .../Api/EditorExtensionServiceProvider.cs | 1 - .../PowerShellEditorServices.csproj | 1 - 3 files changed, 279 deletions(-) delete mode 100644 src/PowerShellEditorServices/Extensions/Api/DocumentSymbolService.cs diff --git a/src/PowerShellEditorServices/Extensions/Api/DocumentSymbolService.cs b/src/PowerShellEditorServices/Extensions/Api/DocumentSymbolService.cs deleted file mode 100644 index 58137ce49..000000000 --- a/src/PowerShellEditorServices/Extensions/Api/DocumentSymbolService.cs +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Services; -using Microsoft.PowerShell.EditorServices.Services.TextDocument; - -using Internal = Microsoft.PowerShell.EditorServices.Services.Symbols; - -// TODO: This is currently disabled in the csproj -// Redesign this API and bring it back once it's fit for purpose - -namespace Microsoft.PowerShell.EditorServices.Extensions.Services -{ - /// - /// A way to define symbols on a higher level - /// - public enum SymbolType - { - /// - /// The symbol type is unknown - /// - Unknown = 0, - - /// - /// The symbol is a vairable - /// - Variable = 1, - - /// - /// The symbol is a function - /// - Function = 2, - - /// - /// The symbol is a parameter - /// - Parameter = 3, - - /// - /// The symbol is a DSC configuration - /// - Configuration = 4, - - /// - /// The symbol is a workflow - /// - Workflow = 5, - - /// - /// The symbol is a hashtable key - /// - HashtableKey = 6, - } - - /// - /// Interface to instantiate to create a provider of document symbols. - /// - public interface IDocumentSymbolProvider - { - /// - /// The unique ID of this provider. - /// - string ProviderId { get; } - - /// - /// Run this provider to provide symbols to PSES from the given file. - /// - /// The script file to provide symbols for. - /// Symbols about the file. - IEnumerable ProvideDocumentSymbols(IEditorScriptFile scriptFile); - } - - /// - /// A class that holds the type, name, script extent, and source line of a symbol - /// - [DebuggerDisplay("SymbolType = {SymbolType}, SymbolName = {SymbolName}")] - public class SymbolReference - { - /// - /// Constructs an instance of a SymbolReference - /// - /// The higher level type of the symbol - /// The script extent of the symbol - /// The file path of the symbol - /// The line contents of the given symbol (defaults to empty string) - public SymbolReference(SymbolType symbolType, IScriptExtent scriptExtent) - : this(symbolType, scriptExtent.Text, scriptExtent) - { - } - - /// - /// Constructs and instance of a SymbolReference - /// - /// The higher level type of the symbol - /// The name of the symbol - /// The script extent of the symbol - /// The file path of the symbol - /// The line contents of the given symbol (defaults to empty string) - public SymbolReference( - SymbolType symbolType, - string symbolName, - IScriptExtent scriptExtent) - : this(symbolType, scriptExtent, symbolName, filePath: string.Empty, sourceLine: string.Empty) - { - } - - public SymbolReference( - SymbolType symbolType, - IScriptExtent scriptExtent, - string symbolName, - string filePath) - : this(symbolType, scriptExtent, symbolName, filePath, sourceLine: string.Empty) - { - } - - public SymbolReference(SymbolType symbolType, IScriptExtent scriptExtent, string symbolName, string filePath, string sourceLine) - { - // TODO: Verify params - SymbolType = symbolType; - ScriptRegion = ScriptRegion.Create(scriptExtent); - SymbolName = symbolName; - FilePath = filePath; - SourceLine = sourceLine; - - // TODO: Make sure end column number usage is correct - } - - #region Properties - - /// - /// Gets the symbol's type - /// - public SymbolType SymbolType { get; } - - /// - /// Gets the name of the symbol - /// - public string SymbolName { get; } - - /// - /// Gets the script extent of the symbol - /// - public ScriptRegion ScriptRegion { get; } - - /// - /// Gets the contents of the line the given symbol is on - /// - public string SourceLine { get; } - - /// - /// Gets the path of the file in which the symbol was found. - /// - public string FilePath { get; internal set; } - - #endregion - } - - /// - /// Service for registration of document symbol providers in PSES. - /// - public interface IDocumentSymbolService - { - /// - /// Register a document symbol provider by its ID. - /// If another provider is already registered by the same ID, this will fail and return false. - /// - /// The document symbol provider to register. - /// True if the symbol provider was successfully registered, false otherwise. - bool RegisterDocumentSymbolProvider(IDocumentSymbolProvider documentSymbolProvider); - - /// - /// Deregister a symbol provider of the given ID. - /// - /// The ID of the provider to deregister. - /// True if a provider by the given ID was deregistered, false if no such provider was found. - bool DeregisterDocumentSymbolProvider(string providerId); - } - - internal class DocumentSymbolService : IDocumentSymbolService - { - private readonly SymbolsService _symbolsService; - - internal DocumentSymbolService(SymbolsService symbolsService) - { - _symbolsService = symbolsService; - } - - public bool RegisterDocumentSymbolProvider(IDocumentSymbolProvider documentSymbolProvider) - { - return _symbolsService.TryRegisterDocumentSymbolProvider(new ExternalDocumentSymbolProviderAdapter(documentSymbolProvider)); - } - - public bool DeregisterDocumentSymbolProvider(string providerId) - { - return _symbolsService.DeregisterCodeLensProvider(providerId); - } - } - - internal class ExternalDocumentSymbolProviderAdapter : Internal.IDocumentSymbolProvider - { - private readonly IDocumentSymbolProvider _symbolProvider; - - public ExternalDocumentSymbolProviderAdapter( - IDocumentSymbolProvider externalDocumentSymbolProvider) - { - _symbolProvider = externalDocumentSymbolProvider; - } - - public string ProviderId => _symbolProvider.ProviderId; - - public IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile) - { - foreach (SymbolReference symbolReference in _symbolProvider.ProvideDocumentSymbols(new EditorScriptFile(scriptFile))) - { - yield return new ExternalSymbolReferenceAdapter(symbolReference); - } - } - } - - internal class ExternalSymbolReferenceAdapter : Internal.ISymbolReference - { - private readonly SymbolReference _symbolReference; - - public ExternalSymbolReferenceAdapter(SymbolReference symbolReference) - { - _symbolReference = symbolReference; - } - - public Internal.SymbolType SymbolType => _symbolReference.SymbolType.ToInternalSymbolType(); - - public string SymbolName => _symbolReference.SymbolName; - - public ScriptRegion ScriptRegion => _symbolReference.ScriptRegion; - - public string SourceLine => _symbolReference.SourceLine; - - public string FilePath => _symbolReference.FilePath; - } - - internal static class SymbolTypeExtensions - { - public static Internal.SymbolType ToInternalSymbolType(this SymbolType symbolType) - { - switch (symbolType) - { - case SymbolType.Unknown: - return Internal.SymbolType.Unknown; - - case SymbolType.Variable: - return Internal.SymbolType.Variable; - - case SymbolType.Function: - return Internal.SymbolType.Function; - - case SymbolType.Parameter: - return Internal.SymbolType.Parameter; - - case SymbolType.Configuration: - return Internal.SymbolType.Configuration; - - case SymbolType.Workflow: - return Internal.SymbolType.Workflow; - - case SymbolType.HashtableKey: - return Internal.SymbolType.HashtableKey; - - default: - throw new InvalidOperationException($"Unknown symbol type '{symbolType}'"); - } - } - } -} - diff --git a/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs b/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs index dc2809962..d750923fb 100644 --- a/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs +++ b/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs @@ -42,7 +42,6 @@ internal EditorExtensionServiceProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; LanguageServer = new LanguageServerService(_serviceProvider.GetService()); - //DocumentSymbols = new DocumentSymbolService(_serviceProvider.GetService()); ExtensionCommands = new ExtensionCommandService(_serviceProvider.GetService()); Workspace = new WorkspaceService(_serviceProvider.GetService()); EditorContext = new EditorContextService(_serviceProvider.GetService()); diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 9eefce9de..ef9b2696b 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -55,7 +55,6 @@ - From 6469411c58518ef1ceba202f856b2b2c5bc02596 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 10 Jan 2023 17:03:51 -0800 Subject: [PATCH 173/327] Rename overloaded method to `ScanForReferencesOfSymbol` Since `FindReferencesOfSymbol` is already used. --- .../Services/CodeLens/ReferencesCodeLensProvider.cs | 2 +- .../Services/Symbols/PsdDocumentSymbolProvider.cs | 2 +- .../Services/Symbols/SymbolsService.cs | 5 +++-- .../Services/TextDocument/Handlers/ReferencesHandler.cs | 4 ++-- .../Language/SymbolsServiceTests.cs | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index e1ae41c13..9a3166a4d 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -104,7 +104,7 @@ public async Task ResolveCodeLens( codeLens.Range.Start.Line + 1, codeLens.Range.Start.Character + 1); - List referencesResult = await _symbolsService.FindReferencesOfSymbol( + List referencesResult = await _symbolsService.ScanForReferencesOfSymbol( foundSymbol, references, cancellationToken).ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs index ff3ffe47b..323390bee 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs @@ -35,7 +35,7 @@ IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( /// Checks if a given ast represents the root node of a *.psd1 file. /// /// The abstract syntax tree of the given script - /// true if the AST represts a *.psd1 file, otherwise false + /// true if the AST represents a *.psd1 file, otherwise false public static bool IsPowerShellDataFileAst(Ast ast) { // sometimes we don't have reliable access to the filename diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 1c464f23f..1a53f2182 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -78,6 +78,7 @@ public SymbolsService( PesterCodeLensProvider pesterProvider = new(configurationService); _codeLensProviders.TryAdd(pesterProvider.ProviderId, pesterProvider); + // TODO: Is this complication so necessary? _documentSymbolProviders = new ConcurrentDictionary(); IDocumentSymbolProvider[] documentSymbolProviders = new IDocumentSymbolProvider[] { @@ -160,10 +161,10 @@ public static SymbolReference FindSymbolAtLocation( /// Finds all the references of a symbol /// /// The symbol to find all references for - /// An array of scriptFiles too search for references in + /// An array of scriptFiles to search for references in /// /// FindReferencesResult - public async Task> FindReferencesOfSymbol( + public async Task> ScanForReferencesOfSymbol( SymbolReference foundSymbol, ScriptFile[] referencedFiles, CancellationToken cancellationToken = default) diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index a2c1f01ad..2b9cb4fa4 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; @@ -42,7 +42,7 @@ public override async Task Handle(ReferenceParams request, Ca request.Position.Character + 1); List referencesResult = - await _symbolsService.FindReferencesOfSymbol( + await _symbolsService.ScanForReferencesOfSymbol( foundSymbol, _workspaceService.ExpandScriptReferences(scriptFile), cancellationToken).ConfigureAwait(false); diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 8bbe0211a..e0dd3203f 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -92,7 +92,7 @@ private Task> GetReferences(ScriptRegion scriptRegion) Assert.NotNull(symbolReference); - return symbolsService.FindReferencesOfSymbol( + return symbolsService.ScanForReferencesOfSymbol( symbolReference, workspace.ExpandScriptReferences(scriptFile)); } From cf79fd031ec1f45678fae04557f8d0d8355d0d69 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 10 Jan 2023 17:10:15 -0800 Subject: [PATCH 174/327] Migrate `FindReferencesVisitor` to `ReferenceTable.ReferenceVisitor` This required using a `ScriptFile` for the associated test in `AstOperationsTests`. The coupling of the visitor to the file seems fine since that's the _point_ of this improvement: a cached sets of references for each file. We also have to carefully sort our list of symbol references for things that expect them in order. --- .../Symbols/IDocumentSymbolProvider.cs | 2 +- .../Symbols/PesterDocumentSymbolProvider.cs | 2 +- .../Symbols/PsdDocumentSymbolProvider.cs | 2 +- .../Services/Symbols/ReferenceTable.cs | 140 +++++++-- .../Symbols/ScriptDocumentSymbolProvider.cs | 2 +- .../Services/Symbols/SymbolReference.cs | 20 ++ .../Services/Symbols/SymbolsService.cs | 60 ++-- .../Services/Symbols/Vistors/AstOperations.cs | 42 +-- .../Symbols/Vistors/FindReferencesVisitor.cs | 272 ------------------ .../Symbols/Vistors/FindSymbolsVisitor.cs | 2 + .../Handlers/DocumentSymbolHandler.cs | 6 +- .../Handlers/ReferencesHandler.cs | 2 +- .../Utility/VisitorUtils.cs | 18 ++ .../References/FunctionReference.ps1 | 24 ++ .../Language/SymbolsServiceTests.cs | 27 +- .../Services/Symbols/AstOperationsTests.cs | 56 ++-- 16 files changed, 259 insertions(+), 418 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/References/FunctionReference.ps1 diff --git a/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs index 3b01e382a..6abcdb162 100644 --- a/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs @@ -20,6 +20,6 @@ internal interface IDocumentSymbolProvider /// The document for which SymbolReferences should be provided. /// /// An IEnumerable collection of SymbolReferences. - IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile); + IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile); } } diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index 3c6469bc6..cbdedb96d 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -17,7 +17,7 @@ internal class PesterDocumentSymbolProvider : IDocumentSymbolProvider { string IDocumentSymbolProvider.ProviderId => nameof(PesterDocumentSymbolProvider); - IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( + IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( ScriptFile scriptFile) { if (!scriptFile.FilePath.EndsWith( diff --git a/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs index 323390bee..567887d66 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs @@ -17,7 +17,7 @@ internal class PsdDocumentSymbolProvider : IDocumentSymbolProvider { string IDocumentSymbolProvider.ProviderId => nameof(PsdDocumentSymbolProvider); - IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( + IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( ScriptFile scriptFile) { if ((scriptFile.FilePath?.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase) == true) || diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index b2af70005..deafed8f2 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -9,6 +9,8 @@ using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Services.Symbols; +using System.Collections.Generic; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services; @@ -19,7 +21,7 @@ internal sealed class ReferenceTable { private readonly ScriptFile _parent; - private readonly ConcurrentDictionary> _symbolReferences = new(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary> _symbolReferences = new(StringComparer.OrdinalIgnoreCase); private bool _isInited; @@ -41,12 +43,24 @@ public void TagAsChanged() /// private bool IsInitialized => !_symbolReferences.IsEmpty || _isInited; - internal bool TryGetReferences(string command, out ConcurrentBag? references) + internal bool TryGetReferences(string command, out ConcurrentBag? references) { EnsureInitialized(); return _symbolReferences.TryGetValue(command, out references); } + // TODO: Should this be improved, or pre-sorted? + internal IReadOnlyList GetAllReferences() + { + EnsureInitialized(); + List allReferences = new(); + foreach (ConcurrentBag bag in _symbolReferences.Values) + { + allReferences.AddRange(bag); + } + return allReferences; + } + internal void EnsureInitialized() { if (IsInitialized) @@ -57,51 +71,73 @@ internal void EnsureInitialized() _parent.ScriptAst.Visit(new ReferenceVisitor(this)); } - private void AddReference(string symbol, IScriptExtent extent) + private void AddReference(SymbolType type, string name, IScriptExtent extent) { + SymbolReference symbol = new(type, name, extent, _parent); _symbolReferences.AddOrUpdate( - symbol, - _ => new ConcurrentBag { extent }, + name, + _ => new ConcurrentBag { symbol }, (_, existing) => { - existing.Add(extent); + existing.Add(symbol); return existing; }); } - private sealed class ReferenceVisitor : AstVisitor + // TODO: Should we move this to AstOperations.cs? It is highly coupled to `ReferenceTable`, + // perhaps it doesn't have to be. + private sealed class ReferenceVisitor : AstVisitor2 { private readonly ReferenceTable _references; public ReferenceVisitor(ReferenceTable references) => _references = references; - private static string? GetCommandName(CommandAst commandAst) + public override AstVisitAction VisitCommand(CommandAst commandAst) { - string commandName = commandAst.GetCommandName(); - if (!string.IsNullOrEmpty(commandName)) + string? commandName = VisitorUtils.GetCommandName(commandAst); + if (string.IsNullOrEmpty(commandName)) { - return commandName; + return AstVisitAction.Continue; } - if (commandAst.CommandElements[0] is not ExpandableStringExpressionAst expandableStringExpressionAst) - { - return null; - } + _references.AddReference( + SymbolType.Function, + CommandHelpers.StripModuleQualification(commandName, out _), + commandAst.CommandElements[0].Extent + ); - return AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null; + return AstVisitAction.Continue; } - public override AstVisitAction VisitCommand(CommandAst commandAst) + // TODO: We should examine if we really want to constrain the extents to the name only. This + // means that highlighting only highlights the symbol name, but providing the whole extend + // means the whole function (or class etc.) gets highlighted, which seems to be a personal + // preference. + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - string? commandName = GetCommandName(commandAst); - if (string.IsNullOrEmpty(commandName)) + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. + // This will not exclude nested functions as they have ScriptBlockAst as parent + if (functionDefinitionAst.Parent is FunctionMemberAst) { return AstVisitAction.Continue; } + // We only want the function name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); _references.AddReference( - CommandHelpers.StripModuleQualification(commandName, out _), - commandAst.CommandElements[0].Extent); + SymbolType.Function, + functionDefinitionAst.Name, + nameExtent); + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) + { + _references.AddReference( + SymbolType.Parameter, + commandParameterAst.Extent.Text, + commandParameterAst.Extent); return AstVisitAction.Continue; } @@ -111,10 +147,70 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var // TODO: Consider tracking unscoped variable references only when they declared within // the same function definition. _references.AddReference( + SymbolType.Variable, $"${variableExpressionAst.VariablePath.UserPath}", - variableExpressionAst.Extent); + variableExpressionAst.Extent + ); + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + SymbolType symbolType = typeDefinitionAst.IsEnum ? SymbolType.Enum : SymbolType.Class; + + // We only want the type name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); + _references.AddReference(symbolType, typeDefinitionAst.Name, nameExtent); + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) + { + _references.AddReference( + SymbolType.Type, + typeExpressionAst.TypeName.Name, + typeExpressionAst.Extent); return AstVisitAction.Continue; } + + public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) + { + _references.AddReference(SymbolType.Type, typeConstraintAst.TypeName.Name, typeConstraintAst.Extent); + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + SymbolType symbolType = functionMemberAst.IsConstructor + ? SymbolType.Constructor + : SymbolType.Method; + + // We only want the method/ctor name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, false); + _references.AddReference( + symbolType, + VisitorUtils.GetMemberOverloadName(functionMemberAst, true, false), + nameExtent); + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + // We only want the property name. Get start-location for name + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); + _references.AddReference( + SymbolType.Property, + VisitorUtils.GetMemberOverloadName(propertyMemberAst, false), + nameExtent); + + return AstVisitAction.Continue; + } + + // TODO: What else can we implement? } } diff --git a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs index 0b40a5cc1..b9fa29f33 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs @@ -16,7 +16,7 @@ internal class ScriptDocumentSymbolProvider : IDocumentSymbolProvider { string IDocumentSymbolProvider.ProviderId => nameof(ScriptDocumentSymbolProvider); - IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( + IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( ScriptFile scriptFile) { // If we have an AST, then we know it's a PowerShell file diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index 25cb15cc1..ccedc0468 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -100,6 +100,26 @@ public SymbolReference( // "{0} {1}") } + public SymbolReference( + SymbolType symbolType, + string symbolName, + IScriptExtent scriptExtent, + ScriptFile file) + { + SymbolType = symbolType; + SymbolName = symbolName; + ScriptRegion = ScriptRegion.Create(scriptExtent); + FilePath = file.FilePath; + try + { + SourceLine = file.GetLine(ScriptRegion.StartLineNumber); + } + catch (System.ArgumentOutOfRangeException) + { + SourceLine = string.Empty; + } + } + /// /// Constructs an instance of a SymbolReference /// diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 1a53f2182..d2a842680 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -110,23 +110,18 @@ public SymbolsService( /// Finds all the symbols in a file. /// /// The ScriptFile in which the symbol can be located. - /// public List FindSymbolsInFile(ScriptFile scriptFile) { Validate.IsNotNull(nameof(scriptFile), scriptFile); - List foundOccurrences = new(); + List symbols = new(); foreach (IDocumentSymbolProvider symbolProvider in GetDocumentSymbolProviders()) { - foreach (SymbolReference reference in symbolProvider.ProvideDocumentSymbols(scriptFile)) - { - reference.SourceLine = scriptFile.GetLine(reference.ScriptRegion.StartLineNumber); - reference.FilePath = scriptFile.FilePath; - foundOccurrences.Add(reference); - } + // TODO: Each provider needs to set the source line and filepath. + symbols.AddRange(symbolProvider.ProvideDocumentSymbols(scriptFile)); } - return foundOccurrences; + return symbols; } /// @@ -174,6 +169,7 @@ public async Task> ScanForReferencesOfSymbol( return null; } + // TODO: Should we handle aliases at a lower level? CommandHelpers.AliasMap aliases = await CommandHelpers.GetAliasesAsync( _executionService, cancellationToken).ConfigureAwait(false); @@ -226,36 +222,21 @@ static string[] GetIdentifiers(string symbolName, SymbolType symbolType, Command foreach (ScriptFile file in _workspaceService.GetOpenedFiles()) { + List fileReferences = new(); foreach (string targetIdentifier in allIdentifiers) { - if (!file.References.TryGetReferences(targetIdentifier, out ConcurrentBag references)) + if (!file.References.TryGetReferences(targetIdentifier, out ConcurrentBag references)) { continue; } - foreach (IScriptExtent extent in references.OrderBy(e => e.StartOffset)) - { - SymbolReference reference = new( - SymbolType.Function, - foundSymbol.SymbolName, - extent); - - try - { - reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber); - } - catch (ArgumentOutOfRangeException e) - { - reference.SourceLine = string.Empty; - _logger.LogException("Found reference is out of range in script file", e); - } - reference.FilePath = file.FilePath; - symbolReferences.Add(reference); - } + fileReferences.AddRange(references); await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); } + + symbolReferences.AddRange(fileReferences.OrderBy(symbol => symbol.ScriptRegion.StartOffset)); } return symbolReferences; @@ -283,7 +264,12 @@ public static IReadOnlyList FindOccurrencesInFile( return null; } - return AstOperations.FindReferencesOfSymbol(file.ScriptAst, foundSymbol).ToArray(); + if (file.References.TryGetReferences(foundSymbol.SymbolName, out ConcurrentBag references)) + { + return references.OrderBy(symbol => symbol.ScriptRegion.StartOffset).ToArray(); + } + + return null; } /// @@ -425,19 +411,17 @@ public async Task GetDefinitionOfSymbolAsync( (Dictionary> _, Dictionary aliasToCmdlets) = await CommandHelpers.GetAliasesAsync(_executionService).ConfigureAwait(false); - if (aliasToCmdlets.ContainsKey(foundSymbol.SymbolName)) + if (aliasToCmdlets.TryGetValue(foundSymbol.SymbolName, out string value)) { foundSymbol = new SymbolReference( foundSymbol.SymbolType, - aliasToCmdlets[foundSymbol.SymbolName], +value, foundSymbol.ScriptRegion, foundSymbol.FilePath, foundSymbol.SourceLine); } - ScriptFile[] referencedFiles = - _workspaceService.ExpandScriptReferences( - sourceFile); + ScriptFile[] referencedFiles = _workspaceService.ExpandScriptReferences(sourceFile); HashSet filesSearched = new(StringComparer.OrdinalIgnoreCase); @@ -461,7 +445,11 @@ public async Task GetDefinitionOfSymbolAsync( string dotSourcedPath = GetDotSourcedPath(foundSymbol, scriptFile); if (scriptFile.FilePath == dotSourcedPath) { - foundDefinition = new SymbolReference(SymbolType.Function, foundSymbol.SymbolName, scriptFile.ScriptAst.Extent, scriptFile.FilePath); + foundDefinition = new SymbolReference( + SymbolType.Function, + foundSymbol.SymbolName, + scriptFile.ScriptAst.Extent, + scriptFile.FilePath); break; } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 97ba26250..8799a9ce9 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -157,15 +157,13 @@ public static SymbolReference FindSymbolAtPosition( bool includeDefinitions = false, bool returnFullSignature = false) { - FindSymbolVisitor symbolVisitor = - new( - lineNumber, - columnNumber, - includeDefinitions, - returnFullSignature); + FindSymbolVisitor symbolVisitor = new( + lineNumber, + columnNumber, + includeDefinitions, + returnFullSignature); scriptAst.Visit(symbolVisitor); - return symbolVisitor.FoundSymbolReference; } @@ -180,35 +178,9 @@ public static SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumbe { FindCommandVisitor commandVisitor = new(lineNumber, columnNumber); scriptAst.Visit(commandVisitor); - return commandVisitor.FoundCommandReference; } - /// - /// Finds all references (including aliases) in a script for the given symbol - /// - /// The abstract syntax tree of the given script - /// The symbol that we are looking for references of - /// Dictionary mapping cmdlets to aliases for finding alias references - /// Dictionary mapping aliases to cmdlets for finding alias references - /// The found symbol references as an enumerable - public static IEnumerable FindReferencesOfSymbol( - Ast scriptAst, - SymbolReference symbolReference, - IDictionary> cmdletToAliasDictionary = default, - IDictionary aliasToCmdletDictionary = default) - { - // find the symbol evaluators for the node types we are handling - FindReferencesVisitor referencesVisitor = new( - symbolReference, - cmdletToAliasDictionary, - aliasToCmdletDictionary); - - scriptAst.Visit(referencesVisitor); - - return referencesVisitor.FoundReferences; - } - /// /// Finds the definition of the symbol /// @@ -240,7 +212,7 @@ public static IEnumerable FindSymbolsInDocument(Ast scriptAst) /// Checks if a given ast represents the root node of a *.psd1 file. /// /// The abstract syntax tree of the given script - /// true if the AST represts a *.psd1 file, otherwise false + /// true if the AST represents a *.psd1 file, otherwise false public static bool IsPowerShellDataFileAst(Ast ast) { // sometimes we don't have reliable access to the filename @@ -295,12 +267,10 @@ private static bool IsPowerShellDataFileAstNode(dynamic node, Type[] levelAstMap /// /// The abstract syntax tree of the given script /// Pre-calculated value of $PSScriptRoot - /// public static string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot) { FindDotSourcedVisitor dotSourcedVisitor = new(psScriptRoot); scriptAst.Visit(dotSourcedVisitor); - return dotSourcedVisitor.DotSourcedFiles.ToArray(); } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs deleted file mode 100644 index 11c297ee9..000000000 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.Symbols -{ - /// - /// The visitor used to find the references of a symbol in a script's AST - /// - internal class FindReferencesVisitor : AstVisitor2 - { - private readonly SymbolReference _symbolRef; - private readonly IDictionary> _cmdletToAliasDictionary; - private readonly IDictionary _aliasToCmdletDictionary; - private readonly string _symbolRefCommandName; - private readonly bool _needsAliases; - - public List FoundReferences { get; set; } - - /// - /// Constructor used when searching for aliases is needed - /// - /// The found symbolReference that other symbols are being compared to - /// Dictionary mapping cmdlets to aliases for finding alias references - /// Dictionary mapping aliases to cmdlets for finding alias references - public FindReferencesVisitor( - SymbolReference symbolReference, - IDictionary> cmdletToAliasDictionary = default, - IDictionary aliasToCmdletDictionary = default) - { - _symbolRef = symbolReference; - FoundReferences = new List(); - - if (cmdletToAliasDictionary is null || aliasToCmdletDictionary is null) - { - _needsAliases = false; - return; - } - - _needsAliases = true; - _cmdletToAliasDictionary = cmdletToAliasDictionary; - _aliasToCmdletDictionary = aliasToCmdletDictionary; - - // Try to get the symbolReference's command name of an alias. If a command name does not - // exists (if the symbol isn't an alias to a command) set symbolRefCommandName to an - // empty string. - aliasToCmdletDictionary.TryGetValue(symbolReference.ScriptRegion.Text, out _symbolRefCommandName); - _symbolRefCommandName ??= string.Empty; - } - - /// - /// Decides if the current command is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Function - /// and have the same name as the symbol - /// - /// A CommandAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitCommand(CommandAst commandAst) - { - Ast commandNameAst = commandAst.CommandElements[0]; - string commandName = commandNameAst.Extent.Text; - - if (_symbolRef.SymbolType.Equals(SymbolType.Function)) - { - if (_needsAliases) - { - // Try to get the commandAst's name and aliases. - // - // If a command does not exist (if the symbol isn't an alias to a command) set - // command to an empty string value string command. - // - // If the aliases do not exist (if the symbol isn't a command that has aliases) - // set aliases to an empty List - _cmdletToAliasDictionary.TryGetValue(commandName, out List aliases); - _aliasToCmdletDictionary.TryGetValue(commandName, out string command); - aliases ??= new List(); - command ??= string.Empty; - - // Check if the found symbol's name is the same as the commandAst's name OR - // if the symbol's name is an alias for this commandAst's name (commandAst is a cmdlet) OR - // if the symbol's name is the same as the commandAst's cmdlet name (commandAst is a alias) - if (commandName.Equals(_symbolRef.SymbolName, StringComparison.OrdinalIgnoreCase) - // Note that PowerShell command names and aliases are case insensitive. - || aliases.Exists((match) => string.Equals(match, _symbolRef.ScriptRegion.Text, StringComparison.OrdinalIgnoreCase)) - || command.Equals(_symbolRef.ScriptRegion.Text, StringComparison.OrdinalIgnoreCase) - || (!string.IsNullOrEmpty(command) - && command.Equals(_symbolRefCommandName, StringComparison.OrdinalIgnoreCase))) - { - FoundReferences.Add(new SymbolReference(SymbolType.Function, commandNameAst.Extent)); - } - } - else // search does not include aliases - { - if (commandName.Equals(_symbolRef.SymbolName, StringComparison.OrdinalIgnoreCase)) - { - FoundReferences.Add(new SymbolReference(SymbolType.Function, commandNameAst.Extent)); - } - } - } - - return base.VisitCommand(commandAst); - } - - /// - /// Decides if the current function definition is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Function and have the same name as the symbol - /// - /// A FunctionDefinitionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - { - // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. - // This will not exclude nested functions as they have ScriptBlockAst as parent - if (functionDefinitionAst.Parent is FunctionMemberAst) - { - return AstVisitAction.Continue; - } - - if (_symbolRef.SymbolType.Equals(SymbolType.Function) && - functionDefinitionAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - // We only want the function name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); - FoundReferences.Add(new SymbolReference(SymbolType.Function, nameExtent)); - } - return base.VisitFunctionDefinition(functionDefinitionAst); - } - - /// - /// Decides if the current command parameter is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Parameter and have the same name as the symbol - /// - /// A CommandParameterAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) - { - if (_symbolRef.SymbolType.Equals(SymbolType.Parameter) && - commandParameterAst.Extent.Text.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - FoundReferences.Add(new SymbolReference(SymbolType.Parameter, commandParameterAst.Extent)); - } - return AstVisitAction.Continue; - } - - /// - /// Decides if the current variable expression is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Variable and have the same name as the symbol - /// - /// A VariableExpressionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - { - if (_symbolRef.SymbolType.Equals(SymbolType.Variable) - && variableExpressionAst.Extent.Text.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - FoundReferences.Add(new SymbolReference(SymbolType.Variable, variableExpressionAst.Extent)); - } - return AstVisitAction.Continue; - } - - /// - /// Decides if the current type definition is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Class or SymbolType.Enum and have the same name as the symbol - /// - /// A TypeDefinitionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) - { - SymbolType symbolType = - typeDefinitionAst.IsEnum ? - SymbolType.Enum : SymbolType.Class; - - if ((_symbolRef.SymbolType is SymbolType.Type || _symbolRef.SymbolType.Equals(symbolType)) && - typeDefinitionAst.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - // We only want the type name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); - FoundReferences.Add(new SymbolReference(symbolType, nameExtent)); - } - return AstVisitAction.Continue; - } - - /// - /// Decides if the current type expression is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Type and have the same name as the symbol - /// - /// A TypeExpressionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) - { - // We don't know if we're looking at a class or enum, but name is likely unique - if (IsTypeSymbol(_symbolRef.SymbolType) && - typeExpressionAst.TypeName.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - FoundReferences.Add(new SymbolReference(SymbolType.Type, typeExpressionAst.Extent)); - } - return AstVisitAction.Continue; - } - - /// - /// Decides if the current type constraint is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Type and have the same name as the symbol - /// - /// A TypeConstraintAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) - { - // We don't know if we're looking at a class or enum, but name is likely unique - if (IsTypeSymbol(_symbolRef.SymbolType) && - typeConstraintAst.TypeName.Name.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - FoundReferences.Add(new SymbolReference(SymbolType.Type, typeConstraintAst.Extent)); - } - return AstVisitAction.Continue; - } - - /// - /// Decides if the current function member is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Constructor or SymbolType.Method and have the same name as the symbol - /// - /// A FunctionMemberAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) - { - SymbolType symbolType = - functionMemberAst.IsConstructor ? - SymbolType.Constructor : SymbolType.Method; - - if (_symbolRef.SymbolType.Equals(symbolType) && - VisitorUtils.GetMemberOverloadName(functionMemberAst, true, false).Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - // We only want the method/ctor name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, false); - FoundReferences.Add(new SymbolReference(symbolType, nameExtent)); - } - return AstVisitAction.Continue; - } - - /// - /// Decides if the current property member is a reference of the symbol being searched for. - /// A reference of the symbol will be a of type SymbolType.Property or SymbolType.EnumMember - /// and have the same name as the symbol. - /// - /// A PropertyMemberAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) - { - SymbolType symbolType = - propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? - SymbolType.EnumMember : SymbolType.Property; - - if (_symbolRef.SymbolType.Equals(symbolType) && - VisitorUtils.GetMemberOverloadName(propertyMemberAst, false).Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - // We only want the property name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); - FoundReferences.Add(new SymbolReference(SymbolType.Property, nameExtent)); - } - return AstVisitAction.Continue; - } - - /// - /// Tests if symbol type is a type (class/enum) definition or type reference. - /// - private static bool IsTypeSymbol(SymbolType symbolType) - => symbolType is SymbolType.Class or SymbolType.Enum or SymbolType.Type; - } -} diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs index 55c90b4fd..3fc1fd70b 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using Microsoft.PowerShell.EditorServices.Utility; using System.Collections.Generic; using System.Management.Automation.Language; diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index 5071cc745..04dfa7b8e 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -48,7 +48,7 @@ public override Task Handle(Document { ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); - IEnumerable foundSymbols = + IEnumerable foundSymbols = ProvideDocumentSymbols(scriptFile); SymbolInformationOrDocumentSymbol[] symbols = null; @@ -77,7 +77,7 @@ public override Task Handle(Document return Task.FromResult(new SymbolInformationOrDocumentSymbolContainer(symbols)); } - private IEnumerable ProvideDocumentSymbols( + private IEnumerable ProvideDocumentSymbols( ScriptFile scriptFile) { return @@ -139,7 +139,7 @@ private static SymbolKind GetSymbolKind(SymbolType symbolType) }; } - private static string GetDecoratedSymbolName(ISymbolReference symbolReference) + private static string GetDecoratedSymbolName(SymbolReference symbolReference) { string name = symbolReference.SymbolName; diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index 2b9cb4fa4..882b7fe4b 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index 3fc3f7ea1..56a393c2b 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Collections.Generic; using System.Management.Automation.Language; @@ -14,6 +16,22 @@ namespace Microsoft.PowerShell.EditorServices.Utility /// internal static class VisitorUtils { + internal static string? GetCommandName(CommandAst commandAst) + { + string commandName = commandAst.GetCommandName(); + if (!string.IsNullOrEmpty(commandName)) + { + return commandName; + } + + if (commandAst.CommandElements[0] is not ExpandableStringExpressionAst expandableStringExpressionAst) + { + return null; + } + + return PSESSymbols.AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null; + } + /// /// Calculates the start line and column of the actual symbol name in a AST. /// diff --git a/test/PowerShellEditorServices.Test.Shared/References/FunctionReference.ps1 b/test/PowerShellEditorServices.Test.Shared/References/FunctionReference.ps1 new file mode 100644 index 000000000..279f262b7 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/References/FunctionReference.ps1 @@ -0,0 +1,24 @@ +function BasicFunction {} +BasicFunction + +function FunctionWithExtraSpace +{ + +} FunctionWithExtraSpace + +function + + + FunctionNameOnDifferentLine + + + + + + + {} + + + FunctionNameOnDifferentLine + + function IndentedFunction { } IndentedFunction diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index e0dd3203f..6f9909ef8 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -153,9 +153,9 @@ await psesHost.ExecutePSCommandAsync( public async Task FindsReferencesOnFunction() { List referencesResult = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); - Assert.Equal(2, referencesResult.Count); - Assert.Equal(3, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(2, referencesResult[0].ScriptRegion.StartColumnNumber); + Assert.Equal(3, referencesResult.Count); + Assert.Equal(1, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber); } [Fact] @@ -167,9 +167,9 @@ await psesHost.ExecutePSCommandAsync( CancellationToken.None).ConfigureAwait(true); List referencesResult = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); - Assert.Equal(3, referencesResult.Count); - Assert.Equal(3, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(2, referencesResult[0].ScriptRegion.StartColumnNumber); + Assert.Equal(4, referencesResult.Count); + Assert.Equal(1, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber); } [Fact] @@ -255,8 +255,11 @@ public void FindsOccurrencesOnParameter() public async Task FindsReferencesOnCommandWithAlias() { List referencesResult = await GetReferences(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails).ConfigureAwait(true); - Assert.Equal(4, referencesResult.Count); - Assert.All(referencesResult, (i) => Assert.Equal("Get-ChildItem", i.SymbolName)); + Assert.Collection(referencesResult, + (i) => Assert.Equal("Get-ChildItem", i.SymbolName), + (i) => Assert.Equal("gci", i.SymbolName), + (i) => Assert.Equal("dir", i.SymbolName), + (i) => Assert.Equal("Get-ChildItem", i.SymbolName)); } [Fact] @@ -282,7 +285,7 @@ public void FindsOccurrencesOnClass() { IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ClassSourceDetails); Assert.Equal(2, occurrencesResult.Count); - Assert.Equal("[SuperClass]", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal("SuperClass", occurrencesResult[occurrencesResult.Count - 1].SymbolName); Assert.Equal(34, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); } @@ -309,7 +312,7 @@ public void FindsOccurrencesOnEnum() { IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumSourceDetails); Assert.Equal(4, occurrencesResult.Count); - Assert.Equal("[MyEnum]", occurrencesResult[occurrencesResult.Count - 1].SymbolName); + Assert.Equal("MyEnum", occurrencesResult[occurrencesResult.Count - 1].SymbolName); Assert.Equal(46, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); } @@ -479,14 +482,14 @@ public void FindsOccurrencesOnEnumMember() public async Task FindsReferencesOnFileWithReferencesFileB() { List referencesResult = await GetReferences(FindsReferencesOnFunctionMultiFileDotSourceFileB.SourceDetails).ConfigureAwait(true); - Assert.Equal(3, referencesResult.Count); + Assert.Equal(4, referencesResult.Count); } [Fact] public async Task FindsReferencesOnFileWithReferencesFileC() { List referencesResult = await GetReferences(FindsReferencesOnFunctionMultiFileDotSourceFileC.SourceDetails).ConfigureAwait(true); - Assert.Equal(3, referencesResult.Count); + Assert.Equal(4, referencesResult.Count); } [Fact] diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs index 6789c4323..44cebe878 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs @@ -1,10 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Language; +using System.Collections.Concurrent; +using System.Linq; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Test.Shared; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Xunit; @@ -13,32 +16,13 @@ namespace PowerShellEditorServices.Test.Services.Symbols [Trait("Category", "AstOperations")] public class AstOperationsTests { - private const string s_scriptString = @"function BasicFunction {} -BasicFunction + private readonly ScriptFile scriptFile; -function FunctionWithExtraSpace -{ - -} FunctionWithExtraSpace - -function - - - FunctionNameOnDifferentLine - - - - - - - {} - - - FunctionNameOnDifferentLine - - function IndentedFunction { } IndentedFunction -"; - private static readonly ScriptBlockAst s_ast = (ScriptBlockAst)ScriptBlock.Create(s_scriptString).Ast; + public AstOperationsTests() + { + WorkspaceService workspace = new(NullLoggerFactory.Instance); + scriptFile = workspace.GetFile(TestUtilities.GetSharedPath("References/FunctionReference.ps1")); + } [Theory] [InlineData(1, 15, "BasicFunction")] @@ -51,7 +35,10 @@ function IndentedFunction { } IndentedFunction [InlineData(24, 52, "IndentedFunction")] public void CanFindSymbolAtPosition(int lineNumber, int columnNumber, string expectedName) { - SymbolReference reference = AstOperations.FindSymbolAtPosition(s_ast, lineNumber, columnNumber); + SymbolReference reference = AstOperations.FindSymbolAtPosition( + scriptFile.ScriptAst, + lineNumber, + columnNumber); Assert.NotNull(reference); Assert.Equal(expectedName, reference.SymbolName); } @@ -60,12 +47,17 @@ public void CanFindSymbolAtPosition(int lineNumber, int columnNumber, string exp [MemberData(nameof(FindReferencesOfSymbolAtPositionData))] public void CanFindReferencesOfSymbolAtPosition(int lineNumber, int columnNumber, Range[] symbolRange) { - SymbolReference symbol = AstOperations.FindSymbolAtPosition(s_ast, lineNumber, columnNumber); + SymbolReference symbol = AstOperations.FindSymbolAtPosition( + scriptFile.ScriptAst, + lineNumber, + columnNumber); - IEnumerable references = AstOperations.FindReferencesOfSymbol(s_ast, symbol); + Assert.True(scriptFile.References.TryGetReferences( + symbol.SymbolName, + out ConcurrentBag references)); int positionsIndex = 0; - foreach (SymbolReference reference in references) + foreach (SymbolReference reference in references.OrderBy((i) => i.ScriptRegion.StartOffset)) { Assert.Equal(symbolRange[positionsIndex].Start.Line, reference.ScriptRegion.StartLineNumber); Assert.Equal(symbolRange[positionsIndex].Start.Character, reference.ScriptRegion.StartColumnNumber); From a335897c559156f418987910f0873b3a3c11513a Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 10 Jan 2023 17:33:21 -0800 Subject: [PATCH 175/327] Deduplicate `GetSymbolKind`, `GetDecoratedSymbolName`, and `GetRangeFromScriptRegion` At least I'm pretty sure these were unnecessary duplicates. --- .../Services/Symbols/SymbolType.cs | 44 ++++++++++++- .../Handlers/DefinitionHandler.cs | 19 +----- .../Handlers/DocumentSymbolHandler.cs | 59 +----------------- .../TextDocument/Handlers/HoverHandler.cs | 19 +----- .../Handlers/ReferencesHandler.cs | 19 +----- .../Services/TextDocument/ScriptRegion.cs | 17 +++++ .../Handlers/WorkspaceSymbolsHandler.cs | 62 ++----------------- 7 files changed, 70 insertions(+), 169 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index 7f5850f45..c9a113a25 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// @@ -14,7 +16,7 @@ internal enum SymbolType Unknown = 0, /// - /// The symbol is a vairable + /// The symbol is a variable /// Variable, @@ -78,4 +80,44 @@ internal enum SymbolType /// Type, } + + internal static class SymbolTypeUtils + { + internal static SymbolKind GetSymbolKind(SymbolType symbolType) + { + return symbolType switch + { + SymbolType.Function or SymbolType.Configuration or SymbolType.Workflow => SymbolKind.Function, + SymbolType.Enum => SymbolKind.Enum, + SymbolType.Class => SymbolKind.Class, + SymbolType.Constructor => SymbolKind.Constructor, + SymbolType.Method => SymbolKind.Method, + SymbolType.Property => SymbolKind.Property, + SymbolType.EnumMember => SymbolKind.EnumMember, + // TODO: More delicately handle the other symbol types. + _ => SymbolKind.Variable, + }; + } + + internal static string GetDecoratedSymbolName(SymbolReference symbolReference) + { + string name = symbolReference.SymbolName; + + // Append { } for symbols with scriptblock + // Constructors and Methods have overloaded names already + if (symbolReference.SymbolType is + SymbolType.Function or + SymbolType.Enum or + SymbolType.Class or + SymbolType.Constructor or + SymbolType.Method or + SymbolType.Configuration or + SymbolType.Workflow) + { + name += " { }"; + } + + return name; + } + } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs index 8b9c2fc60..95dba3c5a 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs @@ -57,29 +57,12 @@ public override async Task Handle(DefinitionParams requ new Location { Uri = DocumentUri.From(foundDefinition.FilePath), - Range = GetRangeFromScriptRegion(foundDefinition.ScriptRegion) + Range = ScriptRegion.GetRangeFromScriptRegion(foundDefinition.ScriptRegion) })); } } return new LocationOrLocationLinks(definitionLocations); } - - private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion) - { - return new Range - { - Start = new Position - { - Line = scriptRegion.StartLineNumber - 1, - Character = scriptRegion.StartColumnNumber - 1 - }, - End = new Position - { - Line = scriptRegion.EndLineNumber - 1, - Character = scriptRegion.EndColumnNumber - 1 - } - }; - } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index 04dfa7b8e..d54f7232b 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -62,13 +62,13 @@ public override Task Handle(Document return new SymbolInformationOrDocumentSymbol(new SymbolInformation { ContainerName = containerName, - Kind = GetSymbolKind(r.SymbolType), + Kind = SymbolTypeUtils.GetSymbolKind(r.SymbolType), Location = new Location { Uri = DocumentUri.From(r.FilePath), - Range = GetRangeFromScriptRegion(r.ScriptRegion) + Range = ScriptRegion.GetRangeFromScriptRegion(r.ScriptRegion) }, - Name = GetDecoratedSymbolName(r) + Name = SymbolTypeUtils.GetDecoratedSymbolName(r) }); }) .ToArray() @@ -123,58 +123,5 @@ protected IEnumerable InvokeProviders( return providerResults; } - - private static SymbolKind GetSymbolKind(SymbolType symbolType) - { - return symbolType switch - { - SymbolType.Function or SymbolType.Configuration or SymbolType.Workflow => SymbolKind.Function, - SymbolType.Enum => SymbolKind.Enum, - SymbolType.Class => SymbolKind.Class, - SymbolType.Constructor => SymbolKind.Constructor, - SymbolType.Method => SymbolKind.Method, - SymbolType.Property => SymbolKind.Property, - SymbolType.EnumMember => SymbolKind.EnumMember, - _ => SymbolKind.Variable, - }; - } - - private static string GetDecoratedSymbolName(SymbolReference symbolReference) - { - string name = symbolReference.SymbolName; - - // Append { } for symbols with scriptblock - // Constructors and Methods have overloaded names already - if (symbolReference.SymbolType is - SymbolType.Function or - SymbolType.Enum or - SymbolType.Class or - SymbolType.Constructor or - SymbolType.Method or - SymbolType.Configuration or - SymbolType.Workflow) - { - name += " { }"; - } - - return name; - } - - private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion) - { - return new Range - { - Start = new Position - { - Line = scriptRegion.StartLineNumber - 1, - Character = scriptRegion.StartColumnNumber - 1 - }, - End = new Position - { - Line = scriptRegion.EndLineNumber - 1, - Character = scriptRegion.EndColumnNumber - 1 - } - }; - } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs index cf90c6565..02264d469 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs @@ -67,7 +67,7 @@ await _symbolsService.FindSymbolDetailsAtLocationAsync( symbolInfo.Add(new MarkedString("markdown", symbolDetails.Documentation)); } - Range symbolRange = GetRangeFromScriptRegion(symbolDetails.SymbolReference.ScriptRegion); + Range symbolRange = ScriptRegion.GetRangeFromScriptRegion(symbolDetails.SymbolReference.ScriptRegion); return new Hover { @@ -75,22 +75,5 @@ await _symbolsService.FindSymbolDetailsAtLocationAsync( Range = symbolRange }; } - - private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion) - { - return new Range - { - Start = new Position - { - Line = scriptRegion.StartLineNumber - 1, - Character = scriptRegion.StartColumnNumber - 1 - }, - End = new Position - { - Line = scriptRegion.EndLineNumber - 1, - Character = scriptRegion.EndColumnNumber - 1 - } - }; - } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index 882b7fe4b..73bf491bf 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -56,29 +56,12 @@ await _symbolsService.ScanForReferencesOfSymbol( locations.Add(new Location { Uri = DocumentUri.From(foundReference.FilePath), - Range = GetRangeFromScriptRegion(foundReference.ScriptRegion) + Range = ScriptRegion.GetRangeFromScriptRegion(foundReference.ScriptRegion) }); } } return new LocationContainer(locations); } - - private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion) - { - return new Range - { - Start = new Position - { - Line = scriptRegion.StartLineNumber - 1, - Character = scriptRegion.StartColumnNumber - 1 - }, - End = new Position - { - Line = scriptRegion.EndLineNumber - 1, - Character = scriptRegion.EndColumnNumber - 1 - } - }; - } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs index c4d407105..5973f59b1 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs @@ -69,6 +69,23 @@ internal static TextEdit ToTextEdit(ScriptRegion scriptRegion) }; } + internal static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion) + { + return new Range + { + Start = new Position + { + Line = scriptRegion.StartLineNumber - 1, + Character = scriptRegion.StartColumnNumber - 1 + }, + End = new Position + { + Line = scriptRegion.EndLineNumber - 1, + Character = scriptRegion.EndColumnNumber - 1 + } + }; + } + #endregion #region Constructors diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 3fa830f99..8ee6afbe8 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -38,9 +38,7 @@ public override async Task> Handle(WorkspaceSymbolP foreach (ScriptFile scriptFile in _workspaceService.GetOpenedFiles()) { - List foundSymbols = - _symbolsService.FindSymbolsInFile( - scriptFile); + List foundSymbols = _symbolsService.FindSymbolsInFile(scriptFile); // TODO: Need to compute a relative path that is based on common path for all workspace files string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); @@ -66,15 +64,15 @@ public override async Task> Handle(WorkspaceSymbolP Location location = new() { Uri = DocumentUri.From(foundOccurrence.FilePath), - Range = GetRangeFromScriptRegion(foundOccurrence.ScriptRegion) + Range = ScriptRegion.GetRangeFromScriptRegion(foundOccurrence.ScriptRegion) }; symbols.Add(new SymbolInformation { ContainerName = containerName, - Kind = GetSymbolKind(foundOccurrence.SymbolType), + Kind = SymbolTypeUtils.GetSymbolKind(foundOccurrence.SymbolType), Location = location, - Name = GetDecoratedSymbolName(foundOccurrence) + Name = SymbolTypeUtils.GetDecoratedSymbolName(foundOccurrence) }); } } @@ -86,58 +84,6 @@ public override async Task> Handle(WorkspaceSymbolP private static bool IsQueryMatch(string query, string symbolName) => symbolName.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; - private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion) - { - return new Range - { - Start = new Position - { - Line = scriptRegion.StartLineNumber - 1, - Character = scriptRegion.StartColumnNumber - 1 - }, - End = new Position - { - Line = scriptRegion.EndLineNumber - 1, - Character = scriptRegion.EndColumnNumber - 1 - } - }; - } - - private static string GetDecoratedSymbolName(SymbolReference symbolReference) - { - string name = symbolReference.SymbolName; - - // Append { } for symbols with scriptblock - // Constructors and Methods have overloaded names already - if (symbolReference.SymbolType is - SymbolType.Function or - SymbolType.Enum or - SymbolType.Class or - SymbolType.Constructor or - SymbolType.Method or - SymbolType.Configuration or - SymbolType.Workflow) - { - name += " { }"; - } - - return name; - } - - private static SymbolKind GetSymbolKind(SymbolType symbolType) - { - return symbolType switch - { - SymbolType.Function or SymbolType.Configuration or SymbolType.Workflow => SymbolKind.Function, - SymbolType.Enum => SymbolKind.Enum, - SymbolType.Class => SymbolKind.Class, - SymbolType.Constructor => SymbolKind.Constructor, - SymbolType.Method => SymbolKind.Method, - SymbolType.Property => SymbolKind.Property, - _ => SymbolKind.Variable, - }; - } - #endregion } } From fe3a37e83603ae3cc484a9926af7ae7963446e8d Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 11 Jan 2023 14:25:26 -0800 Subject: [PATCH 176/327] Move reference sorting to unit tests Since the client doesn't actually care which order we return the references in, we should skip unnecessarily sorting. It appears to apply its own sorting to the results regardless of their order, and the specification doesn't say it needs to be sorted. Also adjust two tests to account for the definitions being in the reference lists. --- .../CodeLens/ReferencesCodeLensProvider.cs | 3 +- .../Services/Symbols/SymbolsService.cs | 11 +++---- .../Handlers/DocumentHighlightHandler.cs | 15 +++++---- .../Handlers/ReferencesHandler.cs | 2 +- .../Language/SymbolsServiceTests.cs | 32 ++++++++++++------- 5 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index 9a3166a4d..653d15780 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -104,7 +104,8 @@ public async Task ResolveCodeLens( codeLens.Range.Start.Line + 1, codeLens.Range.Start.Character + 1); - List referencesResult = await _symbolsService.ScanForReferencesOfSymbol( + IEnumerable referencesResult = + await _symbolsService.ScanForReferencesOfSymbol( foundSymbol, references, cancellationToken).ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index d2a842680..7daee77c2 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -159,7 +159,7 @@ public static SymbolReference FindSymbolAtLocation( /// An array of scriptFiles to search for references in /// /// FindReferencesResult - public async Task> ScanForReferencesOfSymbol( + public async Task> ScanForReferencesOfSymbol( SymbolReference foundSymbol, ScriptFile[] referencedFiles, CancellationToken cancellationToken = default) @@ -222,7 +222,6 @@ static string[] GetIdentifiers(string symbolName, SymbolType symbolType, Command foreach (ScriptFile file in _workspaceService.GetOpenedFiles()) { - List fileReferences = new(); foreach (string targetIdentifier in allIdentifiers) { if (!file.References.TryGetReferences(targetIdentifier, out ConcurrentBag references)) @@ -230,13 +229,11 @@ static string[] GetIdentifiers(string symbolName, SymbolType symbolType, Command continue; } - fileReferences.AddRange(references); + symbolReferences.AddRange(references); await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); } - - symbolReferences.AddRange(fileReferences.OrderBy(symbol => symbol.ScriptRegion.StartOffset)); } return symbolReferences; @@ -249,7 +246,7 @@ static string[] GetIdentifiers(string symbolName, SymbolType symbolType, Command /// The line number of the cursor for the given script /// The column number of the cursor for the given script /// FindOccurrencesResult - public static IReadOnlyList FindOccurrencesInFile( + public static IEnumerable FindOccurrencesInFile( ScriptFile file, int symbolLineNumber, int symbolColumnNumber) @@ -266,7 +263,7 @@ public static IReadOnlyList FindOccurrencesInFile( if (file.References.TryGetReferences(foundSymbol.SymbolName, out ConcurrentBag references)) { - return references.OrderBy(symbol => symbol.ScriptRegion.StartOffset).ToArray(); + return references; } return null; diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs index 8e318f3bc..8791a6466 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs @@ -40,25 +40,26 @@ public override Task Handle( { ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); - IReadOnlyList symbolOccurrences = SymbolsService.FindOccurrencesInFile( + IEnumerable occurrences = SymbolsService.FindOccurrencesInFile( scriptFile, request.Position.Line + 1, request.Position.Character + 1); - if (symbolOccurrences is null) + if (occurrences is null) { return Task.FromResult(s_emptyHighlightContainer); } - DocumentHighlight[] highlights = new DocumentHighlight[symbolOccurrences.Count]; - for (int i = 0; i < symbolOccurrences.Count; i++) + List highlights = new(); + foreach (SymbolReference occurrence in occurrences) { - highlights[i] = new DocumentHighlight + highlights.Add(new DocumentHighlight { Kind = DocumentHighlightKind.Write, // TODO: Which symbol types are writable? - Range = symbolOccurrences[i].ScriptRegion.ToRange() - }; + Range = occurrence.ScriptRegion.ToRange() + }); } + _logger.LogDebug("Highlights: " + highlights); return Task.FromResult(new DocumentHighlightContainer(highlights)); diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index 73bf491bf..a6dde43e5 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -41,7 +41,7 @@ public override async Task Handle(ReferenceParams request, Ca request.Position.Line + 1, request.Position.Character + 1); - List referencesResult = + IEnumerable referencesResult = await _symbolsService.ScanForReferencesOfSymbol( foundSymbol, _workspaceService.ExpandScriptReferences(scriptFile), diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 6f9909ef8..6de254f44 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -81,7 +81,7 @@ private Task GetDefinition(ScriptRegion scriptRegion) return symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbolReference); } - private Task> GetReferences(ScriptRegion scriptRegion) + private async Task> GetReferences(ScriptRegion scriptRegion) { ScriptFile scriptFile = GetScriptFile(scriptRegion); @@ -92,17 +92,23 @@ private Task> GetReferences(ScriptRegion scriptRegion) Assert.NotNull(symbolReference); - return symbolsService.ScanForReferencesOfSymbol( - symbolReference, - workspace.ExpandScriptReferences(scriptFile)); + IEnumerable symbols = + await symbolsService.ScanForReferencesOfSymbol( + symbolReference, + workspace.ExpandScriptReferences(scriptFile)).ConfigureAwait(true); + + return symbols.OrderBy(symbol => symbol.ScriptRegion.StartOffset).ToList(); } private IReadOnlyList GetOccurrences(ScriptRegion scriptRegion) { - return SymbolsService.FindOccurrencesInFile( - GetScriptFile(scriptRegion), - scriptRegion.StartLineNumber, - scriptRegion.StartColumnNumber); + return SymbolsService + .FindOccurrencesInFile( + GetScriptFile(scriptRegion), + scriptRegion.StartLineNumber, + scriptRegion.StartColumnNumber) + .OrderBy(symbol => symbol.ScriptRegion.StartOffset) + .ToArray(); } private List FindSymbolsInFile(ScriptRegion scriptRegion) => symbolsService.FindSymbolsInFile(GetScriptFile(scriptRegion)); @@ -301,10 +307,11 @@ public async Task FindsEnumDefinition() [Fact] public async Task FindsReferencesOnEnum() { + // TODO: Remove definitions from references. List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumSourceDetails).ConfigureAwait(true); Assert.Equal(4, referencesResult.Count); - Assert.Equal(25, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(19, referencesResult[0].ScriptRegion.StartColumnNumber); + Assert.Equal(25, referencesResult[1].ScriptRegion.StartLineNumber); + Assert.Equal(19, referencesResult[1].ScriptRegion.StartColumnNumber); } [Fact] @@ -355,10 +362,11 @@ public async Task FindsTypeConstraintDefinition() [Fact] public async Task FindsReferencesOnTypeConstraint() { + // TODO: Remove definitions from references. List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeConstraintSourceDetails).ConfigureAwait(true); Assert.Equal(4, referencesResult.Count); - Assert.Equal(25, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(19, referencesResult[0].ScriptRegion.StartColumnNumber); + Assert.Equal(25, referencesResult[1].ScriptRegion.StartLineNumber); + Assert.Equal(19, referencesResult[1].ScriptRegion.StartColumnNumber); } [Fact] From b17059d9ce2179e4503bbdbee0b1b1172a55816d Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 11 Jan 2023 17:37:28 -0800 Subject: [PATCH 177/327] More simplification and dead code elimination --- .../CodeLens/PesterCodeLensProvider.cs | 1 - .../CodeLens/ReferencesCodeLensProvider.cs | 1 - .../Symbols/PesterDocumentSymbolProvider.cs | 2 +- .../Services/Symbols/ReferenceTable.cs | 11 +-- .../Services/Symbols/SymbolReference.cs | 62 +------------ .../Services/Symbols/Vistors/AstOperations.cs | 66 ------------- .../Handlers/CodeActionHandler.cs | 2 +- .../Handlers/DefinitionHandler.cs | 2 +- .../Handlers/DocumentSymbolHandler.cs | 3 +- .../TextDocument/Handlers/HoverHandler.cs | 4 +- .../Handlers/ReferencesHandler.cs | 2 +- .../Services/TextDocument/ScriptFileMarker.cs | 12 +-- .../Services/TextDocument/ScriptRegion.cs | 93 ++++++------------- .../Handlers/WorkspaceSymbolsHandler.cs | 3 +- 14 files changed, 51 insertions(+), 213 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs index 881bbc037..293d2ff48 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs @@ -7,7 +7,6 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; -using Microsoft.PowerShell.EditorServices.Utility; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index 653d15780..eeea04375 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -9,7 +9,6 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; -using Microsoft.PowerShell.EditorServices.Utility; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Models; diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index cbdedb96d..8730ed081 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -196,7 +196,7 @@ internal enum PesterCommandType /// Provides a specialization of SymbolReference containing /// extra information about Pester test symbols. /// - internal class PesterSymbolReference : SymbolReference + internal record PesterSymbolReference : SymbolReference { /// /// Lookup for Pester keywords we support. Ideally we could extract these from Pester itself diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index deafed8f2..d80e0dbd0 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -109,10 +109,6 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) return AstVisitAction.Continue; } - // TODO: We should examine if we really want to constrain the extents to the name only. This - // means that highlighting only highlights the symbol name, but providing the whole extend - // means the whole function (or class etc.) gets highlighted, which seems to be a personal - // preference. public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. @@ -122,7 +118,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.Continue; } - // We only want the function name + // We only want the function name as the extent for highlighting (and so forth). IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); _references.AddReference( SymbolType.Function, @@ -159,7 +155,6 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit { SymbolType symbolType = typeDefinitionAst.IsEnum ? SymbolType.Enum : SymbolType.Class; - // We only want the type name. Get start-location for name IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); _references.AddReference(symbolType, typeDefinitionAst.Name, nameExtent); @@ -189,7 +184,6 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem ? SymbolType.Constructor : SymbolType.Method; - // We only want the method/ctor name. Get start-location for name IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, false); _references.AddReference( symbolType, @@ -201,7 +195,6 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { - // We only want the property name. Get start-location for name IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); _references.AddReference( SymbolType.Property, @@ -210,7 +203,5 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem return AstVisitAction.Continue; } - - // TODO: What else can we implement? } } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index ccedc0468..c3a3c5d32 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -1,75 +1,30 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System.Diagnostics; using System.Management.Automation.Language; using Microsoft.PowerShell.EditorServices.Services.TextDocument; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { - internal interface ISymbolReference - { - /// - /// Gets the symbol's type - /// - SymbolType SymbolType { get; } - - /// - /// Gets the name of the symbol - /// - string SymbolName { get; } - - /// - /// Gets the script extent of the symbol - /// - ScriptRegion ScriptRegion { get; } - - /// - /// Gets the contents of the line the given symbol is on - /// - string SourceLine { get; } - - /// - /// Gets the path of the file in which the symbol was found. - /// - string FilePath { get; } - } - /// /// A class that holds the type, name, script extent, and source line of a symbol /// [DebuggerDisplay("SymbolType = {SymbolType}, SymbolName = {SymbolName}")] - internal class SymbolReference : ISymbolReference + internal record SymbolReference { - #region Properties - - /// - /// Gets the symbol's type - /// public SymbolType SymbolType { get; } - /// - /// Gets the name of the symbol - /// public string SymbolName { get; } - /// - /// Gets the script extent of the symbol - /// public ScriptRegion ScriptRegion { get; } - /// - /// Gets the contents of the line the given symbol is on - /// public string SourceLine { get; internal set; } - /// - /// Gets the path of the file in which the symbol was found. - /// public string FilePath { get; internal set; } - #endregion - /// /// Constructs and instance of a SymbolReference /// @@ -88,16 +43,9 @@ public SymbolReference( // TODO: Verify params SymbolType = symbolType; SymbolName = symbolName; - ScriptRegion = ScriptRegion.Create(scriptExtent); + ScriptRegion = new(scriptExtent); FilePath = filePath; SourceLine = sourceLine; - - // TODO: Make sure end column number usage is correct - - // Build the display string - //this.DisplayString = - // string.Format( - // "{0} {1}") } public SymbolReference( @@ -108,7 +56,7 @@ public SymbolReference( { SymbolType = symbolType; SymbolName = symbolName; - ScriptRegion = ScriptRegion.Create(scriptExtent); + ScriptRegion = new(scriptExtent); FilePath = file.FilePath; try { diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 8799a9ce9..b46e7e83d 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -196,72 +196,6 @@ public static SymbolReference FindDefinitionOfSymbol( return declarationVisitor.FoundDeclaration; } - /// - /// Finds all symbols in a script - /// - /// The abstract syntax tree of the given script - /// A collection of SymbolReference objects - public static IEnumerable FindSymbolsInDocument(Ast scriptAst) - { - FindSymbolsVisitor findSymbolsVisitor = new(); - scriptAst.Visit(findSymbolsVisitor); - return findSymbolsVisitor.SymbolReferences; - } - - /// - /// Checks if a given ast represents the root node of a *.psd1 file. - /// - /// The abstract syntax tree of the given script - /// true if the AST represents a *.psd1 file, otherwise false - public static bool IsPowerShellDataFileAst(Ast ast) - { - // sometimes we don't have reliable access to the filename - // so we employ heuristics to check if the contents are - // part of a psd1 file. - return IsPowerShellDataFileAstNode( - new { Item = ast, Children = new List() }, - new Type[] { - typeof(ScriptBlockAst), - typeof(NamedBlockAst), - typeof(PipelineAst), - typeof(CommandExpressionAst), - typeof(HashtableAst) }, - 0); - } - - private static bool IsPowerShellDataFileAstNode(dynamic node, Type[] levelAstMap, int level) - { - dynamic levelAstTypeMatch = node.Item.GetType().Equals(levelAstMap[level]); - if (!levelAstTypeMatch) - { - return false; - } - - if (level == levelAstMap.Length - 1) - { - return levelAstTypeMatch; - } - - IEnumerable astsFound = (node.Item as Ast)?.FindAll(a => a is not null, false); - if (astsFound != null) - { - foreach (Ast astFound in astsFound) - { - if (!astFound.Equals(node.Item) - && node.Item.Equals(astFound.Parent) - && IsPowerShellDataFileAstNode( - new { Item = astFound, Children = new List() }, - levelAstMap, - level + 1)) - { - return true; - } - } - } - - return false; - } - /// /// Finds all files dot sourced in a script /// diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs index 2681baa19..895c804ec 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs @@ -95,7 +95,7 @@ public override async Task Handle(CodeActionParams { Uri = request.TextDocument.Uri }, - Edits = new TextEditContainer(ScriptRegion.ToTextEdit(markerCorrection.Edit)) + Edits = new TextEditContainer(markerCorrection.Edit.ToTextEdit()) })) } }); diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs index 95dba3c5a..1b00b9805 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs @@ -57,7 +57,7 @@ public override async Task Handle(DefinitionParams requ new Location { Uri = DocumentUri.From(foundDefinition.FilePath), - Range = ScriptRegion.GetRangeFromScriptRegion(foundDefinition.ScriptRegion) + Range = foundDefinition.ScriptRegion.ToRange() })); } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index d54f7232b..8119f5e77 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -59,6 +59,7 @@ public override Task Handle(Document ? foundSymbols .Select(r => { + // TODO: This should be a DocumentSymbol now as SymbolInformation is deprecated. return new SymbolInformationOrDocumentSymbol(new SymbolInformation { ContainerName = containerName, @@ -66,7 +67,7 @@ public override Task Handle(Document Location = new Location { Uri = DocumentUri.From(r.FilePath), - Range = ScriptRegion.GetRangeFromScriptRegion(r.ScriptRegion) + Range = r.ScriptRegion.ToRange() }, Name = SymbolTypeUtils.GetDecoratedSymbolName(r) }); diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs index 02264d469..7dbe07528 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs @@ -67,12 +67,10 @@ await _symbolsService.FindSymbolDetailsAtLocationAsync( symbolInfo.Add(new MarkedString("markdown", symbolDetails.Documentation)); } - Range symbolRange = ScriptRegion.GetRangeFromScriptRegion(symbolDetails.SymbolReference.ScriptRegion); - return new Hover { Contents = new MarkedStringsOrMarkupContent(symbolInfo), - Range = symbolRange + Range = symbolDetails.SymbolReference.ScriptRegion.ToRange() }; } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index a6dde43e5..6e545ba71 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -56,7 +56,7 @@ await _symbolsService.ScanForReferencesOfSymbol( locations.Add(new Location { Uri = DocumentUri.From(foundReference.FilePath), - Range = ScriptRegion.GetRangeFromScriptRegion(foundReference.ScriptRegion) + Range = foundReference.ScriptRegion.ToRange() }); } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs index 43c465701..e3273e3e6 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs @@ -34,19 +34,19 @@ public enum ScriptFileMarkerLevel ///          /// Information: This warning is trivial, but may be useful. They are recommended by PowerShell best practice.         ///  -        Information = 0, + Information = 0,         ///          /// WARNING: This warning may cause a problem or does not follow PowerShell's recommended guidelines.         ///  -        Warning = 1, + Warning = 1,         ///          /// ERROR: This warning is likely to cause a problem or does not follow PowerShell's required guidelines.         ///  -        Error = 2, + Error = 2,         ///          /// ERROR: This diagnostic is caused by an actual parsing error, and is generated only by the engine.         ///  -        ParseError = 3 + ParseError = 3 }; /// @@ -102,7 +102,7 @@ internal static ScriptFileMarker FromParseError( { Message = parseError.Message, Level = ScriptFileMarkerLevel.Error, - ScriptRegion = ScriptRegion.Create(parseError.Extent), + ScriptRegion = new(parseError.Extent), Source = "PowerShell" }; } @@ -157,7 +157,7 @@ internal static ScriptFileMarker FromDiagnosticRecord(PSObject psObject) Message = diagnosticRecord.Message as string ?? string.Empty, RuleName = diagnosticRecord.RuleName as string ?? string.Empty, Level = level, - ScriptRegion = ScriptRegion.Create(diagnosticRecord.Extent as IScriptExtent), + ScriptRegion = new(diagnosticRecord.Extent as IScriptExtent), Corrections = markerCorrections, Source = "PSScriptAnalyzer" }; diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs index 5973f59b1..789d6f11d 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Management.Automation.Language; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -12,82 +14,25 @@ namespace Microsoft.PowerShell.EditorServices.Services.TextDocument /// public sealed class ScriptRegion : IScriptExtent { - #region Static Methods - - /// - /// Creates a new instance of the ScriptRegion class from an - /// instance of an IScriptExtent implementation. - /// - /// - /// The IScriptExtent to copy into the ScriptRegion. - /// - /// - /// A new ScriptRegion instance with the same details as the IScriptExtent. - /// - public static ScriptRegion Create(IScriptExtent scriptExtent) - { - // IScriptExtent throws an ArgumentOutOfRange exception if Text is null - string scriptExtentText; - try - { - scriptExtentText = scriptExtent.Text; - } - catch (ArgumentOutOfRangeException) - { - scriptExtentText = string.Empty; - } - - return new ScriptRegion( - scriptExtent.File, - scriptExtentText, - scriptExtent.StartLineNumber, - scriptExtent.StartColumnNumber, - scriptExtent.StartOffset, - scriptExtent.EndLineNumber, - scriptExtent.EndColumnNumber, - scriptExtent.EndOffset); - } + public TextEdit ToTextEdit() => new() { NewText = Text, Range = ToRange() }; - internal static TextEdit ToTextEdit(ScriptRegion scriptRegion) - { - return new TextEdit - { - NewText = scriptRegion.Text, - Range = new Range - { - Start = new Position - { - Line = scriptRegion.StartLineNumber - 1, - Character = scriptRegion.StartColumnNumber - 1, - }, - End = new Position - { - Line = scriptRegion.EndLineNumber - 1, - Character = scriptRegion.EndColumnNumber - 1, - } - } - }; - } - - internal static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion) + public Range ToRange() { return new Range { Start = new Position { - Line = scriptRegion.StartLineNumber - 1, - Character = scriptRegion.StartColumnNumber - 1 + Line = StartLineNumber - 1, + Character = StartColumnNumber - 1 }, End = new Position { - Line = scriptRegion.EndLineNumber - 1, - Character = scriptRegion.EndColumnNumber - 1 + Line = EndLineNumber - 1, + Character = EndColumnNumber - 1 } }; } - #endregion - #region Constructors public ScriptRegion( @@ -110,6 +55,28 @@ public ScriptRegion( EndOffset = endOffset; } + public ScriptRegion (IScriptExtent scriptExtent) + { + File = scriptExtent.File; + + // IScriptExtent throws an ArgumentOutOfRange exception if Text is null + try + { + Text = scriptExtent.Text; + } + catch (ArgumentOutOfRangeException) + { + Text = string.Empty; + } + + StartLineNumber = scriptExtent.StartLineNumber; + StartColumnNumber = scriptExtent.StartColumnNumber; + StartOffset = scriptExtent.StartOffset; + EndLineNumber = scriptExtent.EndLineNumber; + EndColumnNumber = scriptExtent.EndColumnNumber; + EndOffset = scriptExtent.EndOffset; + } + #endregion #region Properties diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 8ee6afbe8..2fd1a7daf 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -64,9 +64,10 @@ public override async Task> Handle(WorkspaceSymbolP Location location = new() { Uri = DocumentUri.From(foundOccurrence.FilePath), - Range = ScriptRegion.GetRangeFromScriptRegion(foundOccurrence.ScriptRegion) + Range = foundOccurrence.ScriptRegion.ToRange() }; + // TODO: This should be a WorkplaceSymbol now as SymbolInformation is deprecated. symbols.Add(new SymbolInformation { ContainerName = containerName, From 3a3059fceb99a4418f987f215307991504b94484 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 12 Jan 2023 12:35:53 -0800 Subject: [PATCH 178/327] Track declarations on `SymbolType` so we can use `ReferenceTable` more This lets us (nearly) replace `FindSymbolsInDocument` by using the references (symbols) from the `ScriptFile`. --- .../CodeLens/ReferencesCodeLensProvider.cs | 31 +++---------------- .../Services/Symbols/ReferenceTable.cs | 7 +++-- .../Symbols/ScriptDocumentSymbolProvider.cs | 23 +------------- .../Services/Symbols/SymbolReference.cs | 13 ++++++-- .../Services/Symbols/SymbolType.cs | 1 + .../Services/Symbols/SymbolsService.cs | 2 +- .../Handlers/ReferencesHandler.cs | 23 +++++++++----- 7 files changed, 37 insertions(+), 63 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index eeea04375..8fb78d45a 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -84,19 +84,18 @@ SymbolType.Class or } /// - /// Take a codelens and create a new codelens object with updated references. + /// Take a CodeLens and create a new CodeLens object with updated references. /// /// The old code lens to get updated references for. /// /// - /// A new code lens object describing the same data as the old one but with updated references. + /// A new CodeLens object describing the same data as the old one but with updated references. public async Task ResolveCodeLens( CodeLens codeLens, ScriptFile scriptFile, CancellationToken cancellationToken) { - ScriptFile[] references = _workspaceService.ExpandScriptReferences( - scriptFile); + ScriptFile[] references = _workspaceService.ExpandScriptReferences(scriptFile); SymbolReference foundSymbol = SymbolsService.FindSymbolDefinitionAtLocation( scriptFile, @@ -123,7 +122,8 @@ await _symbolsService.ScanForReferencesOfSymbol( // so it's helpful to add some yields. await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); - if (IsReferenceDefinition(foundSymbol, foundReference)) + + if (foundReference.IsDeclaration) { continue; } @@ -165,27 +165,6 @@ await _symbolsService.ScanForReferencesOfSymbol( }; } - /// - /// Check whether a SymbolReference is the actual definition of that symbol. - /// - /// The symbol definition that may be referenced. - /// The reference symbol to check. - /// True if the reference is not a reference to the definition, false otherwise. - private static bool IsReferenceDefinition( - SymbolReference definition, - SymbolReference reference) - { - // First check if we are in the same file as the definition. if we are... - // check if it's on the same line number. - - // TODO: Do we care about two symbol definitions of the same name? - // if we do, how could we possibly know that a reference in one file is a reference - // of a particular symbol definition? - return - definition.FilePath == reference.FilePath && - definition.ScriptRegion.StartLineNumber == reference.ScriptRegion.StartLineNumber; - } - /// /// Get the code lens header for the number of references on a definition, /// given the number of references. diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index d80e0dbd0..3dd6a4804 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -71,9 +71,9 @@ internal void EnsureInitialized() _parent.ScriptAst.Visit(new ReferenceVisitor(this)); } - private void AddReference(SymbolType type, string name, IScriptExtent extent) + private void AddReference(SymbolType type, string name, IScriptExtent extent, bool isDeclaration = false) { - SymbolReference symbol = new(type, name, extent, _parent); + SymbolReference symbol = new(type, name, extent, _parent, isDeclaration); _symbolReferences.AddOrUpdate( name, _ => new ConcurrentBag { symbol }, @@ -123,7 +123,8 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun _references.AddReference( SymbolType.Function, functionDefinitionAst.Name, - nameExtent); + nameExtent, + isDeclaration: true); return AstVisitAction.Continue; } diff --git a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs index b9fa29f33..c2f60f86b 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs @@ -2,8 +2,6 @@ // Licensed under the MIT License. using System.Collections.Generic; -using System.Linq; -using System.Management.Automation.Language; using Microsoft.PowerShell.EditorServices.Services.TextDocument; namespace Microsoft.PowerShell.EditorServices.Services.Symbols @@ -17,25 +15,6 @@ internal class ScriptDocumentSymbolProvider : IDocumentSymbolProvider string IDocumentSymbolProvider.ProviderId => nameof(ScriptDocumentSymbolProvider); IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( - ScriptFile scriptFile) - { - // If we have an AST, then we know it's a PowerShell file - // so lets try to find symbols in the document. - return scriptFile?.ScriptAst != null - ? FindSymbolsInDocument(scriptFile.ScriptAst) - : Enumerable.Empty(); - } - - /// - /// Finds all symbols in a script - /// - /// The abstract syntax tree of the given script - /// A collection of SymbolReference objects - public static IEnumerable FindSymbolsInDocument(Ast scriptAst) - { - FindSymbolsVisitor findSymbolsVisitor = new(); - scriptAst.Visit(findSymbolsVisitor); - return findSymbolsVisitor.SymbolReferences; - } + ScriptFile scriptFile) => scriptFile.References.GetAllReferences(); } } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index c3a3c5d32..25144c89f 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -25,6 +25,8 @@ internal record SymbolReference public string FilePath { get; internal set; } + public bool IsDeclaration { get; } + /// /// Constructs and instance of a SymbolReference /// @@ -33,12 +35,14 @@ internal record SymbolReference /// The script extent of the symbol /// The file path of the symbol /// The line contents of the given symbol (defaults to empty string) + /// True if this reference is the definition of the symbol public SymbolReference( SymbolType symbolType, string symbolName, IScriptExtent scriptExtent, string filePath = "", - string sourceLine = "") + string sourceLine = "", + bool isDeclaration = false) { // TODO: Verify params SymbolType = symbolType; @@ -46,13 +50,15 @@ public SymbolReference( ScriptRegion = new(scriptExtent); FilePath = filePath; SourceLine = sourceLine; + IsDeclaration = isDeclaration; } public SymbolReference( SymbolType symbolType, string symbolName, IScriptExtent scriptExtent, - ScriptFile file) + ScriptFile file, + bool isDeclaration) { SymbolType = symbolType; SymbolName = symbolName; @@ -66,6 +72,7 @@ public SymbolReference( { SourceLine = string.Empty; } + IsDeclaration = isDeclaration; } /// @@ -74,7 +81,7 @@ public SymbolReference( /// The higher level type of the symbol /// The script extent of the symbol public SymbolReference(SymbolType symbolType, IScriptExtent scriptExtent) - : this(symbolType, scriptExtent.Text, scriptExtent, scriptExtent.File, "") + : this(symbolType, scriptExtent.Text, scriptExtent, scriptExtent.File) { } } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index c9a113a25..be6699019 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -94,6 +94,7 @@ internal static SymbolKind GetSymbolKind(SymbolType symbolType) SymbolType.Method => SymbolKind.Method, SymbolType.Property => SymbolKind.Property, SymbolType.EnumMember => SymbolKind.EnumMember, + SymbolType.Variable => SymbolKind.Variable, // TODO: More delicately handle the other symbol types. _ => SymbolKind.Variable, }; diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 7daee77c2..3e28e399f 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -153,7 +153,7 @@ public static SymbolReference FindSymbolAtLocation( } /// - /// Finds all the references of a symbol + /// Finds all the references of a symbol (excluding definitions) /// /// The symbol to find all references for /// An array of scriptFiles to search for references in diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index 6e545ba71..7a8b55fd1 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -47,18 +47,25 @@ await _symbolsService.ScanForReferencesOfSymbol( _workspaceService.ExpandScriptReferences(scriptFile), cancellationToken).ConfigureAwait(false); - List locations = new(); + if (referencesResult is null) + { + return new LocationContainer(); + } - if (referencesResult != null) + List locations = new(); + foreach (SymbolReference foundReference in referencesResult) { - foreach (SymbolReference foundReference in referencesResult) + // Respect the request's setting to include declarations. + if (!request.Context.IncludeDeclaration && foundReference.IsDeclaration) { - locations.Add(new Location - { - Uri = DocumentUri.From(foundReference.FilePath), - Range = foundReference.ScriptRegion.ToRange() - }); + continue; } + + locations.Add(new Location + { + Uri = DocumentUri.From(foundReference.FilePath), + Range = foundReference.ScriptRegion.ToRange() + }); } return new LocationContainer(locations); From c67f9ee41d047b1a558ea7f8221a7ac920eb958c Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 12 Jan 2023 12:36:31 -0800 Subject: [PATCH 179/327] Integrate `FindSymbolsVisitor` into `ReferenceTable` We were missing the distinction between properties and enum members, as well as DSC configuration. --- .../Services/Symbols/ReferenceTable.cs | 30 ++- .../Symbols/Vistors/FindSymbolsVisitor.cs | 173 ------------------ .../Language/SymbolsServiceTests.cs | 45 ++--- 3 files changed, 47 insertions(+), 201 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 3dd6a4804..9bbffb455 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -111,6 +111,10 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { + SymbolType symbolType = functionDefinitionAst.IsWorkflow + ? SymbolType.Workflow + : SymbolType.Function; + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. // This will not exclude nested functions as they have ScriptBlockAst as parent if (functionDefinitionAst.Parent is FunctionMemberAst) @@ -119,9 +123,12 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun } // We only want the function name as the extent for highlighting (and so forth). + // + // TODO: After we replace the deprecated SymbolInformation usage with DocumentSymbol, + // we'll want *both* the name extent and the full extent. IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); _references.AddReference( - SymbolType.Function, + symbolType, functionDefinitionAst.Name, nameExtent, isDeclaration: true); @@ -154,7 +161,9 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { - SymbolType symbolType = typeDefinitionAst.IsEnum ? SymbolType.Enum : SymbolType.Class; + SymbolType symbolType = typeDefinitionAst.IsEnum + ? SymbolType.Enum + : SymbolType.Class; IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); _references.AddReference(symbolType, typeDefinitionAst.Name, nameExtent); @@ -196,13 +205,28 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { + SymbolType symbolType = + propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum + ? SymbolType.EnumMember : SymbolType.Property; + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); _references.AddReference( - SymbolType.Property, + symbolType, VisitorUtils.GetMemberOverloadName(propertyMemberAst, false), nameExtent); return AstVisitAction.Continue; } + + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) + { + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(configurationDefinitionAst); + _references.AddReference( + SymbolType.Configuration, + nameExtent.Text, + nameExtent); + + return AstVisitAction.Continue; + } } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs index 3fc1fd70b..aea76642a 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs @@ -3,184 +3,11 @@ #nullable enable -using Microsoft.PowerShell.EditorServices.Utility; using System.Collections.Generic; using System.Management.Automation.Language; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { - /// - /// The visitor used to find all the symbols (variables, functions and class defs etc) in the AST. - /// - internal class FindSymbolsVisitor : AstVisitor2 - { - public List SymbolReferences { get; } - - public FindSymbolsVisitor() => SymbolReferences = new List(); - - /// - /// Adds each function definition to symbol reference list - /// - /// A FunctionDefinitionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - { - // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. - // This will not exclude nested functions as they have ScriptBlockAst as parent - if (functionDefinitionAst.Parent is FunctionMemberAst) - { - return AstVisitAction.Continue; - } - - (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(functionDefinitionAst); - IScriptExtent nameExtent = GetNewExtent(functionDefinitionAst, functionDefinitionAst.Name, startLine, startColumn); - - SymbolType symbolType = - functionDefinitionAst.IsWorkflow ? - SymbolType.Workflow : SymbolType.Function; - - SymbolReferences.Add( - new SymbolReference( - symbolType, - nameExtent)); - - return AstVisitAction.Continue; - } - - /// - /// Adds each script scoped variable assignment to symbol reference list - /// - /// A VariableExpressionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - { - if (!IsAssignedAtScriptScope(variableExpressionAst)) - { - return AstVisitAction.Continue; - } - - SymbolReferences.Add( - new SymbolReference( - SymbolType.Variable, - variableExpressionAst.Extent)); - - return AstVisitAction.Continue; - } - - private static bool IsAssignedAtScriptScope(VariableExpressionAst variableExpressionAst) - { - Ast parent = variableExpressionAst.Parent; - if (parent is not AssignmentStatementAst) - { - return false; - } - - parent = parent.Parent; - return parent is null || parent.Parent is null || parent.Parent.Parent is null; - } - - /// - /// Adds class and enum AST to symbol reference list - /// - /// A TypeDefinitionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) - { - (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(typeDefinitionAst); - IScriptExtent nameExtent = GetNewExtent(typeDefinitionAst, typeDefinitionAst.Name, startLine, startColumn); - - SymbolType symbolType = - typeDefinitionAst.IsEnum ? - SymbolType.Enum : SymbolType.Class; - - SymbolReferences.Add( - new SymbolReference( - symbolType, - nameExtent)); - - return AstVisitAction.Continue; - } - - /// - /// Adds class method and constructor AST to symbol reference list - /// - /// A FunctionMemberAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) - { - (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(functionMemberAst); - IScriptExtent nameExtent = GetNewExtent(functionMemberAst, VisitorUtils.GetMemberOverloadName(functionMemberAst, false, false), startLine, startColumn); - - SymbolType symbolType = - functionMemberAst.IsConstructor ? - SymbolType.Constructor : SymbolType.Method; - - SymbolReferences.Add( - new SymbolReference( - symbolType, - nameExtent)); - - return AstVisitAction.Continue; - } - - /// - /// Adds class property AST to symbol reference list - /// - /// A PropertyMemberAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) - { - SymbolType symbolType = - propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? - SymbolType.EnumMember : SymbolType.Property; - - bool isEnumMember = symbolType.Equals(SymbolType.EnumMember); - (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(propertyMemberAst, isEnumMember); - IScriptExtent nameExtent = GetNewExtent(propertyMemberAst, propertyMemberAst.Name, startLine, startColumn); - - SymbolReferences.Add( - new SymbolReference( - symbolType, - nameExtent)); - - return AstVisitAction.Continue; - } - - /// - /// Adds DSC configuration AST to symbol reference list - /// - /// A ConfigurationDefinitionAst in the script's AST - /// A visit action that continues the search for references - public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) - { - (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineFromAst(configurationDefinitionAst); - IScriptExtent nameExtent = GetNewExtent(configurationDefinitionAst, configurationDefinitionAst.InstanceName.Extent.Text, startLine, startColumn); - - SymbolReferences.Add( - new SymbolReference( - SymbolType.Configuration, - nameExtent)); - - return AstVisitAction.Continue; - } - - /// - /// Gets a new ScriptExtent for a given Ast with same range but modified Text - /// - private static ScriptExtent GetNewExtent(Ast ast, string text, int startLine, int startColumn) - { - return new ScriptExtent() - { - Text = text, - StartLineNumber = startLine, - EndLineNumber = ast.Extent.EndLineNumber, - StartColumnNumber = startColumn, - EndColumnNumber = ast.Extent.EndColumnNumber, - File = ast.Extent.File - }; - } - } - /// /// Visitor to find all the keys in Hashtable AST /// diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 6de254f44..63383cecf 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -559,9 +559,7 @@ public async Task FindsDetailsWithSignatureForMethod() [Fact] public void FindsSymbolsInFile() { - List symbolsResult = - FindSymbolsInFile( - FindSymbolsInMultiSymbolFile.SourceDetails); + List symbolsResult = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails); Assert.Equal(4, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Function)); Assert.Equal(3, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable)); @@ -622,50 +620,47 @@ public void FindsSymbolsInFile() [Fact] public void FindsSymbolsWithNewLineInFile() { - List symbolsResult = - FindSymbolsInFile( - FindSymbolsInNewLineSymbolFile.SourceDetails); + List symbols = FindSymbolsInFile(FindSymbolsInNewLineSymbolFile.SourceDetails); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Function)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); - - SymbolReference firstFunctionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Function); + SymbolReference firstFunctionSymbol = + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Function)); Assert.Equal("returnTrue", firstFunctionSymbol.SymbolName); Assert.Equal(2, firstFunctionSymbol.ScriptRegion.StartLineNumber); Assert.Equal(1, firstFunctionSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstClassSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Class); + SymbolReference firstClassSymbol = + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); Assert.Equal("NewLineClass", firstClassSymbol.SymbolName); Assert.Equal(7, firstClassSymbol.ScriptRegion.StartLineNumber); Assert.Equal(1, firstClassSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstConstructorSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Constructor); - Assert.Equal("NewLineClass()", firstConstructorSymbol.SymbolName); + SymbolReference firstConstructorSymbol = + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); + Assert.Equal("NewLineClass.NewLineClass()", firstConstructorSymbol.SymbolName); Assert.Equal(8, firstConstructorSymbol.ScriptRegion.StartLineNumber); Assert.Equal(5, firstConstructorSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstPropertySymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Property); - Assert.Equal("SomePropWithDefault", firstPropertySymbol.SymbolName); + SymbolReference firstPropertySymbol = + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); + Assert.Equal("NewLineClass.SomePropWithDefault", firstPropertySymbol.SymbolName); Assert.Equal(15, firstPropertySymbol.ScriptRegion.StartLineNumber); Assert.Equal(5, firstPropertySymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstMethodSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Method); - Assert.Equal("MyClassMethod([MyNewLineEnum]$param1)", firstMethodSymbol.SymbolName); + SymbolReference firstMethodSymbol = + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); + Assert.Equal("NewLineClass.MyClassMethod([MyNewLineEnum]$param1)", firstMethodSymbol.SymbolName); Assert.Equal(20, firstMethodSymbol.ScriptRegion.StartLineNumber); Assert.Equal(5, firstMethodSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstEnumSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Enum); + SymbolReference firstEnumSymbol = + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); Assert.Equal("MyNewLineEnum", firstEnumSymbol.SymbolName); Assert.Equal(26, firstEnumSymbol.ScriptRegion.StartLineNumber); Assert.Equal(1, firstEnumSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstEnumMemberSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.EnumMember); - Assert.Equal("First", firstEnumMemberSymbol.SymbolName); + SymbolReference firstEnumMemberSymbol = + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); + Assert.Equal("MyNewLineEnum.First", firstEnumMemberSymbol.SymbolName); Assert.Equal(27, firstEnumMemberSymbol.ScriptRegion.StartLineNumber); Assert.Equal(5, firstEnumMemberSymbol.ScriptRegion.StartColumnNumber); } From db48839f60207c06ef3639b8285ef7fa65213beb Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 12 Jan 2023 13:48:32 -0800 Subject: [PATCH 180/327] Actually fix sorting in symbols tests `StartOffset` is always 0, sort by `Range.Start` instead. --- .../Services/Symbols/SymbolsService.cs | 2 +- .../Handlers/WorkspaceSymbolsHandler.cs | 2 +- .../Language/SymbolsServiceTests.cs | 74 +++++++++---------- .../Services/Symbols/AstOperationsTests.cs | 2 +- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 3e28e399f..9e624e477 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -110,7 +110,7 @@ public SymbolsService( /// Finds all the symbols in a file. /// /// The ScriptFile in which the symbol can be located. - public List FindSymbolsInFile(ScriptFile scriptFile) + public IEnumerable FindSymbolsInFile(ScriptFile scriptFile) { Validate.IsNotNull(nameof(scriptFile), scriptFile); diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 2fd1a7daf..602ec1230 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -38,7 +38,7 @@ public override async Task> Handle(WorkspaceSymbolP foreach (ScriptFile scriptFile in _workspaceService.GetOpenedFiles()) { - List foundSymbols = _symbolsService.FindSymbolsInFile(scriptFile); + IEnumerable foundSymbols = _symbolsService.FindSymbolsInFile(scriptFile); // TODO: Need to compute a relative path that is based on common path for all workspace files string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 63383cecf..8bcd07c81 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -97,7 +97,7 @@ await symbolsService.ScanForReferencesOfSymbol( symbolReference, workspace.ExpandScriptReferences(scriptFile)).ConfigureAwait(true); - return symbols.OrderBy(symbol => symbol.ScriptRegion.StartOffset).ToList(); + return symbols.OrderBy(symbol => symbol.ScriptRegion.ToRange().Start).ToList(); } private IReadOnlyList GetOccurrences(ScriptRegion scriptRegion) @@ -107,11 +107,11 @@ private IReadOnlyList GetOccurrences(ScriptRegion scriptRegion) GetScriptFile(scriptRegion), scriptRegion.StartLineNumber, scriptRegion.StartColumnNumber) - .OrderBy(symbol => symbol.ScriptRegion.StartOffset) + .OrderBy(symbol => symbol.ScriptRegion.ToRange().Start) .ToArray(); } - private List FindSymbolsInFile(ScriptRegion scriptRegion) => symbolsService.FindSymbolsInFile(GetScriptFile(scriptRegion)); + private IEnumerable FindSymbolsInFile(ScriptRegion scriptRegion) => symbolsService.FindSymbolsInFile(GetScriptFile(scriptRegion)).OrderBy(symbol => symbol.ScriptRegion.ToRange().Start); [Fact] public async Task FindsParameterHintsOnCommand() @@ -310,8 +310,8 @@ public async Task FindsReferencesOnEnum() // TODO: Remove definitions from references. List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumSourceDetails).ConfigureAwait(true); Assert.Equal(4, referencesResult.Count); - Assert.Equal(25, referencesResult[1].ScriptRegion.StartLineNumber); - Assert.Equal(19, referencesResult[1].ScriptRegion.StartColumnNumber); + Assert.Equal(25, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(19, referencesResult[0].ScriptRegion.StartColumnNumber); } [Fact] @@ -365,8 +365,8 @@ public async Task FindsReferencesOnTypeConstraint() // TODO: Remove definitions from references. List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeConstraintSourceDetails).ConfigureAwait(true); Assert.Equal(4, referencesResult.Count); - Assert.Equal(25, referencesResult[1].ScriptRegion.StartLineNumber); - Assert.Equal(19, referencesResult[1].ScriptRegion.StartColumnNumber); + Assert.Equal(25, referencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(19, referencesResult[0].ScriptRegion.StartColumnNumber); } [Fact] @@ -559,17 +559,10 @@ public async Task FindsDetailsWithSignatureForMethod() [Fact] public void FindsSymbolsInFile() { - List symbolsResult = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails); + IEnumerable symbolsResult = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails); - Assert.Equal(4, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Function)); - Assert.Equal(3, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Workflow)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); + Assert.Equal(7, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Function)); + Assert.Equal(13, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable)); SymbolReference firstFunctionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Function); Assert.Equal("AFunction", firstFunctionSymbol.SymbolName); @@ -577,42 +570,49 @@ public void FindsSymbolsInFile() Assert.Equal(10, firstFunctionSymbol.ScriptRegion.StartColumnNumber); SymbolReference lastVariableSymbol = symbolsResult.Last(r => r.SymbolType == SymbolType.Variable); - Assert.Equal("$Script:ScriptVar2", lastVariableSymbol.SymbolName); - Assert.Equal(3, lastVariableSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, lastVariableSymbol.ScriptRegion.StartColumnNumber); + Assert.Equal("$param3", lastVariableSymbol.SymbolName); + Assert.Equal(32, lastVariableSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(50, lastVariableSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstWorkflowSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Workflow); + SymbolReference firstWorkflowSymbol = + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Workflow)); Assert.Equal("AWorkflow", firstWorkflowSymbol.SymbolName); Assert.Equal(23, firstWorkflowSymbol.ScriptRegion.StartLineNumber); Assert.Equal(10, firstWorkflowSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstClassSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Class); + SymbolReference firstClassSymbol = + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); Assert.Equal("AClass", firstClassSymbol.SymbolName); Assert.Equal(25, firstClassSymbol.ScriptRegion.StartLineNumber); Assert.Equal(7, firstClassSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstPropertySymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Property); - Assert.Equal("AProperty", firstPropertySymbol.SymbolName); + SymbolReference firstPropertySymbol = + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); + Assert.Equal("AClass.AProperty", firstPropertySymbol.SymbolName); Assert.Equal(26, firstPropertySymbol.ScriptRegion.StartLineNumber); Assert.Equal(13, firstPropertySymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstConstructorSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Constructor); - Assert.Equal("AClass([string]$AParameter)", firstConstructorSymbol.SymbolName); + SymbolReference firstConstructorSymbol = + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); + Assert.Equal("AClass.AClass([string]$AParameter)", firstConstructorSymbol.SymbolName); Assert.Equal(28, firstConstructorSymbol.ScriptRegion.StartLineNumber); Assert.Equal(5, firstConstructorSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstMethodSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Method); - Assert.Equal("AMethod([string]$param1, [int]$param2, $param3)", firstMethodSymbol.SymbolName); + SymbolReference firstMethodSymbol = + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); + Assert.Equal("AClass.AMethod([string]$param1, [int]$param2, $param3)", firstMethodSymbol.SymbolName); Assert.Equal(32, firstMethodSymbol.ScriptRegion.StartLineNumber); Assert.Equal(11, firstMethodSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstEnumSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Enum); + SymbolReference firstEnumSymbol = + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); Assert.Equal("AEnum", firstEnumSymbol.SymbolName); Assert.Equal(37, firstEnumSymbol.ScriptRegion.StartLineNumber); Assert.Equal(6, firstEnumSymbol.ScriptRegion.StartColumnNumber); - SymbolReference firstEnumMemberSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.EnumMember); - Assert.Equal("AValue", firstEnumMemberSymbol.SymbolName); + SymbolReference firstEnumMemberSymbol = + Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); + Assert.Equal("AEnum.AValue", firstEnumMemberSymbol.SymbolName); Assert.Equal(38, firstEnumMemberSymbol.ScriptRegion.StartLineNumber); Assert.Equal(5, firstEnumMemberSymbol.ScriptRegion.StartColumnNumber); } @@ -620,7 +620,7 @@ public void FindsSymbolsInFile() [Fact] public void FindsSymbolsWithNewLineInFile() { - List symbols = FindSymbolsInFile(FindSymbolsInNewLineSymbolFile.SourceDetails); + IEnumerable symbols = FindSymbolsInFile(FindSymbolsInNewLineSymbolFile.SourceDetails); SymbolReference firstFunctionSymbol = Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Function)); @@ -670,7 +670,7 @@ public void FindsSymbolsInDSCFile() { Skip.If(!s_isWindows, "DSC only works properly on Windows."); - List symbolsResult = FindSymbolsInFile(FindSymbolsInDSCFile.SourceDetails); + IEnumerable symbolsResult = FindSymbolsInFile(FindSymbolsInDSCFile.SourceDetails); Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Configuration)); SymbolReference firstConfigurationSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Configuration); @@ -682,7 +682,7 @@ public void FindsSymbolsInDSCFile() [Fact] public void FindsSymbolsInPesterFile() { - List symbolsResult = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails).OfType().ToList(); + IEnumerable symbolsResult = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails).OfType(); Assert.Equal(12, symbolsResult.Count(r => r.SymbolType == SymbolType.Function)); Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.Describe)); @@ -737,14 +737,14 @@ public void FindsSymbolsInPesterFile() [Fact] public void LangServerFindsSymbolsInPSDFile() { - List symbolsResult = FindSymbolsInFile(FindSymbolsInPSDFile.SourceDetails); - Assert.Equal(3, symbolsResult.Count); + IEnumerable symbolsResult = FindSymbolsInFile(FindSymbolsInPSDFile.SourceDetails); + Assert.Equal(3, symbolsResult.Count()); } [Fact] public void FindsSymbolsInNoSymbolsFile() { - List symbolsResult = FindSymbolsInFile(FindSymbolsInNoSymbolsFile.SourceDetails); + IEnumerable symbolsResult = FindSymbolsInFile(FindSymbolsInNoSymbolsFile.SourceDetails); Assert.Empty(symbolsResult); } } diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs index 44cebe878..67213a71f 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs @@ -57,7 +57,7 @@ public void CanFindReferencesOfSymbolAtPosition(int lineNumber, int columnNumber out ConcurrentBag references)); int positionsIndex = 0; - foreach (SymbolReference reference in references.OrderBy((i) => i.ScriptRegion.StartOffset)) + foreach (SymbolReference reference in references.OrderBy((i) => i.ScriptRegion.ToRange().Start)) { Assert.Equal(symbolRange[positionsIndex].Start.Line, reference.ScriptRegion.StartLineNumber); Assert.Equal(symbolRange[positionsIndex].Start.Character, reference.ScriptRegion.StartColumnNumber); From fd95218b5869a9a5f14cc1851e1b0921e201ef03 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 12 Jan 2023 14:26:44 -0800 Subject: [PATCH 181/327] Make `DocumentSymbolHandler` more readable While we're debugging it. --- .../CodeLens/PesterCodeLensProvider.cs | 2 +- .../Services/Symbols/SymbolsService.cs | 2 + .../Handlers/DocumentSymbolHandler.cs | 60 +++++++++---------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs index 293d2ff48..5eb3830ee 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs @@ -119,7 +119,7 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken can continue; } - // Skip codelense for setup/teardown block + // Skip CodeLens for setup/teardown block if (!PesterSymbolReference.IsPesterTestCommand(pesterSymbol.Command)) { continue; diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 9e624e477..c05722da5 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -231,6 +231,8 @@ static string[] GetIdentifiers(string symbolName, SymbolType symbolType, Command symbolReferences.AddRange(references); + // This async method is pretty dense with synchronous code + // so it's helpful to add some yields. await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index 8119f5e77..8aeb7559d 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -44,45 +44,45 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp DocumentSelector = LspUtils.PowerShellDocumentSelector }; - public override Task Handle(DocumentSymbolParams request, CancellationToken cancellationToken) + public override async Task Handle(DocumentSymbolParams request, CancellationToken cancellationToken) { ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); - IEnumerable foundSymbols = - ProvideDocumentSymbols(scriptFile); - - SymbolInformationOrDocumentSymbol[] symbols = null; + IEnumerable foundSymbols = ProvideDocumentSymbols(scriptFile); + if (foundSymbols is null) + { + return null; + } string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); + List symbols = new(); + foreach (SymbolReference r in foundSymbols) + { + // This async method is pretty dense with synchronous code + // so it's helpful to add some yields. + await Task.Yield(); + cancellationToken.ThrowIfCancellationRequested(); + + // TODO: This should be a DocumentSymbol now as SymbolInformation is deprecated. + symbols.Add(new SymbolInformationOrDocumentSymbol(new SymbolInformation + { + ContainerName = containerName, + Kind = SymbolTypeUtils.GetSymbolKind(r.SymbolType), + Location = new Location + { + Uri = DocumentUri.From(r.FilePath), + Range = r.ScriptRegion.ToRange() + }, + Name = SymbolTypeUtils.GetDecoratedSymbolName(r) + })); + } - symbols = foundSymbols != null - ? foundSymbols - .Select(r => - { - // TODO: This should be a DocumentSymbol now as SymbolInformation is deprecated. - return new SymbolInformationOrDocumentSymbol(new SymbolInformation - { - ContainerName = containerName, - Kind = SymbolTypeUtils.GetSymbolKind(r.SymbolType), - Location = new Location - { - Uri = DocumentUri.From(r.FilePath), - Range = r.ScriptRegion.ToRange() - }, - Name = SymbolTypeUtils.GetDecoratedSymbolName(r) - }); - }) - .ToArray() - : Array.Empty(); - - return Task.FromResult(new SymbolInformationOrDocumentSymbolContainer(symbols)); + return new SymbolInformationOrDocumentSymbolContainer(symbols); } - private IEnumerable ProvideDocumentSymbols( - ScriptFile scriptFile) + private IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile) { - return - InvokeProviders(p => p.ProvideDocumentSymbols(scriptFile)) + return InvokeProviders(p => p.ProvideDocumentSymbols(scriptFile)) .SelectMany(r => r); } From 07e566d43cb05d47284627719094692b73d8da35 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 12 Jan 2023 16:17:26 -0800 Subject: [PATCH 182/327] Fix outline view (filter out `EmptyScriptExtent`) And also prepare `SymbolReference` for eventual support of `DocumentSymbol`. --- .../Services/Symbols/ReferenceTable.cs | 51 +++-- .../Services/Symbols/ScriptExtent.cs | 2 + .../Services/Symbols/SymbolReference.cs | 5 + .../Services/Symbols/SymbolsService.cs | 1 + .../Handlers/DocumentHighlightHandler.cs | 2 +- .../Handlers/DocumentSymbolHandler.cs | 17 +- .../Services/TextDocument/ScriptRegion.cs | 2 + .../Handlers/WorkspaceSymbolsHandler.cs | 6 + .../References/SimpleFile.ps1 | 2 +- .../Language/SymbolsServiceTests.cs | 204 ++++++++++-------- .../Services/Symbols/AstOperationsTests.cs | 8 +- 11 files changed, 193 insertions(+), 107 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 9bbffb455..12c8a61cb 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -71,9 +71,20 @@ internal void EnsureInitialized() _parent.ScriptAst.Visit(new ReferenceVisitor(this)); } - private void AddReference(SymbolType type, string name, IScriptExtent extent, bool isDeclaration = false) + private static bool ExtentIsEmpty(IScriptExtent e) => string.IsNullOrEmpty(e.File) && + e.StartLineNumber == 0 && e.StartColumnNumber == 0 && + e.EndLineNumber == 0 && e.EndColumnNumber == 0 && + string.IsNullOrEmpty(e.Text); + + private void AddReference(SymbolType type, string name, IScriptExtent nameExtent, IScriptExtent extent, bool isDeclaration = false) { - SymbolReference symbol = new(type, name, extent, _parent, isDeclaration); + // We have to exclude implicit things like `$this` that don't actually exist. + if (ExtentIsEmpty(extent)) + { + return; + } + + SymbolReference symbol = new(type, name, nameExtent, extent, _parent, isDeclaration); _symbolReferences.AddOrUpdate( name, _ => new ConcurrentBag { symbol }, @@ -103,7 +114,8 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) _references.AddReference( SymbolType.Function, CommandHelpers.StripModuleQualification(commandName, out _), - commandAst.CommandElements[0].Extent + commandAst.CommandElements[0].Extent, + commandAst.Extent ); return AstVisitAction.Continue; @@ -122,15 +134,12 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.Continue; } - // We only want the function name as the extent for highlighting (and so forth). - // - // TODO: After we replace the deprecated SymbolInformation usage with DocumentSymbol, - // we'll want *both* the name extent and the full extent. IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); _references.AddReference( symbolType, functionDefinitionAst.Name, nameExtent, + functionDefinitionAst.Extent, isDeclaration: true); return AstVisitAction.Continue; @@ -141,6 +150,7 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command _references.AddReference( SymbolType.Parameter, commandParameterAst.Extent.Text, + commandParameterAst.Extent, commandParameterAst.Extent); return AstVisitAction.Continue; @@ -153,6 +163,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var _references.AddReference( SymbolType.Variable, $"${variableExpressionAst.VariablePath.UserPath}", + variableExpressionAst.Extent, variableExpressionAst.Extent ); @@ -166,7 +177,11 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit : SymbolType.Class; IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); - _references.AddReference(symbolType, typeDefinitionAst.Name, nameExtent); + _references.AddReference( + symbolType, + typeDefinitionAst.Name, + nameExtent, + typeDefinitionAst.Extent); return AstVisitAction.Continue; } @@ -176,6 +191,7 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress _references.AddReference( SymbolType.Type, typeExpressionAst.TypeName.Name, + typeExpressionAst.Extent, typeExpressionAst.Extent); return AstVisitAction.Continue; @@ -183,7 +199,11 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { - _references.AddReference(SymbolType.Type, typeConstraintAst.TypeName.Name, typeConstraintAst.Extent); + _references.AddReference( + SymbolType.Type, + typeConstraintAst.TypeName.Name, + typeConstraintAst.Extent, + typeConstraintAst.Extent); return AstVisitAction.Continue; } @@ -197,8 +217,9 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, false); _references.AddReference( symbolType, - VisitorUtils.GetMemberOverloadName(functionMemberAst, true, false), - nameExtent); + nameExtent.Text, + nameExtent, + functionMemberAst.Extent); return AstVisitAction.Continue; } @@ -212,8 +233,9 @@ propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); _references.AddReference( symbolType, - VisitorUtils.GetMemberOverloadName(propertyMemberAst, false), - nameExtent); + nameExtent.Text, + nameExtent, + propertyMemberAst.Extent); return AstVisitAction.Continue; } @@ -224,7 +246,8 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit _references.AddReference( SymbolType.Configuration, nameExtent.Text, - nameExtent); + nameExtent, + configurationDefinitionAst.Extent); return AstVisitAction.Continue; } diff --git a/src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs b/src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs index 2224be725..1cd87f44f 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs @@ -91,6 +91,8 @@ public int EndOffset set; } + public override string ToString() => Text; + /// /// Gets the ending script position of the extent. /// diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index 25144c89f..752d5121b 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -19,6 +19,8 @@ internal record SymbolReference public string SymbolName { get; } + public ScriptRegion NameRegion { get; } + public ScriptRegion ScriptRegion { get; } public string SourceLine { get; internal set; } @@ -48,6 +50,7 @@ public SymbolReference( SymbolType = symbolType; SymbolName = symbolName; ScriptRegion = new(scriptExtent); + NameRegion = ScriptRegion; FilePath = filePath; SourceLine = sourceLine; IsDeclaration = isDeclaration; @@ -56,12 +59,14 @@ public SymbolReference( public SymbolReference( SymbolType symbolType, string symbolName, + IScriptExtent nameExtent, IScriptExtent scriptExtent, ScriptFile file, bool isDeclaration) { SymbolType = symbolType; SymbolName = symbolName; + NameRegion = new(nameExtent); ScriptRegion = new(scriptExtent); FilePath = file.FilePath; try diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index c05722da5..69b823fb3 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -429,6 +429,7 @@ public async Task GetDefinitionOfSymbolAsync( SymbolReference foundDefinition = null; foreach (ScriptFile scriptFile in referencedFiles) { + // TODO: This needs to just search the file's references (filtered to declarations); foundDefinition = AstOperations.FindDefinitionOfSymbol(scriptFile.ScriptAst, foundSymbol); filesSearched.Add(scriptFile.FilePath); diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs index 8791a6466..5758e59a1 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs @@ -56,7 +56,7 @@ public override Task Handle( highlights.Add(new DocumentHighlight { Kind = DocumentHighlightKind.Write, // TODO: Which symbol types are writable? - Range = occurrence.ScriptRegion.ToRange() + Range = occurrence.NameRegion.ToRange() // Just the symbol name }); } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index 8aeb7559d..c1ab8d434 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -55,6 +55,7 @@ public override async Task Handle(Do } string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); + List symbols = new(); foreach (SymbolReference r in foundSymbols) { @@ -63,7 +64,21 @@ public override async Task Handle(Do await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); + if (r.SymbolType is SymbolType.Type) + { + continue; + } + // TODO: This should be a DocumentSymbol now as SymbolInformation is deprecated. + // But this requires figuring out how to populate `children`. + // + // symbols.Add(new SymbolInformationOrDocumentSymbol(new DocumentSymbol + // { + // Name = SymbolTypeUtils.GetDecoratedSymbolName(r), + // Kind = SymbolTypeUtils.GetSymbolKind(r.SymbolType), + // Range = r.ScriptRegion.ToRange(), + // SelectionRange = r.NameRegion.ToRange() + // })); symbols.Add(new SymbolInformationOrDocumentSymbol(new SymbolInformation { ContainerName = containerName, @@ -71,7 +86,7 @@ public override async Task Handle(Do Location = new Location { Uri = DocumentUri.From(r.FilePath), - Range = r.ScriptRegion.ToRange() + Range = r.ScriptRegion.ToRange() // The whole thing, not just the name. }, Name = SymbolTypeUtils.GetDecoratedSymbolName(r) })); diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs index 789d6f11d..5d12077f9 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs @@ -33,6 +33,8 @@ public Range ToRange() }; } + public override string ToString() => $"Start {StartLineNumber}:{StartColumnNumber}, End {EndLineNumber}:{EndColumnNumber}"; + #region Constructors public ScriptRegion( diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 602ec1230..939a80f9d 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -49,6 +49,12 @@ public override async Task> Handle(WorkspaceSymbolP // so it's helpful to add some yields. await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); + + if (foundOccurrence.SymbolType is SymbolType.Type) + { + continue; + } + if (!IsQueryMatch(request.Query, foundOccurrence.SymbolName)) { continue; diff --git a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 index e7a271447..b60389c63 100644 --- a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 @@ -1,6 +1,6 @@ function My-Function ($myInput) { - My-Function $myInput + My-Function $myInput } $things = 4 diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 8bcd07c81..d58d806dc 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -57,6 +57,19 @@ public void Dispose() GC.SuppressFinalize(this); } + private static void AssertIsRegion( + ScriptRegion region, + int startLineNumber, + int startColumnNumber, + int endLineNumber, + int endColumnNumber) + { + Assert.Equal(startLineNumber, region.StartLineNumber); + Assert.Equal(startColumnNumber, region.StartColumnNumber); + Assert.Equal(endLineNumber, region.EndLineNumber); + Assert.Equal(endColumnNumber, region.EndColumnNumber); + } + private ScriptFile GetScriptFile(ScriptRegion scriptRegion) => workspace.GetFile(TestUtilities.GetSharedPath(scriptRegion.File)); private Task GetParamSetSignatures(ScriptRegion scriptRegion) @@ -116,28 +129,31 @@ private IReadOnlyList GetOccurrences(ScriptRegion scriptRegion) [Fact] public async Task FindsParameterHintsOnCommand() { - ParameterSetSignatures paramSignatures = await GetParamSetSignatures(FindsParameterSetsOnCommandData.SourceDetails).ConfigureAwait(true); - Assert.NotNull(paramSignatures); - Assert.Equal("Get-Process", paramSignatures.CommandName); - Assert.Equal(6, paramSignatures.Signatures.Length); + ParameterSetSignatures signatures = await GetParamSetSignatures(FindsParameterSetsOnCommandData.SourceDetails).ConfigureAwait(true); + Assert.NotNull(signatures); + Assert.Equal("Get-Process", signatures.CommandName); + Assert.Equal(6, signatures.Signatures.Length); } [Fact] public async Task FindsCommandForParamHintsWithSpaces() { - ParameterSetSignatures paramSignatures = await GetParamSetSignatures(FindsParameterSetsOnCommandWithSpacesData.SourceDetails).ConfigureAwait(true); - Assert.NotNull(paramSignatures); - Assert.Equal("Write-Host", paramSignatures.CommandName); - Assert.Single(paramSignatures.Signatures); + ParameterSetSignatures signatures = await GetParamSetSignatures(FindsParameterSetsOnCommandWithSpacesData.SourceDetails).ConfigureAwait(true); + Assert.NotNull(signatures); + Assert.Equal("Write-Host", signatures.CommandName); + Assert.Single(signatures.Signatures); } [Fact] public async Task FindsFunctionDefinition() { - SymbolReference definitionResult = await GetDefinition(FindsFunctionDefinitionData.SourceDetails).ConfigureAwait(true); - Assert.Equal(1, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(10, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("My-Function", definitionResult.SymbolName); + SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionData.SourceDetails).ConfigureAwait(true); + Assert.Equal("My-Function", symbol.SymbolName); + AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); + // TODO: This should pull the declaration from references. + // AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); + Assert.Equal(SymbolType.Function, symbol.SymbolType); + // Assert.True(symbol.IsDeclaration); } [Fact] @@ -149,19 +165,22 @@ 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); + SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionOfAliasData.SourceDetails).ConfigureAwait(true); + AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); + Assert.Equal("My-Function", symbol.SymbolName); + Assert.Equal(SymbolType.Function, symbol.SymbolType); + // TODO: This should pull the declaration from references. + // Assert.True(symbol.IsDeclaration); } [Fact] public async Task FindsReferencesOnFunction() { - List referencesResult = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); - Assert.Equal(3, referencesResult.Count); - Assert.Equal(1, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber); + List symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => AssertIsRegion(i.NameRegion, 1, 10, 1, 21), + (i) => AssertIsRegion(i.NameRegion, 3, 5, 3, 16), + (i) => AssertIsRegion(i.NameRegion, 10, 1, 10, 12)); } [Fact] @@ -172,10 +191,14 @@ 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); + List symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => AssertIsRegion(i.NameRegion, 1, 10, 1, 21), + (i) => AssertIsRegion(i.NameRegion, 3, 5, 3, 16), + (i) => AssertIsRegion(i.NameRegion, 10, 1, 10, 12), + // The alias. + (i) => AssertIsRegion(i.NameRegion, 20, 1, 20, 9)); + Assert.Equal("My-Alias", symbols[3].SymbolName); } [Fact] @@ -271,6 +294,7 @@ public async Task FindsReferencesOnCommandWithAlias() [Fact] public async Task FindsClassDefinition() { + // TODO: We should find the definition by searching known symbols filtered to declarations. SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.ClassSourceDetails).ConfigureAwait(true); Assert.Equal(8, definitionResult.ScriptRegion.StartLineNumber); Assert.Equal(7, definitionResult.ScriptRegion.StartColumnNumber); @@ -280,10 +304,14 @@ public async Task FindsClassDefinition() [Fact] public async Task FindsReferencesOnClass() { - List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.ClassSourceDetails).ConfigureAwait(true); - Assert.Equal(2, referencesResult.Count); - Assert.Equal(8, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(7, referencesResult[0].ScriptRegion.StartColumnNumber); + List symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.ClassSourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => AssertIsRegion(i.ScriptRegion, 8, 1, 31, 2), + (i) => AssertIsRegion(i.ScriptRegion, 34, 6, 34, 18)); + Assert.Collection(symbols, + (i) => AssertIsRegion(i.NameRegion, 8, 7, 8, 17), + // TODO: This should exclude the [] and be 34:7 and 34:18 + (i) => AssertIsRegion(i.NameRegion, 34, 6, 34, 18)); } [Fact] @@ -337,8 +365,13 @@ public async Task FindsReferencesOnTypeExpression() { List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeExpressionSourceDetails).ConfigureAwait(true); Assert.Equal(2, referencesResult.Count); - Assert.Equal(8, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(7, referencesResult[0].ScriptRegion.StartColumnNumber); + SymbolReference superClass = referencesResult[0]; + Assert.Equal(8, superClass.ScriptRegion.StartLineNumber); + Assert.Equal(1, superClass.ScriptRegion.StartColumnNumber); + Assert.Equal(31, superClass.ScriptRegion.EndLineNumber); + Assert.Equal(2, superClass.ScriptRegion.EndColumnNumber); + Assert.Equal(8, superClass.NameRegion.StartLineNumber); + Assert.Equal(7, superClass.NameRegion.StartColumnNumber); } [Fact] @@ -417,10 +450,12 @@ public async Task FindsMethodDefinition() [Fact] public async Task FindsReferencesOnMethod() { - List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.MethodSourceDetails).ConfigureAwait(true); - Assert.Single(referencesResult); - Assert.Equal(19, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(13, referencesResult[0].ScriptRegion.StartColumnNumber); + List symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.MethodSourceDetails).ConfigureAwait(true); + SymbolReference symbol = Assert.Single(symbols); + Assert.Equal("SuperClass.MyClassMethod([string]$param1, $param2, [int]$param3)", symbol.SymbolName); + Assert.Equal(SymbolType.Method, symbol.SymbolType); + AssertIsRegion(symbol.NameRegion, 19, 13, 19, 26); + AssertIsRegion(symbol.ScriptRegion, 19, 5, 22, 6); } [Fact] @@ -444,19 +479,25 @@ public async Task FindsPropertyDefinition() [Fact] public async Task FindsReferencesOnProperty() { - List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.PropertySourceDetails).ConfigureAwait(true); - Assert.Single(referencesResult); - Assert.Equal(17, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(10, referencesResult[0].ScriptRegion.StartColumnNumber); + List symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.PropertySourceDetails).ConfigureAwait(true); + SymbolReference symbol = Assert.Single(symbols); + Assert.Equal("SuperClass.SomeProp", symbol.SymbolName); + Assert.Equal(SymbolType.Property, symbol.SymbolType); + AssertIsRegion(symbol.NameRegion, 17, 10, 17, 19); + AssertIsRegion(symbol.ScriptRegion, 17, 5, 17, 19); + // TODO: This should also find $o.SomeProp } [Fact] public void FindsOccurrencesOnProperty() { - IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.PropertySourceDetails); - Assert.Equal(1, occurrencesResult.Count); - Assert.Equal("SuperClass.SomePropWithDefault", occurrencesResult[occurrencesResult.Count - 1].SymbolName); - Assert.Equal(15, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + IReadOnlyList symbols = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.PropertySourceDetails); + SymbolReference symbol = Assert.Single(symbols); + Assert.Equal("SuperClass.SomePropWithDefault", symbol.SymbolName); + Assert.Equal(SymbolType.Property, symbol.SymbolType); + AssertIsRegion(symbol.NameRegion, 15, 13, 15, 33); + AssertIsRegion(symbol.ScriptRegion, 15, 5, 15, 61); + // TODO: This should also find the $this.SomePropWithDefault reference. } [Fact] @@ -559,62 +600,53 @@ public async Task FindsDetailsWithSignatureForMethod() [Fact] public void FindsSymbolsInFile() { - IEnumerable symbolsResult = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails); + IEnumerable symbols = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails); - Assert.Equal(7, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Function)); - Assert.Equal(13, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable)); + Assert.Equal(7, symbols.Count(symbolReference => symbolReference.SymbolType == SymbolType.Function)); + Assert.Equal(12, symbols.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable)); - SymbolReference firstFunctionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Function); + SymbolReference firstFunctionSymbol = symbols.First(r => r.SymbolType == SymbolType.Function); Assert.Equal("AFunction", firstFunctionSymbol.SymbolName); - Assert.Equal(7, firstFunctionSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(10, firstFunctionSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstFunctionSymbol.NameRegion, 7, 10, 7, 19); - SymbolReference lastVariableSymbol = symbolsResult.Last(r => r.SymbolType == SymbolType.Variable); + SymbolReference lastVariableSymbol = symbols.Last(r => r.SymbolType == SymbolType.Variable); Assert.Equal("$param3", lastVariableSymbol.SymbolName); - Assert.Equal(32, lastVariableSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(50, lastVariableSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(lastVariableSymbol.NameRegion, 32, 50, 32, 57); SymbolReference firstWorkflowSymbol = - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Workflow)); + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Workflow)); Assert.Equal("AWorkflow", firstWorkflowSymbol.SymbolName); - Assert.Equal(23, firstWorkflowSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(10, firstWorkflowSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstWorkflowSymbol.NameRegion, 23, 10, 23, 19); SymbolReference firstClassSymbol = - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); Assert.Equal("AClass", firstClassSymbol.SymbolName); - Assert.Equal(25, firstClassSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(7, firstClassSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstClassSymbol.NameRegion, 25, 7, 25, 13); SymbolReference firstPropertySymbol = - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); Assert.Equal("AClass.AProperty", firstPropertySymbol.SymbolName); - Assert.Equal(26, firstPropertySymbol.ScriptRegion.StartLineNumber); - Assert.Equal(13, firstPropertySymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstPropertySymbol.NameRegion, 26, 13, 26, 23); SymbolReference firstConstructorSymbol = - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); Assert.Equal("AClass.AClass([string]$AParameter)", firstConstructorSymbol.SymbolName); - Assert.Equal(28, firstConstructorSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(5, firstConstructorSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstConstructorSymbol.NameRegion, 28, 5, 28, 11); SymbolReference firstMethodSymbol = - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); Assert.Equal("AClass.AMethod([string]$param1, [int]$param2, $param3)", firstMethodSymbol.SymbolName); - Assert.Equal(32, firstMethodSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(11, firstMethodSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstMethodSymbol.NameRegion, 32, 11, 32, 18); SymbolReference firstEnumSymbol = - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); Assert.Equal("AEnum", firstEnumSymbol.SymbolName); - Assert.Equal(37, firstEnumSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(6, firstEnumSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstEnumSymbol.NameRegion, 37, 6, 37, 11); SymbolReference firstEnumMemberSymbol = - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); + Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); Assert.Equal("AEnum.AValue", firstEnumMemberSymbol.SymbolName); - Assert.Equal(38, firstEnumMemberSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(5, firstEnumMemberSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstEnumMemberSymbol.NameRegion, 38, 5, 38, 11); } [Fact] @@ -625,44 +657,44 @@ public void FindsSymbolsWithNewLineInFile() SymbolReference firstFunctionSymbol = Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Function)); Assert.Equal("returnTrue", firstFunctionSymbol.SymbolName); - Assert.Equal(2, firstFunctionSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstFunctionSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstFunctionSymbol.NameRegion, 2, 1, 2, 11); + AssertIsRegion(firstFunctionSymbol.ScriptRegion, 1, 1, 4, 2); SymbolReference firstClassSymbol = Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); Assert.Equal("NewLineClass", firstClassSymbol.SymbolName); - Assert.Equal(7, firstClassSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstClassSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstClassSymbol.NameRegion, 7, 1, 7, 13); + AssertIsRegion(firstClassSymbol.ScriptRegion, 6, 1, 23, 2); SymbolReference firstConstructorSymbol = Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); Assert.Equal("NewLineClass.NewLineClass()", firstConstructorSymbol.SymbolName); - Assert.Equal(8, firstConstructorSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(5, firstConstructorSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstConstructorSymbol.NameRegion, 8, 5, 8, 17); + AssertIsRegion(firstConstructorSymbol.ScriptRegion, 8, 5, 10, 6); SymbolReference firstPropertySymbol = Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); Assert.Equal("NewLineClass.SomePropWithDefault", firstPropertySymbol.SymbolName); - Assert.Equal(15, firstPropertySymbol.ScriptRegion.StartLineNumber); - Assert.Equal(5, firstPropertySymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstPropertySymbol.NameRegion, 15, 5, 15, 25); + AssertIsRegion(firstPropertySymbol.ScriptRegion, 12, 5, 15, 40); SymbolReference firstMethodSymbol = Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); Assert.Equal("NewLineClass.MyClassMethod([MyNewLineEnum]$param1)", firstMethodSymbol.SymbolName); - Assert.Equal(20, firstMethodSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(5, firstMethodSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstMethodSymbol.NameRegion, 20, 5, 20, 18); + AssertIsRegion(firstMethodSymbol.ScriptRegion, 17, 5, 22, 6); SymbolReference firstEnumSymbol = Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); Assert.Equal("MyNewLineEnum", firstEnumSymbol.SymbolName); - Assert.Equal(26, firstEnumSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstEnumSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstEnumSymbol.NameRegion, 26, 1, 26, 14); + AssertIsRegion(firstEnumSymbol.ScriptRegion, 25, 1, 28, 2); SymbolReference firstEnumMemberSymbol = Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); Assert.Equal("MyNewLineEnum.First", firstEnumMemberSymbol.SymbolName); - Assert.Equal(27, firstEnumMemberSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(5, firstEnumMemberSymbol.ScriptRegion.StartColumnNumber); + AssertIsRegion(firstEnumMemberSymbol.NameRegion, 27, 5, 27, 10); + AssertIsRegion(firstEnumMemberSymbol.ScriptRegion, 27, 5, 27, 10); } [SkippableFact] diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs index 67213a71f..157caacd9 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs @@ -59,10 +59,10 @@ public void CanFindReferencesOfSymbolAtPosition(int lineNumber, int columnNumber int positionsIndex = 0; foreach (SymbolReference reference in references.OrderBy((i) => i.ScriptRegion.ToRange().Start)) { - Assert.Equal(symbolRange[positionsIndex].Start.Line, reference.ScriptRegion.StartLineNumber); - Assert.Equal(symbolRange[positionsIndex].Start.Character, reference.ScriptRegion.StartColumnNumber); - Assert.Equal(symbolRange[positionsIndex].End.Line, reference.ScriptRegion.EndLineNumber); - Assert.Equal(symbolRange[positionsIndex].End.Character, reference.ScriptRegion.EndColumnNumber); + Assert.Equal(symbolRange[positionsIndex].Start.Line, reference.NameRegion.StartLineNumber); + Assert.Equal(symbolRange[positionsIndex].Start.Character, reference.NameRegion.StartColumnNumber); + Assert.Equal(symbolRange[positionsIndex].End.Line, reference.NameRegion.EndLineNumber); + Assert.Equal(symbolRange[positionsIndex].End.Character, reference.NameRegion.EndColumnNumber); positionsIndex++; } From 85694a3684dccd04cff2d9583f11fde26db517f2 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 19 Jan 2023 21:45:54 -0800 Subject: [PATCH 183/327] Fix typo --- .../Services/Symbols/SymbolReference.cs | 2 +- .../Language/SymbolsServiceTests.cs | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index 752d5121b..19a3428fd 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -51,7 +51,7 @@ public SymbolReference( SymbolName = symbolName; ScriptRegion = new(scriptExtent); NameRegion = ScriptRegion; - FilePath = filePath; + FilePath = string.IsNullOrEmpty(filePath) ? scriptExtent.File : filePath; SourceLine = sourceLine; IsDeclaration = isDeclaration; } diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index d58d806dc..67769b18e 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -84,33 +84,34 @@ private Task GetDefinition(ScriptRegion scriptRegion) { ScriptFile scriptFile = GetScriptFile(scriptRegion); - SymbolReference symbolReference = SymbolsService.FindSymbolAtLocation( + // TODO: We should just use the name to find it. + SymbolReference symbol = SymbolsService.FindSymbolAtLocation( scriptFile, scriptRegion.StartLineNumber, scriptRegion.StartColumnNumber); - Assert.NotNull(symbolReference); + Assert.NotNull(symbol); - return symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbolReference); + return symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbol); } private async Task> GetReferences(ScriptRegion scriptRegion) { ScriptFile scriptFile = GetScriptFile(scriptRegion); - SymbolReference symbolReference = SymbolsService.FindSymbolAtLocation( + SymbolReference symbol = SymbolsService.FindSymbolAtLocation( scriptFile, scriptRegion.StartLineNumber, scriptRegion.StartColumnNumber); - Assert.NotNull(symbolReference); + Assert.NotNull(symbol); IEnumerable symbols = await symbolsService.ScanForReferencesOfSymbol( - symbolReference, + symbol, workspace.ExpandScriptReferences(scriptFile)).ConfigureAwait(true); - return symbols.OrderBy(symbol => symbol.ScriptRegion.ToRange().Start).ToList(); + return symbols.OrderBy(i => i.ScriptRegion.ToRange().Start).ToList(); } private IReadOnlyList GetOccurrences(ScriptRegion scriptRegion) @@ -423,10 +424,10 @@ public async Task FindsConstructorDefinition() [Fact] public async Task FindsReferencesOnConstructor() { - List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.ConstructorSourceDetails).ConfigureAwait(true); - Assert.Single(referencesResult); - Assert.Equal(9, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(5, referencesResult[0].ScriptRegion.StartColumnNumber); + List symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.ConstructorSourceDetails).ConfigureAwait(true); + Assert.Single(symbols); + Assert.Equal(9, symbols[0].ScriptRegion.StartLineNumber); + Assert.Equal(5, symbols[0].ScriptRegion.StartColumnNumber); } [Fact] @@ -736,10 +737,10 @@ public void FindsSymbolsInPesterFile() Assert.Equal(9, lastItSymbol.ScriptRegion.StartColumnNumber); Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeDiscovery)); - SymbolReference firstBeforeDisocverySymbol = symbolsResult.First(r => r.Command == PesterCommandType.BeforeDiscovery); - Assert.Equal("BeforeDiscovery", firstBeforeDisocverySymbol.SymbolName); - Assert.Equal(1, firstBeforeDisocverySymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstBeforeDisocverySymbol.ScriptRegion.StartColumnNumber); + SymbolReference firstBeforeDiscoverySymbol = symbolsResult.First(r => r.Command == PesterCommandType.BeforeDiscovery); + Assert.Equal("BeforeDiscovery", firstBeforeDiscoverySymbol.SymbolName); + Assert.Equal(1, firstBeforeDiscoverySymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstBeforeDiscoverySymbol.ScriptRegion.StartColumnNumber); Assert.Equal(2, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeAll)); SymbolReference lastBeforeAllSymbol = symbolsResult.Last(r => r.Command == PesterCommandType.BeforeAll); From 5776d20a20e4ca6794b3256c1424a0cab379759c Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 20 Jan 2023 14:12:14 -0800 Subject: [PATCH 184/327] Replace `FindDeclarationVisitor` --- .../CodeLens/ReferencesCodeLensProvider.cs | 11 +- .../Services/Symbols/ReferenceTable.cs | 73 ++++-- .../Services/Symbols/SymbolsService.cs | 210 ++-------------- .../Services/Symbols/Vistors/AstOperations.cs | 15 -- .../Symbols/Vistors/FindDeclarationVisitor.cs | 232 ------------------ .../Symbols/Vistors/FindSymbolVisitor.cs | 75 +++++- .../Handlers/DefinitionHandler.cs | 9 +- .../Handlers/DocumentSymbolHandler.cs | 4 +- .../Services/TextDocument/ScriptFile.cs | 2 + .../Handlers/WorkspaceSymbolsHandler.cs | 3 +- .../Utility/VisitorUtils.cs | 18 +- .../Language/SymbolsServiceTests.cs | 6 +- 12 files changed, 198 insertions(+), 460 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index 8fb78d45a..daa0cd779 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -60,10 +60,12 @@ public ReferencesCodeLensProvider(WorkspaceService workspaceService, SymbolsServ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) { List acc = new(); - foreach (SymbolReference sym in _symbolProvider.ProvideDocumentSymbols(scriptFile)) + foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile)) { cancellationToken.ThrowIfCancellationRequested(); - if (sym.SymbolType is + // TODO: Can we support more here? + if (symbol.IsDeclaration && + symbol.SymbolType is SymbolType.Function or SymbolType.Class or SymbolType.Enum) @@ -75,7 +77,7 @@ SymbolType.Class or Uri = scriptFile.DocumentUri, ProviderId = nameof(ReferencesCodeLensProvider) }, LspSerializer.Instance.JsonSerializer), - Range = sym.ScriptRegion.ToRange(), + Range = symbol.NameRegion.ToRange(), }); } } @@ -123,6 +125,7 @@ await _symbolsService.ScanForReferencesOfSymbol( await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); + // We only show lenses on declarations, so we exclude those from the references. if (foundReference.IsDeclaration) { continue; @@ -140,7 +143,7 @@ await _symbolsService.ScanForReferencesOfSymbol( acc.Add(new Location { Uri = uri, - Range = foundReference.ScriptRegion.ToRange() + Range = foundReference.NameRegion.ToRange() }); } referenceLocations = acc.ToArray(); diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 12c8a61cb..2212ba388 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -95,8 +95,11 @@ private void AddReference(SymbolType type, string name, IScriptExtent nameExtent }); } - // TODO: Should we move this to AstOperations.cs? It is highly coupled to `ReferenceTable`, - // perhaps it doesn't have to be. + // TODO: Reconstruct this to take an action lambda that returns a visit action and accepts a + // symbol. Then ReferenceTable can add a reference and find symbol can just stop. + // + // TODO: Have a symbol name and a separate display name, the first minimally the text so the + // buckets work, the second usually a more complete signature for e.g. outline view. private sealed class ReferenceVisitor : AstVisitor2 { private readonly ReferenceTable _references; @@ -115,8 +118,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) SymbolType.Function, CommandHelpers.StripModuleQualification(commandName, out _), commandAst.CommandElements[0].Extent, - commandAst.Extent - ); + commandAst.Extent); return AstVisitAction.Continue; } @@ -151,7 +153,8 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command SymbolType.Parameter, commandParameterAst.Extent.Text, commandParameterAst.Extent, - commandParameterAst.Extent); + commandParameterAst.Extent, + isDeclaration: true); return AstVisitAction.Continue; } @@ -164,8 +167,8 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var SymbolType.Variable, $"${variableExpressionAst.VariablePath.UserPath}", variableExpressionAst.Extent, - variableExpressionAst.Extent - ); + variableExpressionAst.Extent, // TODO: Maybe parent? + isDeclaration: variableExpressionAst.Parent is AssignmentStatementAst or ParameterAst); return AstVisitAction.Continue; } @@ -181,7 +184,8 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit symbolType, typeDefinitionAst.Name, nameExtent, - typeDefinitionAst.Extent); + typeDefinitionAst.Extent, + isDeclaration: true); return AstVisitAction.Continue; } @@ -214,12 +218,17 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem ? SymbolType.Constructor : SymbolType.Method; - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, false); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent( + functionMemberAst, + useQualifiedName: false, + includeReturnType: false); + _references.AddReference( symbolType, - nameExtent.Text, + functionMemberAst.Name, // We bucket all the overloads. nameExtent, - functionMemberAst.Extent); + functionMemberAst.Extent, + isDeclaration: true); return AstVisitAction.Continue; } @@ -230,12 +239,47 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? SymbolType.EnumMember : SymbolType.Property; - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false, false); _references.AddReference( symbolType, nameExtent.Text, nameExtent, - propertyMemberAst.Extent); + propertyMemberAst.Extent, + isDeclaration: true); + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst) + { + string? memberName = memberExpressionAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null; + if (string.IsNullOrEmpty(memberName)) + { + return AstVisitAction.Continue; + } + + _references.AddReference( + SymbolType.Property, + memberName, + memberExpressionAst.Member.Extent, + memberExpressionAst.Extent); + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) + { + string? memberName = methodCallAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null; + if (string.IsNullOrEmpty(memberName)) + { + return AstVisitAction.Continue; + } + + _references.AddReference( + SymbolType.Method, + memberName, + methodCallAst.Member.Extent, + methodCallAst.Extent); return AstVisitAction.Continue; } @@ -247,7 +291,8 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit SymbolType.Configuration, nameExtent.Text, nameExtent, - configurationDefinitionAst.Extent); + configurationDefinitionAst.Extent, + isDeclaration: true); return AstVisitAction.Continue; } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 69b823fb3..1079f8f94 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -5,12 +5,10 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; -using System.IO; using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; using System.Runtime.InteropServices; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -184,8 +182,12 @@ public async Task> ScanForReferencesOfSymbol( } } - // We want to look for references first in referenced files, hence we use ordered dictionary - // TODO: File system case-sensitivity is based on filesystem not OS, but OS is a much cheaper heuristic + // TODO: This is entirely unused, but we actually DO want to search first the file, then + // its referenced files, then everything else! + // + // We want to look for references first in referenced files, hence we use ordered + // dictionary TODO: File system case-sensitivity is based on filesystem not OS, but OS + // is a much cheaper heuristic OrderedDictionary fileMap = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? new OrderedDictionary() : new OrderedDictionary(StringComparer.OrdinalIgnoreCase); @@ -396,111 +398,37 @@ await CommandHelpers.GetCommandInfoAsync( /// Finds the definition of a symbol in the script file or any of the /// files that it references. /// - /// The initial script file to be searched for the symbol's definition. - /// The symbol for which a definition will be found. + /// The initial script file to be searched for the symbol's definition. + /// The symbol for which a definition will be found. + /// /// The resulting GetDefinitionResult for the symbol's definition. - public async Task GetDefinitionOfSymbolAsync( - ScriptFile sourceFile, - SymbolReference foundSymbol) + public async Task> GetDefinitionOfSymbolAsync( + ScriptFile file, + SymbolReference symbol, + CancellationToken cancellationToken = default) { - 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.TryGetValue(foundSymbol.SymbolName, out string value)) - { - foundSymbol = new SymbolReference( - foundSymbol.SymbolType, -value, - foundSymbol.ScriptRegion, - foundSymbol.FilePath, - foundSymbol.SourceLine); - } - - ScriptFile[] referencedFiles = _workspaceService.ExpandScriptReferences(sourceFile); - - HashSet filesSearched = new(StringComparer.OrdinalIgnoreCase); - - // look through the referenced files until definition is found - // or there are no more file to look through - SymbolReference foundDefinition = null; - foreach (ScriptFile scriptFile in referencedFiles) + if (file.References.TryGetReferences(symbol.SymbolName, out ConcurrentBag symbols)) { - // TODO: This needs to just search the file's references (filtered to declarations); - foundDefinition = AstOperations.FindDefinitionOfSymbol(scriptFile.ScriptAst, foundSymbol); - - filesSearched.Add(scriptFile.FilePath); - if (foundDefinition is not null) + IEnumerable possibleLocalDeclarations = symbols.Where((i) => i.IsDeclaration); + if (possibleLocalDeclarations.Any()) { - foundDefinition.FilePath = scriptFile.FilePath; - break; - } - - if (foundSymbol.SymbolType == SymbolType.Function) - { - // Dot-sourcing is parsed as a "Function" Symbol. - string dotSourcedPath = GetDotSourcedPath(foundSymbol, scriptFile); - if (scriptFile.FilePath == dotSourcedPath) - { - foundDefinition = new SymbolReference( - SymbolType.Function, - foundSymbol.SymbolName, - scriptFile.ScriptAst.Extent, - scriptFile.FilePath); - break; - } + _logger.LogDebug($"Found possible declarations ${possibleLocalDeclarations}"); + return possibleLocalDeclarations; } } - // if the definition the not found in referenced files - // look for it in all the files in the workspace - if (foundDefinition is null) - { - // Get a list of all powershell files in the workspace path - foreach (string file in _workspaceService.EnumeratePSFiles()) - { - if (filesSearched.Contains(file)) - { - continue; - } + IEnumerable allSymbols = await ScanForReferencesOfSymbol( + symbol, + _workspaceService.ExpandScriptReferences(file), + cancellationToken).ConfigureAwait(false); - foundDefinition = - AstOperations.FindDefinitionOfSymbol( - Parser.ParseFile(file, out Token[] tokens, out ParseError[] parseErrors), - foundSymbol); + IEnumerable possibleDeclarations = allSymbols.Where((i) => i.IsDeclaration); + _logger.LogDebug($"Found possible declarations ${possibleDeclarations}"); - filesSearched.Add(file); - if (foundDefinition is not null) - { - foundDefinition.FilePath = file; - break; - } - } - } - - // if the definition is not found in a file in the workspace - // look for it in the builtin commands but only if the symbol - // we are looking at is possibly a Function. - if (foundDefinition is null - && (foundSymbol.SymbolType == SymbolType.Function - || foundSymbol.SymbolType == SymbolType.Unknown)) - { - CommandInfo cmdInfo = - await CommandHelpers.GetCommandInfoAsync( - foundSymbol.SymbolName, - _runspaceContext.CurrentRunspace, - _executionService).ConfigureAwait(false); - - foundDefinition = - FindDeclarationForBuiltinCommand( - cmdInfo, - foundSymbol); - } + return possibleDeclarations; - return foundDefinition; + // TODO: Fix searching for definition of built-in commands. + // TODO: Fix "definition" of dot-source (maybe?) } private Task _workspaceScanCompleted; @@ -556,90 +484,6 @@ private async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = de await Task.WhenAny(scanTask, cancelled.Task).ConfigureAwait(false); } - /// - /// Gets a path from a dot-source symbol. - /// - /// The symbol representing the dot-source expression. - /// The script file containing the symbol - /// - private string GetDotSourcedPath(SymbolReference symbol, ScriptFile scriptFile) - { - string cleanedUpSymbol = PathUtils.NormalizePathSeparators(symbol.SymbolName.Trim('\'', '"')); - string psScriptRoot = Path.GetDirectoryName(scriptFile.FilePath); - return _workspaceService.ResolveRelativeScriptPath(psScriptRoot, - Regex.Replace(cleanedUpSymbol, @"\$PSScriptRoot|\${PSScriptRoot}", psScriptRoot, RegexOptions.IgnoreCase)); - } - - private SymbolReference FindDeclarationForBuiltinCommand( - CommandInfo commandInfo, - SymbolReference foundSymbol) - { - if (commandInfo == null) - { - return null; - } - - ScriptFile[] nestedModuleFiles = - GetBuiltinCommandScriptFiles( - commandInfo.Module); - - SymbolReference foundDefinition = null; - foreach (ScriptFile nestedModuleFile in nestedModuleFiles) - { - foundDefinition = AstOperations.FindDefinitionOfSymbol( - nestedModuleFile.ScriptAst, - foundSymbol); - - if (foundDefinition != null) - { - foundDefinition.FilePath = nestedModuleFile.FilePath; - break; - } - } - - return foundDefinition; - } - - private ScriptFile[] GetBuiltinCommandScriptFiles( - PSModuleInfo moduleInfo) - { - if (moduleInfo == null) - { - return Array.Empty(); - } - - string modPath = moduleInfo.Path; - List scriptFiles = new(); - ScriptFile newFile; - - // find any files where the moduleInfo's path ends with ps1 or psm1 - // and add it to allowed script files - if (modPath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) || - modPath.EndsWith(".psm1", StringComparison.OrdinalIgnoreCase)) - { - newFile = _workspaceService.GetFile(modPath); - newFile.IsAnalysisEnabled = false; - scriptFiles.Add(newFile); - } - - if (moduleInfo.NestedModules.Count > 0) - { - foreach (PSModuleInfo nestedInfo in moduleInfo.NestedModules) - { - string nestedModPath = nestedInfo.Path; - if (nestedModPath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) || - nestedModPath.EndsWith(".psm1", StringComparison.OrdinalIgnoreCase)) - { - newFile = _workspaceService.GetFile(nestedModPath); - newFile.IsAnalysisEnabled = false; - scriptFiles.Add(newFile); - } - } - } - - return scriptFiles.ToArray(); - } - /// /// Finds a function definition that follows or contains the given line number. /// diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index b46e7e83d..7ccc3dc8c 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -181,21 +181,6 @@ public static SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumbe return commandVisitor.FoundCommandReference; } - /// - /// Finds the definition of the symbol - /// - /// The abstract syntax tree of the given script - /// The symbol that we are looking for the definition of - /// A SymbolReference of the definition of the symbolReference - public static SymbolReference FindDefinitionOfSymbol( - Ast scriptAst, - SymbolReference symbolReference) - { - FindDeclarationVisitor declarationVisitor = new(symbolReference); - scriptAst.Visit(declarationVisitor); - return declarationVisitor.FoundDeclaration; - } - /// /// Finds all files dot sourced in a script /// diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs deleted file mode 100644 index 8c8e1842a..000000000 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.Symbols -{ - /// - /// The visitor used to find the definition of a symbol - /// - internal class FindDeclarationVisitor : AstVisitor2 - { - private readonly SymbolReference symbolRef; - private readonly string variableName; - - public SymbolReference FoundDeclaration { get; private set; } - - public FindDeclarationVisitor(SymbolReference symbolRef) - { - this.symbolRef = symbolRef; - if (this.symbolRef.SymbolType == SymbolType.Variable) - { - // converts `$varName` to `varName` or of the form ${varName} to varName - variableName = symbolRef.SymbolName.TrimStart('$').Trim('{', '}'); - } - } - - /// - /// Decides if the current function definition is the right definition - /// for the symbol being searched for. The definition of the symbol will be a of type - /// SymbolType.Function and have the same name as the symbol - /// - /// A FunctionDefinitionAst in the script's AST - /// A decision to stop searching if the right FunctionDefinitionAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - { - // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. - // This will not exclude nested functions as they have ScriptBlockAst as parent - if (functionDefinitionAst.Parent is FunctionMemberAst) - { - return AstVisitAction.Continue; - } - - // We compare to the SymbolName instead of its text because it may have been resolved - // from an alias. - if (symbolRef.SymbolType.Equals(SymbolType.Function) && - functionDefinitionAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - // Get the start column number of the function name, - // instead of the the start column of 'function' and create new extent for the functionName - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); - - FoundDeclaration = - new SymbolReference( - SymbolType.Function, - nameExtent); - - return AstVisitAction.StopVisit; - } - - return base.VisitFunctionDefinition(functionDefinitionAst); - } - - /// - /// Decides if the current type definition is the right definition - /// for the symbol being searched for. The definition of the symbol will be a of type - /// SymbolType.Enum or SymbolType.Class and have the same name as the symbol - /// - /// A TypeDefinitionAst in the script's AST - /// A decision to stop searching if the right TypeDefinitionAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) - { - SymbolType symbolType = - typeDefinitionAst.IsEnum ? - SymbolType.Enum : SymbolType.Class; - - if ((symbolRef.SymbolType is SymbolType.Type || symbolRef.SymbolType.Equals(symbolType)) && - typeDefinitionAst.Name.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - // We only want the type name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); - - FoundDeclaration = - new SymbolReference( - symbolType, - nameExtent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - - /// - /// Decides if the current function member is the right definition - /// for the symbol being searched for. The definition of the symbol will be a of type - /// SymbolType.Constructor or SymbolType.Method and have the same name as the symbol - /// - /// A FunctionMemberAst in the script's AST - /// A decision to stop searching if the right FunctionMemberAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) - { - SymbolType symbolType = - functionMemberAst.IsConstructor ? - SymbolType.Constructor : SymbolType.Method; - - if (symbolRef.SymbolType.Equals(symbolType) && - VisitorUtils.GetMemberOverloadName(functionMemberAst, true, false).Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - // We only want the method/ctor name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, false); - - FoundDeclaration = - new SymbolReference( - symbolType, - nameExtent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - - /// - /// Decides if the current property member is the right definition - /// for the symbol being searched for. The definition of the symbol will be a of type - /// SymbolType.Property or SymbolType.EnumMember and have the same name as the symbol - /// - /// A PropertyMemberAst in the script's AST - /// A decision to stop searching if the right PropertyMemberAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) - { - SymbolType symbolType = - propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? - SymbolType.EnumMember : SymbolType.Property; - - if (symbolRef.SymbolType.Equals(symbolType) && - VisitorUtils.GetMemberOverloadName(propertyMemberAst, false).Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase)) - { - // We only want the property name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false); - - FoundDeclaration = - new SymbolReference( - SymbolType.Property, - nameExtent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - - /// - /// Check if the left hand side of an assignmentStatementAst is a VariableExpressionAst - /// with the same name as that of symbolRef. - /// - /// An AssignmentStatementAst - /// A decision to stop searching if the right VariableExpressionAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) - { - if (variableName == null) - { - return AstVisitAction.Continue; - } - - // We want to check VariableExpressionAsts from within this AssignmentStatementAst so we visit it. - FindDeclarationVariableExpressionVisitor visitor = new(symbolRef); - assignmentStatementAst.Left.Visit(visitor); - - if (visitor.FoundDeclaration != null) - { - FoundDeclaration = visitor.FoundDeclaration; - return AstVisitAction.StopVisit; - } - return AstVisitAction.Continue; - } - - /// - /// The private visitor used to find the variable expression that matches a symbol - /// - private class FindDeclarationVariableExpressionVisitor : AstVisitor - { - private readonly SymbolReference symbolRef; - private readonly string variableName; - - public SymbolReference FoundDeclaration { get; private set; } - - public FindDeclarationVariableExpressionVisitor(SymbolReference symbolRef) - { - this.symbolRef = symbolRef; - if (this.symbolRef.SymbolType == SymbolType.Variable) - { - // converts `$varName` to `varName` or of the form ${varName} to varName - variableName = symbolRef.SymbolName.TrimStart('$').Trim('{', '}'); - } - } - - /// - /// Check if the VariableExpressionAst has the same name as that of symbolRef. - /// - /// A VariableExpressionAst - /// A decision to stop searching if the right VariableExpressionAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - { - if (variableExpressionAst.VariablePath.UserPath.Equals(variableName, StringComparison.OrdinalIgnoreCase)) - { - // TODO also find instances of set-variable - FoundDeclaration = new SymbolReference(SymbolType.Variable, variableExpressionAst.Extent); - return AstVisitAction.StopVisit; - } - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitMemberExpression(MemberExpressionAst functionDefinitionAst) => - // We don't want to discover any variables in member expressisons (`$something.Foo`) - AstVisitAction.SkipChildren; - - public override AstVisitAction VisitIndexExpression(IndexExpressionAst functionDefinitionAst) => - // We don't want to discover any variables in index expressions (`$something[0]`) - AstVisitAction.SkipChildren; - } - } -} diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs index b45ee03eb..934689692 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs @@ -2,12 +2,14 @@ // Licensed under the MIT License. using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// The visitor used to find the symbol at a specific location in the AST + /// TODO: Re-use the ReferenceVisitor. /// internal class FindSymbolVisitor : AstVisitor2 { @@ -40,11 +42,23 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) { Ast commandNameAst = commandAst.CommandElements[0]; + string name; + if (returnFullSignature) + { + name = commandNameAst.Extent.Text; + } + else + { + string commandName = VisitorUtils.GetCommandName(commandAst); + name = CommandHelpers.StripModuleQualification(commandName, out _); + } + if (IsPositionInExtent(commandNameAst.Extent)) { FoundSymbolReference = new SymbolReference( SymbolType.Function, + name, commandNameAst.Extent); return AstVisitAction.StopVisit; @@ -93,6 +107,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun FoundSymbolReference = new SymbolReference( SymbolType.Function, + functionDefinitionAst.Name, nameExtent); return AstVisitAction.StopVisit; @@ -114,6 +129,7 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command FoundSymbolReference = new SymbolReference( SymbolType.Parameter, + commandParameterAst.Extent.Text, commandParameterAst.Extent); return AstVisitAction.StopVisit; } @@ -133,6 +149,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var FoundSymbolReference = new SymbolReference( SymbolType.Variable, + returnFullSignature ? variableExpressionAst.Extent.Text : $"${variableExpressionAst.VariablePath.UserPath}", variableExpressionAst.Extent); return AstVisitAction.StopVisit; @@ -175,6 +192,7 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem FoundSymbolReference = new SymbolReference( symbolType, + returnFullSignature ? nameExtent.Text : VisitorUtils.GetMemberOverloadName(functionMemberAst, useQualifiedName: false, includeReturnType: true), nameExtent); return AstVisitAction.StopVisit; @@ -220,6 +238,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit FoundSymbolReference = new SymbolReference( symbolType, + returnFullSignature ? nameExtent.Text : typeDefinitionAst.Name, nameExtent); return AstVisitAction.StopVisit; @@ -254,6 +273,7 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress FoundSymbolReference = new SymbolReference( SymbolType.Type, + returnFullSignature ? nameExtent.Text : typeExpressionAst.TypeName.Name, nameExtent); return AstVisitAction.StopVisit; } @@ -289,6 +309,7 @@ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstra FoundSymbolReference = new SymbolReference( SymbolType.Type, + returnFullSignature ? nameExtent.Text : typeConstraintAst.TypeName.Name, nameExtent); return AstVisitAction.StopVisit; } @@ -311,6 +332,7 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit FoundSymbolReference = new SymbolReference( SymbolType.Configuration, + nameExtent.Text, nameExtent); return AstVisitAction.StopVisit; @@ -328,7 +350,7 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { // We only want the property name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, returnFullSignature); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, returnFullSignature, returnFullSignature); if (IsPositionInExtent(nameExtent)) { @@ -339,6 +361,9 @@ propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? FoundSymbolReference = new SymbolReference( symbolType, + returnFullSignature + ? nameExtent.Text + : VisitorUtils.GetMemberOverloadName(propertyMemberAst, useQualifiedName: false), nameExtent); return AstVisitAction.StopVisit; @@ -346,5 +371,53 @@ propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? return AstVisitAction.Continue; } + + public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst) + { + string memberName = memberExpressionAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null; + if (string.IsNullOrEmpty(memberName)) + { + return AstVisitAction.Continue; + } + + if (IsPositionInExtent(memberExpressionAst.Member.Extent)) + { + FoundSymbolReference = + new SymbolReference( + SymbolType.Property, + returnFullSignature + ? memberExpressionAst.Member.Extent.Text + : memberName, + memberExpressionAst.Member.Extent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) + { + string? memberName = methodCallAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null; + if (string.IsNullOrEmpty(memberName)) + { + return AstVisitAction.Continue; + } + + if (IsPositionInExtent(methodCallAst.Member.Extent)) + { + FoundSymbolReference = + new SymbolReference( + SymbolType.Method, + returnFullSignature + ? methodCallAst.Member.Extent.Text + : memberName, + methodCallAst.Member.Extent); + + return AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; + } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs index 1b00b9805..94134ab6a 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs @@ -46,11 +46,12 @@ public override async Task Handle(DefinitionParams requ List definitionLocations = new(); if (foundSymbol != null) { - SymbolReference foundDefinition = await _symbolsService.GetDefinitionOfSymbolAsync( - scriptFile, - foundSymbol).ConfigureAwait(false); + IEnumerable foundDefinitions = await _symbolsService.GetDefinitionOfSymbolAsync( + scriptFile, + foundSymbol, + cancellationToken).ConfigureAwait(false); - if (foundDefinition != null) + foreach (SymbolReference foundDefinition in foundDefinitions) { definitionLocations.Add( new LocationOrLocationLink( diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index c1ab8d434..fca46b5e7 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -44,6 +44,7 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp DocumentSelector = LspUtils.PowerShellDocumentSelector }; + // AKA the outline feature public override async Task Handle(DocumentSymbolParams request, CancellationToken cancellationToken) { ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); @@ -64,7 +65,8 @@ public override async Task Handle(Do await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); - if (r.SymbolType is SymbolType.Type) + // Outline view should only include declarations. + if (!r.IsDeclaration) { continue; } diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs index c3e5ac303..a18d0a067 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs @@ -53,6 +53,7 @@ internal sealed class ScriptFile /// Gets or sets a boolean that determines whether /// semantic analysis should be enabled for this file. /// For internal use only. + /// TODO: Actually use and respect this property to avoid built-in files from being analyzed. /// internal bool IsAnalysisEnabled { get; set; } @@ -612,6 +613,7 @@ private void ParseFileContents() } // Get all dot sourced referenced files and store them + // TODO: We're considering removing this notion. ReferencedFiles = AstOperations.FindDotSourcedIncludes(ScriptAst, Path.GetDirectoryName(FilePath)); } diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 939a80f9d..9d594d884 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -36,6 +36,7 @@ public override async Task> Handle(WorkspaceSymbolP { List symbols = new(); + // TODO: This should (optionally) check all the files, like we do for scanning for references by calling ScanWorkspacePSFiles. foreach (ScriptFile scriptFile in _workspaceService.GetOpenedFiles()) { IEnumerable foundSymbols = _symbolsService.FindSymbolsInFile(scriptFile); @@ -50,7 +51,7 @@ public override async Task> Handle(WorkspaceSymbolP await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); - if (foundOccurrence.SymbolType is SymbolType.Type) + if (!foundOccurrence.IsDeclaration) { continue; } diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index 56a393c2b..f839cd4db 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -192,9 +192,13 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functio /// Gets a new ScriptExtent for a given Ast for the property name only /// /// A PropertyMemberAst in the script's AST + /// A bool indicating if class/enum name should be prepended /// A bool indicating if type should be included for class property /// A ScriptExtent with for the symbol name only - internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propertyMemberAst, bool includePropertyType = false) + internal static PSESSymbols.ScriptExtent GetNameExtent( + PropertyMemberAst propertyMemberAst, + bool useQualifiedName = true, + bool includePropertyType = false) { bool isEnumMember = propertyMemberAst.Parent is TypeDefinitionAst typeDef && typeDef.IsEnum; (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(propertyMemberAst, isEnumMember); @@ -206,7 +210,10 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propert return new PSESSymbols.ScriptExtent() { - Text = GetMemberOverloadName(propertyMemberAst, includePropertyType), + Text = GetMemberOverloadName( + propertyMemberAst, + useQualifiedName: useQualifiedName, + includePropertyType: includePropertyType), StartLineNumber = startLine, EndLineNumber = startLine, StartColumnNumber = startColumn, @@ -283,9 +290,11 @@ internal static string GetMemberOverloadName(FunctionMemberAst functionMemberAst /// Gets the property name with type and class/enum. /// /// A PropertyMemberAst object in the script's AST + /// A bool indicating if class/enum name should be prepended /// A bool indicating if type should be included for class property /// Property name with type (optional) and class/enum internal static string GetMemberOverloadName(PropertyMemberAst propertyMemberAst, + bool useQualifiedName = true, bool includePropertyType = false) { StringBuilder sb = new(); @@ -298,7 +307,10 @@ internal static string GetMemberOverloadName(PropertyMemberAst propertyMemberAst sb.Append(propertyMemberAst.PropertyType?.TypeName.Name ?? "object").Append(' '); } - sb.Append(typeAst.Name).Append('.'); + if (useQualifiedName) + { + sb.Append(typeAst.Name).Append('.'); + } } sb.Append(propertyMemberAst.Name); diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 67769b18e..e6befbf56 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -80,7 +80,7 @@ private Task GetParamSetSignatures(ScriptRegion scriptRe scriptRegion.StartColumnNumber); } - private Task GetDefinition(ScriptRegion scriptRegion) + private async Task GetDefinition(ScriptRegion scriptRegion) { ScriptFile scriptFile = GetScriptFile(scriptRegion); @@ -92,7 +92,9 @@ private Task GetDefinition(ScriptRegion scriptRegion) Assert.NotNull(symbol); - return symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbol); + IEnumerable refs = await symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbol).ConfigureAwait(true); + + return refs.FirstOrDefault(); } private async Task> GetReferences(ScriptRegion scriptRegion) From 63b214358fab494f5142afdc9e8411bbb63020c8 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 23 Jan 2023 14:59:55 -0800 Subject: [PATCH 185/327] Remove notion of `ReferencedFiles` This has been unused and sometimes broken, it doesn't actually power any useful features. --- .../CodeLens/ReferencesCodeLensProvider.cs | 6 +- .../Services/Symbols/SymbolsService.cs | 20 ----- .../Handlers/ReferencesHandler.cs | 4 +- .../Services/TextDocument/ScriptFile.cs | 25 ------ .../Services/Workspace/WorkspaceService.cs | 77 ------------------- .../Language/SymbolsServiceTests.cs | 4 +- .../Session/ScriptFileTests.cs | 26 ------- 7 files changed, 3 insertions(+), 159 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index daa0cd779..23b85fe91 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -97,8 +97,6 @@ public async Task ResolveCodeLens( ScriptFile scriptFile, CancellationToken cancellationToken) { - ScriptFile[] references = _workspaceService.ExpandScriptReferences(scriptFile); - SymbolReference foundSymbol = SymbolsService.FindSymbolDefinitionAtLocation( scriptFile, codeLens.Range.Start.Line + 1, @@ -106,9 +104,7 @@ public async Task ResolveCodeLens( IEnumerable referencesResult = await _symbolsService.ScanForReferencesOfSymbol( - foundSymbol, - references, - cancellationToken).ConfigureAwait(false); + foundSymbol, cancellationToken).ConfigureAwait(false); Location[] referenceLocations; if (referencesResult == null) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 1079f8f94..367b5e821 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -4,11 +4,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -154,12 +152,10 @@ public static SymbolReference FindSymbolAtLocation( /// Finds all the references of a symbol (excluding definitions) /// /// The symbol to find all references for - /// An array of scriptFiles to search for references in /// /// FindReferencesResult public async Task> ScanForReferencesOfSymbol( SymbolReference foundSymbol, - ScriptFile[] referencedFiles, CancellationToken cancellationToken = default) { if (foundSymbol == null) @@ -182,21 +178,6 @@ public async Task> ScanForReferencesOfSymbol( } } - // TODO: This is entirely unused, but we actually DO want to search first the file, then - // its referenced files, then everything else! - // - // We want to look for references first in referenced files, hence we use ordered - // dictionary TODO: File system case-sensitivity is based on filesystem not OS, but OS - // is a much cheaper heuristic - OrderedDictionary fileMap = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) - ? new OrderedDictionary() - : new OrderedDictionary(StringComparer.OrdinalIgnoreCase); - - foreach (ScriptFile scriptFile in referencedFiles) - { - fileMap[scriptFile.FilePath] = scriptFile; - } - await ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false); List symbolReferences = new(); @@ -419,7 +400,6 @@ public async Task> GetDefinitionOfSymbolAsync( IEnumerable allSymbols = await ScanForReferencesOfSymbol( symbol, - _workspaceService.ExpandScriptReferences(file), cancellationToken).ConfigureAwait(false); IEnumerable possibleDeclarations = allSymbols.Where((i) => i.IsDeclaration); diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index 7a8b55fd1..97a911172 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -43,9 +43,7 @@ public override async Task Handle(ReferenceParams request, Ca IEnumerable referencesResult = await _symbolsService.ScanForReferencesOfSymbol( - foundSymbol, - _workspaceService.ExpandScriptReferences(scriptFile), - cancellationToken).ConfigureAwait(false); + foundSymbol, cancellationToken).ConfigureAwait(false); if (referencesResult is null) { diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs index a18d0a067..3e52e4938 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.LanguageServer.Protocol; @@ -111,15 +110,6 @@ public Token[] ScriptTokens private set; } - /// - /// Gets the array of filepaths dot sourced in this ScriptFile - /// - public string[] ReferencedFiles - { - get; - private set; - } - internal ReferenceTable References { get; } internal bool IsOpen { get; set; } @@ -600,21 +590,6 @@ private void ParseFileContents() parseErrors .Select(ScriptFileMarker.FromParseError) .ToList(); - - // Untitled files have no directory - // Discussed in https://github.com/PowerShell/PowerShellEditorServices/pull/815. - // Rather than working hard to enable things for untitled files like a phantom directory, - // users should save the file. - if (IsInMemory) - { - // Need to initialize the ReferencedFiles property to an empty array. - ReferencedFiles = Array.Empty(); - return; - } - - // Get all dot sourced referenced files and store them - // TODO: We're considering removing this notion. - ReferencedFiles = AstOperations.FindDotSourcedIncludes(ScriptAst, Path.GetDirectoryName(FilePath)); } #endregion diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs index 0d9a614e4..ab2bb6a11 100644 --- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs @@ -292,35 +292,6 @@ public void CloseFile(ScriptFile scriptFile) workspaceFiles.TryRemove(keyName, out ScriptFile _); } - /// - /// Gets all file references by recursively searching - /// through referenced files in a scriptfile - /// - /// Contains the details and contents of an open script file - /// A scriptfile array where the first file - /// in the array is the "root file" of the search - public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile) - { - Dictionary referencedScriptFiles = new(); - List expandedReferences = new(); - - // add original file so it's not searched for, then find all file references - referencedScriptFiles.Add(scriptFile.Id, scriptFile); - RecursivelyFindReferences(scriptFile, referencedScriptFiles); - - // remove original file from referenced file and add it as the first element of the - // expanded referenced list to maintain order so the original file is always first in the list - referencedScriptFiles.Remove(scriptFile.Id); - expandedReferences.Add(scriptFile); - - if (referencedScriptFiles.Count > 0) - { - expandedReferences.AddRange(referencedScriptFiles.Values); - } - - return expandedReferences.ToArray(); - } - /// /// Gets the workspace-relative path of the given file path. /// @@ -401,54 +372,6 @@ bool ignoreReparsePoints #endregion #region Private Methods - /// - /// Recursively searches through referencedFiles in scriptFiles - /// and builds a Dictionary of the file references - /// - /// Details an contents of "root" script file - /// A Dictionary of referenced script files - private void RecursivelyFindReferences( - ScriptFile scriptFile, - Dictionary referencedScriptFiles) - { - // Get the base path of the current script for use in resolving relative paths - string baseFilePath = scriptFile.IsInMemory - ? WorkspacePath - : Path.GetDirectoryName(scriptFile.FilePath); - - foreach (string referencedFileName in scriptFile.ReferencedFiles) - { - string resolvedScriptPath = - ResolveRelativeScriptPath( - baseFilePath, - referencedFileName); - - // If there was an error resolving the string, skip this reference - if (resolvedScriptPath == null) - { - continue; - } - - logger.LogDebug( - string.Format( - "Resolved relative path '{0}' to '{1}'", - referencedFileName, - resolvedScriptPath)); - - // Get the referenced file if it's not already in referencedScriptFiles - if (TryGetFile(resolvedScriptPath, out ScriptFile referencedFile)) - { - // Normalize the resolved script path and add it to the - // referenced files list if it isn't there already - resolvedScriptPath = resolvedScriptPath.ToLower(); - if (!referencedScriptFiles.ContainsKey(resolvedScriptPath)) - { - referencedScriptFiles.Add(resolvedScriptPath, referencedFile); - RecursivelyFindReferences(referencedFile, referencedScriptFiles); - } - } - } - } internal static StreamReader OpenStreamReader(DocumentUri uri) { diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index e6befbf56..153d3b5d8 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -109,9 +109,7 @@ private async Task> GetReferences(ScriptRegion scriptRegio Assert.NotNull(symbol); IEnumerable symbols = - await symbolsService.ScanForReferencesOfSymbol( - symbol, - workspace.ExpandScriptReferences(scriptFile)).ConfigureAwait(true); + await symbolsService.ScanForReferencesOfSymbol(symbol).ConfigureAwait(true); return symbols.OrderBy(i => i.ScriptRegion.ToRange().Start).ToList(); } diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index 40efc5c26..4839eacfa 100644 --- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -174,30 +174,6 @@ public void CanAppendToEndOfFile() ); } - [Trait("Category", "ScriptFile")] - [Fact] - public void FindsDotSourcedFiles() - { - string exampleScriptContents = TestUtilities.PlatformNormalize( - ". ./athing.ps1\n" + - ". ./somefile.ps1\n" + - ". ./somefile.ps1\n" + - "Do-Stuff $uri\n" + - ". simpleps.ps1"); - - using StringReader stringReader = new(exampleScriptContents); - ScriptFile scriptFile = - new( - // Use any absolute path. Even if it doesn't exist. - DocumentUri.FromFileSystemPath(Path.Combine(Path.GetTempPath(), "TestFile.ps1")), - stringReader, - PowerShellVersion); - - Assert.Equal(3, scriptFile.ReferencedFiles.Length); - Console.Write("a" + scriptFile.ReferencedFiles[0]); - Assert.Equal(TestUtilities.NormalizePath("./athing.ps1"), scriptFile.ReferencedFiles[0]); - } - [Trait("Category", "ScriptFile")] [Fact] public void ThrowsExceptionWithEditOutsideOfRange() @@ -610,7 +586,6 @@ public void PropertiesInitializedCorrectlyForFile() Assert.Equal(path, scriptFile.FilePath, ignoreCase: !VersionUtils.IsLinux); Assert.True(scriptFile.IsAnalysisEnabled); Assert.False(scriptFile.IsInMemory); - Assert.Empty(scriptFile.ReferencedFiles); Assert.Empty(scriptFile.DiagnosticMarkers); Assert.Single(scriptFile.ScriptTokens); Assert.Single(scriptFile.FileLines); @@ -635,7 +610,6 @@ public void PropertiesInitializedCorrectlyForUntitled() Assert.Equal(path, scriptFile.DocumentUri); Assert.True(scriptFile.IsAnalysisEnabled); Assert.True(scriptFile.IsInMemory); - Assert.Empty(scriptFile.ReferencedFiles); Assert.Empty(scriptFile.DiagnosticMarkers); Assert.Equal(10, scriptFile.ScriptTokens.Length); Assert.Equal(3, scriptFile.FileLines.Count); From cd8dd7f202bce564072e9e443acf737278992574 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 23 Jan 2023 16:34:25 -0800 Subject: [PATCH 186/327] Break the soon-to-be-only visitor out --- .../Services/Symbols/ReferenceTable.cs | 216 +---------------- .../Services/Symbols/SymbolReference.cs | 6 +- .../Services/Symbols/Vistors/SymbolVisitor.cs | 218 ++++++++++++++++++ 3 files changed, 229 insertions(+), 211 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/Symbols/Vistors/SymbolVisitor.cs diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 2212ba388..1d5822e72 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -7,10 +7,8 @@ using System.Collections.Concurrent; using System.Management.Automation.Language; using Microsoft.PowerShell.EditorServices.Services.TextDocument; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Services.Symbols; using System.Collections.Generic; -using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services; @@ -68,7 +66,7 @@ internal void EnsureInitialized() return; } - _parent.ScriptAst.Visit(new ReferenceVisitor(this)); + _parent.ScriptAst.Visit(new SymbolVisitor(_parent, AddReference)); } private static bool ExtentIsEmpty(IScriptExtent e) => string.IsNullOrEmpty(e.File) && @@ -76,225 +74,23 @@ private static bool ExtentIsEmpty(IScriptExtent e) => string.IsNullOrEmpty(e.Fil e.EndLineNumber == 0 && e.EndColumnNumber == 0 && string.IsNullOrEmpty(e.Text); - private void AddReference(SymbolType type, string name, IScriptExtent nameExtent, IScriptExtent extent, bool isDeclaration = false) + private AstVisitAction AddReference(SymbolReference symbol) { // We have to exclude implicit things like `$this` that don't actually exist. - if (ExtentIsEmpty(extent)) + if (ExtentIsEmpty(symbol.ScriptRegion)) { - return; + return AstVisitAction.Continue; } - SymbolReference symbol = new(type, name, nameExtent, extent, _parent, isDeclaration); _symbolReferences.AddOrUpdate( - name, + symbol.SymbolName, _ => new ConcurrentBag { symbol }, (_, existing) => { existing.Add(symbol); return existing; }); - } - - // TODO: Reconstruct this to take an action lambda that returns a visit action and accepts a - // symbol. Then ReferenceTable can add a reference and find symbol can just stop. - // - // TODO: Have a symbol name and a separate display name, the first minimally the text so the - // buckets work, the second usually a more complete signature for e.g. outline view. - private sealed class ReferenceVisitor : AstVisitor2 - { - private readonly ReferenceTable _references; - - public ReferenceVisitor(ReferenceTable references) => _references = references; - - public override AstVisitAction VisitCommand(CommandAst commandAst) - { - string? commandName = VisitorUtils.GetCommandName(commandAst); - if (string.IsNullOrEmpty(commandName)) - { - return AstVisitAction.Continue; - } - - _references.AddReference( - SymbolType.Function, - CommandHelpers.StripModuleQualification(commandName, out _), - commandAst.CommandElements[0].Extent, - commandAst.Extent); - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - { - SymbolType symbolType = functionDefinitionAst.IsWorkflow - ? SymbolType.Workflow - : SymbolType.Function; - - // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. - // This will not exclude nested functions as they have ScriptBlockAst as parent - if (functionDefinitionAst.Parent is FunctionMemberAst) - { - return AstVisitAction.Continue; - } - - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); - _references.AddReference( - symbolType, - functionDefinitionAst.Name, - nameExtent, - functionDefinitionAst.Extent, - isDeclaration: true); - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) - { - _references.AddReference( - SymbolType.Parameter, - commandParameterAst.Extent.Text, - commandParameterAst.Extent, - commandParameterAst.Extent, - isDeclaration: true); - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - { - // TODO: Consider tracking unscoped variable references only when they declared within - // the same function definition. - _references.AddReference( - SymbolType.Variable, - $"${variableExpressionAst.VariablePath.UserPath}", - variableExpressionAst.Extent, - variableExpressionAst.Extent, // TODO: Maybe parent? - isDeclaration: variableExpressionAst.Parent is AssignmentStatementAst or ParameterAst); - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) - { - SymbolType symbolType = typeDefinitionAst.IsEnum - ? SymbolType.Enum - : SymbolType.Class; - - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); - _references.AddReference( - symbolType, - typeDefinitionAst.Name, - nameExtent, - typeDefinitionAst.Extent, - isDeclaration: true); - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) - { - _references.AddReference( - SymbolType.Type, - typeExpressionAst.TypeName.Name, - typeExpressionAst.Extent, - typeExpressionAst.Extent); - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) - { - _references.AddReference( - SymbolType.Type, - typeConstraintAst.TypeName.Name, - typeConstraintAst.Extent, - typeConstraintAst.Extent); - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) - { - SymbolType symbolType = functionMemberAst.IsConstructor - ? SymbolType.Constructor - : SymbolType.Method; - - IScriptExtent nameExtent = VisitorUtils.GetNameExtent( - functionMemberAst, - useQualifiedName: false, - includeReturnType: false); - - _references.AddReference( - symbolType, - functionMemberAst.Name, // We bucket all the overloads. - nameExtent, - functionMemberAst.Extent, - isDeclaration: true); - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) - { - SymbolType symbolType = - propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum - ? SymbolType.EnumMember : SymbolType.Property; - - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false, false); - _references.AddReference( - symbolType, - nameExtent.Text, - nameExtent, - propertyMemberAst.Extent, - isDeclaration: true); - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst) - { - string? memberName = memberExpressionAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null; - if (string.IsNullOrEmpty(memberName)) - { - return AstVisitAction.Continue; - } - - _references.AddReference( - SymbolType.Property, - memberName, - memberExpressionAst.Member.Extent, - memberExpressionAst.Extent); - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) - { - string? memberName = methodCallAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null; - if (string.IsNullOrEmpty(memberName)) - { - return AstVisitAction.Continue; - } - - _references.AddReference( - SymbolType.Method, - memberName, - methodCallAst.Member.Extent, - methodCallAst.Extent); - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) - { - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(configurationDefinitionAst); - _references.AddReference( - SymbolType.Configuration, - nameExtent.Text, - nameExtent, - configurationDefinitionAst.Extent, - isDeclaration: true); - - return AstVisitAction.Continue; - } + return AstVisitAction.Continue; } } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index 19a3428fd..db60d5482 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -17,6 +17,8 @@ internal record SymbolReference { public SymbolType SymbolType { get; } + // TODO: Have a symbol name and a separate display name, the first minimally the text so the + // buckets work, the second usually a more complete signature for e.g. outline view. public string SymbolName { get; } public ScriptRegion NameRegion { get; } @@ -31,6 +33,7 @@ internal record SymbolReference /// /// Constructs and instance of a SymbolReference + /// TODO: Remove this when it becomes unused. /// /// The higher level type of the symbol /// The name of the symbol @@ -81,7 +84,8 @@ public SymbolReference( } /// - /// Constructs an instance of a SymbolReference + /// Constructs an instance of a SymbolReference. + /// TODO: Remove this when it becomes unused. /// /// The higher level type of the symbol /// The script extent of the symbol diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/SymbolVisitor.cs new file mode 100644 index 000000000..01eb093ee --- /dev/null +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/SymbolVisitor.cs @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; + +namespace Microsoft.PowerShell.EditorServices.Services.Symbols; + +/// +/// The goal of this is to be our one and only visitor, which parses a file when necessary +/// performing Action, which takes a SymbolReference (that this visitor creates) and returns an +/// AstVisitAction. In this way, all our symbols are created with the same initialization logic. +/// +internal sealed class SymbolVisitor : AstVisitor2 +{ + private readonly ScriptFile _file; + + private readonly Func _action; + + public SymbolVisitor(ScriptFile file, Func action) + { + _file = file; + _action = action; + } + + public override AstVisitAction VisitCommand(CommandAst commandAst) + { + string? commandName = VisitorUtils.GetCommandName(commandAst); + if (string.IsNullOrEmpty(commandName)) + { + return AstVisitAction.Continue; + } + + return _action(new SymbolReference( + SymbolType.Function, + CommandHelpers.StripModuleQualification(commandName, out _), + commandAst.CommandElements[0].Extent, + commandAst.Extent, + _file, + isDeclaration: false)); + } + + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + { + SymbolType symbolType = functionDefinitionAst.IsWorkflow + ? SymbolType.Workflow + : SymbolType.Function; + + // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. + // This will not exclude nested functions as they have ScriptBlockAst as parent + if (functionDefinitionAst.Parent is FunctionMemberAst) + { + return AstVisitAction.Continue; + } + + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); + return _action(new SymbolReference( + symbolType, + functionDefinitionAst.Name, + nameExtent, + functionDefinitionAst.Extent, + _file, + isDeclaration: true)); + } + + public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) + { + return _action(new SymbolReference( + SymbolType.Parameter, + commandParameterAst.Extent.Text, + commandParameterAst.Extent, + commandParameterAst.Extent, + _file, + isDeclaration: true)); + } + + public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) + { + // TODO: Consider tracking unscoped variable references only when they declared within + // the same function definition. + return _action(new SymbolReference( + SymbolType.Variable, + $"${variableExpressionAst.VariablePath.UserPath}", + variableExpressionAst.Extent, + variableExpressionAst.Extent, // TODO: Maybe parent? + _file, + isDeclaration: variableExpressionAst.Parent is AssignmentStatementAst or ParameterAst)); + } + + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + SymbolType symbolType = typeDefinitionAst.IsEnum + ? SymbolType.Enum + : SymbolType.Class; + + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); + return _action(new SymbolReference( + symbolType, + typeDefinitionAst.Name, + nameExtent, + typeDefinitionAst.Extent, + _file, + isDeclaration: true)); + } + + public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) + { + return _action(new SymbolReference( + SymbolType.Type, + typeExpressionAst.TypeName.Name, + typeExpressionAst.Extent, + typeExpressionAst.Extent, + _file, + isDeclaration: false)); + } + + public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) + { + return _action(new SymbolReference( + SymbolType.Type, + typeConstraintAst.TypeName.Name, + typeConstraintAst.Extent, + typeConstraintAst.Extent, + _file, + isDeclaration: false)); + } + + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + SymbolType symbolType = functionMemberAst.IsConstructor + ? SymbolType.Constructor + : SymbolType.Method; + + IScriptExtent nameExtent = VisitorUtils.GetNameExtent( + functionMemberAst, + useQualifiedName: false, + includeReturnType: false); + + return _action(new SymbolReference( + symbolType, + functionMemberAst.Name, // We bucket all the overloads. + nameExtent, + functionMemberAst.Extent, + _file, + isDeclaration: true)); + } + + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + SymbolType symbolType = + propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum + ? SymbolType.EnumMember : SymbolType.Property; + + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false, false); + return _action(new SymbolReference( + symbolType, + nameExtent.Text, + nameExtent, + propertyMemberAst.Extent, + _file, + isDeclaration: true)); + } + + public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst) + { + string? memberName = memberExpressionAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null; + if (string.IsNullOrEmpty(memberName)) + { + return AstVisitAction.Continue; + } + + return _action(new SymbolReference( + SymbolType.Property, +#pragma warning disable CS8604 // Possible null reference argument. + memberName, +#pragma warning restore CS8604 + memberExpressionAst.Member.Extent, + memberExpressionAst.Extent, + _file, + isDeclaration: false)); + } + + public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) + { + string? memberName = methodCallAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null; + if (string.IsNullOrEmpty(memberName)) + { + return AstVisitAction.Continue; + } + + return _action(new SymbolReference( + SymbolType.Method, +#pragma warning disable CS8604 // Possible null reference argument. + memberName, +#pragma warning restore CS8604 + methodCallAst.Member.Extent, + methodCallAst.Extent, + _file, + isDeclaration: false)); + } + + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) + { + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(configurationDefinitionAst); + return _action(new SymbolReference( + SymbolType.Configuration, + nameExtent.Text, + nameExtent, + configurationDefinitionAst.Extent, + _file, + isDeclaration: true)); + } +} From 6652213f5287de486d9b36f13a36393bf99738ab Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 23 Jan 2023 17:40:23 -0800 Subject: [PATCH 187/327] Remove `FindCommandVisitor`, `FindSymbolVisitor` and `FindDotSourcedVisitor` `SymbolsService` is now nullable too. --- .../CodeLens/ReferencesCodeLensProvider.cs | 2 +- .../Services/Symbols/ReferenceTable.cs | 21 +- .../Services/Symbols/SymbolsService.cs | 276 ++++-------- .../Services/Symbols/Vistors/AstOperations.cs | 53 --- .../Symbols/Vistors/FindCommandVisitor.cs | 70 --- .../Symbols/Vistors/FindDotSourcedVisitor.cs | 78 ---- .../Symbols/Vistors/FindSymbolVisitor.cs | 423 ------------------ .../Handlers/DocumentSymbolHandler.cs | 3 + .../Handlers/ReferencesHandler.cs | 2 +- .../Services/TextDocument/ScriptRegion.cs | 50 +++ .../Language/SymbolsServiceTests.cs | 2 +- .../Services/Symbols/AstOperationsTests.cs | 18 +- 12 files changed, 166 insertions(+), 832 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/Symbols/Vistors/FindCommandVisitor.cs delete mode 100644 src/PowerShellEditorServices/Services/Symbols/Vistors/FindDotSourcedVisitor.cs delete mode 100644 src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index 23b85fe91..c19a8c8e9 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -103,7 +103,7 @@ public async Task ResolveCodeLens( codeLens.Range.Start.Character + 1); IEnumerable referencesResult = - await _symbolsService.ScanForReferencesOfSymbol( + await _symbolsService.ScanForReferencesOfSymbolAsync( foundSymbol, cancellationToken).ConfigureAwait(false); Location[] referenceLocations; diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 1d5822e72..8bbb0359a 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -9,6 +9,7 @@ using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Services.Symbols; using System.Collections.Generic; +using System.Linq; namespace Microsoft.PowerShell.EditorServices.Services; @@ -47,16 +48,19 @@ internal bool TryGetReferences(string command, out ConcurrentBag GetAllReferences() + internal SymbolReference? TryGetSymbolAtPosition(int line, int column) => GetAllReferences() + .FirstOrDefault((i) => i.NameRegion.ContainsPosition(line, column)); + + internal IEnumerable GetAllReferences() { EnsureInitialized(); - List allReferences = new(); foreach (ConcurrentBag bag in _symbolReferences.Values) { - allReferences.AddRange(bag); + foreach (SymbolReference symbol in bag) + { + yield return symbol; + } } - return allReferences; } internal void EnsureInitialized() @@ -69,15 +73,10 @@ internal void EnsureInitialized() _parent.ScriptAst.Visit(new SymbolVisitor(_parent, AddReference)); } - private static bool ExtentIsEmpty(IScriptExtent e) => string.IsNullOrEmpty(e.File) && - e.StartLineNumber == 0 && e.StartColumnNumber == 0 && - e.EndLineNumber == 0 && e.EndColumnNumber == 0 && - string.IsNullOrEmpty(e.Text); - private AstVisitAction AddReference(SymbolReference symbol) { // We have to exclude implicit things like `$this` that don't actually exist. - if (ExtentIsEmpty(symbol.ScriptRegion)) + if (symbol.ScriptRegion.IsEmpty()) { return AstVisitAction.Continue; } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 367b5e821..7a2352551 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -38,8 +40,9 @@ internal class SymbolsService private readonly ConcurrentDictionary _codeLensProviders; private readonly ConcurrentDictionary _documentSymbolProviders; private readonly ConfigurationService _configurationService; - #endregion + private Task? _workspaceScanCompleted; + #endregion Private Fields #region Constructors /// @@ -68,11 +71,11 @@ public SymbolsService( if (configurationService.CurrentSettings.EnableReferencesCodeLens) { ReferencesCodeLensProvider referencesProvider = new(_workspaceService, this); - _codeLensProviders.TryAdd(referencesProvider.ProviderId, referencesProvider); + _ = _codeLensProviders.TryAdd(referencesProvider.ProviderId, referencesProvider); } PesterCodeLensProvider pesterProvider = new(configurationService); - _codeLensProviders.TryAdd(pesterProvider.ProviderId, pesterProvider); + _ = _codeLensProviders.TryAdd(pesterProvider.ProviderId, pesterProvider); // TODO: Is this complication so necessary? _documentSymbolProviders = new ConcurrentDictionary(); @@ -82,13 +85,14 @@ public SymbolsService( new PsdDocumentSymbolProvider(), new PesterDocumentSymbolProvider(), }; + foreach (IDocumentSymbolProvider documentSymbolProvider in documentSymbolProviders) { - _documentSymbolProviders.TryAdd(documentSymbolProvider.ProviderId, documentSymbolProvider); + _ = _documentSymbolProviders.TryAdd(documentSymbolProvider.ProviderId, documentSymbolProvider); } } - #endregion + #endregion Constructors public bool TryRegisterCodeLensProvider(ICodeLensProvider codeLensProvider) => _codeLensProviders.TryAdd(codeLensProvider.ProviderId, codeLensProvider); @@ -103,7 +107,7 @@ public SymbolsService( public IEnumerable GetDocumentSymbolProviders() => _documentSymbolProviders.Values; /// - /// Finds all the symbols in a file. + /// Finds all the symbols in a file, through all document symbol providers. /// /// The ScriptFile in which the symbol can be located. public IEnumerable FindSymbolsInFile(ScriptFile scriptFile) @@ -121,44 +125,43 @@ public IEnumerable FindSymbolsInFile(ScriptFile scriptFile) } /// - /// Finds the symbol in the script given a file location + /// Finds the symbol in the script given a file location. /// - /// The details and contents of a open script file - /// The line number of the cursor for the given script - /// The column number of the cursor for the given script - /// A SymbolReference of the symbol found at the given location - /// or null if there is no symbol at that location - /// - public static SymbolReference FindSymbolAtLocation( - ScriptFile scriptFile, - int lineNumber, - int columnNumber) + public static SymbolReference? FindSymbolAtLocation( + ScriptFile scriptFile, int line, int column) { - SymbolReference symbolReference = - AstOperations.FindSymbolAtPosition( - scriptFile.ScriptAst, - lineNumber, - columnNumber); + Validate.IsNotNull(nameof(scriptFile), scriptFile); + return scriptFile.References.TryGetSymbolAtPosition(line, column); + } - if (symbolReference != null) + // Using a private method here to get a bit more readability and to avoid roslynator + // asserting we should use a giant nested ternary. + private static string[] GetIdentifiers(string symbolName, SymbolType symbolType, CommandHelpers.AliasMap aliases) + { + if (symbolType is not SymbolType.Function) { - symbolReference.FilePath = scriptFile.FilePath; + return new[] { symbolName }; } - return symbolReference; + if (!aliases.CmdletToAliases.TryGetValue(symbolName, out List foundAliasList)) + { + return new[] { symbolName }; + } + + return foundAliasList.Prepend(symbolName) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); } /// - /// Finds all the references of a symbol (excluding definitions) + /// Finds all the references of a symbol in the workspace, resolving aliases. + /// TODO: Make it not return a nullable. /// - /// The symbol to find all references for - /// - /// FindReferencesResult - public async Task> ScanForReferencesOfSymbol( - SymbolReference foundSymbol, + public async Task?> ScanForReferencesOfSymbolAsync( + SymbolReference symbol, CancellationToken cancellationToken = default) { - if (foundSymbol == null) + if (symbol is null) { return null; } @@ -168,148 +171,79 @@ public async Task> ScanForReferencesOfSymbol( _executionService, cancellationToken).ConfigureAwait(false); - string targetName = foundSymbol.SymbolName; - if (foundSymbol.SymbolType is SymbolType.Function) + string targetName = symbol.SymbolName; + if (symbol.SymbolType is SymbolType.Function + && aliases.AliasToCmdlets.TryGetValue(symbol.SymbolName, out string aliasDefinition)) { - targetName = CommandHelpers.StripModuleQualification(targetName, out _); - if (aliases.AliasToCmdlets.TryGetValue(foundSymbol.SymbolName, out string aliasDefinition)) - { - targetName = aliasDefinition; - } + targetName = aliasDefinition; } await ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false); - List symbolReferences = new(); - - // Using a nested method here to get a bit more readability and to avoid roslynator - // asserting we should use a giant nested ternary here. - static string[] GetIdentifiers(string symbolName, SymbolType symbolType, CommandHelpers.AliasMap aliases) - { - if (symbolType is not SymbolType.Function) - { - return new[] { symbolName }; - } - - if (!aliases.CmdletToAliases.TryGetValue(symbolName, out List foundAliasList)) - { - return new[] { symbolName }; - } - - return foundAliasList.Prepend(symbolName) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray(); - } + List symbols = new(); - string[] allIdentifiers = GetIdentifiers(targetName, foundSymbol.SymbolType, aliases); + string[] allIdentifiers = GetIdentifiers(targetName, symbol.SymbolType, aliases); foreach (ScriptFile file in _workspaceService.GetOpenedFiles()) { foreach (string targetIdentifier in allIdentifiers) { - if (!file.References.TryGetReferences(targetIdentifier, out ConcurrentBag references)) + await Task.Yield(); + cancellationToken.ThrowIfCancellationRequested(); + + if (!file.References.TryGetReferences(targetIdentifier, out ConcurrentBag? references)) { continue; } - symbolReferences.AddRange(references); - - // This async method is pretty dense with synchronous code - // so it's helpful to add some yields. - await Task.Yield(); - cancellationToken.ThrowIfCancellationRequested(); + symbols.AddRange(references); } } - return symbolReferences; + return symbols; } /// - /// Finds all the occurrences of a symbol in the script given a file location + /// Finds all the occurrences of a symbol in the script given a file location. /// - /// The details and contents of a open script file - /// The line number of the cursor for the given script - /// The column number of the cursor for the given script - /// FindOccurrencesResult - public static IEnumerable FindOccurrencesInFile( - ScriptFile file, - int symbolLineNumber, - int symbolColumnNumber) + public static IEnumerable? FindOccurrencesInFile( + ScriptFile scriptFile, int line, int column) { - SymbolReference foundSymbol = AstOperations.FindSymbolAtPosition( - file.ScriptAst, - symbolLineNumber, - symbolColumnNumber); + SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column); - if (foundSymbol == null) + if (symbol is null) { return null; } - if (file.References.TryGetReferences(foundSymbol.SymbolName, out ConcurrentBag references)) - { - return references; - } - - return null; + scriptFile.References.TryGetReferences(symbol.SymbolName, out ConcurrentBag? references); + return references; } /// - /// Finds a function, class or enum definition in the script given a file location + /// Finds the symbol at the location and returns it if it's a declaration. /// - /// The details and contents of a open script file - /// The line number of the cursor for the given script - /// The column number of the cursor for the given script - /// A SymbolReference of the symbol found at the given location - /// or null if there is no symbol at that location - /// - public static SymbolReference FindSymbolDefinitionAtLocation( - ScriptFile scriptFile, - int lineNumber, - int columnNumber) + public static SymbolReference? FindSymbolDefinitionAtLocation( + ScriptFile scriptFile, int line, int column) { - SymbolReference symbolReference = - AstOperations.FindSymbolAtPosition( - scriptFile.ScriptAst, - lineNumber, - columnNumber, - includeDefinitions: true); - - if (symbolReference != null) - { - symbolReference.FilePath = scriptFile.FilePath; - } - - return symbolReference; + SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column); + return symbol?.IsDeclaration == true ? symbol : null; } /// /// Finds the details of the symbol at the given script file location. /// - /// The ScriptFile in which the symbol can be located. - /// The line number at which the symbol can be located. - /// The column number at which the symbol can be located. - /// - public Task FindSymbolDetailsAtLocationAsync( - ScriptFile scriptFile, - int lineNumber, - int columnNumber) + public Task FindSymbolDetailsAtLocationAsync( + ScriptFile scriptFile, int line, int column) { - SymbolReference symbolReference = - AstOperations.FindSymbolAtPosition( - scriptFile.ScriptAst, - lineNumber, - columnNumber, - returnFullSignature: true); - - if (symbolReference == null) + SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column); + if (symbol is null) { - return Task.FromResult(null); + return Task.FromResult(null); } - symbolReference.FilePath = scriptFile.FilePath; return SymbolDetails.CreateAsync( - symbolReference, + symbol, _runspaceContext.CurrentRunspace, _executionService); } @@ -317,25 +251,15 @@ public Task FindSymbolDetailsAtLocationAsync( /// /// Finds the parameter set hints of a specific command (determined by a given file location) /// - /// The details and contents of a open script file - /// The line number of the cursor for the given script - /// The column number of the cursor for the given script - /// ParameterSetSignatures - public async Task FindParameterSetsInFileAsync( - ScriptFile file, - int lineNumber, - int columnNumber) + public async Task FindParameterSetsInFileAsync( + ScriptFile scriptFile, int line, int column) { - SymbolReference foundSymbol = - AstOperations.FindCommandAtPosition( - file.ScriptAst, - lineNumber, - columnNumber); + SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column); // If we are not possibly looking at a Function, we don't // need to continue because we won't be able to get the // CommandInfo object. - if (foundSymbol?.SymbolType is not SymbolType.Function + if (symbol?.SymbolType is not SymbolType.Function and not SymbolType.Unknown) { return null; @@ -343,11 +267,11 @@ public async Task FindParameterSetsInFileAsync( CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( - foundSymbol.SymbolName, + symbol.SymbolName, _runspaceContext.CurrentRunspace, _executionService).ConfigureAwait(false); - if (commandInfo == null) + if (commandInfo is null) { return null; } @@ -355,7 +279,7 @@ await CommandHelpers.GetCommandInfoAsync( try { IEnumerable commandParamSets = commandInfo.ParameterSets; - return new ParameterSetSignatures(commandParamSets, foundSymbol); + return new ParameterSetSignatures(commandParamSets, symbol); } catch (RuntimeException e) { @@ -376,19 +300,14 @@ await CommandHelpers.GetCommandInfoAsync( } /// - /// Finds the definition of a symbol in the script file or any of the - /// files that it references. + /// Finds the possible definitions of the symbol in the file or workspace. /// - /// The initial script file to be searched for the symbol's definition. - /// The symbol for which a definition will be found. - /// - /// The resulting GetDefinitionResult for the symbol's definition. public async Task> GetDefinitionOfSymbolAsync( - ScriptFile file, + ScriptFile scriptFile, SymbolReference symbol, CancellationToken cancellationToken = default) { - if (file.References.TryGetReferences(symbol.SymbolName, out ConcurrentBag symbols)) + if (scriptFile.References.TryGetReferences(symbol.SymbolName, out ConcurrentBag? symbols)) { IEnumerable possibleLocalDeclarations = symbols.Where((i) => i.IsDeclaration); if (possibleLocalDeclarations.Any()) @@ -398,7 +317,7 @@ public async Task> GetDefinitionOfSymbolAsync( } } - IEnumerable allSymbols = await ScanForReferencesOfSymbol( + IEnumerable? allSymbols = await ScanForReferencesOfSymbolAsync( symbol, cancellationToken).ConfigureAwait(false); @@ -411,8 +330,6 @@ public async Task> GetDefinitionOfSymbolAsync( // TODO: Fix "definition" of dot-source (maybe?) } - private Task _workspaceScanCompleted; - private async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = default) { if (_configurationService.CurrentSettings.AnalyzeOpenDocumentsOnly) @@ -420,7 +337,7 @@ private async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = de return; } - Task scanTask = _workspaceScanCompleted; + Task? scanTask = _workspaceScanCompleted; // It's not impossible for two scans to start at once but it should be exceedingly // unlikely, and shouldn't break anything if it happens to. So we can save some // lock time by accepting that possibility. @@ -460,25 +377,22 @@ private async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = de // TODO: There's a new API in net6 that lets you await a task with a cancellation token. // we should #if that in if feasible. TaskCompletionSource cancelled = new(); - cancellationToken.Register(() => cancelled.TrySetCanceled()); - await Task.WhenAny(scanTask, cancelled.Task).ConfigureAwait(false); + _ = cancellationToken.Register(() => cancelled.TrySetCanceled()); + _ = await Task.WhenAny(scanTask, cancelled.Task).ConfigureAwait(false); } /// /// Finds a function definition that follows or contains the given line number. /// - /// Open script file. - /// The 1 based line on which to look for function definition. - /// - /// If found, returns the function definition, otherwise, returns null. - public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment( + public static FunctionDefinitionAst? GetFunctionDefinitionForHelpComment( ScriptFile scriptFile, - int lineNumber, - out string helpLocation) + int line, + out string? helpLocation) { + Validate.IsNotNull(nameof(scriptFile), scriptFile); // check if the next line contains a function definition - FunctionDefinitionAst funcDefnAst = GetFunctionDefinitionAtLine(scriptFile, lineNumber + 1); - if (funcDefnAst != null) + FunctionDefinitionAst? funcDefnAst = GetFunctionDefinitionAtLine(scriptFile, line + 1); + if (funcDefnAst is not null) { helpLocation = "before"; return funcDefnAst; @@ -493,8 +407,8 @@ public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment( return false; } - return fdAst.Body.Extent.StartLineNumber < lineNumber && - fdAst.Body.Extent.EndLineNumber > lineNumber; + return fdAst.Body.Extent.StartLineNumber < line && + fdAst.Body.Extent.EndLineNumber > line; }, true); @@ -508,7 +422,7 @@ public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment( // definition that contains `lineNumber` foreach (FunctionDefinitionAst foundAst in foundAsts.Cast()) { - if (funcDefnAst == null) + if (funcDefnAst is null) { funcDefnAst = foundAst; continue; @@ -521,14 +435,14 @@ public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment( } } - // TODO use tokens to check for non empty character instead of just checking for line offset - if (funcDefnAst.Body.Extent.StartLineNumber == lineNumber - 1) + // TODO: use tokens to check for non empty character instead of just checking for line offset + if (funcDefnAst?.Body.Extent.StartLineNumber == line - 1) { helpLocation = "begin"; return funcDefnAst; } - if (funcDefnAst.Body.Extent.EndLineNumber == lineNumber + 1) + if (funcDefnAst?.Body.Extent.EndLineNumber == line + 1) { helpLocation = "end"; return funcDefnAst; @@ -541,11 +455,9 @@ public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment( /// /// Gets the function defined on a given line. + /// TODO: Remove this. /// - /// Open script file. - /// The 1 based line on which to look for function definition. - /// If found, returns the function definition on the given line. Otherwise, returns null. - public static FunctionDefinitionAst GetFunctionDefinitionAtLine( + public static FunctionDefinitionAst? GetFunctionDefinitionAtLine( ScriptFile scriptFile, int lineNumber) { @@ -560,7 +472,7 @@ internal void OnConfigurationUpdated(object _, LanguageServerSettings e) { if (e.AnalyzeOpenDocumentsOnly) { - Task scanInProgress = _workspaceScanCompleted; + Task? scanInProgress = _workspaceScanCompleted; if (scanInProgress is not null) { // Wait until after the scan completes to close unopened files. diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 7ccc3dc8c..93b8fd924 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Linq.Expressions; using System.Management.Automation; using System.Management.Automation.Language; @@ -141,58 +140,6 @@ await executionService.ExecuteDelegateAsync( return commandCompletion; } - /// - /// Finds the symbol at a given file location - /// - /// The abstract syntax tree of the given script - /// The line number of the cursor for the given script - /// The column number of the cursor for the given script - /// Includes full symbol definition ranges in the search. - /// Includes return or property type in symbol name. - /// SymbolReference of found symbol - public static SymbolReference FindSymbolAtPosition( - Ast scriptAst, - int lineNumber, - int columnNumber, - bool includeDefinitions = false, - bool returnFullSignature = false) - { - FindSymbolVisitor symbolVisitor = new( - lineNumber, - columnNumber, - includeDefinitions, - returnFullSignature); - - scriptAst.Visit(symbolVisitor); - return symbolVisitor.FoundSymbolReference; - } - - /// - /// Finds the symbol (always Command type) at a given file location - /// - /// The abstract syntax tree of the given script - /// The line number of the cursor for the given script - /// The column number of the cursor for the given script - /// SymbolReference of found command - public static SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumber, int columnNumber) - { - FindCommandVisitor commandVisitor = new(lineNumber, columnNumber); - scriptAst.Visit(commandVisitor); - return commandVisitor.FoundCommandReference; - } - - /// - /// Finds all files dot sourced in a script - /// - /// The abstract syntax tree of the given script - /// Pre-calculated value of $PSScriptRoot - public static string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot) - { - FindDotSourcedVisitor dotSourcedVisitor = new(psScriptRoot); - scriptAst.Visit(dotSourcedVisitor); - return dotSourcedVisitor.DotSourcedFiles.ToArray(); - } - internal static bool TryGetInferredValue(ExpandableStringExpressionAst expandableStringExpressionAst, out string value) { // Currently we only support inferring the value of `$PSScriptRoot`. We could potentially diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindCommandVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindCommandVisitor.cs deleted file mode 100644 index 4a7b78faa..000000000 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindCommandVisitor.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Linq; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell.EditorServices.Services.Symbols -{ - /// - /// The vistior used to find the commandAst of a specific location in an AST - /// - internal class FindCommandVisitor : AstVisitor - { - private readonly int lineNumber; - private readonly int columnNumber; - - public SymbolReference FoundCommandReference { get; private set; } - - public FindCommandVisitor(int lineNumber, int columnNumber) - { - this.lineNumber = lineNumber; - this.columnNumber = columnNumber; - } - - public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) - { - if (lineNumber == pipelineAst.Extent.StartLineNumber) - { - // Which command is the cursor in? - foreach (CommandAst commandAst in pipelineAst.PipelineElements.OfType()) - { - int trueEndColumnNumber = commandAst.Extent.EndColumnNumber; - string currentLine = commandAst.Extent.StartScriptPosition.Line; - - if (currentLine.Length >= trueEndColumnNumber) - { - // Get the text left in the line after the command's extent - string remainingLine = - currentLine.Substring( - commandAst.Extent.EndColumnNumber); - - // Calculate the "true" end column number by finding out how many - // whitespace characters are between this command and the next (or - // the end of the line). - // NOTE: +1 is added to trueEndColumnNumber to account for the position - // just after the last character in the command string or script line. - int preTrimLength = remainingLine.Length; - int postTrimLength = remainingLine.TrimStart().Length; - trueEndColumnNumber = - commandAst.Extent.EndColumnNumber + - (preTrimLength - postTrimLength) + 1; - } - - if (commandAst.Extent.StartColumnNumber <= columnNumber && - trueEndColumnNumber >= columnNumber) - { - FoundCommandReference = - new SymbolReference( - SymbolType.Function, - commandAst.CommandElements[0].Extent); - - return AstVisitAction.StopVisit; - } - } - } - - return base.VisitPipeline(pipelineAst); - } - } -} diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDotSourcedVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDotSourcedVisitor.cs deleted file mode 100644 index 416a5d611..000000000 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDotSourcedVisitor.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.Symbols -{ - /// - /// The visitor used to find the dot-sourced files in an AST - /// - internal class FindDotSourcedVisitor : AstVisitor - { - private readonly string _psScriptRoot; - - /// - /// A hash set of the dot sourced files (because we don't want duplicates) - /// - public HashSet DotSourcedFiles { get; } - - /// - /// Creates a new instance of the FindDotSourcedVisitor class. - /// - /// Pre-calculated value of $PSScriptRoot - public FindDotSourcedVisitor(string psScriptRoot) - { - DotSourcedFiles = new HashSet(StringComparer.CurrentCultureIgnoreCase); - _psScriptRoot = psScriptRoot; - } - - /// - /// Checks to see if the command invocation is a dot - /// in order to find a dot sourced file - /// - /// A CommandAst object in the script's AST - /// A decision to stop searching if the right commandAst was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitCommand(CommandAst commandAst) - { - CommandElementAst commandElementAst = commandAst.CommandElements[0]; - if (commandAst.InvocationOperator.Equals(TokenKind.Dot)) - { - string path = commandElementAst switch - { - StringConstantExpressionAst stringConstantExpressionAst => stringConstantExpressionAst.Value, - ExpandableStringExpressionAst expandableStringExpressionAst => GetPathFromExpandableStringExpression(expandableStringExpressionAst), - _ => null, - }; - if (!string.IsNullOrWhiteSpace(path)) - { - DotSourcedFiles.Add(PathUtils.NormalizePathSeparators(path)); - } - } - - return base.VisitCommand(commandAst); - } - - private string GetPathFromExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) - { - string path = expandableStringExpressionAst.Value; - foreach (ExpressionAst nestedExpression in expandableStringExpressionAst.NestedExpressions) - { - // If the string contains the variable $PSScriptRoot, we replace it with the corresponding value. - if (!(nestedExpression is VariableExpressionAst variableAst - && variableAst.VariablePath.UserPath.Equals("PSScriptRoot", StringComparison.OrdinalIgnoreCase))) - { - return null; // We return null instead of a partially evaluated ExpandableStringExpression. - } - - path = path.Replace(variableAst.ToString(), _psScriptRoot); - } - - return path; - } - } -} diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs deleted file mode 100644 index 934689692..000000000 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Management.Automation.Language; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.Symbols -{ - /// - /// The visitor used to find the symbol at a specific location in the AST - /// TODO: Re-use the ReferenceVisitor. - /// - internal class FindSymbolVisitor : AstVisitor2 - { - private readonly int lineNumber; - private readonly int columnNumber; - private readonly bool includeDefinitions; - private readonly bool returnFullSignature; - - public SymbolReference FoundSymbolReference { get; private set; } - - public FindSymbolVisitor( - int lineNumber, - int columnNumber, - bool includeDefinitions, - bool returnFullSignature) - { - this.lineNumber = lineNumber; - this.columnNumber = columnNumber; - this.includeDefinitions = includeDefinitions; - this.returnFullSignature = returnFullSignature; - } - - /// - /// Checks to see if this command ast is the symbol we are looking for. - /// - /// A CommandAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitCommand(CommandAst commandAst) - { - Ast commandNameAst = commandAst.CommandElements[0]; - - string name; - if (returnFullSignature) - { - name = commandNameAst.Extent.Text; - } - else - { - string commandName = VisitorUtils.GetCommandName(commandAst); - name = CommandHelpers.StripModuleQualification(commandName, out _); - } - - if (IsPositionInExtent(commandNameAst.Extent)) - { - FoundSymbolReference = - new SymbolReference( - SymbolType.Function, - name, - commandNameAst.Extent); - - return AstVisitAction.StopVisit; - } - - return base.VisitCommand(commandAst); - } - - /// - /// Checks to see if this function definition is the symbol we are looking for. - /// - /// A functionDefinitionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) - { - // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter. - // This will not exclude nested functions as they have ScriptBlockAst as parent - if (functionDefinitionAst.Parent is FunctionMemberAst) - { - return AstVisitAction.Continue; - } - - IScriptExtent nameExtent; - - if (includeDefinitions) - { - nameExtent = new ScriptExtent() - { - Text = functionDefinitionAst.Name, - StartLineNumber = functionDefinitionAst.Extent.StartLineNumber, - EndLineNumber = functionDefinitionAst.Extent.EndLineNumber, - StartColumnNumber = functionDefinitionAst.Extent.StartColumnNumber, - EndColumnNumber = functionDefinitionAst.Extent.EndColumnNumber, - File = functionDefinitionAst.Extent.File - }; - } - else - { - // We only want the function name - nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); - } - - if (nameExtent.Contains(lineNumber, columnNumber)) - { - FoundSymbolReference = - new SymbolReference( - SymbolType.Function, - functionDefinitionAst.Name, - nameExtent); - - return AstVisitAction.StopVisit; - } - - return base.VisitFunctionDefinition(functionDefinitionAst); - } - - /// - /// Checks to see if this command parameter is the symbol we are looking for. - /// - /// A CommandParameterAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) - { - if (IsPositionInExtent(commandParameterAst.Extent)) - { - FoundSymbolReference = - new SymbolReference( - SymbolType.Parameter, - commandParameterAst.Extent.Text, - commandParameterAst.Extent); - return AstVisitAction.StopVisit; - } - return AstVisitAction.Continue; - } - - /// - /// Checks to see if this variable expression is the symbol we are looking for. - /// - /// A VariableExpressionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) - { - if (IsPositionInExtent(variableExpressionAst.Extent)) - { - FoundSymbolReference = - new SymbolReference( - SymbolType.Variable, - returnFullSignature ? variableExpressionAst.Extent.Text : $"${variableExpressionAst.VariablePath.UserPath}", - variableExpressionAst.Extent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - - /// - /// Is the position of the given location is in the ast's extent. - /// Only works with single-line extents like name extents. - /// Use extension for definition extents. - /// - /// The script extent of the element - /// True if the given position is in the range of the element's extent - private bool IsPositionInExtent(IScriptExtent extent) - { - return extent.StartLineNumber == lineNumber && - extent.StartColumnNumber <= columnNumber && - extent.EndColumnNumber >= columnNumber; - } - - /// - /// Checks to see if this function member is the symbol we are looking for. - /// - /// A FunctionMemberAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) - { - // We only want the method/ctor name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst, true, returnFullSignature); - - if (IsPositionInExtent(nameExtent)) - { - SymbolType symbolType = - functionMemberAst.IsConstructor ? - SymbolType.Constructor : SymbolType.Method; - - FoundSymbolReference = - new SymbolReference( - symbolType, - returnFullSignature ? nameExtent.Text : VisitorUtils.GetMemberOverloadName(functionMemberAst, useQualifiedName: false, includeReturnType: true), - nameExtent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - - /// - /// Checks to see if this type definition is the symbol we are looking for. - /// - /// A TypeDefinitionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) - { - IScriptExtent nameExtent; - - if (includeDefinitions) - { - nameExtent = new ScriptExtent() - { - Text = typeDefinitionAst.Name, - StartLineNumber = typeDefinitionAst.Extent.StartLineNumber, - EndLineNumber = typeDefinitionAst.Extent.EndLineNumber, - StartColumnNumber = typeDefinitionAst.Extent.StartColumnNumber, - EndColumnNumber = typeDefinitionAst.Extent.EndColumnNumber, - File = typeDefinitionAst.Extent.File - }; - } - else - { - // We only want the type name - nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); - } - - if (nameExtent.Contains(lineNumber, columnNumber)) - { - SymbolType symbolType = - typeDefinitionAst.IsEnum ? - SymbolType.Enum : SymbolType.Class; - - FoundSymbolReference = - new SymbolReference( - symbolType, - returnFullSignature ? nameExtent.Text : typeDefinitionAst.Name, - nameExtent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - - /// - /// Checks to see if this type expression is the symbol we are looking for. - /// - /// A TypeExpressionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) - { - // Show only type name (skip leading '['). Offset by StartColumn to include indentation etc. - int startColumnNumber = typeExpressionAst.Extent.StartColumnNumber + 1; - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = typeExpressionAst.TypeName.Name, - StartLineNumber = typeExpressionAst.Extent.StartLineNumber, - EndLineNumber = typeExpressionAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + typeExpressionAst.TypeName.Name.Length, - File = typeExpressionAst.Extent.File - }; - - if (IsPositionInExtent(nameExtent)) - { - FoundSymbolReference = - new SymbolReference( - SymbolType.Type, - returnFullSignature ? nameExtent.Text : typeExpressionAst.TypeName.Name, - nameExtent); - return AstVisitAction.StopVisit; - } - return AstVisitAction.Continue; - } - - /// - /// Checks to see if this type constraint is the symbol we are looking for. - /// - /// A TypeConstraintAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) - { - // Show only type name (skip leading '[' if present). It's not present for inherited types - // Offset by StartColumn to include indentation etc. - int startColumnNumber = - typeConstraintAst.Extent.Text[0] == '[' ? - typeConstraintAst.Extent.StartColumnNumber + 1 : typeConstraintAst.Extent.StartColumnNumber; - - IScriptExtent nameExtent = new ScriptExtent() - { - Text = typeConstraintAst.TypeName.Name, - StartLineNumber = typeConstraintAst.Extent.StartLineNumber, - EndLineNumber = typeConstraintAst.Extent.StartLineNumber, - StartColumnNumber = startColumnNumber, - EndColumnNumber = startColumnNumber + typeConstraintAst.TypeName.Name.Length, - File = typeConstraintAst.Extent.File - }; - - if (IsPositionInExtent(nameExtent)) - { - FoundSymbolReference = - new SymbolReference( - SymbolType.Type, - returnFullSignature ? nameExtent.Text : typeConstraintAst.TypeName.Name, - nameExtent); - return AstVisitAction.StopVisit; - } - return AstVisitAction.Continue; - } - - /// - /// Checks to see if this configuration definition is the symbol we are looking for. - /// - /// A ConfigurationDefinitionAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) - { - // We only want the configuration name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(configurationDefinitionAst); - - if (IsPositionInExtent(nameExtent)) - { - FoundSymbolReference = - new SymbolReference( - SymbolType.Configuration, - nameExtent.Text, - nameExtent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - - /// - /// Checks to see if this property member is the symbol we are looking for. - /// - /// A PropertyMemberAst object in the script's AST - /// A decision to stop searching if the right symbol was found, - /// or a decision to continue if it wasn't found - public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) - { - // We only want the property name. Get start-location for name - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, returnFullSignature, returnFullSignature); - - if (IsPositionInExtent(nameExtent)) - { - SymbolType symbolType = - propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? - SymbolType.EnumMember : SymbolType.Property; - - FoundSymbolReference = - new SymbolReference( - symbolType, - returnFullSignature - ? nameExtent.Text - : VisitorUtils.GetMemberOverloadName(propertyMemberAst, useQualifiedName: false), - nameExtent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst) - { - string memberName = memberExpressionAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null; - if (string.IsNullOrEmpty(memberName)) - { - return AstVisitAction.Continue; - } - - if (IsPositionInExtent(memberExpressionAst.Member.Extent)) - { - FoundSymbolReference = - new SymbolReference( - SymbolType.Property, - returnFullSignature - ? memberExpressionAst.Member.Extent.Text - : memberName, - memberExpressionAst.Member.Extent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) - { - string? memberName = methodCallAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null; - if (string.IsNullOrEmpty(memberName)) - { - return AstVisitAction.Continue; - } - - if (IsPositionInExtent(methodCallAst.Member.Extent)) - { - FoundSymbolReference = - new SymbolReference( - SymbolType.Method, - returnFullSignature - ? methodCallAst.Member.Extent.Text - : memberName, - methodCallAst.Member.Extent); - - return AstVisitAction.StopVisit; - } - - return AstVisitAction.Continue; - } - } -} diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index fca46b5e7..ff5d5c39e 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -66,6 +66,9 @@ public override async Task Handle(Do cancellationToken.ThrowIfCancellationRequested(); // Outline view should only include declarations. + // + // TODO: We should also include function invocations that are part of DSLs (like + // Invoke-Build etc.). if (!r.IsDeclaration) { continue; diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index 97a911172..41b09f29e 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -42,7 +42,7 @@ public override async Task Handle(ReferenceParams request, Ca request.Position.Character + 1); IEnumerable referencesResult = - await _symbolsService.ScanForReferencesOfSymbol( + await _symbolsService.ScanForReferencesOfSymbolAsync( foundSymbol, cancellationToken).ConfigureAwait(false); if (referencesResult is null) diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs index 5d12077f9..138c6bbe4 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs @@ -33,6 +33,56 @@ public Range ToRange() }; } + // Same as PowerShell's EmptyScriptExtent + public bool IsEmpty() + { + return StartLineNumber == 0 && StartColumnNumber == 0 + && EndLineNumber == 0 && EndColumnNumber == 0 + && string.IsNullOrEmpty(File) + && string.IsNullOrEmpty(Text); + } + + // Same as PowerShell's ContainsLineAndColumn + public bool ContainsPosition(int line, int column) + { + if (StartLineNumber == line) + { + if (column == 0) + { + return true; + } + + if (column >= StartColumnNumber) + { + if (EndLineNumber != StartLineNumber) + { + return true; + } + + return column < EndColumnNumber; + } + + return false; + } + + if (StartLineNumber > line) + { + return false; + } + + if (line > EndLineNumber) + { + return false; + } + + if (EndLineNumber == line) + { + return column < EndColumnNumber; + } + + return true; + } + public override string ToString() => $"Start {StartLineNumber}:{StartColumnNumber}, End {EndLineNumber}:{EndColumnNumber}"; #region Constructors diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 153d3b5d8..2fa819360 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -109,7 +109,7 @@ private async Task> GetReferences(ScriptRegion scriptRegio Assert.NotNull(symbol); IEnumerable symbols = - await symbolsService.ScanForReferencesOfSymbol(symbol).ConfigureAwait(true); + await symbolsService.ScanForReferencesOfSymbolAsync(symbol).ConfigureAwait(true); return symbols.OrderBy(i => i.ScriptRegion.ToRange().Start).ToList(); } diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs index 157caacd9..4d73a9e57 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs @@ -33,24 +33,18 @@ public AstOperationsTests() [InlineData(22, 13, "FunctionNameOnDifferentLine")] [InlineData(24, 30, "IndentedFunction")] [InlineData(24, 52, "IndentedFunction")] - public void CanFindSymbolAtPosition(int lineNumber, int columnNumber, string expectedName) + public void CanFindSymbolAtPosition(int line, int column, string expectedName) { - SymbolReference reference = AstOperations.FindSymbolAtPosition( - scriptFile.ScriptAst, - lineNumber, - columnNumber); - Assert.NotNull(reference); - Assert.Equal(expectedName, reference.SymbolName); + SymbolReference symbol = scriptFile.References.TryGetSymbolAtPosition(line, column); + Assert.NotNull(symbol); + Assert.Equal(expectedName, symbol.SymbolName); } [Theory] [MemberData(nameof(FindReferencesOfSymbolAtPositionData))] - public void CanFindReferencesOfSymbolAtPosition(int lineNumber, int columnNumber, Range[] symbolRange) + public void CanFindReferencesOfSymbolAtPosition(int line, int column, Range[] symbolRange) { - SymbolReference symbol = AstOperations.FindSymbolAtPosition( - scriptFile.ScriptAst, - lineNumber, - columnNumber); + SymbolReference symbol = scriptFile.References.TryGetSymbolAtPosition(line, column); Assert.True(scriptFile.References.TryGetReferences( symbol.SymbolName, From 9a857ea5bfa990ab39cc02acd8ec9580520acc07 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 24 Jan 2023 12:07:03 -0800 Subject: [PATCH 188/327] Replace `VisitCommandParameter` with `VisitParameter` --- .../Services/Symbols/ReferenceTable.cs | 3 ++- .../Services/Symbols/Vistors/SymbolVisitor.cs | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 8bbb0359a..48ecb3a8f 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -90,6 +90,7 @@ private AstVisitAction AddReference(SymbolReference symbol) return existing; }); - return AstVisitAction.Continue; + return symbol.SymbolType is SymbolType.Parameter + ? AstVisitAction.SkipChildren : AstVisitAction.Continue; } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/SymbolVisitor.cs index 01eb093ee..83b97dcdf 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/SymbolVisitor.cs @@ -68,13 +68,14 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun isDeclaration: true)); } - public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) + public override AstVisitAction VisitParameter(ParameterAst parameterAst) { + // TODO: When we add DisplayString, include the default value. return _action(new SymbolReference( SymbolType.Parameter, - commandParameterAst.Extent.Text, - commandParameterAst.Extent, - commandParameterAst.Extent, + $"${parameterAst.Name.VariablePath.UserPath}", + parameterAst.Name.Extent, + parameterAst.Extent, _file, isDeclaration: true)); } From e5cf5bd395af0f8745cf4dfd02a731f1cde44192 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 24 Jan 2023 12:20:34 -0800 Subject: [PATCH 189/327] Mark Pester symbols as declarations --- .../Services/Symbols/PesterDocumentSymbolProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index 8730ed081..f41df34cc 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -229,7 +229,8 @@ internal PesterSymbolReference( testLine.TrimStart().TrimEnd(DefinitionTrimChars), scriptExtent, scriptFile.FilePath, - testLine) + testLine, + isDeclaration: true) { Command = commandType; TestName = testName; From 8c2c9c46d7b90327ecfd76960fb28bdbb3921aa4 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 24 Jan 2023 13:56:10 -0800 Subject: [PATCH 190/327] Clean up `SymbolReference` constructors --- .../CodeLens/PesterCodeLensProvider.cs | 4 +-- .../Symbols/PesterDocumentSymbolProvider.cs | 20 ++++++----- .../Symbols/PsdDocumentSymbolProvider.cs | 2 +- .../Services/Symbols/SymbolReference.cs | 34 ++----------------- .../Symbols/Vistors/FindSymbolsVisitor.cs | 21 +++++++++--- 5 files changed, 32 insertions(+), 49 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs index 5eb3830ee..50ddcfc22 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs @@ -46,7 +46,7 @@ public PesterCodeLensProvider(ConfigurationService configurationService) private static CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, ScriptFile scriptFile) { string word = pesterSymbol.Command == PesterCommandType.It ? "test" : "tests"; - CodeLens[] codeLensResults = new CodeLens[] + return new CodeLens[] { new CodeLens() { @@ -91,8 +91,6 @@ private static CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, Scri } } }; - - return codeLensResults; } /// diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index f41df34cc..e60a3b279 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -75,6 +75,8 @@ private static bool IsPesterCommand(CommandAst commandAst) return true; } + private static readonly char[] DefinitionTrimChars = new char[] { ' ', '{' }; + /// /// Convert a CommandAst known to represent a Pester command and a reference to the scriptfile /// it is in into symbol representing a Pester call for code lens @@ -84,7 +86,11 @@ private static bool IsPesterCommand(CommandAst commandAst) /// a symbol representing the Pester call containing metadata for CodeLens to use private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFile scriptFile, CommandAst pesterCommandAst) { - string testLine = scriptFile.GetLine(pesterCommandAst.Extent.StartLineNumber); + string symbolName = scriptFile + .GetLine(pesterCommandAst.Extent.StartLineNumber) + .TrimStart() + .TrimEnd(DefinitionTrimChars); + PesterCommandType? commandName = PesterSymbolReference.GetCommandType(pesterCommandAst.GetCommandName()); if (commandName == null) { @@ -126,7 +132,7 @@ private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFil return new PesterSymbolReference( scriptFile, commandName.Value, - testLine, + symbolName, testName, pesterCommandAst.Extent ); @@ -206,8 +212,6 @@ internal record PesterSymbolReference : SymbolReference .Cast() .ToDictionary(pct => pct.ToString(), pct => pct, StringComparer.OrdinalIgnoreCase); - private static readonly char[] DefinitionTrimChars = new char[] { ' ', '{' }; - /// /// Gets the name of the test /// @@ -221,15 +225,15 @@ internal record PesterSymbolReference : SymbolReference internal PesterSymbolReference( ScriptFile scriptFile, PesterCommandType commandType, - string testLine, + string symbolName, string testName, IScriptExtent scriptExtent) : base( SymbolType.Function, - testLine.TrimStart().TrimEnd(DefinitionTrimChars), + symbolName, + scriptExtent, scriptExtent, - scriptFile.FilePath, - testLine, + scriptFile, isDeclaration: true) { Command = commandType; diff --git a/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs index 567887d66..a0a032578 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs @@ -23,7 +23,7 @@ IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( if ((scriptFile.FilePath?.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase) == true) || IsPowerShellDataFileAst(scriptFile.ScriptAst)) { - FindHashtableSymbolsVisitor findHashtableSymbolsVisitor = new(); + FindHashtableSymbolsVisitor findHashtableSymbolsVisitor = new(scriptFile); scriptFile.ScriptAst.Visit(findHashtableSymbolsVisitor); return findHashtableSymbolsVisitor.SymbolReferences; } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index db60d5482..8be22a3da 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -33,32 +33,13 @@ internal record SymbolReference /// /// Constructs and instance of a SymbolReference - /// TODO: Remove this when it becomes unused. /// /// The higher level type of the symbol /// The name of the symbol + /// The extent of the symbol's name /// The script extent of the symbol - /// The file path of the symbol - /// The line contents of the given symbol (defaults to empty string) + /// The script file that has the symbol /// True if this reference is the definition of the symbol - public SymbolReference( - SymbolType symbolType, - string symbolName, - IScriptExtent scriptExtent, - string filePath = "", - string sourceLine = "", - bool isDeclaration = false) - { - // TODO: Verify params - SymbolType = symbolType; - SymbolName = symbolName; - ScriptRegion = new(scriptExtent); - NameRegion = ScriptRegion; - FilePath = string.IsNullOrEmpty(filePath) ? scriptExtent.File : filePath; - SourceLine = sourceLine; - IsDeclaration = isDeclaration; - } - public SymbolReference( SymbolType symbolType, string symbolName, @@ -82,16 +63,5 @@ public SymbolReference( } IsDeclaration = isDeclaration; } - - /// - /// Constructs an instance of a SymbolReference. - /// TODO: Remove this when it becomes unused. - /// - /// The higher level type of the symbol - /// The script extent of the symbol - public SymbolReference(SymbolType symbolType, IScriptExtent scriptExtent) - : this(symbolType, scriptExtent.Text, scriptExtent, scriptExtent.File) - { - } } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs index aea76642a..cc6cc68f0 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { @@ -13,6 +14,8 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols /// internal class FindHashtableSymbolsVisitor : AstVisitor { + private readonly ScriptFile _file; + /// /// List of symbols (keys) found in the hashtable /// @@ -21,7 +24,11 @@ internal class FindHashtableSymbolsVisitor : AstVisitor /// /// Initializes a new instance of FindHashtableSymbolsVisitor class /// - public FindHashtableSymbolsVisitor() => SymbolReferences = new List(); + public FindHashtableSymbolsVisitor(ScriptFile file) + { + SymbolReferences = new List(); + _file = file; + } /// /// Adds keys in the input hashtable to the symbol reference @@ -49,12 +56,16 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) File = hashtableAst.Extent.File }; - const SymbolType symbolType = SymbolType.HashtableKey; - SymbolReferences.Add( new SymbolReference( - symbolType, - nameExtent)); + SymbolType.HashtableKey, + nameExtent.Text, + nameExtent, + // TODO: Should this be more? + nameExtent, + _file, + // TODO: Should this be true? + isDeclaration: false)); } } From cee70336f770cff90668529ab0566bd42c49a015 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 24 Jan 2023 13:57:48 -0800 Subject: [PATCH 191/327] Fix file names for visitors --- .../Services/Symbols/{Vistors => Visitors}/AstOperations.cs | 0 .../FindSymbolsVisitor.cs => Visitors/HashTableVisitor.cs} | 0 .../Services/Symbols/{Vistors => Visitors}/SymbolVisitor.cs | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/PowerShellEditorServices/Services/Symbols/{Vistors => Visitors}/AstOperations.cs (100%) rename src/PowerShellEditorServices/Services/Symbols/{Vistors/FindSymbolsVisitor.cs => Visitors/HashTableVisitor.cs} (100%) rename src/PowerShellEditorServices/Services/Symbols/{Vistors => Visitors}/SymbolVisitor.cs (100%) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/AstOperations.cs similarity index 100% rename from src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs rename to src/PowerShellEditorServices/Services/Symbols/Visitors/AstOperations.cs diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/HashTableVisitor.cs similarity index 100% rename from src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs rename to src/PowerShellEditorServices/Services/Symbols/Visitors/HashTableVisitor.cs diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs similarity index 100% rename from src/PowerShellEditorServices/Services/Symbols/Vistors/SymbolVisitor.cs rename to src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs From 6b093da6f905f7362a1fdd825321ae57382cae8c Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 24 Jan 2023 16:29:24 -0800 Subject: [PATCH 192/327] Clean up some `IEnumerables` It's so sad we can't use `IAsyncEnumerable`. --- .../CodeLens/ReferencesCodeLensProvider.cs | 61 +++++++------------ .../Services/Symbols/SymbolsService.cs | 59 ++++++++++-------- .../Handlers/DefinitionHandler.cs | 10 +-- .../Handlers/ReferencesHandler.cs | 12 +--- .../Services/Workspace/WorkspaceService.cs | 6 +- 5 files changed, 63 insertions(+), 85 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index c19a8c8e9..da717d5c3 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; using System.Text; using System.Threading; @@ -21,8 +20,6 @@ namespace Microsoft.PowerShell.EditorServices.CodeLenses /// internal class ReferencesCodeLensProvider : ICodeLensProvider { - private static readonly Location[] s_emptyLocationArray = Array.Empty(); - /// /// The document symbol provider to supply symbols to generate the code lenses. /// @@ -102,49 +99,33 @@ public async Task ResolveCodeLens( codeLens.Range.Start.Line + 1, codeLens.Range.Start.Character + 1); - IEnumerable referencesResult = - await _symbolsService.ScanForReferencesOfSymbolAsync( - foundSymbol, cancellationToken).ConfigureAwait(false); - - Location[] referenceLocations; - if (referencesResult == null) - { - referenceLocations = s_emptyLocationArray; - } - else + List acc = new(); + foreach (SymbolReference foundReference in await _symbolsService.ScanForReferencesOfSymbolAsync( + foundSymbol, cancellationToken).ConfigureAwait(false)) { - List acc = new(); - foreach (SymbolReference foundReference in referencesResult) + // We only show lenses on declarations, so we exclude those from the references. + if (foundReference.IsDeclaration) { - // This async method is pretty dense with synchronous code - // so it's helpful to add some yields. - await Task.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - - // We only show lenses on declarations, so we exclude those from the references. - if (foundReference.IsDeclaration) - { - continue; - } - - DocumentUri uri = DocumentUri.From(foundReference.FilePath); - // For any vscode-notebook-cell, we need to ignore the backing file on disk. - if (uri.Scheme == "file" && - scriptFile.DocumentUri.Scheme == "vscode-notebook-cell" && - uri.Path == scriptFile.DocumentUri.Path) - { - continue; - } + continue; + } - acc.Add(new Location - { - Uri = uri, - Range = foundReference.NameRegion.ToRange() - }); + DocumentUri uri = DocumentUri.From(foundReference.FilePath); + // For any vscode-notebook-cell, we need to ignore the backing file on disk. + if (uri.Scheme == "file" && + scriptFile.DocumentUri.Scheme == "vscode-notebook-cell" && + uri.Path == scriptFile.DocumentUri.Path) + { + continue; } - referenceLocations = acc.ToArray(); + + acc.Add(new Location + { + Uri = uri, + Range = foundReference.NameRegion.ToRange() + }); } + Location[] referenceLocations = acc.ToArray(); return new CodeLens { Data = codeLens.Data, diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 7a2352551..2318e0b7d 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -114,14 +114,13 @@ public IEnumerable FindSymbolsInFile(ScriptFile scriptFile) { Validate.IsNotNull(nameof(scriptFile), scriptFile); - List symbols = new(); foreach (IDocumentSymbolProvider symbolProvider in GetDocumentSymbolProviders()) { - // TODO: Each provider needs to set the source line and filepath. - symbols.AddRange(symbolProvider.ProvideDocumentSymbols(scriptFile)); + foreach (SymbolReference symbol in symbolProvider.ProvideDocumentSymbols(scriptFile)) + { + yield return symbol; + } } - - return symbols; } /// @@ -155,15 +154,15 @@ private static string[] GetIdentifiers(string symbolName, SymbolType symbolType, /// /// Finds all the references of a symbol in the workspace, resolving aliases. - /// TODO: Make it not return a nullable. + /// TODO: One day use IAsyncEnumerable. /// - public async Task?> ScanForReferencesOfSymbolAsync( + public async Task> ScanForReferencesOfSymbolAsync( SymbolReference symbol, CancellationToken cancellationToken = default) { if (symbol is null) { - return null; + return Enumerable.Empty(); } // TODO: Should we handle aliases at a lower level? @@ -181,7 +180,6 @@ private static string[] GetIdentifiers(string symbolName, SymbolType symbolType, await ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false); List symbols = new(); - string[] allIdentifiers = GetIdentifiers(targetName, symbol.SymbolType, aliases); foreach (ScriptFile file in _workspaceService.GetOpenedFiles()) @@ -191,7 +189,8 @@ private static string[] GetIdentifiers(string symbolName, SymbolType symbolType, await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); - if (!file.References.TryGetReferences(targetIdentifier, out ConcurrentBag? references)) + _ = file.References.TryGetReferences(targetIdentifier, out ConcurrentBag? references); + if (references is null) { continue; } @@ -301,33 +300,45 @@ await CommandHelpers.GetCommandInfoAsync( /// /// Finds the possible definitions of the symbol in the file or workspace. + /// TODO: One day use IAsyncEnumerable. + /// TODO: Fix searching for definition of built-in commands. + /// TODO: Fix "definition" of dot-source (maybe?) /// public async Task> GetDefinitionOfSymbolAsync( ScriptFile scriptFile, SymbolReference symbol, CancellationToken cancellationToken = default) { - if (scriptFile.References.TryGetReferences(symbol.SymbolName, out ConcurrentBag? symbols)) + List declarations = new(); + _ = scriptFile.References.TryGetReferences(symbol.SymbolName, out ConcurrentBag? symbols); + if (symbols is not null) { - IEnumerable possibleLocalDeclarations = symbols.Where((i) => i.IsDeclaration); - if (possibleLocalDeclarations.Any()) + foreach (SymbolReference foundReference in symbols) { - _logger.LogDebug($"Found possible declarations ${possibleLocalDeclarations}"); - return possibleLocalDeclarations; + if (foundReference.IsDeclaration) + { + _logger.LogDebug($"Found possible declaration in same file ${foundReference}"); + declarations.Add(foundReference); + } } } - IEnumerable? allSymbols = await ScanForReferencesOfSymbolAsync( - symbol, - cancellationToken).ConfigureAwait(false); - - IEnumerable possibleDeclarations = allSymbols.Where((i) => i.IsDeclaration); - _logger.LogDebug($"Found possible declarations ${possibleDeclarations}"); + if (declarations.Any()) + { + return declarations; + } - return possibleDeclarations; + foreach (SymbolReference foundReference in await ScanForReferencesOfSymbolAsync( + symbol, cancellationToken).ConfigureAwait(false)) + { + if (foundReference.IsDeclaration) + { + _logger.LogDebug($"Found possible declaration in workspace ${foundReference}"); + declarations.Add(foundReference); + } + } - // TODO: Fix searching for definition of built-in commands. - // TODO: Fix "definition" of dot-source (maybe?) + return declarations; } private async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = default) diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs index 94134ab6a..b1d272691 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs @@ -44,14 +44,10 @@ public override async Task Handle(DefinitionParams requ request.Position.Character + 1); List definitionLocations = new(); - if (foundSymbol != null) + if (foundSymbol is not null) { - IEnumerable foundDefinitions = await _symbolsService.GetDefinitionOfSymbolAsync( - scriptFile, - foundSymbol, - cancellationToken).ConfigureAwait(false); - - foreach (SymbolReference foundDefinition in foundDefinitions) + foreach (SymbolReference foundDefinition in await _symbolsService.GetDefinitionOfSymbolAsync( + scriptFile, foundSymbol, cancellationToken).ConfigureAwait(false)) { definitionLocations.Add( new LocationOrLocationLink( diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index 41b09f29e..154e7b35b 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -41,17 +41,9 @@ public override async Task Handle(ReferenceParams request, Ca request.Position.Line + 1, request.Position.Character + 1); - IEnumerable referencesResult = - await _symbolsService.ScanForReferencesOfSymbolAsync( - foundSymbol, cancellationToken).ConfigureAwait(false); - - if (referencesResult is null) - { - return new LocationContainer(); - } - List locations = new(); - foreach (SymbolReference foundReference in referencesResult) + foreach (SymbolReference foundReference in await _symbolsService.ScanForReferencesOfSymbolAsync( + foundSymbol, cancellationToken).ConfigureAwait(false)) { // Respect the request's setting to include declarations. if (!request.Context.IncludeDeclaration && foundReference.IsDeclaration) diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs index ab2bb6a11..bf5f2ce12 100644 --- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Security; using System.Text; using Microsoft.Extensions.FileSystemGlobbing; @@ -275,10 +274,9 @@ public ScriptFile GetFileBuffer(DocumentUri documentUri, string initialBuffer) } /// - /// Gets an array of all opened ScriptFiles in the workspace. + /// Gets an IEnumerable of all opened ScriptFiles in the workspace. /// - /// An array of all opened ScriptFiles in the workspace. - public ScriptFile[] GetOpenedFiles() => workspaceFiles.Values.ToArray(); + public IEnumerable GetOpenedFiles() => workspaceFiles.Values; /// /// Closes a currently open script file with the given file path. From 57c563b75f0c89eb8685d9cbba72586fbbccc0af Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 24 Jan 2023 19:23:38 -0800 Subject: [PATCH 193/327] Add `DisplayName` to clean up outline --- .../Symbols/PesterDocumentSymbolProvider.cs | 2 + .../Services/Symbols/SymbolDetails.cs | 80 +++++-------------- .../Services/Symbols/SymbolReference.cs | 16 ++-- .../Services/Symbols/SymbolType.cs | 21 ----- .../Symbols/Visitors/HashTableVisitor.cs | 4 +- .../Symbols/Visitors/SymbolVisitor.cs | 15 +++- .../Handlers/DocumentSymbolHandler.cs | 4 +- .../TextDocument/Handlers/HoverHandler.cs | 4 +- .../Handlers/WorkspaceSymbolsHandler.cs | 2 +- .../Utility/VisitorUtils.cs | 50 ++++++++++++ .../Language/SymbolsServiceTests.cs | 8 +- 11 files changed, 104 insertions(+), 102 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index e60a3b279..f38e72372 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -214,6 +214,7 @@ internal record PesterSymbolReference : SymbolReference /// /// Gets the name of the test + /// TODO: We could get rid of this and use DisplayName now, but first attempt didn't work great. /// public string TestName { get; } @@ -231,6 +232,7 @@ internal PesterSymbolReference( : base( SymbolType.Function, symbolName, + symbolName + " { }", scriptExtent, scriptExtent, scriptFile, diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index 84d0cef29..0cf42dc0f 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -12,6 +12,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols { /// /// Provides detailed information for a given symbol. + /// TODO: Get rid of this and just use return documentation. /// [DebuggerDisplay("SymbolReference = {SymbolReference.SymbolType}/{SymbolReference.SymbolName}, DisplayString = {DisplayString}")] internal class SymbolDetails @@ -23,11 +24,6 @@ internal class SymbolDetails /// public SymbolReference SymbolReference { get; private set; } - /// - /// Gets the display string for this symbol. - /// - public string DisplayString { get; private set; } - /// /// Gets the documentation string for this symbol. Returns an /// empty string if the symbol has no documentation. @@ -48,66 +44,28 @@ internal static async Task CreateAsync( SymbolReference = symbolReference }; - switch (symbolReference.SymbolType) + if (symbolReference.SymbolType is SymbolType.Function) { - case SymbolType.Function: - CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( - symbolReference.SymbolName, - currentRunspace, - executionService).ConfigureAwait(false); - - if (commandInfo != null) + CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( + symbolReference.SymbolName, + currentRunspace, + executionService).ConfigureAwait(false); + + if (commandInfo is not null) + { + symbolDetails.Documentation = + await CommandHelpers.GetCommandSynopsisAsync( + commandInfo, + executionService).ConfigureAwait(false); + + if (commandInfo.CommandType == CommandTypes.Application) { - symbolDetails.Documentation = - await CommandHelpers.GetCommandSynopsisAsync( - commandInfo, - executionService).ConfigureAwait(false); - - if (commandInfo.CommandType == CommandTypes.Application) - { - symbolDetails.DisplayString = "(application) " + symbolReference.SymbolName; - return symbolDetails; - } + symbolDetails.SymbolReference = symbolReference with { DisplayString = $"(application) ${symbolReference.DisplayString}" }; } - - symbolDetails.DisplayString = "function " + symbolReference.SymbolName; - return symbolDetails; - - case SymbolType.Parameter: - // TODO: Get parameter help - symbolDetails.DisplayString = "(parameter) " + symbolReference.SymbolName; - return symbolDetails; - - case SymbolType.Variable: - symbolDetails.DisplayString = symbolReference.SymbolName; - return symbolDetails; - - case SymbolType.Class: - symbolDetails.DisplayString = "class " + symbolReference.SymbolName; - return symbolDetails; - - case SymbolType.Enum: - symbolDetails.DisplayString = "enum " + symbolReference.SymbolName; - return symbolDetails; - - case SymbolType.Type: - symbolDetails.DisplayString = "type " + symbolReference.SymbolName; - return symbolDetails; - - case SymbolType.Constructor: - case SymbolType.Method: - case SymbolType.EnumMember: - case SymbolType.Property: - symbolDetails.DisplayString = symbolReference.SymbolName; - return symbolDetails; - - case SymbolType.Configuration: - symbolDetails.DisplayString = "configuration " + symbolReference.SymbolName; - return symbolDetails; - - default: - return symbolDetails; + } } + + return symbolDetails; } #endregion diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index 8be22a3da..385de3099 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -15,21 +15,21 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols [DebuggerDisplay("SymbolType = {SymbolType}, SymbolName = {SymbolName}")] internal record SymbolReference { - public SymbolType SymbolType { get; } + public SymbolType SymbolType { get; init; } - // TODO: Have a symbol name and a separate display name, the first minimally the text so the - // buckets work, the second usually a more complete signature for e.g. outline view. - public string SymbolName { get; } + public string SymbolName { get; init; } - public ScriptRegion NameRegion { get; } + public string DisplayString { get; init; } - public ScriptRegion ScriptRegion { get; } + public ScriptRegion NameRegion { get; init; } + + public ScriptRegion ScriptRegion { get; init; } public string SourceLine { get; internal set; } public string FilePath { get; internal set; } - public bool IsDeclaration { get; } + public bool IsDeclaration { get; init; } /// /// Constructs and instance of a SymbolReference @@ -43,6 +43,7 @@ internal record SymbolReference public SymbolReference( SymbolType symbolType, string symbolName, + string displayString, IScriptExtent nameExtent, IScriptExtent scriptExtent, ScriptFile file, @@ -50,6 +51,7 @@ public SymbolReference( { SymbolType = symbolType; SymbolName = symbolName; + DisplayString = displayString; NameRegion = new(nameExtent); ScriptRegion = new(scriptExtent); FilePath = file.FilePath; diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index be6699019..dbcb95dfb 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -99,26 +99,5 @@ internal static SymbolKind GetSymbolKind(SymbolType symbolType) _ => SymbolKind.Variable, }; } - - internal static string GetDecoratedSymbolName(SymbolReference symbolReference) - { - string name = symbolReference.SymbolName; - - // Append { } for symbols with scriptblock - // Constructors and Methods have overloaded names already - if (symbolReference.SymbolType is - SymbolType.Function or - SymbolType.Enum or - SymbolType.Class or - SymbolType.Constructor or - SymbolType.Method or - SymbolType.Configuration or - SymbolType.Workflow) - { - name += " { }"; - } - - return name; - } } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/HashTableVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/HashTableVisitor.cs index cc6cc68f0..3d9100cf5 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/HashTableVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/HashTableVisitor.cs @@ -57,14 +57,14 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) }; SymbolReferences.Add( + // TODO: Should we fill this out better? new SymbolReference( SymbolType.HashtableKey, nameExtent.Text, + nameExtent.Text, nameExtent, - // TODO: Should this be more? nameExtent, _file, - // TODO: Should this be true? isDeclaration: false)); } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index 83b97dcdf..0c0596843 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -31,7 +31,7 @@ public SymbolVisitor(ScriptFile file, Func acti public override AstVisitAction VisitCommand(CommandAst commandAst) { string? commandName = VisitorUtils.GetCommandName(commandAst); - if (string.IsNullOrEmpty(commandName)) + if (commandName is null) { return AstVisitAction.Continue; } @@ -39,6 +39,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) return _action(new SymbolReference( SymbolType.Function, CommandHelpers.StripModuleQualification(commandName, out _), + commandName, commandAst.CommandElements[0].Extent, commandAst.Extent, _file, @@ -62,6 +63,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return _action(new SymbolReference( symbolType, functionDefinitionAst.Name, + VisitorUtils.GetFunctionDisplayName(functionDefinitionAst), nameExtent, functionDefinitionAst.Extent, _file, @@ -70,10 +72,10 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun public override AstVisitAction VisitParameter(ParameterAst parameterAst) { - // TODO: When we add DisplayString, include the default value. return _action(new SymbolReference( SymbolType.Parameter, $"${parameterAst.Name.VariablePath.UserPath}", + VisitorUtils.GetParamDisplayName(parameterAst), parameterAst.Name.Extent, parameterAst.Extent, _file, @@ -87,6 +89,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var return _action(new SymbolReference( SymbolType.Variable, $"${variableExpressionAst.VariablePath.UserPath}", + $"${variableExpressionAst.VariablePath.UserPath}", variableExpressionAst.Extent, variableExpressionAst.Extent, // TODO: Maybe parent? _file, @@ -103,6 +106,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit return _action(new SymbolReference( symbolType, typeDefinitionAst.Name, + (symbolType is SymbolType.Enum ? "enum " : "class ") + typeDefinitionAst.Name + " { }", nameExtent, typeDefinitionAst.Extent, _file, @@ -114,6 +118,7 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress return _action(new SymbolReference( SymbolType.Type, typeExpressionAst.TypeName.Name, + typeExpressionAst.TypeName.Name, typeExpressionAst.Extent, typeExpressionAst.Extent, _file, @@ -125,6 +130,7 @@ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstra return _action(new SymbolReference( SymbolType.Type, typeConstraintAst.TypeName.Name, + "[" + typeConstraintAst.TypeName.Name + "]", typeConstraintAst.Extent, typeConstraintAst.Extent, _file, @@ -145,6 +151,7 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem return _action(new SymbolReference( symbolType, functionMemberAst.Name, // We bucket all the overloads. + VisitorUtils.GetMemberOverloadName(functionMemberAst, false, true), nameExtent, functionMemberAst.Extent, _file, @@ -161,6 +168,7 @@ propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum return _action(new SymbolReference( symbolType, nameExtent.Text, + VisitorUtils.GetMemberOverloadName(propertyMemberAst, false, true), nameExtent, propertyMemberAst.Extent, _file, @@ -180,6 +188,7 @@ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberE #pragma warning disable CS8604 // Possible null reference argument. memberName, #pragma warning restore CS8604 + memberExpressionAst.Member.Extent.Text, memberExpressionAst.Member.Extent, memberExpressionAst.Extent, _file, @@ -199,6 +208,7 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio #pragma warning disable CS8604 // Possible null reference argument. memberName, #pragma warning restore CS8604 + methodCallAst.Member.Extent.Text, methodCallAst.Member.Extent, methodCallAst.Extent, _file, @@ -211,6 +221,7 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit return _action(new SymbolReference( SymbolType.Configuration, nameExtent.Text, + "Configuration " + nameExtent.Text + " { }", nameExtent, configurationDefinitionAst.Extent, _file, diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index ff5d5c39e..baf00943e 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -69,7 +69,7 @@ public override async Task Handle(Do // // TODO: We should also include function invocations that are part of DSLs (like // Invoke-Build etc.). - if (!r.IsDeclaration) + if (!r.IsDeclaration || r.SymbolType is SymbolType.Parameter) { continue; } @@ -93,7 +93,7 @@ public override async Task Handle(Do Uri = DocumentUri.From(r.FilePath), Range = r.ScriptRegion.ToRange() // The whole thing, not just the name. }, - Name = SymbolTypeUtils.GetDecoratedSymbolName(r) + Name = r.DisplayString })); } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs index 7dbe07528..05f70e57f 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs @@ -52,14 +52,14 @@ await _symbolsService.FindSymbolDetailsAtLocationAsync( request.Position.Line + 1, request.Position.Character + 1).ConfigureAwait(false); - if (symbolDetails == null) + if (symbolDetails is null) { return null; } List symbolInfo = new() { - new MarkedString("PowerShell", symbolDetails.DisplayString) + new MarkedString("PowerShell", symbolDetails.SymbolReference.DisplayString) }; if (!string.IsNullOrEmpty(symbolDetails.Documentation)) diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 9d594d884..d6b1c6e87 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -80,7 +80,7 @@ public override async Task> Handle(WorkspaceSymbolP ContainerName = containerName, Kind = SymbolTypeUtils.GetSymbolKind(foundOccurrence.SymbolType), Location = location, - Name = SymbolTypeUtils.GetDecoratedSymbolName(foundOccurrence) + Name = foundOccurrence.DisplayString }); } } diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index f839cd4db..d4b9c01bf 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -243,6 +243,56 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(ConfigurationDefinitionAs }; } + /// + /// Gets the function name with parameters and return type. + /// + internal static string GetFunctionDisplayName(FunctionDefinitionAst functionDefinitionAst) + { + StringBuilder sb = new(); + sb.Append("function").Append(' '); + sb.Append(functionDefinitionAst.Name); + // Add parameters + sb.Append('('); + // TODO: Fix the parameters, this doesn't work for those specified in the body. + if (functionDefinitionAst.Parameters?.Count > 0) + { + List parameters = new(functionDefinitionAst.Parameters.Count); + foreach (ParameterAst param in functionDefinitionAst.Parameters) + { + parameters.Add(param.Extent.Text); + } + + sb.Append(string.Join(", ", parameters)); + } + sb.Append(')'); + + return sb.ToString(); + } + + /// + /// Gets the display name of a parameter with its default value. + /// + internal static string GetParamDisplayName(ParameterAst parameterAst) + { + StringBuilder sb = new(); + + sb.Append("(parameter) "); + if (parameterAst.StaticType is not null) + { + sb.Append('[').Append(parameterAst.StaticType).Append(']'); + } + sb.Append('$').Append(parameterAst.Name.VariablePath.UserPath); + string? constantValue = parameterAst.DefaultValue is ConstantExpressionAst constant + ? constant.Value.ToString() : null; + + if (!string.IsNullOrEmpty(constantValue)) + { + sb.Append(" = ").Append(constantValue); + } + + return sb.ToString(); + } + /// /// Gets the method or constructor name with parameters for current overload. /// diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 2fa819360..cc1579285 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -562,7 +562,7 @@ public async Task FindsDetailsWithSignatureForEnumMember() FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails.StartLineNumber, FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails.StartColumnNumber).ConfigureAwait(true); - Assert.Equal("MyEnum.First", symbolDetails.DisplayString); + Assert.Equal("MyEnum.First", symbolDetails.SymbolReference.DisplayString); } [Fact] @@ -573,7 +573,7 @@ public async Task FindsDetailsWithSignatureForProperty() FindsDetailsForTypeSymbolsData.PropertySourceDetails.StartLineNumber, FindsDetailsForTypeSymbolsData.PropertySourceDetails.StartColumnNumber).ConfigureAwait(true); - Assert.Equal("string SuperClass.SomePropWithDefault", symbolDetails.DisplayString); + Assert.Equal("string SuperClass.SomePropWithDefault", symbolDetails.SymbolReference.DisplayString); } [Fact] @@ -584,7 +584,7 @@ public async Task FindsDetailsWithSignatureForConstructor() FindsDetailsForTypeSymbolsData.ConstructorSourceDetails.StartLineNumber, FindsDetailsForTypeSymbolsData.ConstructorSourceDetails.StartColumnNumber).ConfigureAwait(true); - Assert.Equal("SuperClass.SuperClass([string]$name)", symbolDetails.DisplayString); + Assert.Equal("SuperClass.SuperClass([string]$name)", symbolDetails.SymbolReference.DisplayString); } [Fact] @@ -595,7 +595,7 @@ public async Task FindsDetailsWithSignatureForMethod() FindsDetailsForTypeSymbolsData.MethodSourceDetails.StartLineNumber, FindsDetailsForTypeSymbolsData.MethodSourceDetails.StartColumnNumber).ConfigureAwait(true); - Assert.Equal("string SuperClass.MyClassMethod([string]$param1, $param2, [int]$param3)", symbolDetails.DisplayString); + Assert.Equal("string SuperClass.MyClassMethod([string]$param1, $param2, [int]$param3)", symbolDetails.SymbolReference.DisplayString); } [Fact] From 74c4382391cc0a29d48f7534737e2cc4e683e0c3 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 24 Jan 2023 19:27:07 -0800 Subject: [PATCH 194/327] Exclude parameters from workplace symbols --- .../Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index d6b1c6e87..2b5724e06 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -56,6 +56,11 @@ public override async Task> Handle(WorkspaceSymbolP continue; } + if (foundOccurrence.SymbolType is SymbolType.Parameter) + { + continue; + } + if (!IsQueryMatch(request.Query, foundOccurrence.SymbolName)) { continue; From 8410180f10043038c439a4b0f2012f6d8995a3b3 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 24 Jan 2023 19:32:40 -0800 Subject: [PATCH 195/327] Optionally open all workspace files and scan for symbols --- src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs | 2 +- .../Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 2318e0b7d..49649bad1 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -341,7 +341,7 @@ public async Task> GetDefinitionOfSymbolAsync( return declarations; } - private async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = default) + internal async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = default) { if (_configurationService.CurrentSettings.AnalyzeOpenDocumentsOnly) { diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 2b5724e06..1dc367517 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -34,9 +34,9 @@ public PsesWorkspaceSymbolsHandler(ILoggerFactory loggerFactory, SymbolsService public override async Task> Handle(WorkspaceSymbolParams request, CancellationToken cancellationToken) { + await _symbolsService.ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false); List symbols = new(); - // TODO: This should (optionally) check all the files, like we do for scanning for references by calling ScanWorkspacePSFiles. foreach (ScriptFile scriptFile in _workspaceService.GetOpenedFiles()) { IEnumerable foundSymbols = _symbolsService.FindSymbolsInFile(scriptFile); From c2169ed7a4cab5c6c4d3064b74b202e4b523d245 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 24 Jan 2023 19:41:56 -0800 Subject: [PATCH 196/327] Use `NameRegion` for definition, workplace jump and hover --- .../Services/Symbols/ParameterSetSignatures.cs | 4 ++-- .../Services/TextDocument/Handlers/DefinitionHandler.cs | 2 +- .../Services/TextDocument/Handlers/DocumentSymbolHandler.cs | 5 +++-- .../Services/TextDocument/Handlers/HoverHandler.cs | 2 +- .../Services/TextDocument/Handlers/ReferencesHandler.cs | 2 +- .../Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs b/src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs index aa094ce7b..8f8af2ca4 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs @@ -46,8 +46,8 @@ public ParameterSetSignatures(IEnumerable commandInfoSe paramSetSignatures.Add(new ParameterSetSignature(setInfo)); } Signatures = paramSetSignatures.ToArray(); - CommandName = foundSymbol.ScriptRegion.Text; - ScriptRegion = foundSymbol.ScriptRegion; + CommandName = foundSymbol.NameRegion.Text; + ScriptRegion = foundSymbol.NameRegion; } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs index b1d272691..c3446936f 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs @@ -54,7 +54,7 @@ public override async Task Handle(DefinitionParams requ new Location { Uri = DocumentUri.From(foundDefinition.FilePath), - Range = foundDefinition.ScriptRegion.ToRange() + Range = foundDefinition.NameRegion.ToRange() })); } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index baf00943e..a4ae0920a 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -74,8 +74,9 @@ public override async Task Handle(Do continue; } - // TODO: This should be a DocumentSymbol now as SymbolInformation is deprecated. - // But this requires figuring out how to populate `children`. + // TODO: This should be a DocumentSymbol now as SymbolInformation is deprecated. But + // this requires figuring out how to populate `children`. Once we do that, the range + // can be NameRegion. // // symbols.Add(new SymbolInformationOrDocumentSymbol(new DocumentSymbol // { diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs index 05f70e57f..b0cc4515c 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs @@ -70,7 +70,7 @@ await _symbolsService.FindSymbolDetailsAtLocationAsync( return new Hover { Contents = new MarkedStringsOrMarkupContent(symbolInfo), - Range = symbolDetails.SymbolReference.ScriptRegion.ToRange() + Range = symbolDetails.SymbolReference.NameRegion.ToRange() }; } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index 154e7b35b..1d41f3571 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -54,7 +54,7 @@ public override async Task Handle(ReferenceParams request, Ca locations.Add(new Location { Uri = DocumentUri.From(foundReference.FilePath), - Range = foundReference.ScriptRegion.ToRange() + Range = foundReference.NameRegion.ToRange() }); } diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 1dc367517..8d0d0bf46 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -76,7 +76,7 @@ public override async Task> Handle(WorkspaceSymbolP Location location = new() { Uri = DocumentUri.From(foundOccurrence.FilePath), - Range = foundOccurrence.ScriptRegion.ToRange() + Range = foundOccurrence.NameRegion.ToRange() }; // TODO: This should be a WorkplaceSymbol now as SymbolInformation is deprecated. From f1fc140b0cbf3e342cc8364678ac459d1bbf94e9 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Wed, 25 Jan 2023 10:42:35 -0800 Subject: [PATCH 197/327] Fix jump to definition from outline By starting at the start of `NameRegion`. Co-authored-by: Frode Flaten <3436158+fflaten@users.noreply.github.com> --- .../Services/TextDocument/Handlers/DocumentSymbolHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index a4ae0920a..307d27b73 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -92,7 +92,7 @@ public override async Task Handle(Do Location = new Location { Uri = DocumentUri.From(r.FilePath), - Range = r.ScriptRegion.ToRange() // The whole thing, not just the name. + Range = new Range(r.NameRegion.ToRange().Start, r.ScriptRegion.ToRange().End) // Jump to name start, but keep whole range to support symbol tree in outline }, Name = r.DisplayString })); From 7471401d60f520d3c40a9141d4b0c58d6ec04a45 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 25 Jan 2023 11:03:18 -0800 Subject: [PATCH 198/327] Improve function's display strings --- .../Services/Symbols/Visitors/SymbolVisitor.cs | 3 ++- .../Utility/VisitorUtils.cs | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index 0c0596843..cd15422d8 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -28,6 +28,7 @@ public SymbolVisitor(ScriptFile file, Func acti _action = action; } + // TODO: Make all the display strings better (and performant). public override AstVisitAction VisitCommand(CommandAst commandAst) { string? commandName = VisitorUtils.GetCommandName(commandAst); @@ -221,7 +222,7 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit return _action(new SymbolReference( SymbolType.Configuration, nameExtent.Text, - "Configuration " + nameExtent.Text + " { }", + "configuration " + nameExtent.Text + " { }", nameExtent, configurationDefinitionAst.Extent, _file, diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index d4b9c01bf..0dc685fe3 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -249,10 +249,20 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(ConfigurationDefinitionAs internal static string GetFunctionDisplayName(FunctionDefinitionAst functionDefinitionAst) { StringBuilder sb = new(); - sb.Append("function").Append(' '); - sb.Append(functionDefinitionAst.Name); + if (functionDefinitionAst.IsWorkflow) + { + sb.Append("workflow"); + } + else if (functionDefinitionAst.IsFilter) + { + sb.Append("filter"); + } + else + { + sb.Append("function"); + } + sb.Append(' ').Append(functionDefinitionAst.Name).Append('('); // Add parameters - sb.Append('('); // TODO: Fix the parameters, this doesn't work for those specified in the body. if (functionDefinitionAst.Parameters?.Count > 0) { From 494b0d9d795313ba5b064fcd94fd7a4e1f1bf4d2 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 25 Jan 2023 16:58:21 -0800 Subject: [PATCH 199/327] Filter symbol references to type matches --- .../Services/Symbols/ReferenceTable.cs | 7 +- .../Services/Symbols/SymbolType.cs | 10 +++ .../Services/Symbols/SymbolsService.cs | 66 ++++--------------- .../Services/Symbols/AstOperationsTests.cs | 7 +- 4 files changed, 32 insertions(+), 58 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 48ecb3a8f..da73c68b8 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -42,10 +42,13 @@ public void TagAsChanged() /// private bool IsInitialized => !_symbolReferences.IsEmpty || _isInited; - internal bool TryGetReferences(string command, out ConcurrentBag? references) + internal IEnumerable TryGetReferences(SymbolReference? symbol) { EnsureInitialized(); - return _symbolReferences.TryGetValue(command, out references); + return symbol is not null + && _symbolReferences.TryGetValue(symbol.SymbolName, out ConcurrentBag? bag) + ? bag.Where(i => SymbolTypeUtils.SymbolTypeMatches(symbol.SymbolType, i.SymbolType)) + : Enumerable.Empty(); } internal SymbolReference? TryGetSymbolAtPosition(int line, int column) => GetAllReferences() diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index dbcb95dfb..3e1b0a9af 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -99,5 +99,15 @@ internal static SymbolKind GetSymbolKind(SymbolType symbolType) _ => SymbolKind.Variable, }; } + + // Provides a partial equivalence between type constraints and custom types. + internal static bool SymbolTypeMatches(SymbolType left, SymbolType right) + { + return left == right + || (left is SymbolType.Class or SymbolType.Enum or SymbolType.Type + && right is SymbolType.Class or SymbolType.Enum or SymbolType.Type) + || (left is SymbolType.EnumMember or SymbolType.Property + && right is SymbolType.EnumMember or SymbolType.Property); + } } } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 49649bad1..a28cd1b8b 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -188,14 +188,7 @@ public async Task> ScanForReferencesOfSymbolAsync( { await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); - - _ = file.References.TryGetReferences(targetIdentifier, out ConcurrentBag? references); - if (references is null) - { - continue; - } - - symbols.AddRange(references); + symbols.AddRange(file.References.TryGetReferences(symbol with { SymbolName = targetIdentifier })); } } @@ -205,19 +198,10 @@ public async Task> ScanForReferencesOfSymbolAsync( /// /// Finds all the occurrences of a symbol in the script given a file location. /// - public static IEnumerable? FindOccurrencesInFile( - ScriptFile scriptFile, int line, int column) - { - SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column); - - if (symbol is null) - { - return null; - } - - scriptFile.References.TryGetReferences(symbol.SymbolName, out ConcurrentBag? references); - return references; - } + public static IEnumerable FindOccurrencesInFile( + ScriptFile scriptFile, int line, int column) => scriptFile + .References + .TryGetReferences(FindSymbolAtLocation(scriptFile, line, column)); /// /// Finds the symbol at the location and returns it if it's a declaration. @@ -236,15 +220,9 @@ public async Task> ScanForReferencesOfSymbolAsync( ScriptFile scriptFile, int line, int column) { SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column); - if (symbol is null) - { - return Task.FromResult(null); - } - - return SymbolDetails.CreateAsync( - symbol, - _runspaceContext.CurrentRunspace, - _executionService); + return symbol is null + ? Task.FromResult(null) + : SymbolDetails.CreateAsync(symbol, _runspaceContext.CurrentRunspace, _executionService); } /// @@ -310,34 +288,18 @@ public async Task> GetDefinitionOfSymbolAsync( CancellationToken cancellationToken = default) { List declarations = new(); - _ = scriptFile.References.TryGetReferences(symbol.SymbolName, out ConcurrentBag? symbols); - if (symbols is not null) - { - foreach (SymbolReference foundReference in symbols) - { - if (foundReference.IsDeclaration) - { - _logger.LogDebug($"Found possible declaration in same file ${foundReference}"); - declarations.Add(foundReference); - } - } - } - + declarations.AddRange(scriptFile.References.TryGetReferences(symbol).Where(i => i.IsDeclaration)); if (declarations.Any()) { + _logger.LogDebug($"Found possible declaration in same file ${declarations}"); return declarations; } - foreach (SymbolReference foundReference in await ScanForReferencesOfSymbolAsync( - symbol, cancellationToken).ConfigureAwait(false)) - { - if (foundReference.IsDeclaration) - { - _logger.LogDebug($"Found possible declaration in workspace ${foundReference}"); - declarations.Add(foundReference); - } - } + IEnumerable references = + await ScanForReferencesOfSymbolAsync(symbol, cancellationToken).ConfigureAwait(false); + declarations.AddRange(references.Where(i => i.IsDeclaration)); + _logger.LogDebug($"Found possible declaration in workspace ${declarations}"); return declarations; } diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs index 4d73a9e57..23e7019e5 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Services; @@ -46,9 +46,8 @@ public void CanFindReferencesOfSymbolAtPosition(int line, int column, Range[] sy { SymbolReference symbol = scriptFile.References.TryGetSymbolAtPosition(line, column); - Assert.True(scriptFile.References.TryGetReferences( - symbol.SymbolName, - out ConcurrentBag references)); + IEnumerable references = scriptFile.References.TryGetReferences(symbol); + Assert.NotEmpty(references); int positionsIndex = 0; foreach (SymbolReference reference in references.OrderBy((i) => i.ScriptRegion.ToRange().Start)) From ec8cf7b09dfd109d3ede4216bf1472669ba586fb Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 25 Jan 2023 16:59:00 -0800 Subject: [PATCH 200/327] Short-circuit find-definition when given definition --- .../TextDocument/Handlers/DefinitionHandler.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs index c3446936f..a0251e7b5 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs @@ -43,6 +43,19 @@ public override async Task Handle(DefinitionParams requ request.Position.Line + 1, request.Position.Character + 1); + // Short-circuit if we're already on the definition. + if (foundSymbol.IsDeclaration) + { + return new LocationOrLocationLinks( + new LocationOrLocationLink[] { + new LocationOrLocationLink( + new Location + { + Uri = DocumentUri.From(foundSymbol.FilePath), + Range = foundSymbol.NameRegion.ToRange() + })}); + } + List definitionLocations = new(); if (foundSymbol is not null) { From 43baed3d514bbf62cdb7765d2912c25d54d5803a Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 25 Jan 2023 17:28:15 -0800 Subject: [PATCH 201/327] Decorate more type names And guard from null exception in definition handler. --- .../Services/Symbols/ReferenceTable.cs | 2 +- .../Symbols/Visitors/SymbolVisitor.cs | 13 +++++----- .../Handlers/DefinitionHandler.cs | 26 ++++++++++--------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index da73c68b8..f2f4c5da0 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -52,7 +52,7 @@ internal IEnumerable TryGetReferences(SymbolReference? symbol) } internal SymbolReference? TryGetSymbolAtPosition(int line, int column) => GetAllReferences() - .FirstOrDefault((i) => i.NameRegion.ContainsPosition(line, column)); + .FirstOrDefault(i => i.NameRegion.ContainsPosition(line, column)); internal IEnumerable GetAllReferences() { diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index cd15422d8..f0acee2ac 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -89,8 +89,8 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var // the same function definition. return _action(new SymbolReference( SymbolType.Variable, - $"${variableExpressionAst.VariablePath.UserPath}", - $"${variableExpressionAst.VariablePath.UserPath}", + "$" + variableExpressionAst.VariablePath.UserPath, + "$" + variableExpressionAst.VariablePath.UserPath, variableExpressionAst.Extent, variableExpressionAst.Extent, // TODO: Maybe parent? _file, @@ -119,7 +119,7 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress return _action(new SymbolReference( SymbolType.Type, typeExpressionAst.TypeName.Name, - typeExpressionAst.TypeName.Name, + "(type) " + typeExpressionAst.TypeName.Name, typeExpressionAst.Extent, typeExpressionAst.Extent, _file, @@ -131,7 +131,7 @@ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstra return _action(new SymbolReference( SymbolType.Type, typeConstraintAst.TypeName.Name, - "[" + typeConstraintAst.TypeName.Name + "]", + "(type) " + typeConstraintAst.TypeName.Name, typeConstraintAst.Extent, typeConstraintAst.Extent, _file, @@ -184,12 +184,13 @@ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberE return AstVisitAction.Continue; } + // TODO: It's too bad we can't get the member's real symbol and reuse its display string. return _action(new SymbolReference( SymbolType.Property, #pragma warning disable CS8604 // Possible null reference argument. memberName, #pragma warning restore CS8604 - memberExpressionAst.Member.Extent.Text, + "(method) " + memberName, memberExpressionAst.Member.Extent, memberExpressionAst.Extent, _file, @@ -209,7 +210,7 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio #pragma warning disable CS8604 // Possible null reference argument. memberName, #pragma warning restore CS8604 - methodCallAst.Member.Extent.Text, + "(method) " + memberName, methodCallAst.Member.Extent, methodCallAst.Extent, _file, diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs index a0251e7b5..f39ca9917 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs @@ -43,6 +43,11 @@ public override async Task Handle(DefinitionParams requ request.Position.Line + 1, request.Position.Character + 1); + if (foundSymbol is null) + { + return new LocationOrLocationLinks(); + } + // Short-circuit if we're already on the definition. if (foundSymbol.IsDeclaration) { @@ -57,19 +62,16 @@ public override async Task Handle(DefinitionParams requ } List definitionLocations = new(); - if (foundSymbol is not null) + foreach (SymbolReference foundDefinition in await _symbolsService.GetDefinitionOfSymbolAsync( + scriptFile, foundSymbol, cancellationToken).ConfigureAwait(false)) { - foreach (SymbolReference foundDefinition in await _symbolsService.GetDefinitionOfSymbolAsync( - scriptFile, foundSymbol, cancellationToken).ConfigureAwait(false)) - { - definitionLocations.Add( - new LocationOrLocationLink( - new Location - { - Uri = DocumentUri.From(foundDefinition.FilePath), - Range = foundDefinition.NameRegion.ToRange() - })); - } + definitionLocations.Add( + new LocationOrLocationLink( + new Location + { + Uri = DocumentUri.From(foundDefinition.FilePath), + Range = foundDefinition.NameRegion.ToRange() + })); } return new LocationOrLocationLinks(definitionLocations); From 09fc5d0cf8fc37facd777f9a65f33d195471c764 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 25 Jan 2023 17:28:33 -0800 Subject: [PATCH 202/327] Include parameter type constraints as symbols --- .../Services/Symbols/ReferenceTable.cs | 3 +-- .../Services/Symbols/Visitors/SymbolVisitor.cs | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index f2f4c5da0..19e1bc61e 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -93,7 +93,6 @@ private AstVisitAction AddReference(SymbolReference symbol) return existing; }); - return symbol.SymbolType is SymbolType.Parameter - ? AstVisitAction.SkipChildren : AstVisitAction.Continue; + return AstVisitAction.Continue; } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index f0acee2ac..bdfe48528 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -85,6 +85,13 @@ public override AstVisitAction VisitParameter(ParameterAst parameterAst) public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { + // Parameters are visited earlier, and we don't want to skip their children because we do + // want to visit their type constraints. + if (variableExpressionAst.Parent is ParameterAst) + { + return AstVisitAction.Continue; + } + // TODO: Consider tracking unscoped variable references only when they declared within // the same function definition. return _action(new SymbolReference( From c8a3bb369041f262f7ba31a5589b5ffc8654d4af Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 26 Jan 2023 12:19:24 -0800 Subject: [PATCH 203/327] Ignore some warnings --- .../Services/PowerShell/Console/LegacyReadLine.cs | 2 ++ .../Services/PowerShell/Host/PsesInternalHost.cs | 2 ++ .../Services/Symbols/SymbolReference.cs | 1 + .../Services/TextDocument/TokenOperations.cs | 5 +++-- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index 39e12974b..3c5e7a1d2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -35,7 +35,9 @@ public LegacyReadLine( _onIdleAction = onIdleAction; } +#pragma warning disable CA1502 // Cyclomatic complexity we don't care about public override string ReadLine(CancellationToken cancellationToken) +#pragma warning restore CA1502 { string inputBeforeCompletion = null; string inputAfterCompletion = null; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 5a6c8fb31..419aebf8c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -29,7 +29,9 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host using Microsoft.PowerShell.EditorServices.Server; using OmniSharp.Extensions.DebugAdapter.Protocol.Server; +#pragma warning disable CA1506 // Coupling complexity we don't care about internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext, IInternalPowerShellExecutionService +#pragma warning restore CA1506 { internal const string DefaultPrompt = "> "; diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index 385de3099..732846f1c 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -36,6 +36,7 @@ internal record SymbolReference /// /// The higher level type of the symbol /// The name of the symbol + /// The string used by outline, hover, etc. /// The extent of the symbol's name /// The script extent of the symbol /// The script file that has the symbol diff --git a/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs b/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs index 67853388a..d1a3ca5e5 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs @@ -25,8 +25,9 @@ internal static class TokenOperations /// /// Extracts all of the unique foldable regions in a script given the list tokens /// - internal static FoldingReferenceList FoldableReferences( - Token[] tokens) +#pragma warning disable CA1502 // Cyclomatic complexity we don't care about + internal static FoldingReferenceList FoldableReferences(Token[] tokens) +#pragma warning restore CA1502 { FoldingReferenceList refList = new(); From 0f1aa1932782db72f42ff046befacef8613d6909 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 26 Jan 2023 16:03:43 -0800 Subject: [PATCH 204/327] Fix parameter hints tests (but the whole feature needs work) --- .../Services/Symbols/ReferenceTable.cs | 5 +++++ .../Services/Symbols/SymbolsService.cs | 5 ++++- .../TextDocument/Handlers/SignatureHelpHandler.cs | 6 +++--- .../Language/SymbolsServiceTests.cs | 8 +++++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 19e1bc61e..0062d3fff 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -51,9 +51,14 @@ internal IEnumerable TryGetReferences(SymbolReference? symbol) : Enumerable.Empty(); } + // Gets symbol whose name contains the position internal SymbolReference? TryGetSymbolAtPosition(int line, int column) => GetAllReferences() .FirstOrDefault(i => i.NameRegion.ContainsPosition(line, column)); + // Gets symbol whose whole extent contains the position + internal SymbolReference? TryGetSymbolContainingPosition(int line, int column) => GetAllReferences() + .FirstOrDefault(i => i.ScriptRegion.ContainsPosition(line, column)); + internal IEnumerable GetAllReferences() { EnsureInitialized(); diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index a28cd1b8b..c8b5b71a5 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -231,7 +231,9 @@ public static IEnumerable FindOccurrencesInFile( public async Task FindParameterSetsInFileAsync( ScriptFile scriptFile, int line, int column) { - SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column); + // This needs to get by whole extent, not just the name, as it completes e.g. + // `Get-Process -` (after the dash) and so also needs to look backwards a column. + SymbolReference? symbol = scriptFile.References.TryGetSymbolContainingPosition(line, column - 1); // If we are not possibly looking at a Function, we don't // need to continue because we won't be able to get the @@ -255,6 +257,7 @@ await CommandHelpers.GetCommandInfoAsync( try { + // TODO: We should probably look at 'Parameters' instead of 'ParameterSets' IEnumerable commandParamSets = commandInfo.ParameterSets; return new ParameterSetSignatures(commandParamSets, symbol); } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs index 3e294a1e2..240cef13d 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs @@ -34,7 +34,7 @@ public PsesSignatureHelpHandler( protected override SignatureHelpRegistrationOptions CreateRegistrationOptions(SignatureHelpCapability capability, ClientCapabilities clientCapabilities) => new() { DocumentSelector = LspUtils.PowerShellDocumentSelector, - // A sane default of " ". We may be able to include others like "-". + // TODO: We should evaluate what triggers (and re-triggers) signature help (like dash?) TriggerCharacters = new Container(" ") }; @@ -54,7 +54,7 @@ await _symbolsService.FindParameterSetsInFileAsync( request.Position.Line + 1, request.Position.Character + 1).ConfigureAwait(false); - if (parameterSets == null) + if (parameterSets is null) { return new SignatureHelp(); } @@ -89,7 +89,7 @@ private static ParameterInformation CreateParameterInfo(ParameterInfo parameterI return new ParameterInformation { Label = parameterInfo.Name, - Documentation = string.Empty + Documentation = parameterInfo.HelpMessage }; } } diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index cc1579285..cece35c56 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -125,11 +125,17 @@ private IReadOnlyList GetOccurrences(ScriptRegion scriptRegion) .ToArray(); } - private IEnumerable FindSymbolsInFile(ScriptRegion scriptRegion) => symbolsService.FindSymbolsInFile(GetScriptFile(scriptRegion)).OrderBy(symbol => symbol.ScriptRegion.ToRange().Start); + private IEnumerable FindSymbolsInFile(ScriptRegion scriptRegion) + { + return symbolsService + .FindSymbolsInFile(GetScriptFile(scriptRegion)) + .OrderBy(symbol => symbol.ScriptRegion.ToRange().Start); + } [Fact] public async Task FindsParameterHintsOnCommand() { + // TODO: Fix signatures to use parameters, not sets. ParameterSetSignatures signatures = await GetParamSetSignatures(FindsParameterSetsOnCommandData.SourceDetails).ConfigureAwait(true); Assert.NotNull(signatures); Assert.Equal("Get-Process", signatures.CommandName); From c8abc704e34cb769749ef2273874cb2bfce872cc Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 26 Jan 2023 16:30:05 -0800 Subject: [PATCH 205/327] Fix find function definition tests --- .../Services/Workspace/WorkspaceService.cs | 2 +- .../Language/SymbolsServiceTests.cs | 73 +++++++++---------- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs index bf5f2ce12..002a757ad 100644 --- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs @@ -341,7 +341,7 @@ public IEnumerable EnumeratePSFiles( bool ignoreReparsePoints ) { - if (WorkspacePath == null || !Directory.Exists(WorkspacePath)) + if (WorkspacePath is null || !Directory.Exists(WorkspacePath)) { yield break; } diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index cece35c56..18a5516f1 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Management.Automation; using System.Runtime.InteropServices; @@ -38,7 +37,10 @@ public class SymbolsServiceTests : IDisposable public SymbolsServiceTests() { psesHost = PsesHostFactory.Create(NullLoggerFactory.Instance); - workspace = new WorkspaceService(NullLoggerFactory.Instance); + workspace = new WorkspaceService(NullLoggerFactory.Instance) + { + WorkspacePath = TestUtilities.GetSharedPath("References") + }; symbolsService = new SymbolsService( NullLoggerFactory.Instance, psesHost, @@ -80,7 +82,7 @@ private Task GetParamSetSignatures(ScriptRegion scriptRe scriptRegion.StartColumnNumber); } - private async Task GetDefinition(ScriptRegion scriptRegion) + private async Task> GetDefinitions(ScriptRegion scriptRegion) { ScriptFile scriptFile = GetScriptFile(scriptRegion); @@ -92,9 +94,13 @@ private async Task GetDefinition(ScriptRegion scriptRegion) Assert.NotNull(symbol); - IEnumerable refs = await symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbol).ConfigureAwait(true); + return await symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbol).ConfigureAwait(true); + } - return refs.FirstOrDefault(); + private async Task GetDefinition(ScriptRegion scriptRegion) + { + IEnumerable definitions = await GetDefinitions(scriptRegion).ConfigureAwait(true); + return definitions.FirstOrDefault(); } private async Task> GetReferences(ScriptRegion scriptRegion) @@ -156,11 +162,11 @@ public async Task FindsFunctionDefinition() { SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionData.SourceDetails).ConfigureAwait(true); Assert.Equal("My-Function", symbol.SymbolName); - AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); - // TODO: This should pull the declaration from references. - // AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); + Assert.Equal("function My-Function($myInput)", symbol.DisplayString); Assert.Equal(SymbolType.Function, symbol.SymbolType); - // Assert.True(symbol.IsDeclaration); + AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); + AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); + Assert.True(symbol.IsDeclaration); } [Fact] @@ -173,11 +179,11 @@ await psesHost.ExecutePSCommandAsync( CancellationToken.None).ConfigureAwait(true); SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionOfAliasData.SourceDetails).ConfigureAwait(true); - AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); - Assert.Equal("My-Function", symbol.SymbolName); + Assert.Equal("function My-Function($myInput)", symbol.DisplayString); Assert.Equal(SymbolType.Function, symbol.SymbolType); - // TODO: This should pull the declaration from references. - // Assert.True(symbol.IsDeclaration); + AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); + AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); + Assert.True(symbol.IsDeclaration); } [Fact] @@ -209,37 +215,28 @@ await psesHost.ExecutePSCommandAsync( } [Fact] - public async Task FindsFunctionDefinitionInDotSourceReference() + public async Task FindsFunctionDefinitionsInWorkspace() { - SymbolReference definitionResult = await GetDefinition(FindsFunctionDefinitionInDotSourceReferenceData.SourceDetails).ConfigureAwait(true); - Assert.True( - definitionResult.FilePath.EndsWith(FindsFunctionDefinitionData.SourceDetails.File), - "Unexpected reference file: " + definitionResult.FilePath); - Assert.Equal(1, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(10, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("My-Function", definitionResult.SymbolName); - } - - [Fact] - public async Task FindsDotSourcedFile() - { - SymbolReference definitionResult = await GetDefinition(FindsDotSourcedFileData.SourceDetails).ConfigureAwait(true); - Assert.NotNull(definitionResult); - Assert.True( - definitionResult.FilePath.EndsWith(Path.Combine("References", "ReferenceFileE.ps1")), - "Unexpected reference file: " + definitionResult.FilePath); - Assert.Equal(1, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(1, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("./ReferenceFileE.ps1", definitionResult.SymbolName); + IEnumerable symbols = await GetDefinitions(FindsFunctionDefinitionInDotSourceReferenceData.SourceDetails).ConfigureAwait(true); + Assert.Collection(symbols.OrderBy((i) => i.FilePath), + (i) => + { + Assert.Equal("My-Function", i.SymbolName); + Assert.EndsWith("ReferenceFileA.ps1", i.FilePath); + }, + (i) => + { + Assert.Equal("My-Function", i.SymbolName); + Assert.EndsWith(FindsFunctionDefinitionData.SourceDetails.File, i.FilePath); + }); } [Fact] public async Task FindsFunctionDefinitionInWorkspace() { - workspace.WorkspacePath = TestUtilities.GetSharedPath("References"); - SymbolReference definitionResult = await GetDefinition(FindsFunctionDefinitionInWorkspaceData.SourceDetails).ConfigureAwait(true); - Assert.EndsWith("ReferenceFileE.ps1", definitionResult.FilePath); - Assert.Equal("My-FunctionInFileE", definitionResult.SymbolName); + SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionInWorkspaceData.SourceDetails).ConfigureAwait(true); + Assert.EndsWith("ReferenceFileE.ps1", symbol.FilePath); + Assert.Equal("My-FunctionInFileE", symbol.SymbolName); } [Fact] From 2592af9f3739de6f8cd1c089c541968b54e34bd0 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 26 Jan 2023 17:35:51 -0800 Subject: [PATCH 206/327] Fix find variable and function references tests --- .../Language/SymbolsServiceTests.cs | 105 ++++++++++++++---- 1 file changed, 81 insertions(+), 24 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 18a5516f1..e86a6d142 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -103,7 +103,7 @@ private async Task GetDefinition(ScriptRegion scriptRegion) return definitions.FirstOrDefault(); } - private async Task> GetReferences(ScriptRegion scriptRegion) + private async Task> GetReferences(ScriptRegion scriptRegion) { ScriptFile scriptFile = GetScriptFile(scriptRegion); @@ -117,10 +117,10 @@ private async Task> GetReferences(ScriptRegion scriptRegio IEnumerable symbols = await symbolsService.ScanForReferencesOfSymbolAsync(symbol).ConfigureAwait(true); - return symbols.OrderBy(i => i.ScriptRegion.ToRange().Start).ToList(); + return symbols.OrderBy((i) => i.ScriptRegion.ToRange().Start).ToList(); } - private IReadOnlyList GetOccurrences(ScriptRegion scriptRegion) + private IEnumerable GetOccurrences(ScriptRegion scriptRegion) { return SymbolsService .FindOccurrencesInFile( @@ -189,7 +189,7 @@ await psesHost.ExecutePSCommandAsync( [Fact] public async Task FindsReferencesOnFunction() { - List symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); + IEnumerable symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); Assert.Collection(symbols, (i) => AssertIsRegion(i.NameRegion, 1, 10, 1, 21), (i) => AssertIsRegion(i.NameRegion, 3, 5, 3, 16), @@ -204,14 +204,19 @@ await psesHost.ExecutePSCommandAsync( new PSCommand().AddScript("Set-Alias -Name My-Alias -Value My-Function"), CancellationToken.None).ConfigureAwait(true); - List symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); - Assert.Collection(symbols, + IEnumerable symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); + Assert.Equal(9, symbols.Count()); + + Assert.Collection(symbols.Where((i) => i.FilePath.EndsWith(FindsReferencesOnFunctionData.SourceDetails.File)), (i) => AssertIsRegion(i.NameRegion, 1, 10, 1, 21), (i) => AssertIsRegion(i.NameRegion, 3, 5, 3, 16), (i) => AssertIsRegion(i.NameRegion, 10, 1, 10, 12), // The alias. - (i) => AssertIsRegion(i.NameRegion, 20, 1, 20, 9)); - Assert.Equal("My-Alias", symbols[3].SymbolName); + (i) => + { + AssertIsRegion(i.NameRegion, 20, 1, 20, 9); + Assert.Equal("My-Alias", i.SymbolName); + }); } [Fact] @@ -242,37 +247,89 @@ public async Task FindsFunctionDefinitionInWorkspace() [Fact] public async Task FindsVariableDefinition() { - SymbolReference definitionResult = await GetDefinition(FindsVariableDefinitionData.SourceDetails).ConfigureAwait(true); - Assert.Equal(6, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(1, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("$things", definitionResult.SymbolName); + SymbolReference symbol = await GetDefinition(FindsVariableDefinitionData.SourceDetails).ConfigureAwait(true); + Assert.Equal("$things", symbol.SymbolName); + Assert.Equal("$things", symbol.DisplayString); + Assert.Equal(SymbolType.Variable, symbol.SymbolType); + Assert.True(symbol.IsDeclaration); + AssertIsRegion(symbol.NameRegion, 6, 1, 6, 8); } [Fact] public async Task FindsReferencesOnVariable() { - List referencesResult = await GetReferences(FindsReferencesOnVariableData.SourceDetails).ConfigureAwait(true); - Assert.Equal(3, referencesResult.Count); - Assert.Equal(10, referencesResult[referencesResult.Count - 1].ScriptRegion.StartLineNumber); - Assert.Equal(13, referencesResult[referencesResult.Count - 1].ScriptRegion.StartColumnNumber); + // TODO: Technically this also finds references in the workspace, but since there aren't + // any, it's identical to the test below. + IEnumerable symbols = await GetReferences(FindsReferencesOnVariableData.SourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("$things", i.SymbolName); + Assert.Equal(SymbolType.Variable, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("$things", i.SymbolName); + Assert.Equal(SymbolType.Variable, i.SymbolType); + Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("$things", i.SymbolName); + Assert.Equal(SymbolType.Variable, i.SymbolType); + Assert.False(i.IsDeclaration); + }); } [Fact] public void FindsOccurrencesOnVariable() { - IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnVariableData.SourceDetails); - Assert.Equal(3, occurrencesResult.Count); - Assert.Equal(10, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); - Assert.Equal(13, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartColumnNumber); + IEnumerable symbols = GetOccurrences(FindsOccurrencesOnVariableData.SourceDetails); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("$things", i.SymbolName); + Assert.Equal(SymbolType.Variable, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("$things", i.SymbolName); + Assert.Equal(SymbolType.Variable, i.SymbolType); + Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("$things", i.SymbolName); + Assert.Equal(SymbolType.Variable, i.SymbolType); + Assert.False(i.IsDeclaration); + }); } [Fact] public void FindsOccurrencesOnFunction() { - IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnFunctionData.SourceDetails); - Assert.Equal(3, occurrencesResult.Count); - Assert.Equal(10, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); - Assert.Equal(1, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartColumnNumber); + IEnumerable symbols = GetOccurrences(FindsOccurrencesOnFunctionData.SourceDetails); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("My-Function", i.SymbolName); + Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("My-Function", i.SymbolName); + Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("My-Function", i.SymbolName); + Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.False(i.IsDeclaration); + }); } [Fact] From e2d4e25f99edbd0a7f37a326008ef910bd915492 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 26 Jan 2023 17:50:21 -0800 Subject: [PATCH 207/327] Fix `FindsOccurrencesOnParameter` test --- .../Services/Symbols/SymbolType.cs | 7 ++++-- .../Symbols/Visitors/SymbolVisitor.cs | 4 +++- .../Occurrences/FindOccurrencesOnParameter.cs | 2 +- .../Language/SymbolsServiceTests.cs | 22 +++++++++++++++---- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index 3e1b0a9af..adcff16f1 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -100,14 +100,17 @@ internal static SymbolKind GetSymbolKind(SymbolType symbolType) }; } - // Provides a partial equivalence between type constraints and custom types. + // Provides a partial equivalence between type constraints and custom types, and between + // variables and parameters. internal static bool SymbolTypeMatches(SymbolType left, SymbolType right) { return left == right || (left is SymbolType.Class or SymbolType.Enum or SymbolType.Type && right is SymbolType.Class or SymbolType.Enum or SymbolType.Type) || (left is SymbolType.EnumMember or SymbolType.Property - && right is SymbolType.EnumMember or SymbolType.Property); + && right is SymbolType.EnumMember or SymbolType.Property) + || (left is SymbolType.Variable or SymbolType.Parameter + && right is SymbolType.Variable or SymbolType.Parameter); } } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index bdfe48528..fa809b710 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -73,9 +73,11 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun public override AstVisitAction VisitParameter(ParameterAst parameterAst) { + // TODO: Can we fix the display name's type by visiting this in VisitVariableExpression and + // getting the TypeConstraintAst somehow? return _action(new SymbolReference( SymbolType.Parameter, - $"${parameterAst.Name.VariablePath.UserPath}", + "$" + parameterAst.Name.VariablePath.UserPath, VisitorUtils.GetParamDisplayName(parameterAst), parameterAst.Name.Extent, parameterAst.Extent, diff --git a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindOccurrencesOnParameter.cs b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindOccurrencesOnParameter.cs index 5b708a07d..d948df784 100644 --- a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindOccurrencesOnParameter.cs +++ b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindOccurrencesOnParameter.cs @@ -11,7 +11,7 @@ public static class FindOccurrencesOnParameterData file: TestUtilities.NormalizePath("References/SimpleFile.ps1"), text: string.Empty, startLineNumber: 1, - startColumnNumber: 31, + startColumnNumber: 30, startOffset: 0, endLineNumber: 0, endColumnNumber: 0, diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index e86a6d142..d78f709a1 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -335,10 +335,24 @@ public void FindsOccurrencesOnFunction() [Fact] public void FindsOccurrencesOnParameter() { - IReadOnlyList occurrencesResult = GetOccurrences(FindOccurrencesOnParameterData.SourceDetails); - Assert.Equal(2, occurrencesResult.Count); - Assert.Equal("$myInput", occurrencesResult[occurrencesResult.Count - 1].SymbolName); - Assert.Equal(3, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + IEnumerable symbols = GetOccurrences(FindOccurrencesOnParameterData.SourceDetails); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("$myInput", i.SymbolName); + // TODO: Parameter display strings need work. + Assert.Equal("(parameter) [System.Object]$myInput", i.DisplayString); + Assert.Equal(SymbolType.Parameter, i.SymbolType); + AssertIsRegion(i.NameRegion, 1, 23, 1, 31); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("$myInput", i.SymbolName); + Assert.Equal(SymbolType.Variable, i.SymbolType); + AssertIsRegion(i.NameRegion, 3, 17, 3, 25); + Assert.False(i.IsDeclaration); + }); } [Fact] From bd76db322f9fc9c9c41c4b6e8656c532f2bcdd1c Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 26 Jan 2023 17:50:50 -0800 Subject: [PATCH 208/327] Fix `FindsReferencesOnCommandWithAlias` test --- .../Services/Symbols/SymbolsService.cs | 1 + .../Language/SymbolsServiceTests.cs | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index c8b5b71a5..b57cdd1f0 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -197,6 +197,7 @@ public async Task> ScanForReferencesOfSymbolAsync( /// /// Finds all the occurrences of a symbol in the script given a file location. + /// TODO: Doesn't support aliases, is it worth it? /// public static IEnumerable FindOccurrencesInFile( ScriptFile scriptFile, int line, int column) => scriptFile diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index d78f709a1..5682592c6 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -358,8 +358,11 @@ public void FindsOccurrencesOnParameter() [Fact] public async Task FindsReferencesOnCommandWithAlias() { - List referencesResult = await GetReferences(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails).ConfigureAwait(true); - Assert.Collection(referencesResult, + // NOTE: This doesn't use GetOccurrences as it's testing for aliases. + IEnumerable symbols = await GetReferences(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails).ConfigureAwait(true); + Assert.Collection(symbols.Where( + (i) => i.FilePath + .EndsWith(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails.File)), (i) => Assert.Equal("Get-ChildItem", i.SymbolName), (i) => Assert.Equal("gci", i.SymbolName), (i) => Assert.Equal("dir", i.SymbolName), From 04345dd45a13aa1f769aacec8694d6ecf2c357c6 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 26 Jan 2023 18:25:52 -0800 Subject: [PATCH 209/327] Fix a bunch of class symbol tests --- .../FindsReferencesOnTypeSymbols.cs | 4 +- .../References/TypeAndClassesFile.ps1 | 2 +- .../Language/SymbolsServiceTests.cs | 331 ++++++++++-------- 3 files changed, 196 insertions(+), 141 deletions(-) diff --git a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs index d9cbcf434..f9cdad724 100644 --- a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs +++ b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs @@ -40,8 +40,8 @@ public static class FindsReferencesOnTypeSymbolsData public static readonly ScriptRegion MethodSourceDetails = new( file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), text: string.Empty, - startLineNumber: 19, - startColumnNumber: 20, + startLineNumber: 36, + startColumnNumber: 16, startOffset: 0, endLineNumber: 0, endColumnNumber: 0, diff --git a/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 b/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 index 4fe54505d..ac2888f9f 100644 --- a/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 @@ -33,7 +33,7 @@ class SuperClass : BaseClass { New-Object SuperClass $o = [SuperClass]::new() $o.SomeProp -$o.MyClassMeth +$o.MyClassMethod() enum MyEnum { diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 5682592c6..f007109fa 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -94,7 +94,10 @@ private async Task> GetDefinitions(ScriptRegion scr Assert.NotNull(symbol); - return await symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbol).ConfigureAwait(true); + IEnumerable symbols = + await symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbol).ConfigureAwait(true); + + return symbols.OrderBy((i) => i.ScriptRegion.ToRange().Start); } private async Task GetDefinition(ScriptRegion scriptRegion) @@ -117,7 +120,7 @@ private async Task> GetReferences(ScriptRegion scri IEnumerable symbols = await symbolsService.ScanForReferencesOfSymbolAsync(symbol).ConfigureAwait(true); - return symbols.OrderBy((i) => i.ScriptRegion.ToRange().Start).ToList(); + return symbols.OrderBy((i) => i.ScriptRegion.ToRange().Start); } private IEnumerable GetOccurrences(ScriptRegion scriptRegion) @@ -258,8 +261,6 @@ public async Task FindsVariableDefinition() [Fact] public async Task FindsReferencesOnVariable() { - // TODO: Technically this also finds references in the workspace, but since there aren't - // any, it's identical to the test below. IEnumerable symbols = await GetReferences(FindsReferencesOnVariableData.SourceDetails).ConfigureAwait(true); Assert.Collection(symbols, (i) => @@ -280,31 +281,8 @@ public async Task FindsReferencesOnVariable() Assert.Equal(SymbolType.Variable, i.SymbolType); Assert.False(i.IsDeclaration); }); - } - [Fact] - public void FindsOccurrencesOnVariable() - { - IEnumerable symbols = GetOccurrences(FindsOccurrencesOnVariableData.SourceDetails); - Assert.Collection(symbols, - (i) => - { - Assert.Equal("$things", i.SymbolName); - Assert.Equal(SymbolType.Variable, i.SymbolType); - Assert.True(i.IsDeclaration); - }, - (i) => - { - Assert.Equal("$things", i.SymbolName); - Assert.Equal(SymbolType.Variable, i.SymbolType); - Assert.False(i.IsDeclaration); - }, - (i) => - { - Assert.Equal("$things", i.SymbolName); - Assert.Equal(SymbolType.Variable, i.SymbolType); - Assert.False(i.IsDeclaration); - }); + Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnVariableData.SourceDetails)); } [Fact] @@ -372,177 +350,254 @@ public async Task FindsReferencesOnCommandWithAlias() [Fact] public async Task FindsClassDefinition() { - // TODO: We should find the definition by searching known symbols filtered to declarations. - SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.ClassSourceDetails).ConfigureAwait(true); - Assert.Equal(8, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(7, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("SuperClass", definitionResult.SymbolName); + SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.ClassSourceDetails).ConfigureAwait(true); + Assert.Equal("SuperClass", symbol.SymbolName); + Assert.Equal("class SuperClass { }", symbol.DisplayString); + Assert.Equal(SymbolType.Class, symbol.SymbolType); + Assert.True(symbol.IsDeclaration); + AssertIsRegion(symbol.NameRegion, 8, 7, 8, 17); } [Fact] public async Task FindsReferencesOnClass() { - List symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.ClassSourceDetails).ConfigureAwait(true); + IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.ClassSourceDetails).ConfigureAwait(true); Assert.Collection(symbols, - (i) => AssertIsRegion(i.ScriptRegion, 8, 1, 31, 2), - (i) => AssertIsRegion(i.ScriptRegion, 34, 6, 34, 18)); - Assert.Collection(symbols, - (i) => AssertIsRegion(i.NameRegion, 8, 7, 8, 17), - // TODO: This should exclude the [] and be 34:7 and 34:18 - (i) => AssertIsRegion(i.NameRegion, 34, 6, 34, 18)); - } + (i) => + { + Assert.Equal("SuperClass", i.SymbolName); + Assert.Equal("class SuperClass { }", i.DisplayString); + Assert.Equal(SymbolType.Class, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("SuperClass", i.SymbolName); + Assert.Equal("(type) SuperClass", i.DisplayString); + Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.False(i.IsDeclaration); + }); - [Fact] - public void FindsOccurrencesOnClass() - { - IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ClassSourceDetails); - Assert.Equal(2, occurrencesResult.Count); - Assert.Equal("SuperClass", occurrencesResult[occurrencesResult.Count - 1].SymbolName); - Assert.Equal(34, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ClassSourceDetails)); } [Fact] public async Task FindsEnumDefinition() { - SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumSourceDetails).ConfigureAwait(true); - Assert.Equal(39, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(6, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("MyEnum", definitionResult.SymbolName); + SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumSourceDetails).ConfigureAwait(true); + Assert.Equal("MyEnum", symbol.SymbolName); + Assert.Equal("enum MyEnum { }", symbol.DisplayString); + Assert.Equal(SymbolType.Enum, symbol.SymbolType); + Assert.True(symbol.IsDeclaration); + AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12); } [Fact] public async Task FindsReferencesOnEnum() { - // TODO: Remove definitions from references. - List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumSourceDetails).ConfigureAwait(true); - Assert.Equal(4, referencesResult.Count); - Assert.Equal(25, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(19, referencesResult[0].ScriptRegion.StartColumnNumber); - } + IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumSourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("MyEnum", i.SymbolName); + Assert.Equal("(type) MyEnum", i.DisplayString); + Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("MyEnum", i.SymbolName); + Assert.Equal("enum MyEnum { }", i.DisplayString); + Assert.Equal(SymbolType.Enum, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("MyEnum", i.SymbolName); + Assert.Equal("(type) MyEnum", i.DisplayString); + Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("MyEnum", i.SymbolName); + Assert.Equal("(type) MyEnum", i.DisplayString); + Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.False(i.IsDeclaration); + }); - [Fact] - public void FindsOccurrencesOnEnum() - { - IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumSourceDetails); - Assert.Equal(4, occurrencesResult.Count); - Assert.Equal("MyEnum", occurrencesResult[occurrencesResult.Count - 1].SymbolName); - Assert.Equal(46, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumSourceDetails)); } [Fact] public async Task FindsTypeExpressionDefinition() { - SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeExpressionSourceDetails).ConfigureAwait(true); - Assert.Equal(39, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(6, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("MyEnum", definitionResult.SymbolName); + SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeExpressionSourceDetails).ConfigureAwait(true); + AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12); + Assert.Equal("MyEnum", symbol.SymbolName); + Assert.Equal("enum MyEnum { }", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); } [Fact] public async Task FindsReferencesOnTypeExpression() { - List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeExpressionSourceDetails).ConfigureAwait(true); - Assert.Equal(2, referencesResult.Count); - SymbolReference superClass = referencesResult[0]; - Assert.Equal(8, superClass.ScriptRegion.StartLineNumber); - Assert.Equal(1, superClass.ScriptRegion.StartColumnNumber); - Assert.Equal(31, superClass.ScriptRegion.EndLineNumber); - Assert.Equal(2, superClass.ScriptRegion.EndColumnNumber); - Assert.Equal(8, superClass.NameRegion.StartLineNumber); - Assert.Equal(7, superClass.NameRegion.StartColumnNumber); - } + IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeExpressionSourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("SuperClass", i.SymbolName); + Assert.Equal("class SuperClass { }", i.DisplayString); + Assert.Equal(SymbolType.Class, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("SuperClass", i.SymbolName); + Assert.Equal("(type) SuperClass", i.DisplayString); + Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.False(i.IsDeclaration); + }); - [Fact] - public void FindsOccurrencesOnTypeExpression() - { - IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.TypeExpressionSourceDetails); - Assert.Equal(2, occurrencesResult.Count); - Assert.Equal("SuperClass", occurrencesResult[0].SymbolName); - Assert.Equal(8, occurrencesResult[0].ScriptRegion.StartLineNumber); + Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.TypeExpressionSourceDetails)); } [Fact] public async Task FindsTypeConstraintDefinition() { - SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeConstraintSourceDetails).ConfigureAwait(true); - Assert.Equal(39, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(6, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("MyEnum", definitionResult.SymbolName); + SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeConstraintSourceDetails).ConfigureAwait(true); + AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12); + Assert.Equal("MyEnum", symbol.SymbolName); + Assert.Equal("enum MyEnum { }", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); } [Fact] public async Task FindsReferencesOnTypeConstraint() { - // TODO: Remove definitions from references. - List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeConstraintSourceDetails).ConfigureAwait(true); - Assert.Equal(4, referencesResult.Count); - Assert.Equal(25, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(19, referencesResult[0].ScriptRegion.StartColumnNumber); + IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeConstraintSourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("MyEnum", i.SymbolName); + Assert.Equal("(type) MyEnum", i.DisplayString); + Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("MyEnum", i.SymbolName); + Assert.Equal("enum MyEnum { }", i.DisplayString); + Assert.Equal(SymbolType.Enum, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("MyEnum", i.SymbolName); + Assert.Equal("(type) MyEnum", i.DisplayString); + Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("MyEnum", i.SymbolName); + Assert.Equal("(type) MyEnum", i.DisplayString); + Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.False(i.IsDeclaration); + }); } [Fact] public void FindsOccurrencesOnTypeConstraint() { - IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.TypeConstraintSourceDetails); - Assert.Equal(2, occurrencesResult.Count); - Assert.Equal("BaseClass", occurrencesResult[0].SymbolName); - Assert.Equal(4, occurrencesResult[0].ScriptRegion.StartLineNumber); + IEnumerable symbols = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.TypeConstraintSourceDetails); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("BaseClass", i.SymbolName); + Assert.Equal("class BaseClass { }", i.DisplayString); + Assert.Equal(SymbolType.Class, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("BaseClass", i.SymbolName); + Assert.Equal("(type) BaseClass", i.DisplayString); + Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.False(i.IsDeclaration); + }); } [Fact] public async Task FindsConstructorDefinition() { - SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.ConstructorSourceDetails).ConfigureAwait(true); - Assert.Equal(9, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(5, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("SuperClass.SuperClass([string]$name)", definitionResult.SymbolName); - } - - [Fact] - public async Task FindsReferencesOnConstructor() - { - List symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.ConstructorSourceDetails).ConfigureAwait(true); - Assert.Single(symbols); - Assert.Equal(9, symbols[0].ScriptRegion.StartLineNumber); - Assert.Equal(5, symbols[0].ScriptRegion.StartColumnNumber); - } + IEnumerable symbols = await GetDefinitions(FindsTypeSymbolsDefinitionData.ConstructorSourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("SuperClass", i.SymbolName); + Assert.Equal("SuperClass([string]$name)", i.DisplayString); + Assert.Equal(SymbolType.Constructor, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("SuperClass", i.SymbolName); + Assert.Equal("SuperClass()", i.DisplayString); + Assert.Equal(SymbolType.Constructor, i.SymbolType); + Assert.True(i.IsDeclaration); + }); - [Fact] - public void FindsOccurrencesOnConstructor() - { - IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ConstructorSourceDetails); - Assert.Single(occurrencesResult); - Assert.Equal("SuperClass.SuperClass()", occurrencesResult[occurrencesResult.Count - 1].SymbolName); - Assert.Equal(13, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + Assert.Equal(symbols, await GetReferences(FindsReferencesOnTypeSymbolsData.ConstructorSourceDetails).ConfigureAwait(true)); + Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ConstructorSourceDetails)); } [Fact] public async Task FindsMethodDefinition() { - SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.MethodSourceDetails).ConfigureAwait(true); - Assert.Equal(19, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(13, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("SuperClass.MyClassMethod([string]$param1, $param2, [int]$param3)", definitionResult.SymbolName); + IEnumerable symbols = await GetDefinitions(FindsTypeSymbolsDefinitionData.MethodSourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("MyClassMethod", i.SymbolName); + Assert.Equal("string MyClassMethod([string]$param1, $param2, [int]$param3)", i.DisplayString); + Assert.Equal(SymbolType.Method, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("MyClassMethod", i.SymbolName); + Assert.Equal("string MyClassMethod([MyEnum]$param1)", i.DisplayString); + Assert.Equal(SymbolType.Method, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("MyClassMethod", i.SymbolName); + Assert.Equal("string MyClassMethod()", i.DisplayString); + Assert.Equal(SymbolType.Method, i.SymbolType); + Assert.True(i.IsDeclaration); + }); } [Fact] public async Task FindsReferencesOnMethod() { - List symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.MethodSourceDetails).ConfigureAwait(true); - SymbolReference symbol = Assert.Single(symbols); - Assert.Equal("SuperClass.MyClassMethod([string]$param1, $param2, [int]$param3)", symbol.SymbolName); - Assert.Equal(SymbolType.Method, symbol.SymbolType); - AssertIsRegion(symbol.NameRegion, 19, 13, 19, 26); - AssertIsRegion(symbol.ScriptRegion, 19, 5, 22, 6); - } + IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.MethodSourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => Assert.Equal("string MyClassMethod([string]$param1, $param2, [int]$param3)", i.DisplayString), + (i) => Assert.Equal("string MyClassMethod([MyEnum]$param1)", i.DisplayString), + (i) => Assert.Equal("string MyClassMethod()", i.DisplayString), + (i) => // The invocation! + { + Assert.Equal("MyClassMethod", i.SymbolName); + Assert.Equal("(method) MyClassMethod", i.DisplayString); + Assert.Equal("$o.MyClassMethod()", i.SourceLine); + Assert.Equal(SymbolType.Method, i.SymbolType); + Assert.False(i.IsDeclaration); + }); - [Fact] - public void FindsOccurrencesOnMethod() - { - IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.MethodSourceDetails); - Assert.Single(occurrencesResult); - Assert.Equal("SuperClass.MyClassMethod()", occurrencesResult[occurrencesResult.Count - 1].SymbolName); - Assert.Equal(28, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.MethodSourceDetails)); } [Fact] From 2729e839665a9215c8acc00792c8043543a429ca Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 26 Jan 2023 18:49:52 -0800 Subject: [PATCH 210/327] Fix `FindsPropertyDefinition` test Name was wrong. --- .../Symbols/Visitors/SymbolVisitor.cs | 33 ++++++++---- .../Utility/VisitorUtils.cs | 51 ++++--------------- .../Language/SymbolsServiceTests.cs | 9 ++-- 3 files changed, 38 insertions(+), 55 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index fa809b710..773e9b795 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -153,15 +153,12 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem ? SymbolType.Constructor : SymbolType.Method; - IScriptExtent nameExtent = VisitorUtils.GetNameExtent( - functionMemberAst, - useQualifiedName: false, - includeReturnType: false); + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst); return _action(new SymbolReference( symbolType, functionMemberAst.Name, // We bucket all the overloads. - VisitorUtils.GetMemberOverloadName(functionMemberAst, false, true), + VisitorUtils.GetMemberOverloadName(functionMemberAst), nameExtent, functionMemberAst.Extent, _file, @@ -172,13 +169,18 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem { SymbolType symbolType = propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum - ? SymbolType.EnumMember : SymbolType.Property; + ? SymbolType.EnumMember + : SymbolType.Property; + + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst); + string name = symbolType == SymbolType.EnumMember + ? propertyMemberAst.Name + : "$" + propertyMemberAst.Name; - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst, false, false); return _action(new SymbolReference( symbolType, - nameExtent.Text, - VisitorUtils.GetMemberOverloadName(propertyMemberAst, false, true), + name, + VisitorUtils.GetMemberOverloadName(propertyMemberAst), nameExtent, propertyMemberAst.Extent, _file, @@ -228,11 +230,20 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { + string? name = configurationDefinitionAst.InstanceName is StringConstantExpressionAst stringConstant + ? stringConstant.Value : null; + if (string.IsNullOrEmpty(name)) + { + return AstVisitAction.Continue; + } + IScriptExtent nameExtent = VisitorUtils.GetNameExtent(configurationDefinitionAst); return _action(new SymbolReference( SymbolType.Configuration, - nameExtent.Text, - "configuration " + nameExtent.Text + " { }", +#pragma warning disable CS8604 // Possible null reference argument. + name, +#pragma warning restore CS8604 + "configuration " + name + " { }", nameExtent, configurationDefinitionAst.Extent, _file, diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index 0dc685fe3..401b387ad 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -170,16 +170,14 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(TypeDefinitionAst typeDef /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable) /// /// A FunctionMemberAst in the script's AST - /// A bool indicating if class/enum name should be prepended - /// A bool indicating if return type should be included for methods /// A ScriptExtent with for the symbol name only - internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functionMemberAst, bool useQualifiedName = true, bool includeReturnType = false) + internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functionMemberAst) { (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(functionMemberAst); return new PSESSymbols.ScriptExtent() { - Text = GetMemberOverloadName(functionMemberAst, useQualifiedName, includeReturnType), + Text = GetMemberOverloadName(functionMemberAst), StartLineNumber = startLine, EndLineNumber = startLine, StartColumnNumber = startColumn, @@ -192,13 +190,8 @@ internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functio /// Gets a new ScriptExtent for a given Ast for the property name only /// /// A PropertyMemberAst in the script's AST - /// A bool indicating if class/enum name should be prepended - /// A bool indicating if type should be included for class property /// A ScriptExtent with for the symbol name only - internal static PSESSymbols.ScriptExtent GetNameExtent( - PropertyMemberAst propertyMemberAst, - bool useQualifiedName = true, - bool includePropertyType = false) + internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propertyMemberAst) { bool isEnumMember = propertyMemberAst.Parent is TypeDefinitionAst typeDef && typeDef.IsEnum; (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(propertyMemberAst, isEnumMember); @@ -210,10 +203,7 @@ internal static PSESSymbols.ScriptExtent GetNameExtent( return new PSESSymbols.ScriptExtent() { - Text = GetMemberOverloadName( - propertyMemberAst, - useQualifiedName: useQualifiedName, - includePropertyType: includePropertyType), + Text = GetMemberOverloadName(propertyMemberAst), StartLineNumber = startLine, EndLineNumber = startLine, StartColumnNumber = startColumn, @@ -307,26 +297,17 @@ internal static string GetParamDisplayName(ParameterAst parameterAst) /// Gets the method or constructor name with parameters for current overload. /// /// A FunctionMemberAst object in the script's AST - /// A bool indicating if class/enum name should be prepended - /// A bool indicating if return type should be included for methods /// Function member name with return type (optional) and parameters - internal static string GetMemberOverloadName(FunctionMemberAst functionMemberAst, - bool useQualifiedName = true, - bool includeReturnType = false) + internal static string GetMemberOverloadName(FunctionMemberAst functionMemberAst) { StringBuilder sb = new(); // Prepend return type and class. Used for symbol details (hover) - if (includeReturnType && !functionMemberAst.IsConstructor) + if (!functionMemberAst.IsConstructor) { sb.Append(functionMemberAst.ReturnType?.TypeName.Name ?? "void").Append(' '); } - if (useQualifiedName && functionMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsClass) - { - sb.Append(typeAst.Name).Append('.'); - } - sb.Append(functionMemberAst.Name); // Add parameters @@ -350,27 +331,17 @@ internal static string GetMemberOverloadName(FunctionMemberAst functionMemberAst /// Gets the property name with type and class/enum. /// /// A PropertyMemberAst object in the script's AST - /// A bool indicating if class/enum name should be prepended - /// A bool indicating if type should be included for class property /// Property name with type (optional) and class/enum - internal static string GetMemberOverloadName(PropertyMemberAst propertyMemberAst, - bool useQualifiedName = true, - bool includePropertyType = false) + internal static string GetMemberOverloadName(PropertyMemberAst propertyMemberAst) { StringBuilder sb = new(); // Prepend return type and class. Used for symbol details (hover) - if (propertyMemberAst.Parent is TypeDefinitionAst typeAst) + if (propertyMemberAst.Parent is TypeDefinitionAst typeAst && !typeAst.IsEnum) { - if (includePropertyType && !typeAst.IsEnum) - { - sb.Append(propertyMemberAst.PropertyType?.TypeName.Name ?? "object").Append(' '); - } - - if (useQualifiedName) - { - sb.Append(typeAst.Name).Append('.'); - } + sb.Append('[') + .Append(propertyMemberAst.PropertyType?.TypeName.Name ?? "object") + .Append("] $"); } sb.Append(propertyMemberAst.Name); diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index f007109fa..17059379a 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -603,10 +603,11 @@ public async Task FindsReferencesOnMethod() [Fact] public async Task FindsPropertyDefinition() { - SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.PropertySourceDetails).ConfigureAwait(true); - Assert.Equal(15, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(13, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("SuperClass.SomePropWithDefault", definitionResult.SymbolName); + SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.PropertySourceDetails).ConfigureAwait(true); + Assert.Equal("$SomePropWithDefault", symbol.SymbolName); + Assert.Equal("[string] $SomePropWithDefault", symbol.DisplayString); + Assert.Equal(SymbolType.Property, symbol.SymbolType); + Assert.True(symbol.IsDeclaration); } [Fact] From b634dfada0728ee57fe90bb81ca4f473377b21e0 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 26 Jan 2023 19:02:25 -0800 Subject: [PATCH 211/327] Fix property symbols tests --- .../Symbols/Visitors/SymbolVisitor.cs | 7 +-- .../Language/SymbolsServiceTests.cs | 46 +++++++++++++------ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index 773e9b795..3f38c6d9b 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -195,13 +195,13 @@ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberE return AstVisitAction.Continue; } - // TODO: It's too bad we can't get the member's real symbol and reuse its display string. + // TODO: It's too bad we can't get the property's real symbol and reuse its display string. return _action(new SymbolReference( SymbolType.Property, #pragma warning disable CS8604 // Possible null reference argument. - memberName, + "$" + memberName, #pragma warning restore CS8604 - "(method) " + memberName, + "(property) " + memberName, memberExpressionAst.Member.Extent, memberExpressionAst.Extent, _file, @@ -216,6 +216,7 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio return AstVisitAction.Continue; } + // TODO: It's too bad we can't get the member's real symbol and reuse its display string. return _action(new SymbolReference( SymbolType.Method, #pragma warning disable CS8604 // Possible null reference argument. diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 17059379a..33d2781f9 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -613,25 +613,43 @@ public async Task FindsPropertyDefinition() [Fact] public async Task FindsReferencesOnProperty() { - List symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.PropertySourceDetails).ConfigureAwait(true); - SymbolReference symbol = Assert.Single(symbols); - Assert.Equal("SuperClass.SomeProp", symbol.SymbolName); - Assert.Equal(SymbolType.Property, symbol.SymbolType); - AssertIsRegion(symbol.NameRegion, 17, 10, 17, 19); - AssertIsRegion(symbol.ScriptRegion, 17, 5, 17, 19); - // TODO: This should also find $o.SomeProp + IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.PropertySourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("$SomeProp", i.SymbolName); + Assert.Equal("[int] $SomeProp", i.DisplayString); + Assert.Equal(SymbolType.Property, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("$SomeProp", i.SymbolName); + Assert.Equal("(property) SomeProp", i.DisplayString); + Assert.Equal(SymbolType.Property, i.SymbolType); + Assert.False(i.IsDeclaration); + }); } [Fact] public void FindsOccurrencesOnProperty() { - IReadOnlyList symbols = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.PropertySourceDetails); - SymbolReference symbol = Assert.Single(symbols); - Assert.Equal("SuperClass.SomePropWithDefault", symbol.SymbolName); - Assert.Equal(SymbolType.Property, symbol.SymbolType); - AssertIsRegion(symbol.NameRegion, 15, 13, 15, 33); - AssertIsRegion(symbol.ScriptRegion, 15, 5, 15, 61); - // TODO: This should also find the $this.SomePropWithDefault reference. + IEnumerable symbols = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.PropertySourceDetails); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("$SomePropWithDefault", i.SymbolName); + Assert.Equal("[string] $SomePropWithDefault", i.DisplayString); + Assert.Equal(SymbolType.Property, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("$SomePropWithDefault", i.SymbolName); + Assert.Equal("(property) SomePropWithDefault", i.DisplayString); + Assert.Equal(SymbolType.Property, i.SymbolType); + Assert.False(i.IsDeclaration); + }); } [Fact] From e9906705f35165c361622cbca08b5c94c4d157f6 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 27 Jan 2023 10:16:27 -0800 Subject: [PATCH 212/327] Fix `ScriptRegion.ContainsPosition` To actually be an inclusive check of the given point being between the two boundary points. --- .../Services/Symbols/SymbolsService.cs | 4 +- .../Services/TextDocument/ScriptRegion.cs | 40 ++----------------- .../Occurrences/FindOccurrencesOnParameter.cs | 2 +- .../FindsReferencesOnTypeSymbols.cs | 4 +- .../Language/SymbolsServiceTests.cs | 10 +++-- 5 files changed, 14 insertions(+), 46 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index b57cdd1f0..095426423 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -233,8 +233,8 @@ public static IEnumerable FindOccurrencesInFile( ScriptFile scriptFile, int line, int column) { // This needs to get by whole extent, not just the name, as it completes e.g. - // `Get-Process -` (after the dash) and so also needs to look backwards a column. - SymbolReference? symbol = scriptFile.References.TryGetSymbolContainingPosition(line, column - 1); + // `Get-Process -` (after the dash). + SymbolReference? symbol = scriptFile.References.TryGetSymbolContainingPosition(line, column); // If we are not possibly looking at a Function, we don't // need to continue because we won't be able to get the diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs index 138c6bbe4..0732c4a3b 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs @@ -42,45 +42,11 @@ public bool IsEmpty() && string.IsNullOrEmpty(Text); } - // Same as PowerShell's ContainsLineAndColumn + // Do not use PowerShell's ContainsLineAndColumn, it's nonsense. public bool ContainsPosition(int line, int column) { - if (StartLineNumber == line) - { - if (column == 0) - { - return true; - } - - if (column >= StartColumnNumber) - { - if (EndLineNumber != StartLineNumber) - { - return true; - } - - return column < EndColumnNumber; - } - - return false; - } - - if (StartLineNumber > line) - { - return false; - } - - if (line > EndLineNumber) - { - return false; - } - - if (EndLineNumber == line) - { - return column < EndColumnNumber; - } - - return true; + return StartLineNumber <= line && line <= EndLineNumber + && StartColumnNumber <= column && column <= EndColumnNumber; } public override string ToString() => $"Start {StartLineNumber}:{StartColumnNumber}, End {EndLineNumber}:{EndColumnNumber}"; diff --git a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindOccurrencesOnParameter.cs b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindOccurrencesOnParameter.cs index d948df784..5b708a07d 100644 --- a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindOccurrencesOnParameter.cs +++ b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindOccurrencesOnParameter.cs @@ -11,7 +11,7 @@ public static class FindOccurrencesOnParameterData file: TestUtilities.NormalizePath("References/SimpleFile.ps1"), text: string.Empty, startLineNumber: 1, - startColumnNumber: 30, + startColumnNumber: 31, startOffset: 0, endLineNumber: 0, endColumnNumber: 0, diff --git a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs index f9cdad724..268e68d27 100644 --- a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs +++ b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs @@ -50,8 +50,8 @@ public static class FindsReferencesOnTypeSymbolsData public static readonly ScriptRegion PropertySourceDetails = new( file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), text: string.Empty, - startLineNumber: 17, - startColumnNumber: 15, + startLineNumber: 35, + startColumnNumber: 12, startOffset: 0, endLineNumber: 0, endColumnNumber: 0, diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 33d2781f9..b446287c6 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -655,10 +655,12 @@ public void FindsOccurrencesOnProperty() [Fact] public async Task FindsEnumMemberDefinition() { - SymbolReference definitionResult = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumMemberSourceDetails).ConfigureAwait(true); - Assert.Equal(41, definitionResult.ScriptRegion.StartLineNumber); - Assert.Equal(5, definitionResult.ScriptRegion.StartColumnNumber); - Assert.Equal("MyEnum.Second", definitionResult.SymbolName); + SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumMemberSourceDetails).ConfigureAwait(true); + Assert.Equal("Second", symbol.SymbolName); + Assert.Equal("Second", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); + AssertIsRegion(symbol.NameRegion, 41, 5, 41, 11); + Assert.Equal(41, symbol.ScriptRegion.StartLineNumber); } [Fact] From e40745351935ff8434404900751c847ccecaf51a Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 27 Jan 2023 10:25:46 -0800 Subject: [PATCH 213/327] Fix display string for function definitions Space between name and subsequent parenthesis. --- .../Utility/VisitorUtils.cs | 2 +- .../Language/SymbolsServiceTests.cs | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index 401b387ad..e02dfda40 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -251,7 +251,7 @@ internal static string GetFunctionDisplayName(FunctionDefinitionAst functionDefi { sb.Append("function"); } - sb.Append(' ').Append(functionDefinitionAst.Name).Append('('); + sb.Append(' ').Append(functionDefinitionAst.Name).Append(" ("); // Add parameters // TODO: Fix the parameters, this doesn't work for those specified in the body. if (functionDefinitionAst.Parameters?.Count > 0) diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index b446287c6..86c763535 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -165,7 +165,7 @@ public async Task FindsFunctionDefinition() { SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionData.SourceDetails).ConfigureAwait(true); Assert.Equal("My-Function", symbol.SymbolName); - Assert.Equal("function My-Function($myInput)", symbol.DisplayString); + Assert.Equal("function My-Function ($myInput)", symbol.DisplayString); Assert.Equal(SymbolType.Function, symbol.SymbolType); AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); @@ -182,7 +182,7 @@ await psesHost.ExecutePSCommandAsync( CancellationToken.None).ConfigureAwait(true); SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionOfAliasData.SourceDetails).ConfigureAwait(true); - Assert.Equal("function My-Function($myInput)", symbol.DisplayString); + Assert.Equal("function My-Function ($myInput)", symbol.DisplayString); Assert.Equal(SymbolType.Function, symbol.SymbolType); AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); @@ -193,10 +193,17 @@ await psesHost.ExecutePSCommandAsync( public async Task FindsReferencesOnFunction() { IEnumerable symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); - Assert.Collection(symbols, - (i) => AssertIsRegion(i.NameRegion, 1, 10, 1, 21), - (i) => AssertIsRegion(i.NameRegion, 3, 5, 3, 16), - (i) => AssertIsRegion(i.NameRegion, 10, 1, 10, 12)); + Assert.Equal(8, symbols.Count()); + Assert.All(symbols, (i) => + { + Assert.Equal("My-Function", i.SymbolName); + Assert.Equal(SymbolType.Function, i.SymbolType); + if (i.IsDeclaration) + { + Assert.Equal("function My-Function ($myInput)", i.DisplayString); + } + }); + Assert.Distinct(symbols); } [Fact] From 2f935175fc4b8ac6748a13ab4f675b009618e086 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 27 Jan 2023 11:06:28 -0800 Subject: [PATCH 214/327] Fix find enum member tests --- .../Symbols/Visitors/SymbolVisitor.cs | 11 ++--- .../FindsOccurrencesOnTypeSymbols.cs | 4 +- .../FindsReferencesOnTypeSymbols.cs | 4 +- .../Language/SymbolsServiceTests.cs | 42 ++++++++++++------- 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index 3f38c6d9b..474f7a4e6 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -167,21 +167,18 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { + // Enum members and properties are the "same" according to PowerShell, so the symbol name's + // must be the same since we can't distinguish them in VisitMemberExpression. SymbolType symbolType = propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum ? SymbolType.EnumMember : SymbolType.Property; - IScriptExtent nameExtent = VisitorUtils.GetNameExtent(propertyMemberAst); - string name = symbolType == SymbolType.EnumMember - ? propertyMemberAst.Name - : "$" + propertyMemberAst.Name; - return _action(new SymbolReference( symbolType, - name, + "$" + propertyMemberAst.Name, VisitorUtils.GetMemberOverloadName(propertyMemberAst), - nameExtent, + VisitorUtils.GetNameExtent(propertyMemberAst), propertyMemberAst.Extent, _file, isDeclaration: true)); diff --git a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs index 7c012bc7c..675dbe455 100644 --- a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs +++ b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs @@ -80,8 +80,8 @@ public static class FindsOccurrencesOnTypeSymbolsData public static readonly ScriptRegion EnumMemberSourceDetails = new( file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), text: string.Empty, - startLineNumber: 40, - startColumnNumber: 6, + startLineNumber: 45, + startColumnNumber: 16, startOffset: 0, endLineNumber: 0, endColumnNumber: 0, diff --git a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs index 268e68d27..ecd892610 100644 --- a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs +++ b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs @@ -60,8 +60,8 @@ public static class FindsReferencesOnTypeSymbolsData public static readonly ScriptRegion EnumMemberSourceDetails = new( file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"), text: string.Empty, - startLineNumber: 41, - startColumnNumber: 8, + startLineNumber: 45, + startColumnNumber: 16, startOffset: 0, endLineNumber: 0, endColumnNumber: 0, diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 86c763535..757950998 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -663,29 +663,43 @@ public void FindsOccurrencesOnProperty() public async Task FindsEnumMemberDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumMemberSourceDetails).ConfigureAwait(true); - Assert.Equal("Second", symbol.SymbolName); + Assert.Equal("$Second", symbol.SymbolName); + // Doesn't include [MyEnum]:: because that'd be redundant in the outline. Assert.Equal("Second", symbol.DisplayString); + Assert.Equal(SymbolType.EnumMember, symbol.SymbolType); Assert.True(symbol.IsDeclaration); AssertIsRegion(symbol.NameRegion, 41, 5, 41, 11); - Assert.Equal(41, symbol.ScriptRegion.StartLineNumber); + + symbol = await GetDefinition(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails).ConfigureAwait(true); + Assert.Equal("$First", symbol.SymbolName); + Assert.Equal("First", symbol.DisplayString); + Assert.Equal(SymbolType.EnumMember, symbol.SymbolType); + Assert.True(symbol.IsDeclaration); + AssertIsRegion(symbol.NameRegion, 40, 5, 40, 10); } [Fact] public async Task FindsReferencesOnEnumMember() { - List referencesResult = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails).ConfigureAwait(true); - Assert.Single(referencesResult); - Assert.Equal(41, referencesResult[0].ScriptRegion.StartLineNumber); - Assert.Equal(5, referencesResult[0].ScriptRegion.StartColumnNumber); - } + IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails).ConfigureAwait(true); + Assert.Collection(symbols, + (i) => + { + Assert.Equal("$First", i.SymbolName); + Assert.Equal("First", i.DisplayString); + Assert.Equal(SymbolType.EnumMember, i.SymbolType); + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("$First", i.SymbolName); + // The reference is just a member invocation, and so indistinguishable from a property. + Assert.Equal("(property) First", i.DisplayString); + Assert.Equal(SymbolType.Property, i.SymbolType); + Assert.False(i.IsDeclaration); + }); - [Fact] - public void FindsOccurrencesOnEnumMember() - { - IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumMemberSourceDetails); - Assert.Single(occurrencesResult); - Assert.Equal("MyEnum.First", occurrencesResult[occurrencesResult.Count - 1].SymbolName); - Assert.Equal(40, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber); + Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumMemberSourceDetails)); } [Fact] From fe29524aae10b55844ac90d165a5688deb6994bd Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 27 Jan 2023 11:06:41 -0800 Subject: [PATCH 215/327] Add symbol type equivalencies test --- .../Language/SymbolsServiceTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 757950998..3ddc5839f 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -607,6 +607,20 @@ public async Task FindsReferencesOnMethod() Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.MethodSourceDetails)); } + [Theory] + [InlineData(SymbolType.Class, SymbolType.Type)] + [InlineData(SymbolType.Enum, SymbolType.Type)] + [InlineData(SymbolType.EnumMember, SymbolType.Property)] + [InlineData(SymbolType.Variable, SymbolType.Parameter)] + internal void SymbolTypeEquivalencies(SymbolType left, SymbolType right) + { + // When checking if a symbol's type is the "same" we use this utility method which + // semantically equates the above theory, since for the purposes of narrowing down + // matching symbols, these types are equivalent. + Assert.NotEqual(left, right); + Assert.True(SymbolTypeUtils.SymbolTypeMatches(left, right)); + } + [Fact] public async Task FindsPropertyDefinition() { From f69694c41901ea02d11e0dfff828da67c2be690d Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 27 Jan 2023 11:24:25 -0800 Subject: [PATCH 216/327] Remove deprecated dot-source reference tests (and data) Let it be known that Patrick bets $500 that no one notices this. And to be fair, searching the whole workspace now works great. --- .../Definition/FindsDotSourcedFile.cs | 20 ----- ...sFunctionDefinitionInDotSourceReference.cs | 2 +- .../FindsFunctionDefinitionInWorkspace.cs | 20 ----- .../References/DotSources.ps1 | 15 ---- ...sReferencesOnFunctionMultiFileDotSource.cs | 33 -------- .../References/ReferenceFileA.ps1 | 9 --- .../References/ReferenceFileB.ps1 | 5 -- .../References/ReferenceFileC.ps1 | 4 - .../References/ReferenceFileD.ps1 | 1 - .../References/ReferenceFileE.ps1 | 4 - .../Language/SymbolsServiceTests.cs | 77 ++++++++----------- 11 files changed, 35 insertions(+), 155 deletions(-) delete mode 100644 test/PowerShellEditorServices.Test.Shared/Definition/FindsDotSourcedFile.cs delete mode 100644 test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInWorkspace.cs delete mode 100644 test/PowerShellEditorServices.Test.Shared/References/DotSources.ps1 delete mode 100644 test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs delete mode 100644 test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1 delete mode 100644 test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1 delete mode 100644 test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1 delete mode 100644 test/PowerShellEditorServices.Test.Shared/References/ReferenceFileD.ps1 delete mode 100644 test/PowerShellEditorServices.Test.Shared/References/ReferenceFileE.ps1 diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsDotSourcedFile.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsDotSourcedFile.cs deleted file mode 100644 index e69976d2c..000000000 --- a/test/PowerShellEditorServices.Test.Shared/Definition/FindsDotSourcedFile.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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 FindsDotSourcedFileData - { - public static readonly ScriptRegion SourceDetails = new( - file: TestUtilities.NormalizePath("References/DotSources.ps1"), - text: string.Empty, - startLineNumber: 1, - startColumnNumber: 3, - startOffset: 0, - endLineNumber: 0, - endColumnNumber: 0, - endOffset: 0); - } -} diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs index b7456faeb..842dec9b8 100644 --- a/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs +++ b/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs @@ -5,7 +5,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition { - public static class FindsFunctionDefinitionInDotSourceReferenceData + public static class FindsFunctionDefinitionInWorkspaceData { public static readonly ScriptRegion SourceDetails = new( file: TestUtilities.NormalizePath("References/FileWithReferences.ps1"), diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInWorkspace.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInWorkspace.cs deleted file mode 100644 index a88c143bf..000000000 --- a/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInWorkspace.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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 FindsFunctionDefinitionInWorkspaceData - { - public static readonly ScriptRegion SourceDetails = new( - file: TestUtilities.NormalizePath("References/ReferenceFileD.ps1"), - text: string.Empty, - startLineNumber: 1, - startColumnNumber: 2, - startOffset: 0, - endLineNumber: 0, - endColumnNumber: 0, - endOffset: 0); - } -} diff --git a/test/PowerShellEditorServices.Test.Shared/References/DotSources.ps1 b/test/PowerShellEditorServices.Test.Shared/References/DotSources.ps1 deleted file mode 100644 index 685183ea6..000000000 --- a/test/PowerShellEditorServices.Test.Shared/References/DotSources.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -. ./ReferenceFileE.ps1 -. "$PSScriptRoot/ReferenceFileE.ps1" -. "${PSScriptRoot}/ReferenceFileE.ps1" -. './ReferenceFileE.ps1' -. "./ReferenceFileE.ps1" -. .\ReferenceFileE.ps1 -. '.\ReferenceFileE.ps1' -. ".\ReferenceFileE.ps1" -. ReferenceFileE.ps1 -. 'ReferenceFileE.ps1' -. "ReferenceFileE.ps1" -. ./dir/../ReferenceFileE.ps1 -. ./invalidfile.ps1 -. "" -. $someVar diff --git a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs deleted file mode 100644 index 6a231ae60..000000000 --- a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.PowerShell.EditorServices.Services.TextDocument; - -namespace Microsoft.PowerShell.EditorServices.Test.Shared.References -{ - public static class FindsReferencesOnFunctionMultiFileDotSourceFileB - { - public static readonly ScriptRegion SourceDetails = new( - file: TestUtilities.NormalizePath("References/ReferenceFileB.ps1"), - text: string.Empty, - startLineNumber: 5, - startColumnNumber: 8, - startOffset: 0, - endLineNumber: 0, - endColumnNumber: 0, - endOffset: 0); - } - - public static class FindsReferencesOnFunctionMultiFileDotSourceFileC - { - public static readonly ScriptRegion SourceDetails = new( - file: TestUtilities.NormalizePath("References/ReferenceFileC.ps1"), - text: string.Empty, - startLineNumber: 4, - startColumnNumber: 10, - startOffset: 0, - endLineNumber: 0, - endColumnNumber: 0, - endOffset: 0); - } -} diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1 deleted file mode 100644 index 31ef35600..000000000 --- a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -. .\ReferenceFileA.ps1 -. ./ReferenceFileB.ps1 -. .\ReferenceFileC.ps1 - -function My-Function ($myInput) -{ - My-Function $myInput -} -Get-ChildItem diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1 deleted file mode 100644 index 980ed33da..000000000 --- a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -. "$PSScriptRoot\ReferenceFileC.ps1" - -Get-ChildItem - -My-Function "testb" diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1 deleted file mode 100644 index 6e1ee3131..000000000 --- a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -. ./ReferenceFileA.ps1 -Get-ChildItem - -My-Function "testc" diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileD.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileD.ps1 deleted file mode 100644 index 39aad7d37..000000000 --- a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileD.ps1 +++ /dev/null @@ -1 +0,0 @@ -My-FunctionInFileE "this is my function" \ No newline at end of file diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileE.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileE.ps1 deleted file mode 100644 index 2a3e9b2a9..000000000 --- a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileE.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -function My-FunctionInFileE -{ - -} \ No newline at end of file diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 3ddc5839f..598ff4597 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -193,17 +193,36 @@ await psesHost.ExecutePSCommandAsync( public async Task FindsReferencesOnFunction() { IEnumerable symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); - Assert.Equal(8, symbols.Count()); - Assert.All(symbols, (i) => + Assert.Collection(symbols, + (i) => { Assert.Equal("My-Function", i.SymbolName); + Assert.Equal("function My-Function ($myInput)", i.DisplayString); Assert.Equal(SymbolType.Function, i.SymbolType); - if (i.IsDeclaration) - { - Assert.Equal("function My-Function ($myInput)", i.DisplayString); - } + Assert.True(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("My-Function", i.SymbolName); + Assert.Equal("My-Function", i.DisplayString); + Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.EndsWith(FindsFunctionDefinitionInWorkspaceData.SourceDetails.File, i.FilePath); + Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("My-Function", i.SymbolName); + Assert.Equal("My-Function", i.DisplayString); + Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("My-Function", i.SymbolName); + Assert.Equal("My-Function", i.DisplayString); + Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.False(i.IsDeclaration); }); - Assert.Distinct(symbols); } [Fact] @@ -215,10 +234,10 @@ await psesHost.ExecutePSCommandAsync( CancellationToken.None).ConfigureAwait(true); IEnumerable symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true); - Assert.Equal(9, symbols.Count()); - Assert.Collection(symbols.Where((i) => i.FilePath.EndsWith(FindsReferencesOnFunctionData.SourceDetails.File)), + Assert.Collection(symbols, (i) => AssertIsRegion(i.NameRegion, 1, 10, 1, 21), + (i) => AssertIsRegion(i.NameRegion, 3, 1, 3, 12), (i) => AssertIsRegion(i.NameRegion, 3, 5, 3, 16), (i) => AssertIsRegion(i.NameRegion, 10, 1, 10, 12), // The alias. @@ -229,29 +248,15 @@ await psesHost.ExecutePSCommandAsync( }); } - [Fact] - public async Task FindsFunctionDefinitionsInWorkspace() - { - IEnumerable symbols = await GetDefinitions(FindsFunctionDefinitionInDotSourceReferenceData.SourceDetails).ConfigureAwait(true); - Assert.Collection(symbols.OrderBy((i) => i.FilePath), - (i) => - { - Assert.Equal("My-Function", i.SymbolName); - Assert.EndsWith("ReferenceFileA.ps1", i.FilePath); - }, - (i) => - { - Assert.Equal("My-Function", i.SymbolName); - Assert.EndsWith(FindsFunctionDefinitionData.SourceDetails.File, i.FilePath); - }); - } - [Fact] public async Task FindsFunctionDefinitionInWorkspace() { - SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionInWorkspaceData.SourceDetails).ConfigureAwait(true); - Assert.EndsWith("ReferenceFileE.ps1", symbol.FilePath); - Assert.Equal("My-FunctionInFileE", symbol.SymbolName); + IEnumerable symbols = await GetDefinitions(FindsFunctionDefinitionInWorkspaceData.SourceDetails).ConfigureAwait(true); + SymbolReference symbol = Assert.Single(symbols); + Assert.Equal("My-Function", symbol.SymbolName); + Assert.Equal("function My-Function ($myInput)", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); + Assert.EndsWith(FindsFunctionDefinitionData.SourceDetails.File, symbol.FilePath); } [Fact] @@ -716,20 +721,6 @@ public async Task FindsReferencesOnEnumMember() Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumMemberSourceDetails)); } - [Fact] - public async Task FindsReferencesOnFileWithReferencesFileB() - { - List referencesResult = await GetReferences(FindsReferencesOnFunctionMultiFileDotSourceFileB.SourceDetails).ConfigureAwait(true); - Assert.Equal(4, referencesResult.Count); - } - - [Fact] - public async Task FindsReferencesOnFileWithReferencesFileC() - { - List referencesResult = await GetReferences(FindsReferencesOnFunctionMultiFileDotSourceFileC.SourceDetails).ConfigureAwait(true); - Assert.Equal(4, referencesResult.Count); - } - [Fact] public async Task FindsDetailsForBuiltInCommand() { From aa6bfc73674eca1350955eacad3b4c977ec98e3f Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 27 Jan 2023 11:39:23 -0800 Subject: [PATCH 217/327] Fix `FindsDetailsForBuiltInCommand` test And delete "find details" for non-commands (as they all get their display strings set on creation now). --- .../FindsDetailsForTypeSymbols.cs | 50 ------------------- .../SymbolDetails/TypeSymbolDetails.ps1 | 23 --------- .../Language/SymbolsServiceTests.cs | 47 +---------------- 3 files changed, 1 insertion(+), 119 deletions(-) delete mode 100644 test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs delete mode 100644 test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 diff --git a/test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs deleted file mode 100644 index ff78a2c5a..000000000 --- a/test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForTypeSymbols.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.PowerShell.EditorServices.Services.TextDocument; - -namespace Microsoft.PowerShell.EditorServices.Test.Shared.SymbolDetails -{ - public static class FindsDetailsForTypeSymbolsData - { - public static readonly ScriptRegion EnumMemberSourceDetails = new( - file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), - text: string.Empty, - startLineNumber: 20, - startColumnNumber: 6, - startOffset: 0, - endLineNumber: 0, - endColumnNumber: 0, - endOffset: 0); - - public static readonly ScriptRegion PropertySourceDetails = new( - file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), - text: string.Empty, - startLineNumber: 6, - startColumnNumber: 18, - startOffset: 0, - endLineNumber: 0, - endColumnNumber: 0, - endOffset: 0); - - public static readonly ScriptRegion ConstructorSourceDetails = new( - file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), - text: string.Empty, - startLineNumber: 2, - startColumnNumber: 11, - startOffset: 0, - endLineNumber: 0, - endColumnNumber: 0, - endOffset: 0); - - public static readonly ScriptRegion MethodSourceDetails = new( - file: TestUtilities.NormalizePath("SymbolDetails/TypeSymbolDetails.ps1"), - text: string.Empty, - startLineNumber: 10, - startColumnNumber: 20, - startOffset: 0, - endLineNumber: 0, - endColumnNumber: 0, - endOffset: 0); - } -} diff --git a/test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 b/test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 deleted file mode 100644 index fd4a10a46..000000000 --- a/test/PowerShellEditorServices.Test.Shared/SymbolDetails/TypeSymbolDetails.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -class SuperClass { - SuperClass([string]$name) { - - } - - [string]$SomePropWithDefault = 'this is a default value' - - [int]$SomeProp - - [string]MyClassMethod([string]$param1, $param2, [int]$param3) { - $this.SomePropWithDefault = 'something happend' - return 'finished' - } -} - -New-Object SuperClass -$o = [SuperClass]::new() - -enum MyEnum { - First - Second - Third -} diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 598ff4597..5265dfb93 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -729,52 +729,7 @@ public async Task FindsDetailsForBuiltInCommand() FindsDetailsForBuiltInCommandData.SourceDetails.StartLineNumber, FindsDetailsForBuiltInCommandData.SourceDetails.StartColumnNumber).ConfigureAwait(true); - Assert.NotNull(symbolDetails.Documentation); - Assert.NotEqual("", symbolDetails.Documentation); - } - - [Fact] - public async Task FindsDetailsWithSignatureForEnumMember() - { - SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( - GetScriptFile(FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails), - FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails.StartLineNumber, - FindsDetailsForTypeSymbolsData.EnumMemberSourceDetails.StartColumnNumber).ConfigureAwait(true); - - Assert.Equal("MyEnum.First", symbolDetails.SymbolReference.DisplayString); - } - - [Fact] - public async Task FindsDetailsWithSignatureForProperty() - { - SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( - GetScriptFile(FindsDetailsForTypeSymbolsData.PropertySourceDetails), - FindsDetailsForTypeSymbolsData.PropertySourceDetails.StartLineNumber, - FindsDetailsForTypeSymbolsData.PropertySourceDetails.StartColumnNumber).ConfigureAwait(true); - - Assert.Equal("string SuperClass.SomePropWithDefault", symbolDetails.SymbolReference.DisplayString); - } - - [Fact] - public async Task FindsDetailsWithSignatureForConstructor() - { - SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( - GetScriptFile(FindsDetailsForTypeSymbolsData.ConstructorSourceDetails), - FindsDetailsForTypeSymbolsData.ConstructorSourceDetails.StartLineNumber, - FindsDetailsForTypeSymbolsData.ConstructorSourceDetails.StartColumnNumber).ConfigureAwait(true); - - Assert.Equal("SuperClass.SuperClass([string]$name)", symbolDetails.SymbolReference.DisplayString); - } - - [Fact] - public async Task FindsDetailsWithSignatureForMethod() - { - SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( - GetScriptFile(FindsDetailsForTypeSymbolsData.MethodSourceDetails), - FindsDetailsForTypeSymbolsData.MethodSourceDetails.StartLineNumber, - FindsDetailsForTypeSymbolsData.MethodSourceDetails.StartColumnNumber).ConfigureAwait(true); - - Assert.Equal("string SuperClass.MyClassMethod([string]$param1, $param2, [int]$param3)", symbolDetails.SymbolReference.DisplayString); + Assert.Equal("Gets the processes that are running on the local computer.", symbolDetails.Documentation); } [Fact] From 18290934bdfd086de0118921b57b5c3aa1f45e1e Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 27 Jan 2023 12:12:42 -0800 Subject: [PATCH 218/327] Fix the rest of the symbols service tests --- .../Language/SymbolsServiceTests.cs | 281 +++++++++--------- 1 file changed, 140 insertions(+), 141 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 5265dfb93..cd007dc96 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -737,51 +737,60 @@ public void FindsSymbolsInFile() { IEnumerable symbols = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails); - Assert.Equal(7, symbols.Count(symbolReference => symbolReference.SymbolType == SymbolType.Function)); - Assert.Equal(12, symbols.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable)); + Assert.Equal(7, symbols.Count(i => i.SymbolType == SymbolType.Function)); + Assert.Equal(8, symbols.Count(i => i.SymbolType == SymbolType.Variable)); + Assert.Equal(4, symbols.Count(i => i.SymbolType == SymbolType.Parameter)); + Assert.Equal(12, symbols.Count(i => SymbolTypeUtils.SymbolTypeMatches(SymbolType.Variable, i.SymbolType))); + + SymbolReference symbol = symbols.First(i => i.SymbolType == SymbolType.Function); + Assert.Equal("AFunction", symbol.SymbolName); + Assert.Equal("function AFunction ()", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); - SymbolReference firstFunctionSymbol = symbols.First(r => r.SymbolType == SymbolType.Function); - Assert.Equal("AFunction", firstFunctionSymbol.SymbolName); - AssertIsRegion(firstFunctionSymbol.NameRegion, 7, 10, 7, 19); + symbol = symbols.First(i => i.SymbolName == "AFilter"); + Assert.Equal("filter AFilter ()", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); - SymbolReference lastVariableSymbol = symbols.Last(r => r.SymbolType == SymbolType.Variable); - Assert.Equal("$param3", lastVariableSymbol.SymbolName); - AssertIsRegion(lastVariableSymbol.NameRegion, 32, 50, 32, 57); + symbol = symbols.Last(i => i.SymbolType == SymbolType.Variable); + Assert.Equal("$nestedVar", symbol.SymbolName); + Assert.Equal("$nestedVar", symbol.DisplayString); + Assert.False(symbol.IsDeclaration); + AssertIsRegion(symbol.NameRegion, 16, 29, 16, 39); - SymbolReference firstWorkflowSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Workflow)); - Assert.Equal("AWorkflow", firstWorkflowSymbol.SymbolName); - AssertIsRegion(firstWorkflowSymbol.NameRegion, 23, 10, 23, 19); + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Workflow)); + Assert.Equal("AWorkflow", symbol.SymbolName); + Assert.Equal("workflow AWorkflow ()", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); - SymbolReference firstClassSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); - Assert.Equal("AClass", firstClassSymbol.SymbolName); - AssertIsRegion(firstClassSymbol.NameRegion, 25, 7, 25, 13); + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Class)); + Assert.Equal("AClass", symbol.SymbolName); + Assert.Equal("class AClass { }", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); - SymbolReference firstPropertySymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); - Assert.Equal("AClass.AProperty", firstPropertySymbol.SymbolName); - AssertIsRegion(firstPropertySymbol.NameRegion, 26, 13, 26, 23); + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Property)); + Assert.Equal("$AProperty", symbol.SymbolName); + Assert.Equal("[string] $AProperty", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); - SymbolReference firstConstructorSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); - Assert.Equal("AClass.AClass([string]$AParameter)", firstConstructorSymbol.SymbolName); - AssertIsRegion(firstConstructorSymbol.NameRegion, 28, 5, 28, 11); + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Constructor)); + Assert.Equal("AClass", symbol.SymbolName); + Assert.Equal("AClass([string]$AParameter)", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); - SymbolReference firstMethodSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); - Assert.Equal("AClass.AMethod([string]$param1, [int]$param2, $param3)", firstMethodSymbol.SymbolName); - AssertIsRegion(firstMethodSymbol.NameRegion, 32, 11, 32, 18); + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Method)); + Assert.Equal("AMethod", symbol.SymbolName); + Assert.Equal("void AMethod([string]$param1, [int]$param2, $param3)", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); - SymbolReference firstEnumSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); - Assert.Equal("AEnum", firstEnumSymbol.SymbolName); - AssertIsRegion(firstEnumSymbol.NameRegion, 37, 6, 37, 11); + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Enum)); + Assert.Equal("AEnum", symbol.SymbolName); + Assert.Equal("enum AEnum { }", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); - SymbolReference firstEnumMemberSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); - Assert.Equal("AEnum.AValue", firstEnumMemberSymbol.SymbolName); - AssertIsRegion(firstEnumMemberSymbol.NameRegion, 38, 5, 38, 11); + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.EnumMember)); + Assert.Equal("$AValue", symbol.SymbolName); + Assert.Equal("AValue", symbol.DisplayString); + Assert.True(symbol.IsDeclaration); } [Fact] @@ -789,47 +798,41 @@ public void FindsSymbolsWithNewLineInFile() { IEnumerable symbols = FindSymbolsInFile(FindSymbolsInNewLineSymbolFile.SourceDetails); - SymbolReference firstFunctionSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Function)); - Assert.Equal("returnTrue", firstFunctionSymbol.SymbolName); - AssertIsRegion(firstFunctionSymbol.NameRegion, 2, 1, 2, 11); - AssertIsRegion(firstFunctionSymbol.ScriptRegion, 1, 1, 4, 2); - - SymbolReference firstClassSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Class)); - Assert.Equal("NewLineClass", firstClassSymbol.SymbolName); - AssertIsRegion(firstClassSymbol.NameRegion, 7, 1, 7, 13); - AssertIsRegion(firstClassSymbol.ScriptRegion, 6, 1, 23, 2); - - SymbolReference firstConstructorSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Constructor)); - Assert.Equal("NewLineClass.NewLineClass()", firstConstructorSymbol.SymbolName); - AssertIsRegion(firstConstructorSymbol.NameRegion, 8, 5, 8, 17); - AssertIsRegion(firstConstructorSymbol.ScriptRegion, 8, 5, 10, 6); - - SymbolReference firstPropertySymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Property)); - Assert.Equal("NewLineClass.SomePropWithDefault", firstPropertySymbol.SymbolName); - AssertIsRegion(firstPropertySymbol.NameRegion, 15, 5, 15, 25); - AssertIsRegion(firstPropertySymbol.ScriptRegion, 12, 5, 15, 40); - - SymbolReference firstMethodSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Method)); - Assert.Equal("NewLineClass.MyClassMethod([MyNewLineEnum]$param1)", firstMethodSymbol.SymbolName); - AssertIsRegion(firstMethodSymbol.NameRegion, 20, 5, 20, 18); - AssertIsRegion(firstMethodSymbol.ScriptRegion, 17, 5, 22, 6); - - SymbolReference firstEnumSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.Enum)); - Assert.Equal("MyNewLineEnum", firstEnumSymbol.SymbolName); - AssertIsRegion(firstEnumSymbol.NameRegion, 26, 1, 26, 14); - AssertIsRegion(firstEnumSymbol.ScriptRegion, 25, 1, 28, 2); - - SymbolReference firstEnumMemberSymbol = - Assert.Single(symbols.Where(symbolReference => symbolReference.SymbolType == SymbolType.EnumMember)); - Assert.Equal("MyNewLineEnum.First", firstEnumMemberSymbol.SymbolName); - AssertIsRegion(firstEnumMemberSymbol.NameRegion, 27, 5, 27, 10); - AssertIsRegion(firstEnumMemberSymbol.ScriptRegion, 27, 5, 27, 10); + SymbolReference symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Function)); + Assert.Equal("returnTrue", symbol.SymbolName); + AssertIsRegion(symbol.NameRegion, 2, 1, 2, 11); + AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); + + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Class)); + Assert.Equal("NewLineClass", symbol.SymbolName); + AssertIsRegion(symbol.NameRegion, 7, 1, 7, 13); + AssertIsRegion(symbol.ScriptRegion, 6, 1, 23, 2); + + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Constructor)); + Assert.Equal("NewLineClass", symbol.SymbolName); + AssertIsRegion(symbol.NameRegion, 8, 5, 8, 17); + AssertIsRegion(symbol.ScriptRegion, 8, 5, 10, 6); + + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Property)); + Assert.Equal("$SomePropWithDefault", symbol.SymbolName); + AssertIsRegion(symbol.NameRegion, 15, 5, 15, 25); + AssertIsRegion(symbol.ScriptRegion, 12, 5, 15, 40); + + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Method)); + Assert.Equal("MyClassMethod", symbol.SymbolName); + Assert.Equal("string MyClassMethod([MyNewLineEnum]$param1)", symbol.DisplayString); + AssertIsRegion(symbol.NameRegion, 20, 5, 20, 18); + AssertIsRegion(symbol.ScriptRegion, 17, 5, 22, 6); + + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Enum)); + Assert.Equal("MyNewLineEnum", symbol.SymbolName); + AssertIsRegion(symbol.NameRegion, 26, 1, 26, 14); + AssertIsRegion(symbol.ScriptRegion, 25, 1, 28, 2); + + symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.EnumMember)); + Assert.Equal("$First", symbol.SymbolName); + AssertIsRegion(symbol.NameRegion, 27, 5, 27, 10); + AssertIsRegion(symbol.ScriptRegion, 27, 5, 27, 10); } [SkippableFact] @@ -837,75 +840,71 @@ public void FindsSymbolsInDSCFile() { Skip.If(!s_isWindows, "DSC only works properly on Windows."); - IEnumerable symbolsResult = FindSymbolsInFile(FindSymbolsInDSCFile.SourceDetails); - - Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Configuration)); - SymbolReference firstConfigurationSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Configuration); - Assert.Equal("AConfiguration", firstConfigurationSymbol.SymbolName); - Assert.Equal(2, firstConfigurationSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(15, firstConfigurationSymbol.ScriptRegion.StartColumnNumber); + IEnumerable symbols = FindSymbolsInFile(FindSymbolsInDSCFile.SourceDetails); + SymbolReference symbol = Assert.Single(symbols, i => i.SymbolType == SymbolType.Configuration); + Assert.Equal("AConfiguration", symbol.SymbolName); + Assert.Equal(2, symbol.ScriptRegion.StartLineNumber); + Assert.Equal(15, symbol.ScriptRegion.StartColumnNumber); } [Fact] public void FindsSymbolsInPesterFile() { - IEnumerable symbolsResult = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails).OfType(); - Assert.Equal(12, symbolsResult.Count(r => r.SymbolType == SymbolType.Function)); - - Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.Describe)); - SymbolReference firstDescribeSymbol = symbolsResult.First(r => r.Command == PesterCommandType.Describe); - Assert.Equal("Describe \"Testing Pester symbols\"", firstDescribeSymbol.SymbolName); - Assert.Equal(9, firstDescribeSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstDescribeSymbol.ScriptRegion.StartColumnNumber); - - Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.Context)); - SymbolReference firstContextSymbol = symbolsResult.First(r => r.Command == PesterCommandType.Context); - Assert.Equal("Context \"When a Pester file is given\"", firstContextSymbol.SymbolName); - Assert.Equal(10, firstContextSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(5, firstContextSymbol.ScriptRegion.StartColumnNumber); - - Assert.Equal(4, symbolsResult.Count(r => r.Command == PesterCommandType.It)); - SymbolReference lastItSymbol = symbolsResult.Last(r => r.Command == PesterCommandType.It); - Assert.Equal("It \"Should return setup and teardown symbols\"", lastItSymbol.SymbolName); - Assert.Equal(31, lastItSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(9, lastItSymbol.ScriptRegion.StartColumnNumber); - - Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeDiscovery)); - SymbolReference firstBeforeDiscoverySymbol = symbolsResult.First(r => r.Command == PesterCommandType.BeforeDiscovery); - Assert.Equal("BeforeDiscovery", firstBeforeDiscoverySymbol.SymbolName); - Assert.Equal(1, firstBeforeDiscoverySymbol.ScriptRegion.StartLineNumber); - Assert.Equal(1, firstBeforeDiscoverySymbol.ScriptRegion.StartColumnNumber); - - Assert.Equal(2, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeAll)); - SymbolReference lastBeforeAllSymbol = symbolsResult.Last(r => r.Command == PesterCommandType.BeforeAll); - Assert.Equal("BeforeAll", lastBeforeAllSymbol.SymbolName); - Assert.Equal(11, lastBeforeAllSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(9, lastBeforeAllSymbol.ScriptRegion.StartColumnNumber); - - Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeEach)); - SymbolReference firstBeforeEachSymbol = symbolsResult.First(r => r.Command == PesterCommandType.BeforeEach); - Assert.Equal("BeforeEach", firstBeforeEachSymbol.SymbolName); - Assert.Equal(15, firstBeforeEachSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(9, firstBeforeEachSymbol.ScriptRegion.StartColumnNumber); - - Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.AfterEach)); - SymbolReference firstAfterEachSymbol = symbolsResult.First(r => r.Command == PesterCommandType.AfterEach); - Assert.Equal("AfterEach", firstAfterEachSymbol.SymbolName); - Assert.Equal(35, firstAfterEachSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(9, firstAfterEachSymbol.ScriptRegion.StartColumnNumber); - - Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.AfterAll)); - SymbolReference firstAfterAllSymbol = symbolsResult.First(r => r.Command == PesterCommandType.AfterAll); - Assert.Equal("AfterAll", firstAfterAllSymbol.SymbolName); - Assert.Equal(40, firstAfterAllSymbol.ScriptRegion.StartLineNumber); - Assert.Equal(5, firstAfterAllSymbol.ScriptRegion.StartColumnNumber); - } - - [Fact] - public void LangServerFindsSymbolsInPSDFile() - { - IEnumerable symbolsResult = FindSymbolsInFile(FindSymbolsInPSDFile.SourceDetails); - Assert.Equal(3, symbolsResult.Count()); + IEnumerable symbols = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails).OfType(); + Assert.Equal(12, symbols.Count(i => i.SymbolType == SymbolType.Function)); + + SymbolReference symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.Describe); + Assert.Equal("Describe \"Testing Pester symbols\"", symbol.SymbolName); + Assert.Equal(9, symbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, symbol.ScriptRegion.StartColumnNumber); + + symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.Context); + Assert.Equal("Context \"When a Pester file is given\"", symbol.SymbolName); + Assert.Equal(10, symbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, symbol.ScriptRegion.StartColumnNumber); + + Assert.Equal(4, symbols.Count(i => i.Command == PesterCommandType.It)); + symbol = symbols.Last(i => i.Command == PesterCommandType.It); + Assert.Equal("It \"Should return setup and teardown symbols\"", symbol.SymbolName); + Assert.Equal(31, symbol.ScriptRegion.StartLineNumber); + Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber); + + symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.BeforeDiscovery); + Assert.Equal("BeforeDiscovery", symbol.SymbolName); + Assert.Equal(1, symbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, symbol.ScriptRegion.StartColumnNumber); + + Assert.Equal(2, symbols.Count(i => i.Command == PesterCommandType.BeforeAll)); + symbol = symbols.Last(i => i.Command == PesterCommandType.BeforeAll); + Assert.Equal("BeforeAll", symbol.SymbolName); + Assert.Equal(11, symbol.ScriptRegion.StartLineNumber); + Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber); + + symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.BeforeEach); + Assert.Equal("BeforeEach", symbol.SymbolName); + Assert.Equal(15, symbol.ScriptRegion.StartLineNumber); + Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber); + + symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.AfterEach); + Assert.Equal("AfterEach", symbol.SymbolName); + Assert.Equal(35, symbol.ScriptRegion.StartLineNumber); + Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber); + + symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.AfterAll); + Assert.Equal("AfterAll", symbol.SymbolName); + Assert.Equal(40, symbol.ScriptRegion.StartLineNumber); + Assert.Equal(5, symbol.ScriptRegion.StartColumnNumber); + } + + [Fact] + public void FindsSymbolsInPSDFile() + { + IEnumerable symbols = FindSymbolsInFile(FindSymbolsInPSDFile.SourceDetails); + Assert.All(symbols, i => Assert.Equal(SymbolType.HashtableKey, i.SymbolType)); + Assert.Collection(symbols, + i => Assert.Equal("property1", i.SymbolName), + i => Assert.Equal("property2", i.SymbolName), + i => Assert.Equal("property3", i.SymbolName)); } [Fact] From 6a21c57aa16915126e2a9e32b88ce0d7049ee974 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 27 Jan 2023 12:22:16 -0800 Subject: [PATCH 219/327] Run `FindsDetailsForBuiltInCommand` only on macOS --- .../Language/SymbolsServiceTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index cd007dc96..395d0cfd3 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -22,6 +22,7 @@ using Microsoft.PowerShell.EditorServices.Test.Shared.References; using Microsoft.PowerShell.EditorServices.Test.Shared.SymbolDetails; using Microsoft.PowerShell.EditorServices.Test.Shared.Symbols; +using Microsoft.PowerShell.EditorServices.Utility; using Xunit; namespace PowerShellEditorServices.Test.Language @@ -721,9 +722,10 @@ public async Task FindsReferencesOnEnumMember() Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumMemberSourceDetails)); } - [Fact] + [SkippableFact] public async Task FindsDetailsForBuiltInCommand() { + Skip.IfNot(VersionUtils.IsMacOS, "macOS gets the right synopsis but others don't."); SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( GetScriptFile(FindsDetailsForBuiltInCommandData.SourceDetails), FindsDetailsForBuiltInCommandData.SourceDetails.StartLineNumber, From 6d8b0bac6b53f94afaae26639f04c2df71d4059a Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 27 Jan 2023 12:41:44 -0800 Subject: [PATCH 220/327] Skip `FindsSymbolsInDSCFile` as DSC symbols don't yet work The visitor exists but it's not called, probably a module-import issue. --- .../Language/SymbolsServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 395d0cfd3..8214ef6eb 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -837,7 +837,7 @@ public void FindsSymbolsWithNewLineInFile() AssertIsRegion(symbol.ScriptRegion, 27, 5, 27, 10); } - [SkippableFact] + [Fact(Skip="DSC symbols don't work yet.")] public void FindsSymbolsInDSCFile() { Skip.If(!s_isWindows, "DSC only works properly on Windows."); From fd04c34d577424c9c8147337eb37f31182f32081 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 27 Jan 2023 18:33:10 -0800 Subject: [PATCH 221/327] Mark some methods internal Co-authored-by: Patrick Meinecke --- .../Services/Symbols/SymbolsService.cs | 2 +- .../Services/TextDocument/ScriptRegion.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 095426423..049b847be 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -293,7 +293,7 @@ public async Task> GetDefinitionOfSymbolAsync( { List declarations = new(); declarations.AddRange(scriptFile.References.TryGetReferences(symbol).Where(i => i.IsDeclaration)); - if (declarations.Any()) + if (declarations.Count > 0) { _logger.LogDebug($"Found possible declaration in same file ${declarations}"); return declarations; diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs index 0732c4a3b..9f926cbea 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.TextDocument /// public sealed class ScriptRegion : IScriptExtent { - public TextEdit ToTextEdit() => new() { NewText = Text, Range = ToRange() }; + internal TextEdit ToTextEdit() => new() { NewText = Text, Range = ToRange() }; public Range ToRange() { @@ -34,7 +34,7 @@ public Range ToRange() } // Same as PowerShell's EmptyScriptExtent - public bool IsEmpty() + internal bool IsEmpty() { return StartLineNumber == 0 && StartColumnNumber == 0 && EndLineNumber == 0 && EndColumnNumber == 0 @@ -43,7 +43,7 @@ public bool IsEmpty() } // Do not use PowerShell's ContainsLineAndColumn, it's nonsense. - public bool ContainsPosition(int line, int column) + internal bool ContainsPosition(int line, int column) { return StartLineNumber <= line && line <= EndLineNumber && StartColumnNumber <= column && column <= EndColumnNumber; From 301006554ba960335b58458aef263066379c5d8d Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 30 Jan 2023 16:47:02 -0800 Subject: [PATCH 222/327] Rename `SymbolReference` fields --- .../CodeLens/ReferencesCodeLensProvider.cs | 2 +- .../Services/Symbols/ReferenceTable.cs | 6 +- .../Services/Symbols/SymbolDetails.cs | 6 +- .../Services/Symbols/SymbolReference.cs | 26 +- .../Services/Symbols/SymbolsService.cs | 14 +- .../Handlers/DocumentSymbolHandler.cs | 6 +- .../TextDocument/Handlers/HoverHandler.cs | 2 +- .../Handlers/WorkspaceSymbolsHandler.cs | 18 +- .../Language/SymbolsServiceTests.cs | 414 +++++++++--------- .../Services/Symbols/AstOperationsTests.cs | 2 +- 10 files changed, 248 insertions(+), 248 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index da717d5c3..6e1e4cc81 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -62,7 +62,7 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken can cancellationToken.ThrowIfCancellationRequested(); // TODO: Can we support more here? if (symbol.IsDeclaration && - symbol.SymbolType is + symbol.Type is SymbolType.Function or SymbolType.Class or SymbolType.Enum) diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 0062d3fff..c421953f4 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -46,8 +46,8 @@ internal IEnumerable TryGetReferences(SymbolReference? symbol) { EnsureInitialized(); return symbol is not null - && _symbolReferences.TryGetValue(symbol.SymbolName, out ConcurrentBag? bag) - ? bag.Where(i => SymbolTypeUtils.SymbolTypeMatches(symbol.SymbolType, i.SymbolType)) + && _symbolReferences.TryGetValue(symbol.Id, out ConcurrentBag? bag) + ? bag.Where(i => SymbolTypeUtils.SymbolTypeMatches(symbol.Type, i.Type)) : Enumerable.Empty(); } @@ -90,7 +90,7 @@ private AstVisitAction AddReference(SymbolReference symbol) } _symbolReferences.AddOrUpdate( - symbol.SymbolName, + symbol.Id, _ => new ConcurrentBag { symbol }, (_, existing) => { diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index 0cf42dc0f..ddca0f0a7 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -44,10 +44,10 @@ internal static async Task CreateAsync( SymbolReference = symbolReference }; - if (symbolReference.SymbolType is SymbolType.Function) + if (symbolReference.Type is SymbolType.Function) { CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( - symbolReference.SymbolName, + symbolReference.Id, currentRunspace, executionService).ConfigureAwait(false); @@ -60,7 +60,7 @@ await CommandHelpers.GetCommandSynopsisAsync( if (commandInfo.CommandType == CommandTypes.Application) { - symbolDetails.SymbolReference = symbolReference with { DisplayString = $"(application) ${symbolReference.DisplayString}" }; + symbolDetails.SymbolReference = symbolReference with { Name = $"(application) ${symbolReference.Name}" }; } } } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index 732846f1c..2c6c6ef8f 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -12,14 +12,14 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols /// /// A class that holds the type, name, script extent, and source line of a symbol /// - [DebuggerDisplay("SymbolType = {SymbolType}, SymbolName = {SymbolName}")] + [DebuggerDisplay("Type = {Type}, Id = {Id}, Name = {Name}")] internal record SymbolReference { - public SymbolType SymbolType { get; init; } + public SymbolType Type { get; init; } - public string SymbolName { get; init; } + public string Id { get; init; } - public string DisplayString { get; init; } + public string Name { get; init; } public ScriptRegion NameRegion { get; init; } @@ -34,25 +34,25 @@ internal record SymbolReference /// /// Constructs and instance of a SymbolReference /// - /// The higher level type of the symbol - /// The name of the symbol - /// The string used by outline, hover, etc. + /// The higher level type of the symbol + /// The name of the symbol + /// The string used by outline, hover, etc. /// The extent of the symbol's name /// The script extent of the symbol /// The script file that has the symbol /// True if this reference is the definition of the symbol public SymbolReference( - SymbolType symbolType, - string symbolName, - string displayString, + SymbolType type, + string id, + string name, IScriptExtent nameExtent, IScriptExtent scriptExtent, ScriptFile file, bool isDeclaration) { - SymbolType = symbolType; - SymbolName = symbolName; - DisplayString = displayString; + Type = type; + Id = id; + Name = name; NameRegion = new(nameExtent); ScriptRegion = new(scriptExtent); FilePath = file.FilePath; diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 049b847be..cf4040642 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -170,9 +170,9 @@ public async Task> ScanForReferencesOfSymbolAsync( _executionService, cancellationToken).ConfigureAwait(false); - string targetName = symbol.SymbolName; - if (symbol.SymbolType is SymbolType.Function - && aliases.AliasToCmdlets.TryGetValue(symbol.SymbolName, out string aliasDefinition)) + string targetName = symbol.Id; + if (symbol.Type is SymbolType.Function + && aliases.AliasToCmdlets.TryGetValue(symbol.Id, out string aliasDefinition)) { targetName = aliasDefinition; } @@ -180,7 +180,7 @@ public async Task> ScanForReferencesOfSymbolAsync( await ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false); List symbols = new(); - string[] allIdentifiers = GetIdentifiers(targetName, symbol.SymbolType, aliases); + string[] allIdentifiers = GetIdentifiers(targetName, symbol.Type, aliases); foreach (ScriptFile file in _workspaceService.GetOpenedFiles()) { @@ -188,7 +188,7 @@ public async Task> ScanForReferencesOfSymbolAsync( { await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); - symbols.AddRange(file.References.TryGetReferences(symbol with { SymbolName = targetIdentifier })); + symbols.AddRange(file.References.TryGetReferences(symbol with { Id = targetIdentifier })); } } @@ -239,7 +239,7 @@ public static IEnumerable FindOccurrencesInFile( // If we are not possibly looking at a Function, we don't // need to continue because we won't be able to get the // CommandInfo object. - if (symbol?.SymbolType is not SymbolType.Function + if (symbol?.Type is not SymbolType.Function and not SymbolType.Unknown) { return null; @@ -247,7 +247,7 @@ public static IEnumerable FindOccurrencesInFile( CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( - symbol.SymbolName, + symbol.Id, _runspaceContext.CurrentRunspace, _executionService).ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index 307d27b73..823d01f26 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -69,7 +69,7 @@ public override async Task Handle(Do // // TODO: We should also include function invocations that are part of DSLs (like // Invoke-Build etc.). - if (!r.IsDeclaration || r.SymbolType is SymbolType.Parameter) + if (!r.IsDeclaration || r.Type is SymbolType.Parameter) { continue; } @@ -88,13 +88,13 @@ public override async Task Handle(Do symbols.Add(new SymbolInformationOrDocumentSymbol(new SymbolInformation { ContainerName = containerName, - Kind = SymbolTypeUtils.GetSymbolKind(r.SymbolType), + Kind = SymbolTypeUtils.GetSymbolKind(r.Type), Location = new Location { Uri = DocumentUri.From(r.FilePath), Range = new Range(r.NameRegion.ToRange().Start, r.ScriptRegion.ToRange().End) // Jump to name start, but keep whole range to support symbol tree in outline }, - Name = r.DisplayString + Name = r.Name })); } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs index b0cc4515c..567da0159 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs @@ -59,7 +59,7 @@ await _symbolsService.FindSymbolDetailsAtLocationAsync( List symbolInfo = new() { - new MarkedString("PowerShell", symbolDetails.SymbolReference.DisplayString) + new MarkedString("PowerShell", symbolDetails.SymbolReference.Name) }; if (!string.IsNullOrEmpty(symbolDetails.Documentation)) diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 8d0d0bf46..0e89eace9 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -44,30 +44,30 @@ public override async Task> Handle(WorkspaceSymbolP // TODO: Need to compute a relative path that is based on common path for all workspace files string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); - foreach (SymbolReference foundOccurrence in foundSymbols) + foreach (SymbolReference symbol in foundSymbols) { // This async method is pretty dense with synchronous code // so it's helpful to add some yields. await Task.Yield(); cancellationToken.ThrowIfCancellationRequested(); - if (!foundOccurrence.IsDeclaration) + if (!symbol.IsDeclaration) { continue; } - if (foundOccurrence.SymbolType is SymbolType.Parameter) + if (symbol.Type is SymbolType.Parameter) { continue; } - if (!IsQueryMatch(request.Query, foundOccurrence.SymbolName)) + if (!IsQueryMatch(request.Query, symbol.Id)) { continue; } // Exclude Pester setup/teardown symbols as they're unnamed - if (foundOccurrence is PesterSymbolReference pesterSymbol && + if (symbol is PesterSymbolReference pesterSymbol && !PesterSymbolReference.IsPesterTestCommand(pesterSymbol.Command)) { continue; @@ -75,17 +75,17 @@ public override async Task> Handle(WorkspaceSymbolP Location location = new() { - Uri = DocumentUri.From(foundOccurrence.FilePath), - Range = foundOccurrence.NameRegion.ToRange() + Uri = DocumentUri.From(symbol.FilePath), + Range = symbol.NameRegion.ToRange() }; // TODO: This should be a WorkplaceSymbol now as SymbolInformation is deprecated. symbols.Add(new SymbolInformation { ContainerName = containerName, - Kind = SymbolTypeUtils.GetSymbolKind(foundOccurrence.SymbolType), + Kind = SymbolTypeUtils.GetSymbolKind(symbol.Type), Location = location, - Name = foundOccurrence.DisplayString + Name = symbol.Name }); } } diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 8214ef6eb..1b23b0663 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -165,9 +165,9 @@ public async Task FindsCommandForParamHintsWithSpaces() public async Task FindsFunctionDefinition() { SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionData.SourceDetails).ConfigureAwait(true); - Assert.Equal("My-Function", symbol.SymbolName); - Assert.Equal("function My-Function ($myInput)", symbol.DisplayString); - Assert.Equal(SymbolType.Function, symbol.SymbolType); + Assert.Equal("My-Function", symbol.Id); + Assert.Equal("function My-Function ($myInput)", symbol.Name); + Assert.Equal(SymbolType.Function, symbol.Type); AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); Assert.True(symbol.IsDeclaration); @@ -183,8 +183,8 @@ await psesHost.ExecutePSCommandAsync( CancellationToken.None).ConfigureAwait(true); SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionOfAliasData.SourceDetails).ConfigureAwait(true); - Assert.Equal("function My-Function ($myInput)", symbol.DisplayString); - Assert.Equal(SymbolType.Function, symbol.SymbolType); + Assert.Equal("function My-Function ($myInput)", symbol.Name); + Assert.Equal(SymbolType.Function, symbol.Type); AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); Assert.True(symbol.IsDeclaration); @@ -197,31 +197,31 @@ public async Task FindsReferencesOnFunction() Assert.Collection(symbols, (i) => { - Assert.Equal("My-Function", i.SymbolName); - Assert.Equal("function My-Function ($myInput)", i.DisplayString); - Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.Equal("My-Function", i.Id); + Assert.Equal("function My-Function ($myInput)", i.Name); + Assert.Equal(SymbolType.Function, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("My-Function", i.SymbolName); - Assert.Equal("My-Function", i.DisplayString); - Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.Equal("My-Function", i.Id); + Assert.Equal("My-Function", i.Name); + Assert.Equal(SymbolType.Function, i.Type); Assert.EndsWith(FindsFunctionDefinitionInWorkspaceData.SourceDetails.File, i.FilePath); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("My-Function", i.SymbolName); - Assert.Equal("My-Function", i.DisplayString); - Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.Equal("My-Function", i.Id); + Assert.Equal("My-Function", i.Name); + Assert.Equal(SymbolType.Function, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("My-Function", i.SymbolName); - Assert.Equal("My-Function", i.DisplayString); - Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.Equal("My-Function", i.Id); + Assert.Equal("My-Function", i.Name); + Assert.Equal(SymbolType.Function, i.Type); Assert.False(i.IsDeclaration); }); } @@ -245,7 +245,7 @@ await psesHost.ExecutePSCommandAsync( (i) => { AssertIsRegion(i.NameRegion, 20, 1, 20, 9); - Assert.Equal("My-Alias", i.SymbolName); + Assert.Equal("My-Alias", i.Id); }); } @@ -254,8 +254,8 @@ public async Task FindsFunctionDefinitionInWorkspace() { IEnumerable symbols = await GetDefinitions(FindsFunctionDefinitionInWorkspaceData.SourceDetails).ConfigureAwait(true); SymbolReference symbol = Assert.Single(symbols); - Assert.Equal("My-Function", symbol.SymbolName); - Assert.Equal("function My-Function ($myInput)", symbol.DisplayString); + Assert.Equal("My-Function", symbol.Id); + Assert.Equal("function My-Function ($myInput)", symbol.Name); Assert.True(symbol.IsDeclaration); Assert.EndsWith(FindsFunctionDefinitionData.SourceDetails.File, symbol.FilePath); } @@ -264,9 +264,9 @@ public async Task FindsFunctionDefinitionInWorkspace() public async Task FindsVariableDefinition() { SymbolReference symbol = await GetDefinition(FindsVariableDefinitionData.SourceDetails).ConfigureAwait(true); - Assert.Equal("$things", symbol.SymbolName); - Assert.Equal("$things", symbol.DisplayString); - Assert.Equal(SymbolType.Variable, symbol.SymbolType); + Assert.Equal("$things", symbol.Id); + Assert.Equal("$things", symbol.Name); + Assert.Equal(SymbolType.Variable, symbol.Type); Assert.True(symbol.IsDeclaration); AssertIsRegion(symbol.NameRegion, 6, 1, 6, 8); } @@ -278,20 +278,20 @@ public async Task FindsReferencesOnVariable() Assert.Collection(symbols, (i) => { - Assert.Equal("$things", i.SymbolName); - Assert.Equal(SymbolType.Variable, i.SymbolType); + Assert.Equal("$things", i.Id); + Assert.Equal(SymbolType.Variable, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("$things", i.SymbolName); - Assert.Equal(SymbolType.Variable, i.SymbolType); + Assert.Equal("$things", i.Id); + Assert.Equal(SymbolType.Variable, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("$things", i.SymbolName); - Assert.Equal(SymbolType.Variable, i.SymbolType); + Assert.Equal("$things", i.Id); + Assert.Equal(SymbolType.Variable, i.Type); Assert.False(i.IsDeclaration); }); @@ -305,20 +305,20 @@ public void FindsOccurrencesOnFunction() Assert.Collection(symbols, (i) => { - Assert.Equal("My-Function", i.SymbolName); - Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.Equal("My-Function", i.Id); + Assert.Equal(SymbolType.Function, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("My-Function", i.SymbolName); - Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.Equal("My-Function", i.Id); + Assert.Equal(SymbolType.Function, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("My-Function", i.SymbolName); - Assert.Equal(SymbolType.Function, i.SymbolType); + Assert.Equal("My-Function", i.Id); + Assert.Equal(SymbolType.Function, i.Type); Assert.False(i.IsDeclaration); }); } @@ -330,17 +330,17 @@ public void FindsOccurrencesOnParameter() Assert.Collection(symbols, (i) => { - Assert.Equal("$myInput", i.SymbolName); + Assert.Equal("$myInput", i.Id); // TODO: Parameter display strings need work. - Assert.Equal("(parameter) [System.Object]$myInput", i.DisplayString); - Assert.Equal(SymbolType.Parameter, i.SymbolType); + Assert.Equal("(parameter) [System.Object]$myInput", i.Name); + Assert.Equal(SymbolType.Parameter, i.Type); AssertIsRegion(i.NameRegion, 1, 23, 1, 31); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("$myInput", i.SymbolName); - Assert.Equal(SymbolType.Variable, i.SymbolType); + Assert.Equal("$myInput", i.Id); + Assert.Equal(SymbolType.Variable, i.Type); AssertIsRegion(i.NameRegion, 3, 17, 3, 25); Assert.False(i.IsDeclaration); }); @@ -354,19 +354,19 @@ public async Task FindsReferencesOnCommandWithAlias() Assert.Collection(symbols.Where( (i) => i.FilePath .EndsWith(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails.File)), - (i) => Assert.Equal("Get-ChildItem", i.SymbolName), - (i) => Assert.Equal("gci", i.SymbolName), - (i) => Assert.Equal("dir", i.SymbolName), - (i) => Assert.Equal("Get-ChildItem", i.SymbolName)); + (i) => Assert.Equal("Get-ChildItem", i.Id), + (i) => Assert.Equal("gci", i.Id), + (i) => Assert.Equal("dir", i.Id), + (i) => Assert.Equal("Get-ChildItem", i.Id)); } [Fact] public async Task FindsClassDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.ClassSourceDetails).ConfigureAwait(true); - Assert.Equal("SuperClass", symbol.SymbolName); - Assert.Equal("class SuperClass { }", symbol.DisplayString); - Assert.Equal(SymbolType.Class, symbol.SymbolType); + Assert.Equal("SuperClass", symbol.Id); + Assert.Equal("class SuperClass { }", symbol.Name); + Assert.Equal(SymbolType.Class, symbol.Type); Assert.True(symbol.IsDeclaration); AssertIsRegion(symbol.NameRegion, 8, 7, 8, 17); } @@ -378,16 +378,16 @@ public async Task FindsReferencesOnClass() Assert.Collection(symbols, (i) => { - Assert.Equal("SuperClass", i.SymbolName); - Assert.Equal("class SuperClass { }", i.DisplayString); - Assert.Equal(SymbolType.Class, i.SymbolType); + Assert.Equal("SuperClass", i.Id); + Assert.Equal("class SuperClass { }", i.Name); + Assert.Equal(SymbolType.Class, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("SuperClass", i.SymbolName); - Assert.Equal("(type) SuperClass", i.DisplayString); - Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.Equal("SuperClass", i.Id); + Assert.Equal("(type) SuperClass", i.Name); + Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }); @@ -398,9 +398,9 @@ public async Task FindsReferencesOnClass() public async Task FindsEnumDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumSourceDetails).ConfigureAwait(true); - Assert.Equal("MyEnum", symbol.SymbolName); - Assert.Equal("enum MyEnum { }", symbol.DisplayString); - Assert.Equal(SymbolType.Enum, symbol.SymbolType); + Assert.Equal("MyEnum", symbol.Id); + Assert.Equal("enum MyEnum { }", symbol.Name); + Assert.Equal(SymbolType.Enum, symbol.Type); Assert.True(symbol.IsDeclaration); AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12); } @@ -412,30 +412,30 @@ public async Task FindsReferencesOnEnum() Assert.Collection(symbols, (i) => { - Assert.Equal("MyEnum", i.SymbolName); - Assert.Equal("(type) MyEnum", i.DisplayString); - Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.Equal("MyEnum", i.Id); + Assert.Equal("(type) MyEnum", i.Name); + Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.SymbolName); - Assert.Equal("enum MyEnum { }", i.DisplayString); - Assert.Equal(SymbolType.Enum, i.SymbolType); + Assert.Equal("MyEnum", i.Id); + Assert.Equal("enum MyEnum { }", i.Name); + Assert.Equal(SymbolType.Enum, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.SymbolName); - Assert.Equal("(type) MyEnum", i.DisplayString); - Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.Equal("MyEnum", i.Id); + Assert.Equal("(type) MyEnum", i.Name); + Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.SymbolName); - Assert.Equal("(type) MyEnum", i.DisplayString); - Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.Equal("MyEnum", i.Id); + Assert.Equal("(type) MyEnum", i.Name); + Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }); @@ -447,8 +447,8 @@ public async Task FindsTypeExpressionDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeExpressionSourceDetails).ConfigureAwait(true); AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12); - Assert.Equal("MyEnum", symbol.SymbolName); - Assert.Equal("enum MyEnum { }", symbol.DisplayString); + Assert.Equal("MyEnum", symbol.Id); + Assert.Equal("enum MyEnum { }", symbol.Name); Assert.True(symbol.IsDeclaration); } @@ -459,16 +459,16 @@ public async Task FindsReferencesOnTypeExpression() Assert.Collection(symbols, (i) => { - Assert.Equal("SuperClass", i.SymbolName); - Assert.Equal("class SuperClass { }", i.DisplayString); - Assert.Equal(SymbolType.Class, i.SymbolType); + Assert.Equal("SuperClass", i.Id); + Assert.Equal("class SuperClass { }", i.Name); + Assert.Equal(SymbolType.Class, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("SuperClass", i.SymbolName); - Assert.Equal("(type) SuperClass", i.DisplayString); - Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.Equal("SuperClass", i.Id); + Assert.Equal("(type) SuperClass", i.Name); + Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }); @@ -480,8 +480,8 @@ public async Task FindsTypeConstraintDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeConstraintSourceDetails).ConfigureAwait(true); AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12); - Assert.Equal("MyEnum", symbol.SymbolName); - Assert.Equal("enum MyEnum { }", symbol.DisplayString); + Assert.Equal("MyEnum", symbol.Id); + Assert.Equal("enum MyEnum { }", symbol.Name); Assert.True(symbol.IsDeclaration); } @@ -492,30 +492,30 @@ public async Task FindsReferencesOnTypeConstraint() Assert.Collection(symbols, (i) => { - Assert.Equal("MyEnum", i.SymbolName); - Assert.Equal("(type) MyEnum", i.DisplayString); - Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.Equal("MyEnum", i.Id); + Assert.Equal("(type) MyEnum", i.Name); + Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.SymbolName); - Assert.Equal("enum MyEnum { }", i.DisplayString); - Assert.Equal(SymbolType.Enum, i.SymbolType); + Assert.Equal("MyEnum", i.Id); + Assert.Equal("enum MyEnum { }", i.Name); + Assert.Equal(SymbolType.Enum, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.SymbolName); - Assert.Equal("(type) MyEnum", i.DisplayString); - Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.Equal("MyEnum", i.Id); + Assert.Equal("(type) MyEnum", i.Name); + Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.SymbolName); - Assert.Equal("(type) MyEnum", i.DisplayString); - Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.Equal("MyEnum", i.Id); + Assert.Equal("(type) MyEnum", i.Name); + Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }); } @@ -527,16 +527,16 @@ public void FindsOccurrencesOnTypeConstraint() Assert.Collection(symbols, (i) => { - Assert.Equal("BaseClass", i.SymbolName); - Assert.Equal("class BaseClass { }", i.DisplayString); - Assert.Equal(SymbolType.Class, i.SymbolType); + Assert.Equal("BaseClass", i.Id); + Assert.Equal("class BaseClass { }", i.Name); + Assert.Equal(SymbolType.Class, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("BaseClass", i.SymbolName); - Assert.Equal("(type) BaseClass", i.DisplayString); - Assert.Equal(SymbolType.Type, i.SymbolType); + Assert.Equal("BaseClass", i.Id); + Assert.Equal("(type) BaseClass", i.Name); + Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }); } @@ -548,16 +548,16 @@ public async Task FindsConstructorDefinition() Assert.Collection(symbols, (i) => { - Assert.Equal("SuperClass", i.SymbolName); - Assert.Equal("SuperClass([string]$name)", i.DisplayString); - Assert.Equal(SymbolType.Constructor, i.SymbolType); + Assert.Equal("SuperClass", i.Id); + Assert.Equal("SuperClass([string]$name)", i.Name); + Assert.Equal(SymbolType.Constructor, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("SuperClass", i.SymbolName); - Assert.Equal("SuperClass()", i.DisplayString); - Assert.Equal(SymbolType.Constructor, i.SymbolType); + Assert.Equal("SuperClass", i.Id); + Assert.Equal("SuperClass()", i.Name); + Assert.Equal(SymbolType.Constructor, i.Type); Assert.True(i.IsDeclaration); }); @@ -572,23 +572,23 @@ public async Task FindsMethodDefinition() Assert.Collection(symbols, (i) => { - Assert.Equal("MyClassMethod", i.SymbolName); - Assert.Equal("string MyClassMethod([string]$param1, $param2, [int]$param3)", i.DisplayString); - Assert.Equal(SymbolType.Method, i.SymbolType); + Assert.Equal("MyClassMethod", i.Id); + Assert.Equal("string MyClassMethod([string]$param1, $param2, [int]$param3)", i.Name); + Assert.Equal(SymbolType.Method, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("MyClassMethod", i.SymbolName); - Assert.Equal("string MyClassMethod([MyEnum]$param1)", i.DisplayString); - Assert.Equal(SymbolType.Method, i.SymbolType); + Assert.Equal("MyClassMethod", i.Id); + Assert.Equal("string MyClassMethod([MyEnum]$param1)", i.Name); + Assert.Equal(SymbolType.Method, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("MyClassMethod", i.SymbolName); - Assert.Equal("string MyClassMethod()", i.DisplayString); - Assert.Equal(SymbolType.Method, i.SymbolType); + Assert.Equal("MyClassMethod", i.Id); + Assert.Equal("string MyClassMethod()", i.Name); + Assert.Equal(SymbolType.Method, i.Type); Assert.True(i.IsDeclaration); }); } @@ -598,15 +598,15 @@ public async Task FindsReferencesOnMethod() { IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.MethodSourceDetails).ConfigureAwait(true); Assert.Collection(symbols, - (i) => Assert.Equal("string MyClassMethod([string]$param1, $param2, [int]$param3)", i.DisplayString), - (i) => Assert.Equal("string MyClassMethod([MyEnum]$param1)", i.DisplayString), - (i) => Assert.Equal("string MyClassMethod()", i.DisplayString), + (i) => Assert.Equal("string MyClassMethod([string]$param1, $param2, [int]$param3)", i.Name), + (i) => Assert.Equal("string MyClassMethod([MyEnum]$param1)", i.Name), + (i) => Assert.Equal("string MyClassMethod()", i.Name), (i) => // The invocation! { - Assert.Equal("MyClassMethod", i.SymbolName); - Assert.Equal("(method) MyClassMethod", i.DisplayString); + Assert.Equal("MyClassMethod", i.Id); + Assert.Equal("(method) MyClassMethod", i.Name); Assert.Equal("$o.MyClassMethod()", i.SourceLine); - Assert.Equal(SymbolType.Method, i.SymbolType); + Assert.Equal(SymbolType.Method, i.Type); Assert.False(i.IsDeclaration); }); @@ -631,9 +631,9 @@ internal void SymbolTypeEquivalencies(SymbolType left, SymbolType right) public async Task FindsPropertyDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.PropertySourceDetails).ConfigureAwait(true); - Assert.Equal("$SomePropWithDefault", symbol.SymbolName); - Assert.Equal("[string] $SomePropWithDefault", symbol.DisplayString); - Assert.Equal(SymbolType.Property, symbol.SymbolType); + Assert.Equal("$SomePropWithDefault", symbol.Id); + Assert.Equal("[string] $SomePropWithDefault", symbol.Name); + Assert.Equal(SymbolType.Property, symbol.Type); Assert.True(symbol.IsDeclaration); } @@ -644,16 +644,16 @@ public async Task FindsReferencesOnProperty() Assert.Collection(symbols, (i) => { - Assert.Equal("$SomeProp", i.SymbolName); - Assert.Equal("[int] $SomeProp", i.DisplayString); - Assert.Equal(SymbolType.Property, i.SymbolType); + Assert.Equal("$SomeProp", i.Id); + Assert.Equal("[int] $SomeProp", i.Name); + Assert.Equal(SymbolType.Property, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("$SomeProp", i.SymbolName); - Assert.Equal("(property) SomeProp", i.DisplayString); - Assert.Equal(SymbolType.Property, i.SymbolType); + Assert.Equal("$SomeProp", i.Id); + Assert.Equal("(property) SomeProp", i.Name); + Assert.Equal(SymbolType.Property, i.Type); Assert.False(i.IsDeclaration); }); } @@ -665,16 +665,16 @@ public void FindsOccurrencesOnProperty() Assert.Collection(symbols, (i) => { - Assert.Equal("$SomePropWithDefault", i.SymbolName); - Assert.Equal("[string] $SomePropWithDefault", i.DisplayString); - Assert.Equal(SymbolType.Property, i.SymbolType); + Assert.Equal("$SomePropWithDefault", i.Id); + Assert.Equal("[string] $SomePropWithDefault", i.Name); + Assert.Equal(SymbolType.Property, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("$SomePropWithDefault", i.SymbolName); - Assert.Equal("(property) SomePropWithDefault", i.DisplayString); - Assert.Equal(SymbolType.Property, i.SymbolType); + Assert.Equal("$SomePropWithDefault", i.Id); + Assert.Equal("(property) SomePropWithDefault", i.Name); + Assert.Equal(SymbolType.Property, i.Type); Assert.False(i.IsDeclaration); }); } @@ -683,17 +683,17 @@ public void FindsOccurrencesOnProperty() public async Task FindsEnumMemberDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumMemberSourceDetails).ConfigureAwait(true); - Assert.Equal("$Second", symbol.SymbolName); + Assert.Equal("$Second", symbol.Id); // Doesn't include [MyEnum]:: because that'd be redundant in the outline. - Assert.Equal("Second", symbol.DisplayString); - Assert.Equal(SymbolType.EnumMember, symbol.SymbolType); + Assert.Equal("Second", symbol.Name); + Assert.Equal(SymbolType.EnumMember, symbol.Type); Assert.True(symbol.IsDeclaration); AssertIsRegion(symbol.NameRegion, 41, 5, 41, 11); symbol = await GetDefinition(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails).ConfigureAwait(true); - Assert.Equal("$First", symbol.SymbolName); - Assert.Equal("First", symbol.DisplayString); - Assert.Equal(SymbolType.EnumMember, symbol.SymbolType); + Assert.Equal("$First", symbol.Id); + Assert.Equal("First", symbol.Name); + Assert.Equal(SymbolType.EnumMember, symbol.Type); Assert.True(symbol.IsDeclaration); AssertIsRegion(symbol.NameRegion, 40, 5, 40, 10); } @@ -705,17 +705,17 @@ public async Task FindsReferencesOnEnumMember() Assert.Collection(symbols, (i) => { - Assert.Equal("$First", i.SymbolName); - Assert.Equal("First", i.DisplayString); - Assert.Equal(SymbolType.EnumMember, i.SymbolType); + Assert.Equal("$First", i.Id); + Assert.Equal("First", i.Name); + Assert.Equal(SymbolType.EnumMember, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("$First", i.SymbolName); + Assert.Equal("$First", i.Id); // The reference is just a member invocation, and so indistinguishable from a property. - Assert.Equal("(property) First", i.DisplayString); - Assert.Equal(SymbolType.Property, i.SymbolType); + Assert.Equal("(property) First", i.Name); + Assert.Equal(SymbolType.Property, i.Type); Assert.False(i.IsDeclaration); }); @@ -739,59 +739,59 @@ public void FindsSymbolsInFile() { IEnumerable symbols = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails); - Assert.Equal(7, symbols.Count(i => i.SymbolType == SymbolType.Function)); - Assert.Equal(8, symbols.Count(i => i.SymbolType == SymbolType.Variable)); - Assert.Equal(4, symbols.Count(i => i.SymbolType == SymbolType.Parameter)); - Assert.Equal(12, symbols.Count(i => SymbolTypeUtils.SymbolTypeMatches(SymbolType.Variable, i.SymbolType))); + Assert.Equal(7, symbols.Count(i => i.Type == SymbolType.Function)); + Assert.Equal(8, symbols.Count(i => i.Type == SymbolType.Variable)); + Assert.Equal(4, symbols.Count(i => i.Type == SymbolType.Parameter)); + Assert.Equal(12, symbols.Count(i => SymbolTypeUtils.SymbolTypeMatches(SymbolType.Variable, i.Type))); - SymbolReference symbol = symbols.First(i => i.SymbolType == SymbolType.Function); - Assert.Equal("AFunction", symbol.SymbolName); - Assert.Equal("function AFunction ()", symbol.DisplayString); + SymbolReference symbol = symbols.First(i => i.Type == SymbolType.Function); + Assert.Equal("AFunction", symbol.Id); + Assert.Equal("function AFunction ()", symbol.Name); Assert.True(symbol.IsDeclaration); - symbol = symbols.First(i => i.SymbolName == "AFilter"); - Assert.Equal("filter AFilter ()", symbol.DisplayString); + symbol = symbols.First(i => i.Id == "AFilter"); + Assert.Equal("filter AFilter ()", symbol.Name); Assert.True(symbol.IsDeclaration); - symbol = symbols.Last(i => i.SymbolType == SymbolType.Variable); - Assert.Equal("$nestedVar", symbol.SymbolName); - Assert.Equal("$nestedVar", symbol.DisplayString); + symbol = symbols.Last(i => i.Type == SymbolType.Variable); + Assert.Equal("$nestedVar", symbol.Id); + Assert.Equal("$nestedVar", symbol.Name); Assert.False(symbol.IsDeclaration); AssertIsRegion(symbol.NameRegion, 16, 29, 16, 39); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Workflow)); - Assert.Equal("AWorkflow", symbol.SymbolName); - Assert.Equal("workflow AWorkflow ()", symbol.DisplayString); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Workflow)); + Assert.Equal("AWorkflow", symbol.Id); + Assert.Equal("workflow AWorkflow ()", symbol.Name); Assert.True(symbol.IsDeclaration); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Class)); - Assert.Equal("AClass", symbol.SymbolName); - Assert.Equal("class AClass { }", symbol.DisplayString); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Class)); + Assert.Equal("AClass", symbol.Id); + Assert.Equal("class AClass { }", symbol.Name); Assert.True(symbol.IsDeclaration); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Property)); - Assert.Equal("$AProperty", symbol.SymbolName); - Assert.Equal("[string] $AProperty", symbol.DisplayString); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Property)); + Assert.Equal("$AProperty", symbol.Id); + Assert.Equal("[string] $AProperty", symbol.Name); Assert.True(symbol.IsDeclaration); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Constructor)); - Assert.Equal("AClass", symbol.SymbolName); - Assert.Equal("AClass([string]$AParameter)", symbol.DisplayString); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Constructor)); + Assert.Equal("AClass", symbol.Id); + Assert.Equal("AClass([string]$AParameter)", symbol.Name); Assert.True(symbol.IsDeclaration); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Method)); - Assert.Equal("AMethod", symbol.SymbolName); - Assert.Equal("void AMethod([string]$param1, [int]$param2, $param3)", symbol.DisplayString); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Method)); + Assert.Equal("AMethod", symbol.Id); + Assert.Equal("void AMethod([string]$param1, [int]$param2, $param3)", symbol.Name); Assert.True(symbol.IsDeclaration); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Enum)); - Assert.Equal("AEnum", symbol.SymbolName); - Assert.Equal("enum AEnum { }", symbol.DisplayString); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Enum)); + Assert.Equal("AEnum", symbol.Id); + Assert.Equal("enum AEnum { }", symbol.Name); Assert.True(symbol.IsDeclaration); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.EnumMember)); - Assert.Equal("$AValue", symbol.SymbolName); - Assert.Equal("AValue", symbol.DisplayString); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.EnumMember)); + Assert.Equal("$AValue", symbol.Id); + Assert.Equal("AValue", symbol.Name); Assert.True(symbol.IsDeclaration); } @@ -800,39 +800,39 @@ public void FindsSymbolsWithNewLineInFile() { IEnumerable symbols = FindSymbolsInFile(FindSymbolsInNewLineSymbolFile.SourceDetails); - SymbolReference symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Function)); - Assert.Equal("returnTrue", symbol.SymbolName); + SymbolReference symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Function)); + Assert.Equal("returnTrue", symbol.Id); AssertIsRegion(symbol.NameRegion, 2, 1, 2, 11); AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Class)); - Assert.Equal("NewLineClass", symbol.SymbolName); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Class)); + Assert.Equal("NewLineClass", symbol.Id); AssertIsRegion(symbol.NameRegion, 7, 1, 7, 13); AssertIsRegion(symbol.ScriptRegion, 6, 1, 23, 2); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Constructor)); - Assert.Equal("NewLineClass", symbol.SymbolName); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Constructor)); + Assert.Equal("NewLineClass", symbol.Id); AssertIsRegion(symbol.NameRegion, 8, 5, 8, 17); AssertIsRegion(symbol.ScriptRegion, 8, 5, 10, 6); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Property)); - Assert.Equal("$SomePropWithDefault", symbol.SymbolName); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Property)); + Assert.Equal("$SomePropWithDefault", symbol.Id); AssertIsRegion(symbol.NameRegion, 15, 5, 15, 25); AssertIsRegion(symbol.ScriptRegion, 12, 5, 15, 40); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Method)); - Assert.Equal("MyClassMethod", symbol.SymbolName); - Assert.Equal("string MyClassMethod([MyNewLineEnum]$param1)", symbol.DisplayString); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Method)); + Assert.Equal("MyClassMethod", symbol.Id); + Assert.Equal("string MyClassMethod([MyNewLineEnum]$param1)", symbol.Name); AssertIsRegion(symbol.NameRegion, 20, 5, 20, 18); AssertIsRegion(symbol.ScriptRegion, 17, 5, 22, 6); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.Enum)); - Assert.Equal("MyNewLineEnum", symbol.SymbolName); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Enum)); + Assert.Equal("MyNewLineEnum", symbol.Id); AssertIsRegion(symbol.NameRegion, 26, 1, 26, 14); AssertIsRegion(symbol.ScriptRegion, 25, 1, 28, 2); - symbol = Assert.Single(symbols.Where(i => i.SymbolType == SymbolType.EnumMember)); - Assert.Equal("$First", symbol.SymbolName); + symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.EnumMember)); + Assert.Equal("$First", symbol.Id); AssertIsRegion(symbol.NameRegion, 27, 5, 27, 10); AssertIsRegion(symbol.ScriptRegion, 27, 5, 27, 10); } @@ -843,8 +843,8 @@ public void FindsSymbolsInDSCFile() Skip.If(!s_isWindows, "DSC only works properly on Windows."); IEnumerable symbols = FindSymbolsInFile(FindSymbolsInDSCFile.SourceDetails); - SymbolReference symbol = Assert.Single(symbols, i => i.SymbolType == SymbolType.Configuration); - Assert.Equal("AConfiguration", symbol.SymbolName); + SymbolReference symbol = Assert.Single(symbols, i => i.Type == SymbolType.Configuration); + Assert.Equal("AConfiguration", symbol.Id); Assert.Equal(2, symbol.ScriptRegion.StartLineNumber); Assert.Equal(15, symbol.ScriptRegion.StartColumnNumber); } @@ -853,47 +853,47 @@ public void FindsSymbolsInDSCFile() public void FindsSymbolsInPesterFile() { IEnumerable symbols = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails).OfType(); - Assert.Equal(12, symbols.Count(i => i.SymbolType == SymbolType.Function)); + Assert.Equal(12, symbols.Count(i => i.Type == SymbolType.Function)); SymbolReference symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.Describe); - Assert.Equal("Describe \"Testing Pester symbols\"", symbol.SymbolName); + Assert.Equal("Describe \"Testing Pester symbols\"", symbol.Id); Assert.Equal(9, symbol.ScriptRegion.StartLineNumber); Assert.Equal(1, symbol.ScriptRegion.StartColumnNumber); symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.Context); - Assert.Equal("Context \"When a Pester file is given\"", symbol.SymbolName); + Assert.Equal("Context \"When a Pester file is given\"", symbol.Id); Assert.Equal(10, symbol.ScriptRegion.StartLineNumber); Assert.Equal(5, symbol.ScriptRegion.StartColumnNumber); Assert.Equal(4, symbols.Count(i => i.Command == PesterCommandType.It)); symbol = symbols.Last(i => i.Command == PesterCommandType.It); - Assert.Equal("It \"Should return setup and teardown symbols\"", symbol.SymbolName); + Assert.Equal("It \"Should return setup and teardown symbols\"", symbol.Id); Assert.Equal(31, symbol.ScriptRegion.StartLineNumber); Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber); symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.BeforeDiscovery); - Assert.Equal("BeforeDiscovery", symbol.SymbolName); + Assert.Equal("BeforeDiscovery", symbol.Id); Assert.Equal(1, symbol.ScriptRegion.StartLineNumber); Assert.Equal(1, symbol.ScriptRegion.StartColumnNumber); Assert.Equal(2, symbols.Count(i => i.Command == PesterCommandType.BeforeAll)); symbol = symbols.Last(i => i.Command == PesterCommandType.BeforeAll); - Assert.Equal("BeforeAll", symbol.SymbolName); + Assert.Equal("BeforeAll", symbol.Id); Assert.Equal(11, symbol.ScriptRegion.StartLineNumber); Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber); symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.BeforeEach); - Assert.Equal("BeforeEach", symbol.SymbolName); + Assert.Equal("BeforeEach", symbol.Id); Assert.Equal(15, symbol.ScriptRegion.StartLineNumber); Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber); symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.AfterEach); - Assert.Equal("AfterEach", symbol.SymbolName); + Assert.Equal("AfterEach", symbol.Id); Assert.Equal(35, symbol.ScriptRegion.StartLineNumber); Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber); symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.AfterAll); - Assert.Equal("AfterAll", symbol.SymbolName); + Assert.Equal("AfterAll", symbol.Id); Assert.Equal(40, symbol.ScriptRegion.StartLineNumber); Assert.Equal(5, symbol.ScriptRegion.StartColumnNumber); } @@ -902,11 +902,11 @@ public void FindsSymbolsInPesterFile() public void FindsSymbolsInPSDFile() { IEnumerable symbols = FindSymbolsInFile(FindSymbolsInPSDFile.SourceDetails); - Assert.All(symbols, i => Assert.Equal(SymbolType.HashtableKey, i.SymbolType)); + Assert.All(symbols, i => Assert.Equal(SymbolType.HashtableKey, i.Type)); Assert.Collection(symbols, - i => Assert.Equal("property1", i.SymbolName), - i => Assert.Equal("property2", i.SymbolName), - i => Assert.Equal("property3", i.SymbolName)); + i => Assert.Equal("property1", i.Id), + i => Assert.Equal("property2", i.Id), + i => Assert.Equal("property3", i.Id)); } [Fact] diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs index 23e7019e5..edc1ece27 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs @@ -37,7 +37,7 @@ public void CanFindSymbolAtPosition(int line, int column, string expectedName) { SymbolReference symbol = scriptFile.References.TryGetSymbolAtPosition(line, column); Assert.NotNull(symbol); - Assert.Equal(expectedName, symbol.SymbolName); + Assert.Equal(expectedName, symbol.Id); } [Theory] From 0135baaf8d6c118457867ac7481a74d067a2426a Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 30 Jan 2023 17:19:31 -0800 Subject: [PATCH 223/327] Replace `SymbolTypeMatches` logic with prefixed identifiers So that we rely on the dictionary to sort our symbols into the equivalent types instead of having to perform a filter. --- .../Services/Symbols/ReferenceTable.cs | 2 +- .../Services/Symbols/SymbolType.cs | 13 -- .../Symbols/Visitors/SymbolVisitor.cs | 30 ++-- .../Language/SymbolsServiceTests.cs | 162 ++++++++---------- .../Services/Symbols/AstOperationsTests.cs | 16 +- 5 files changed, 97 insertions(+), 126 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index c421953f4..620fe2ea1 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -47,7 +47,7 @@ internal IEnumerable TryGetReferences(SymbolReference? symbol) EnsureInitialized(); return symbol is not null && _symbolReferences.TryGetValue(symbol.Id, out ConcurrentBag? bag) - ? bag.Where(i => SymbolTypeUtils.SymbolTypeMatches(symbol.Type, i.Type)) + ? bag : Enumerable.Empty(); } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index adcff16f1..dbcb95dfb 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -99,18 +99,5 @@ internal static SymbolKind GetSymbolKind(SymbolType symbolType) _ => SymbolKind.Variable, }; } - - // Provides a partial equivalence between type constraints and custom types, and between - // variables and parameters. - internal static bool SymbolTypeMatches(SymbolType left, SymbolType right) - { - return left == right - || (left is SymbolType.Class or SymbolType.Enum or SymbolType.Type - && right is SymbolType.Class or SymbolType.Enum or SymbolType.Type) - || (left is SymbolType.EnumMember or SymbolType.Property - && right is SymbolType.EnumMember or SymbolType.Property) - || (left is SymbolType.Variable or SymbolType.Parameter - && right is SymbolType.Variable or SymbolType.Parameter); - } } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index 474f7a4e6..6186e2933 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -39,7 +39,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) return _action(new SymbolReference( SymbolType.Function, - CommandHelpers.StripModuleQualification(commandName, out _), + "fn " + CommandHelpers.StripModuleQualification(commandName, out _), commandName, commandAst.CommandElements[0].Extent, commandAst.Extent, @@ -63,7 +63,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); return _action(new SymbolReference( symbolType, - functionDefinitionAst.Name, + "fn " + functionDefinitionAst.Name, VisitorUtils.GetFunctionDisplayName(functionDefinitionAst), nameExtent, functionDefinitionAst.Extent, @@ -77,7 +77,7 @@ public override AstVisitAction VisitParameter(ParameterAst parameterAst) // getting the TypeConstraintAst somehow? return _action(new SymbolReference( SymbolType.Parameter, - "$" + parameterAst.Name.VariablePath.UserPath, + "var " + parameterAst.Name.VariablePath.UserPath, VisitorUtils.GetParamDisplayName(parameterAst), parameterAst.Name.Extent, parameterAst.Extent, @@ -98,7 +98,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var // the same function definition. return _action(new SymbolReference( SymbolType.Variable, - "$" + variableExpressionAst.VariablePath.UserPath, + "var " + variableExpressionAst.VariablePath.UserPath, "$" + variableExpressionAst.VariablePath.UserPath, variableExpressionAst.Extent, variableExpressionAst.Extent, // TODO: Maybe parent? @@ -115,7 +115,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst); return _action(new SymbolReference( symbolType, - typeDefinitionAst.Name, + "type " + typeDefinitionAst.Name, (symbolType is SymbolType.Enum ? "enum " : "class ") + typeDefinitionAst.Name + " { }", nameExtent, typeDefinitionAst.Extent, @@ -127,7 +127,7 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress { return _action(new SymbolReference( SymbolType.Type, - typeExpressionAst.TypeName.Name, + "type " + typeExpressionAst.TypeName.Name, "(type) " + typeExpressionAst.TypeName.Name, typeExpressionAst.Extent, typeExpressionAst.Extent, @@ -139,7 +139,7 @@ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstra { return _action(new SymbolReference( SymbolType.Type, - typeConstraintAst.TypeName.Name, + "type " + typeConstraintAst.TypeName.Name, "(type) " + typeConstraintAst.TypeName.Name, typeConstraintAst.Extent, typeConstraintAst.Extent, @@ -157,7 +157,7 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem return _action(new SymbolReference( symbolType, - functionMemberAst.Name, // We bucket all the overloads. + "fn " + functionMemberAst.Name, // We bucket all the overloads. VisitorUtils.GetMemberOverloadName(functionMemberAst), nameExtent, functionMemberAst.Extent, @@ -176,7 +176,7 @@ propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum return _action(new SymbolReference( symbolType, - "$" + propertyMemberAst.Name, + "var " + propertyMemberAst.Name, VisitorUtils.GetMemberOverloadName(propertyMemberAst), VisitorUtils.GetNameExtent(propertyMemberAst), propertyMemberAst.Extent, @@ -195,9 +195,7 @@ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberE // TODO: It's too bad we can't get the property's real symbol and reuse its display string. return _action(new SymbolReference( SymbolType.Property, -#pragma warning disable CS8604 // Possible null reference argument. - "$" + memberName, -#pragma warning restore CS8604 + "var " + memberName, "(property) " + memberName, memberExpressionAst.Member.Extent, memberExpressionAst.Extent, @@ -216,9 +214,7 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio // TODO: It's too bad we can't get the member's real symbol and reuse its display string. return _action(new SymbolReference( SymbolType.Method, -#pragma warning disable CS8604 // Possible null reference argument. - memberName, -#pragma warning restore CS8604 + "fn " + memberName, "(method) " + memberName, methodCallAst.Member.Extent, methodCallAst.Extent, @@ -238,9 +234,7 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit IScriptExtent nameExtent = VisitorUtils.GetNameExtent(configurationDefinitionAst); return _action(new SymbolReference( SymbolType.Configuration, -#pragma warning disable CS8604 // Possible null reference argument. - name, -#pragma warning restore CS8604 + "dsc " + name, "configuration " + name + " { }", nameExtent, configurationDefinitionAst.Extent, diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 1b23b0663..adfcec550 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -165,7 +165,7 @@ public async Task FindsCommandForParamHintsWithSpaces() public async Task FindsFunctionDefinition() { SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionData.SourceDetails).ConfigureAwait(true); - Assert.Equal("My-Function", symbol.Id); + Assert.Equal("fn My-Function", symbol.Id); Assert.Equal("function My-Function ($myInput)", symbol.Name); Assert.Equal(SymbolType.Function, symbol.Type); AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21); @@ -197,14 +197,14 @@ public async Task FindsReferencesOnFunction() Assert.Collection(symbols, (i) => { - Assert.Equal("My-Function", i.Id); + Assert.Equal("fn My-Function", i.Id); Assert.Equal("function My-Function ($myInput)", i.Name); Assert.Equal(SymbolType.Function, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("My-Function", i.Id); + Assert.Equal("fn My-Function", i.Id); Assert.Equal("My-Function", i.Name); Assert.Equal(SymbolType.Function, i.Type); Assert.EndsWith(FindsFunctionDefinitionInWorkspaceData.SourceDetails.File, i.FilePath); @@ -212,14 +212,14 @@ public async Task FindsReferencesOnFunction() }, (i) => { - Assert.Equal("My-Function", i.Id); + Assert.Equal("fn My-Function", i.Id); Assert.Equal("My-Function", i.Name); Assert.Equal(SymbolType.Function, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("My-Function", i.Id); + Assert.Equal("fn My-Function", i.Id); Assert.Equal("My-Function", i.Name); Assert.Equal(SymbolType.Function, i.Type); Assert.False(i.IsDeclaration); @@ -245,7 +245,7 @@ await psesHost.ExecutePSCommandAsync( (i) => { AssertIsRegion(i.NameRegion, 20, 1, 20, 9); - Assert.Equal("My-Alias", i.Id); + Assert.Equal("fn My-Alias", i.Id); }); } @@ -254,7 +254,7 @@ public async Task FindsFunctionDefinitionInWorkspace() { IEnumerable symbols = await GetDefinitions(FindsFunctionDefinitionInWorkspaceData.SourceDetails).ConfigureAwait(true); SymbolReference symbol = Assert.Single(symbols); - Assert.Equal("My-Function", symbol.Id); + Assert.Equal("fn My-Function", symbol.Id); Assert.Equal("function My-Function ($myInput)", symbol.Name); Assert.True(symbol.IsDeclaration); Assert.EndsWith(FindsFunctionDefinitionData.SourceDetails.File, symbol.FilePath); @@ -264,7 +264,7 @@ public async Task FindsFunctionDefinitionInWorkspace() public async Task FindsVariableDefinition() { SymbolReference symbol = await GetDefinition(FindsVariableDefinitionData.SourceDetails).ConfigureAwait(true); - Assert.Equal("$things", symbol.Id); + Assert.Equal("var things", symbol.Id); Assert.Equal("$things", symbol.Name); Assert.Equal(SymbolType.Variable, symbol.Type); Assert.True(symbol.IsDeclaration); @@ -278,19 +278,22 @@ public async Task FindsReferencesOnVariable() Assert.Collection(symbols, (i) => { - Assert.Equal("$things", i.Id); + Assert.Equal("var things", i.Id); + Assert.Equal("$things", i.Name); Assert.Equal(SymbolType.Variable, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("$things", i.Id); + Assert.Equal("var things", i.Id); + Assert.Equal("$things", i.Name); Assert.Equal(SymbolType.Variable, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("$things", i.Id); + Assert.Equal("var things", i.Id); + Assert.Equal("$things", i.Name); Assert.Equal(SymbolType.Variable, i.Type); Assert.False(i.IsDeclaration); }); @@ -305,19 +308,19 @@ public void FindsOccurrencesOnFunction() Assert.Collection(symbols, (i) => { - Assert.Equal("My-Function", i.Id); + Assert.Equal("fn My-Function", i.Id); Assert.Equal(SymbolType.Function, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("My-Function", i.Id); + Assert.Equal("fn My-Function", i.Id); Assert.Equal(SymbolType.Function, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("My-Function", i.Id); + Assert.Equal("fn My-Function", i.Id); Assert.Equal(SymbolType.Function, i.Type); Assert.False(i.IsDeclaration); }); @@ -330,8 +333,8 @@ public void FindsOccurrencesOnParameter() Assert.Collection(symbols, (i) => { - Assert.Equal("$myInput", i.Id); - // TODO: Parameter display strings need work. + Assert.Equal("var myInput", i.Id); + // TODO: Parameter names need work. Assert.Equal("(parameter) [System.Object]$myInput", i.Name); Assert.Equal(SymbolType.Parameter, i.Type); AssertIsRegion(i.NameRegion, 1, 23, 1, 31); @@ -339,7 +342,8 @@ public void FindsOccurrencesOnParameter() }, (i) => { - Assert.Equal("$myInput", i.Id); + Assert.Equal("var myInput", i.Id); + Assert.Equal("$myInput", i.Name); Assert.Equal(SymbolType.Variable, i.Type); AssertIsRegion(i.NameRegion, 3, 17, 3, 25); Assert.False(i.IsDeclaration); @@ -354,17 +358,17 @@ public async Task FindsReferencesOnCommandWithAlias() Assert.Collection(symbols.Where( (i) => i.FilePath .EndsWith(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails.File)), - (i) => Assert.Equal("Get-ChildItem", i.Id), - (i) => Assert.Equal("gci", i.Id), - (i) => Assert.Equal("dir", i.Id), - (i) => Assert.Equal("Get-ChildItem", i.Id)); + (i) => Assert.Equal("fn Get-ChildItem", i.Id), + (i) => Assert.Equal("fn gci", i.Id), + (i) => Assert.Equal("fn dir", i.Id), + (i) => Assert.Equal("fn Get-ChildItem", i.Id)); } [Fact] public async Task FindsClassDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.ClassSourceDetails).ConfigureAwait(true); - Assert.Equal("SuperClass", symbol.Id); + Assert.Equal("type SuperClass", symbol.Id); Assert.Equal("class SuperClass { }", symbol.Name); Assert.Equal(SymbolType.Class, symbol.Type); Assert.True(symbol.IsDeclaration); @@ -378,14 +382,14 @@ public async Task FindsReferencesOnClass() Assert.Collection(symbols, (i) => { - Assert.Equal("SuperClass", i.Id); + Assert.Equal("type SuperClass", i.Id); Assert.Equal("class SuperClass { }", i.Name); Assert.Equal(SymbolType.Class, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("SuperClass", i.Id); + Assert.Equal("type SuperClass", i.Id); Assert.Equal("(type) SuperClass", i.Name); Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); @@ -398,7 +402,7 @@ public async Task FindsReferencesOnClass() public async Task FindsEnumDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumSourceDetails).ConfigureAwait(true); - Assert.Equal("MyEnum", symbol.Id); + Assert.Equal("type MyEnum", symbol.Id); Assert.Equal("enum MyEnum { }", symbol.Name); Assert.Equal(SymbolType.Enum, symbol.Type); Assert.True(symbol.IsDeclaration); @@ -412,28 +416,28 @@ public async Task FindsReferencesOnEnum() Assert.Collection(symbols, (i) => { - Assert.Equal("MyEnum", i.Id); + Assert.Equal("type MyEnum", i.Id); Assert.Equal("(type) MyEnum", i.Name); Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.Id); + Assert.Equal("type MyEnum", i.Id); Assert.Equal("enum MyEnum { }", i.Name); Assert.Equal(SymbolType.Enum, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.Id); + Assert.Equal("type MyEnum", i.Id); Assert.Equal("(type) MyEnum", i.Name); Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.Id); + Assert.Equal("type MyEnum", i.Id); Assert.Equal("(type) MyEnum", i.Name); Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); @@ -447,7 +451,7 @@ public async Task FindsTypeExpressionDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeExpressionSourceDetails).ConfigureAwait(true); AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12); - Assert.Equal("MyEnum", symbol.Id); + Assert.Equal("type MyEnum", symbol.Id); Assert.Equal("enum MyEnum { }", symbol.Name); Assert.True(symbol.IsDeclaration); } @@ -459,14 +463,14 @@ public async Task FindsReferencesOnTypeExpression() Assert.Collection(symbols, (i) => { - Assert.Equal("SuperClass", i.Id); + Assert.Equal("type SuperClass", i.Id); Assert.Equal("class SuperClass { }", i.Name); Assert.Equal(SymbolType.Class, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("SuperClass", i.Id); + Assert.Equal("type SuperClass", i.Id); Assert.Equal("(type) SuperClass", i.Name); Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); @@ -480,7 +484,7 @@ public async Task FindsTypeConstraintDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeConstraintSourceDetails).ConfigureAwait(true); AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12); - Assert.Equal("MyEnum", symbol.Id); + Assert.Equal("type MyEnum", symbol.Id); Assert.Equal("enum MyEnum { }", symbol.Name); Assert.True(symbol.IsDeclaration); } @@ -492,28 +496,28 @@ public async Task FindsReferencesOnTypeConstraint() Assert.Collection(symbols, (i) => { - Assert.Equal("MyEnum", i.Id); + Assert.Equal("type MyEnum", i.Id); Assert.Equal("(type) MyEnum", i.Name); Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.Id); + Assert.Equal("type MyEnum", i.Id); Assert.Equal("enum MyEnum { }", i.Name); Assert.Equal(SymbolType.Enum, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.Id); + Assert.Equal("type MyEnum", i.Id); Assert.Equal("(type) MyEnum", i.Name); Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); }, (i) => { - Assert.Equal("MyEnum", i.Id); + Assert.Equal("type MyEnum", i.Id); Assert.Equal("(type) MyEnum", i.Name); Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); @@ -527,14 +531,14 @@ public void FindsOccurrencesOnTypeConstraint() Assert.Collection(symbols, (i) => { - Assert.Equal("BaseClass", i.Id); + Assert.Equal("type BaseClass", i.Id); Assert.Equal("class BaseClass { }", i.Name); Assert.Equal(SymbolType.Class, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("BaseClass", i.Id); + Assert.Equal("type BaseClass", i.Id); Assert.Equal("(type) BaseClass", i.Name); Assert.Equal(SymbolType.Type, i.Type); Assert.False(i.IsDeclaration); @@ -548,14 +552,14 @@ public async Task FindsConstructorDefinition() Assert.Collection(symbols, (i) => { - Assert.Equal("SuperClass", i.Id); + Assert.Equal("fn SuperClass", i.Id); Assert.Equal("SuperClass([string]$name)", i.Name); Assert.Equal(SymbolType.Constructor, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("SuperClass", i.Id); + Assert.Equal("fn SuperClass", i.Id); Assert.Equal("SuperClass()", i.Name); Assert.Equal(SymbolType.Constructor, i.Type); Assert.True(i.IsDeclaration); @@ -572,21 +576,21 @@ public async Task FindsMethodDefinition() Assert.Collection(symbols, (i) => { - Assert.Equal("MyClassMethod", i.Id); + Assert.Equal("fn MyClassMethod", i.Id); Assert.Equal("string MyClassMethod([string]$param1, $param2, [int]$param3)", i.Name); Assert.Equal(SymbolType.Method, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("MyClassMethod", i.Id); + Assert.Equal("fn MyClassMethod", i.Id); Assert.Equal("string MyClassMethod([MyEnum]$param1)", i.Name); Assert.Equal(SymbolType.Method, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("MyClassMethod", i.Id); + Assert.Equal("fn MyClassMethod", i.Id); Assert.Equal("string MyClassMethod()", i.Name); Assert.Equal(SymbolType.Method, i.Type); Assert.True(i.IsDeclaration); @@ -603,7 +607,7 @@ public async Task FindsReferencesOnMethod() (i) => Assert.Equal("string MyClassMethod()", i.Name), (i) => // The invocation! { - Assert.Equal("MyClassMethod", i.Id); + Assert.Equal("fn MyClassMethod", i.Id); Assert.Equal("(method) MyClassMethod", i.Name); Assert.Equal("$o.MyClassMethod()", i.SourceLine); Assert.Equal(SymbolType.Method, i.Type); @@ -613,25 +617,11 @@ public async Task FindsReferencesOnMethod() Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.MethodSourceDetails)); } - [Theory] - [InlineData(SymbolType.Class, SymbolType.Type)] - [InlineData(SymbolType.Enum, SymbolType.Type)] - [InlineData(SymbolType.EnumMember, SymbolType.Property)] - [InlineData(SymbolType.Variable, SymbolType.Parameter)] - internal void SymbolTypeEquivalencies(SymbolType left, SymbolType right) - { - // When checking if a symbol's type is the "same" we use this utility method which - // semantically equates the above theory, since for the purposes of narrowing down - // matching symbols, these types are equivalent. - Assert.NotEqual(left, right); - Assert.True(SymbolTypeUtils.SymbolTypeMatches(left, right)); - } - [Fact] public async Task FindsPropertyDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.PropertySourceDetails).ConfigureAwait(true); - Assert.Equal("$SomePropWithDefault", symbol.Id); + Assert.Equal("var SomePropWithDefault", symbol.Id); Assert.Equal("[string] $SomePropWithDefault", symbol.Name); Assert.Equal(SymbolType.Property, symbol.Type); Assert.True(symbol.IsDeclaration); @@ -644,14 +634,14 @@ public async Task FindsReferencesOnProperty() Assert.Collection(symbols, (i) => { - Assert.Equal("$SomeProp", i.Id); + Assert.Equal("var SomeProp", i.Id); Assert.Equal("[int] $SomeProp", i.Name); Assert.Equal(SymbolType.Property, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("$SomeProp", i.Id); + Assert.Equal("var SomeProp", i.Id); Assert.Equal("(property) SomeProp", i.Name); Assert.Equal(SymbolType.Property, i.Type); Assert.False(i.IsDeclaration); @@ -665,14 +655,14 @@ public void FindsOccurrencesOnProperty() Assert.Collection(symbols, (i) => { - Assert.Equal("$SomePropWithDefault", i.Id); + Assert.Equal("var SomePropWithDefault", i.Id); Assert.Equal("[string] $SomePropWithDefault", i.Name); Assert.Equal(SymbolType.Property, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("$SomePropWithDefault", i.Id); + Assert.Equal("var SomePropWithDefault", i.Id); Assert.Equal("(property) SomePropWithDefault", i.Name); Assert.Equal(SymbolType.Property, i.Type); Assert.False(i.IsDeclaration); @@ -683,7 +673,7 @@ public void FindsOccurrencesOnProperty() public async Task FindsEnumMemberDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumMemberSourceDetails).ConfigureAwait(true); - Assert.Equal("$Second", symbol.Id); + Assert.Equal("var Second", symbol.Id); // Doesn't include [MyEnum]:: because that'd be redundant in the outline. Assert.Equal("Second", symbol.Name); Assert.Equal(SymbolType.EnumMember, symbol.Type); @@ -691,7 +681,7 @@ public async Task FindsEnumMemberDefinition() AssertIsRegion(symbol.NameRegion, 41, 5, 41, 11); symbol = await GetDefinition(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails).ConfigureAwait(true); - Assert.Equal("$First", symbol.Id); + Assert.Equal("var First", symbol.Id); Assert.Equal("First", symbol.Name); Assert.Equal(SymbolType.EnumMember, symbol.Type); Assert.True(symbol.IsDeclaration); @@ -705,14 +695,14 @@ public async Task FindsReferencesOnEnumMember() Assert.Collection(symbols, (i) => { - Assert.Equal("$First", i.Id); + Assert.Equal("var First", i.Id); Assert.Equal("First", i.Name); Assert.Equal(SymbolType.EnumMember, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("$First", i.Id); + Assert.Equal("var First", i.Id); // The reference is just a member invocation, and so indistinguishable from a property. Assert.Equal("(property) First", i.Name); Assert.Equal(SymbolType.Property, i.Type); @@ -742,55 +732,55 @@ public void FindsSymbolsInFile() Assert.Equal(7, symbols.Count(i => i.Type == SymbolType.Function)); Assert.Equal(8, symbols.Count(i => i.Type == SymbolType.Variable)); Assert.Equal(4, symbols.Count(i => i.Type == SymbolType.Parameter)); - Assert.Equal(12, symbols.Count(i => SymbolTypeUtils.SymbolTypeMatches(SymbolType.Variable, i.Type))); + Assert.Equal(14, symbols.Count(i => i.Id.StartsWith("var "))); SymbolReference symbol = symbols.First(i => i.Type == SymbolType.Function); - Assert.Equal("AFunction", symbol.Id); + Assert.Equal("fn AFunction", symbol.Id); Assert.Equal("function AFunction ()", symbol.Name); Assert.True(symbol.IsDeclaration); - symbol = symbols.First(i => i.Id == "AFilter"); + symbol = symbols.First(i => i.Id == "fn AFilter"); Assert.Equal("filter AFilter ()", symbol.Name); Assert.True(symbol.IsDeclaration); symbol = symbols.Last(i => i.Type == SymbolType.Variable); - Assert.Equal("$nestedVar", symbol.Id); + Assert.Equal("var nestedVar", symbol.Id); Assert.Equal("$nestedVar", symbol.Name); Assert.False(symbol.IsDeclaration); AssertIsRegion(symbol.NameRegion, 16, 29, 16, 39); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Workflow)); - Assert.Equal("AWorkflow", symbol.Id); + Assert.Equal("fn AWorkflow", symbol.Id); Assert.Equal("workflow AWorkflow ()", symbol.Name); Assert.True(symbol.IsDeclaration); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Class)); - Assert.Equal("AClass", symbol.Id); + Assert.Equal("type AClass", symbol.Id); Assert.Equal("class AClass { }", symbol.Name); Assert.True(symbol.IsDeclaration); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Property)); - Assert.Equal("$AProperty", symbol.Id); + Assert.Equal("var AProperty", symbol.Id); Assert.Equal("[string] $AProperty", symbol.Name); Assert.True(symbol.IsDeclaration); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Constructor)); - Assert.Equal("AClass", symbol.Id); + Assert.Equal("fn AClass", symbol.Id); Assert.Equal("AClass([string]$AParameter)", symbol.Name); Assert.True(symbol.IsDeclaration); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Method)); - Assert.Equal("AMethod", symbol.Id); + Assert.Equal("fn AMethod", symbol.Id); Assert.Equal("void AMethod([string]$param1, [int]$param2, $param3)", symbol.Name); Assert.True(symbol.IsDeclaration); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Enum)); - Assert.Equal("AEnum", symbol.Id); + Assert.Equal("type AEnum", symbol.Id); Assert.Equal("enum AEnum { }", symbol.Name); Assert.True(symbol.IsDeclaration); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.EnumMember)); - Assert.Equal("$AValue", symbol.Id); + Assert.Equal("var AValue", symbol.Id); Assert.Equal("AValue", symbol.Name); Assert.True(symbol.IsDeclaration); } @@ -801,38 +791,38 @@ public void FindsSymbolsWithNewLineInFile() IEnumerable symbols = FindSymbolsInFile(FindSymbolsInNewLineSymbolFile.SourceDetails); SymbolReference symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Function)); - Assert.Equal("returnTrue", symbol.Id); + Assert.Equal("fn returnTrue", symbol.Id); AssertIsRegion(symbol.NameRegion, 2, 1, 2, 11); AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Class)); - Assert.Equal("NewLineClass", symbol.Id); + Assert.Equal("type NewLineClass", symbol.Id); AssertIsRegion(symbol.NameRegion, 7, 1, 7, 13); AssertIsRegion(symbol.ScriptRegion, 6, 1, 23, 2); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Constructor)); - Assert.Equal("NewLineClass", symbol.Id); + Assert.Equal("fn NewLineClass", symbol.Id); AssertIsRegion(symbol.NameRegion, 8, 5, 8, 17); AssertIsRegion(symbol.ScriptRegion, 8, 5, 10, 6); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Property)); - Assert.Equal("$SomePropWithDefault", symbol.Id); + Assert.Equal("var SomePropWithDefault", symbol.Id); AssertIsRegion(symbol.NameRegion, 15, 5, 15, 25); AssertIsRegion(symbol.ScriptRegion, 12, 5, 15, 40); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Method)); - Assert.Equal("MyClassMethod", symbol.Id); + Assert.Equal("fn MyClassMethod", symbol.Id); Assert.Equal("string MyClassMethod([MyNewLineEnum]$param1)", symbol.Name); AssertIsRegion(symbol.NameRegion, 20, 5, 20, 18); AssertIsRegion(symbol.ScriptRegion, 17, 5, 22, 6); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Enum)); - Assert.Equal("MyNewLineEnum", symbol.Id); + Assert.Equal("type MyNewLineEnum", symbol.Id); AssertIsRegion(symbol.NameRegion, 26, 1, 26, 14); AssertIsRegion(symbol.ScriptRegion, 25, 1, 28, 2); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.EnumMember)); - Assert.Equal("$First", symbol.Id); + Assert.Equal("var First", symbol.Id); AssertIsRegion(symbol.NameRegion, 27, 5, 27, 10); AssertIsRegion(symbol.ScriptRegion, 27, 5, 27, 10); } diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs index edc1ece27..649ef32fd 100644 --- a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs @@ -25,14 +25,14 @@ public AstOperationsTests() } [Theory] - [InlineData(1, 15, "BasicFunction")] - [InlineData(2, 3, "BasicFunction")] - [InlineData(4, 31, "FunctionWithExtraSpace")] - [InlineData(7, 18, "FunctionWithExtraSpace")] - [InlineData(12, 22, "FunctionNameOnDifferentLine")] - [InlineData(22, 13, "FunctionNameOnDifferentLine")] - [InlineData(24, 30, "IndentedFunction")] - [InlineData(24, 52, "IndentedFunction")] + [InlineData(1, 15, "fn BasicFunction")] + [InlineData(2, 3, "fn BasicFunction")] + [InlineData(4, 31, "fn FunctionWithExtraSpace")] + [InlineData(7, 18, "fn FunctionWithExtraSpace")] + [InlineData(12, 22, "fn FunctionNameOnDifferentLine")] + [InlineData(22, 13, "fn FunctionNameOnDifferentLine")] + [InlineData(24, 30, "fn IndentedFunction")] + [InlineData(24, 52, "fn IndentedFunction")] public void CanFindSymbolAtPosition(int line, int column, string expectedName) { SymbolReference symbol = scriptFile.References.TryGetSymbolAtPosition(line, column); From 7da6f9d1fad7a925d5c311b4d32c544506c42225 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 30 Jan 2023 17:20:06 -0800 Subject: [PATCH 224/327] Strip qualifier from variable name in symbol identifier --- .../Services/Symbols/Visitors/SymbolVisitor.cs | 4 ++-- src/PowerShellEditorServices/Utility/VisitorUtils.cs | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index 6186e2933..d913d713b 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -94,11 +94,11 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var return AstVisitAction.Continue; } - // TODO: Consider tracking unscoped variable references only when they declared within + // TODO: Consider tracking unscoped variable references only when they're declared within // the same function definition. return _action(new SymbolReference( SymbolType.Variable, - "var " + variableExpressionAst.VariablePath.UserPath, + "var " + VisitorUtils.GetUnqualifiedVariableName(variableExpressionAst.VariablePath), "$" + variableExpressionAst.VariablePath.UserPath, variableExpressionAst.Extent, variableExpressionAst.Extent, // TODO: Maybe parent? diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index e02dfda40..fa4951163 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Management.Automation; using System.Management.Automation.Language; using System.Text; using PSESSymbols = Microsoft.PowerShell.EditorServices.Services.Symbols; @@ -32,6 +33,14 @@ internal static class VisitorUtils return PSESSymbols.AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null; } + // Strip the qualification, if there is any, so $var is a reference of $script:var etc. + internal static string GetUnqualifiedVariableName(VariablePath variablePath) + { + return variablePath.IsUnqualified + ? variablePath.UserPath + : variablePath.UserPath.Substring(variablePath.UserPath.IndexOf(':') + 1); + } + /// /// Calculates the start line and column of the actual symbol name in a AST. /// From 4a90f28c79f03896d1d6e61e1b17e3e52ed045fb Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 30 Jan 2023 18:06:05 -0800 Subject: [PATCH 225/327] Handle symbol prefix identifier in `CommandHelpers` I think there's a cleaner way to do this, but this works. --- .../Services/PowerShell/Utility/CommandHelpers.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index f5590a026..da4a89a34 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -14,6 +14,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility { /// /// Provides utility methods for working with PowerShell commands. + /// TODO: Handle the `fn ` prefix better. /// internal static class CommandHelpers { @@ -114,6 +115,12 @@ public static async Task GetCommandInfoAsync( Validate.IsNotNull(nameof(commandName), commandName); Validate.IsNotNull(nameof(executionService), executionService); + // Remove the bucket identifier from symbol references. + if (commandName.StartsWith("fn ")) + { + commandName = commandName.Substring(3); + } + // If we have a CommandInfo cached, return that. if (s_commandInfoCache.TryGetValue(commandName, out CommandInfo cmdInfo)) { @@ -239,11 +246,11 @@ public static async Task GetAliasesAsync( // TODO: When we move to netstandard2.1, we can use another overload which generates // static delegates and thus reduces allocations. s_cmdletToAliasCache.AddOrUpdate( - aliasInfo.Definition, - (_) => new List { aliasInfo.Name }, - (_, v) => { v.Add(aliasInfo.Name); return v; }); + "fn " + aliasInfo.Definition, + (_) => new List { "fn " + aliasInfo.Name }, + (_, v) => { v.Add("fn " + aliasInfo.Name); return v; }); - s_aliasToCmdletCache.TryAdd(aliasInfo.Name, aliasInfo.Definition); + s_aliasToCmdletCache.TryAdd("fn " + aliasInfo.Name, "fn " + aliasInfo.Definition); } return new AliasMap( From 3b081899ac0b2edec3612da16a034e732019cc01 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 30 Jan 2023 18:32:37 -0800 Subject: [PATCH 226/327] More leniently filter workspace symbols Against the full name and not just the identifier, since it's what's displayed in the search menu. Now you can search methods by their parameters' names. --- .../Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 0e89eace9..7f9a43049 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -61,7 +61,7 @@ public override async Task> Handle(WorkspaceSymbolP continue; } - if (!IsQueryMatch(request.Query, symbol.Id)) + if (!IsQueryMatch(request.Query, symbol.Name)) { continue; } From 6d2d5a75b645463d2e9bec463748aabd31577132 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 30 Jan 2023 18:32:59 -0800 Subject: [PATCH 227/327] Map symbol types to LSP better --- .../Services/Symbols/SymbolType.cs | 7 ++++--- .../Services/Symbols/Visitors/SymbolVisitor.cs | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index dbcb95dfb..6533e7726 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -94,9 +94,10 @@ internal static SymbolKind GetSymbolKind(SymbolType symbolType) SymbolType.Method => SymbolKind.Method, SymbolType.Property => SymbolKind.Property, SymbolType.EnumMember => SymbolKind.EnumMember, - SymbolType.Variable => SymbolKind.Variable, - // TODO: More delicately handle the other symbol types. - _ => SymbolKind.Variable, + SymbolType.Variable or SymbolType.Parameter => SymbolKind.Variable, + SymbolType.HashtableKey => SymbolKind.Key, + SymbolType.Type => SymbolKind.TypeParameter, + SymbolType.Unknown or _ => SymbolKind.Object, }; } } diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index d913d713b..ac12bbfba 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -15,6 +15,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols; /// The goal of this is to be our one and only visitor, which parses a file when necessary /// performing Action, which takes a SymbolReference (that this visitor creates) and returns an /// AstVisitAction. In this way, all our symbols are created with the same initialization logic. +/// TODO: Visit hashtable keys, constants, arrays, namespaces, interfaces, operators, etc. /// internal sealed class SymbolVisitor : AstVisitor2 { From 4c1538b2bb6ec6cb3c18e8a2db1e9f2ad370ce17 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 31 Jan 2023 16:35:05 -0800 Subject: [PATCH 228/327] Restore `public ScriptRegion Create(...)` method --- .../Services/TextDocument/ScriptRegion.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs index 9f926cbea..e818b6d63 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs @@ -16,7 +16,7 @@ public sealed class ScriptRegion : IScriptExtent { internal TextEdit ToTextEdit() => new() { NewText = Text, Range = ToRange() }; - public Range ToRange() + internal Range ToRange() { return new Range { @@ -73,7 +73,7 @@ public ScriptRegion( EndOffset = endOffset; } - public ScriptRegion (IScriptExtent scriptExtent) + public ScriptRegion(IScriptExtent scriptExtent) { File = scriptExtent.File; @@ -95,6 +95,11 @@ public ScriptRegion (IScriptExtent scriptExtent) EndOffset = scriptExtent.EndOffset; } + /// + /// NOTE: While unused, we kept this as it was previously exposed on a public class. + /// + public static ScriptRegion Create(IScriptExtent scriptExtent) => new(scriptExtent); + #endregion #region Properties From 9e126703abfbe8beb160761a69f8f3c1e8732920 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 31 Jan 2023 16:41:50 -0800 Subject: [PATCH 229/327] Fix bugs in `VisitorUtils` around `LastIndexOf` Should have been just `IndexOf`, and also use `+` instead of `string.Concat()` for simplicity and speed. --- src/PowerShellEditorServices/Utility/VisitorUtils.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index fa4951163..fbfdd1413 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -106,7 +106,8 @@ internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAs internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(FunctionMemberAst functionMemberAst) { // find name index to get offset even with attributes, static, hidden ++ - int nameStartIndex = functionMemberAst.Extent.Text.LastIndexOf(string.Concat(functionMemberAst.Name, '('), StringComparison.OrdinalIgnoreCase); + int nameStartIndex = functionMemberAst.Extent.Text.IndexOf( + functionMemberAst.Name + '(', StringComparison.OrdinalIgnoreCase); return GetNameStartColumnAndLineFromAst(functionMemberAst, nameStartIndex); } @@ -119,8 +120,10 @@ internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAs internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(PropertyMemberAst propertyMemberAst, bool isEnumMember) { // find name index to get offset even with attributes, static, hidden ++ - string searchString = isEnumMember ? propertyMemberAst.Name : string.Concat('$', propertyMemberAst.Name); - int nameStartIndex = propertyMemberAst.Extent.Text.LastIndexOf(searchString, StringComparison.OrdinalIgnoreCase); + string searchString = isEnumMember + ? propertyMemberAst.Name : '$' + propertyMemberAst.Name; + int nameStartIndex = propertyMemberAst.Extent.Text.IndexOf( + searchString, StringComparison.OrdinalIgnoreCase); return GetNameStartColumnAndLineFromAst(propertyMemberAst, nameStartIndex); } From 0739fc1e952590e1a0a4ef26ff70113ea965bd0c Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 31 Jan 2023 16:51:11 -0800 Subject: [PATCH 230/327] Distinguish properties/enums from variables --- .../Symbols/Visitors/SymbolVisitor.cs | 4 +-- .../Language/SymbolsServiceTests.cs | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index ac12bbfba..6f868ef36 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -177,7 +177,7 @@ propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum return _action(new SymbolReference( symbolType, - "var " + propertyMemberAst.Name, + "prop " + propertyMemberAst.Name, VisitorUtils.GetMemberOverloadName(propertyMemberAst), VisitorUtils.GetNameExtent(propertyMemberAst), propertyMemberAst.Extent, @@ -196,7 +196,7 @@ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberE // TODO: It's too bad we can't get the property's real symbol and reuse its display string. return _action(new SymbolReference( SymbolType.Property, - "var " + memberName, + "prop " + memberName, "(property) " + memberName, memberExpressionAst.Member.Extent, memberExpressionAst.Extent, diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index adfcec550..5f7a6127d 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -621,7 +621,7 @@ public async Task FindsReferencesOnMethod() public async Task FindsPropertyDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.PropertySourceDetails).ConfigureAwait(true); - Assert.Equal("var SomePropWithDefault", symbol.Id); + Assert.Equal("prop SomePropWithDefault", symbol.Id); Assert.Equal("[string] $SomePropWithDefault", symbol.Name); Assert.Equal(SymbolType.Property, symbol.Type); Assert.True(symbol.IsDeclaration); @@ -634,14 +634,14 @@ public async Task FindsReferencesOnProperty() Assert.Collection(symbols, (i) => { - Assert.Equal("var SomeProp", i.Id); + Assert.Equal("prop SomeProp", i.Id); Assert.Equal("[int] $SomeProp", i.Name); Assert.Equal(SymbolType.Property, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("var SomeProp", i.Id); + Assert.Equal("prop SomeProp", i.Id); Assert.Equal("(property) SomeProp", i.Name); Assert.Equal(SymbolType.Property, i.Type); Assert.False(i.IsDeclaration); @@ -655,14 +655,14 @@ public void FindsOccurrencesOnProperty() Assert.Collection(symbols, (i) => { - Assert.Equal("var SomePropWithDefault", i.Id); + Assert.Equal("prop SomePropWithDefault", i.Id); Assert.Equal("[string] $SomePropWithDefault", i.Name); Assert.Equal(SymbolType.Property, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("var SomePropWithDefault", i.Id); + Assert.Equal("prop SomePropWithDefault", i.Id); Assert.Equal("(property) SomePropWithDefault", i.Name); Assert.Equal(SymbolType.Property, i.Type); Assert.False(i.IsDeclaration); @@ -673,7 +673,7 @@ public void FindsOccurrencesOnProperty() public async Task FindsEnumMemberDefinition() { SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumMemberSourceDetails).ConfigureAwait(true); - Assert.Equal("var Second", symbol.Id); + Assert.Equal("prop Second", symbol.Id); // Doesn't include [MyEnum]:: because that'd be redundant in the outline. Assert.Equal("Second", symbol.Name); Assert.Equal(SymbolType.EnumMember, symbol.Type); @@ -681,7 +681,7 @@ public async Task FindsEnumMemberDefinition() AssertIsRegion(symbol.NameRegion, 41, 5, 41, 11); symbol = await GetDefinition(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails).ConfigureAwait(true); - Assert.Equal("var First", symbol.Id); + Assert.Equal("prop First", symbol.Id); Assert.Equal("First", symbol.Name); Assert.Equal(SymbolType.EnumMember, symbol.Type); Assert.True(symbol.IsDeclaration); @@ -695,14 +695,14 @@ public async Task FindsReferencesOnEnumMember() Assert.Collection(symbols, (i) => { - Assert.Equal("var First", i.Id); + Assert.Equal("prop First", i.Id); Assert.Equal("First", i.Name); Assert.Equal(SymbolType.EnumMember, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("var First", i.Id); + Assert.Equal("prop First", i.Id); // The reference is just a member invocation, and so indistinguishable from a property. Assert.Equal("(property) First", i.Name); Assert.Equal(SymbolType.Property, i.Type); @@ -732,7 +732,8 @@ public void FindsSymbolsInFile() Assert.Equal(7, symbols.Count(i => i.Type == SymbolType.Function)); Assert.Equal(8, symbols.Count(i => i.Type == SymbolType.Variable)); Assert.Equal(4, symbols.Count(i => i.Type == SymbolType.Parameter)); - Assert.Equal(14, symbols.Count(i => i.Id.StartsWith("var "))); + Assert.Equal(12, symbols.Count(i => i.Id.StartsWith("var "))); + Assert.Equal(2, symbols.Count(i => i.Id.StartsWith("prop "))); SymbolReference symbol = symbols.First(i => i.Type == SymbolType.Function); Assert.Equal("fn AFunction", symbol.Id); @@ -760,7 +761,7 @@ public void FindsSymbolsInFile() Assert.True(symbol.IsDeclaration); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Property)); - Assert.Equal("var AProperty", symbol.Id); + Assert.Equal("prop AProperty", symbol.Id); Assert.Equal("[string] $AProperty", symbol.Name); Assert.True(symbol.IsDeclaration); @@ -780,7 +781,7 @@ public void FindsSymbolsInFile() Assert.True(symbol.IsDeclaration); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.EnumMember)); - Assert.Equal("var AValue", symbol.Id); + Assert.Equal("prop AValue", symbol.Id); Assert.Equal("AValue", symbol.Name); Assert.True(symbol.IsDeclaration); } @@ -806,7 +807,7 @@ public void FindsSymbolsWithNewLineInFile() AssertIsRegion(symbol.ScriptRegion, 8, 5, 10, 6); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Property)); - Assert.Equal("var SomePropWithDefault", symbol.Id); + Assert.Equal("prop SomePropWithDefault", symbol.Id); AssertIsRegion(symbol.NameRegion, 15, 5, 15, 25); AssertIsRegion(symbol.ScriptRegion, 12, 5, 15, 40); @@ -822,7 +823,7 @@ public void FindsSymbolsWithNewLineInFile() AssertIsRegion(symbol.ScriptRegion, 25, 1, 28, 2); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.EnumMember)); - Assert.Equal("var First", symbol.Id); + Assert.Equal("prop First", symbol.Id); AssertIsRegion(symbol.NameRegion, 27, 5, 27, 10); AssertIsRegion(symbol.ScriptRegion, 27, 5, 27, 10); } From 429655390f873a8eb8fc7f565386ea6a13c2f61e Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 31 Jan 2023 16:55:13 -0800 Subject: [PATCH 231/327] Distinguish constructors/methods from functions --- .../Symbols/Visitors/SymbolVisitor.cs | 4 ++-- .../Language/SymbolsServiceTests.cs | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index 6f868ef36..1035f2557 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -158,7 +158,7 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem return _action(new SymbolReference( symbolType, - "fn " + functionMemberAst.Name, // We bucket all the overloads. + "mtd " + functionMemberAst.Name, // We bucket all the overloads. VisitorUtils.GetMemberOverloadName(functionMemberAst), nameExtent, functionMemberAst.Extent, @@ -215,7 +215,7 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio // TODO: It's too bad we can't get the member's real symbol and reuse its display string. return _action(new SymbolReference( SymbolType.Method, - "fn " + memberName, + "mtd " + memberName, "(method) " + memberName, methodCallAst.Member.Extent, methodCallAst.Extent, diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 5f7a6127d..53993a484 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -552,14 +552,14 @@ public async Task FindsConstructorDefinition() Assert.Collection(symbols, (i) => { - Assert.Equal("fn SuperClass", i.Id); + Assert.Equal("mtd SuperClass", i.Id); Assert.Equal("SuperClass([string]$name)", i.Name); Assert.Equal(SymbolType.Constructor, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("fn SuperClass", i.Id); + Assert.Equal("mtd SuperClass", i.Id); Assert.Equal("SuperClass()", i.Name); Assert.Equal(SymbolType.Constructor, i.Type); Assert.True(i.IsDeclaration); @@ -576,21 +576,21 @@ public async Task FindsMethodDefinition() Assert.Collection(symbols, (i) => { - Assert.Equal("fn MyClassMethod", i.Id); + Assert.Equal("mtd MyClassMethod", i.Id); Assert.Equal("string MyClassMethod([string]$param1, $param2, [int]$param3)", i.Name); Assert.Equal(SymbolType.Method, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("fn MyClassMethod", i.Id); + Assert.Equal("mtd MyClassMethod", i.Id); Assert.Equal("string MyClassMethod([MyEnum]$param1)", i.Name); Assert.Equal(SymbolType.Method, i.Type); Assert.True(i.IsDeclaration); }, (i) => { - Assert.Equal("fn MyClassMethod", i.Id); + Assert.Equal("mtd MyClassMethod", i.Id); Assert.Equal("string MyClassMethod()", i.Name); Assert.Equal(SymbolType.Method, i.Type); Assert.True(i.IsDeclaration); @@ -607,7 +607,7 @@ public async Task FindsReferencesOnMethod() (i) => Assert.Equal("string MyClassMethod()", i.Name), (i) => // The invocation! { - Assert.Equal("fn MyClassMethod", i.Id); + Assert.Equal("mtd MyClassMethod", i.Id); Assert.Equal("(method) MyClassMethod", i.Name); Assert.Equal("$o.MyClassMethod()", i.SourceLine); Assert.Equal(SymbolType.Method, i.Type); @@ -766,12 +766,12 @@ public void FindsSymbolsInFile() Assert.True(symbol.IsDeclaration); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Constructor)); - Assert.Equal("fn AClass", symbol.Id); + Assert.Equal("mtd AClass", symbol.Id); Assert.Equal("AClass([string]$AParameter)", symbol.Name); Assert.True(symbol.IsDeclaration); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Method)); - Assert.Equal("fn AMethod", symbol.Id); + Assert.Equal("mtd AMethod", symbol.Id); Assert.Equal("void AMethod([string]$param1, [int]$param2, $param3)", symbol.Name); Assert.True(symbol.IsDeclaration); @@ -802,7 +802,7 @@ public void FindsSymbolsWithNewLineInFile() AssertIsRegion(symbol.ScriptRegion, 6, 1, 23, 2); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Constructor)); - Assert.Equal("fn NewLineClass", symbol.Id); + Assert.Equal("mtd NewLineClass", symbol.Id); AssertIsRegion(symbol.NameRegion, 8, 5, 8, 17); AssertIsRegion(symbol.ScriptRegion, 8, 5, 10, 6); @@ -812,7 +812,7 @@ public void FindsSymbolsWithNewLineInFile() AssertIsRegion(symbol.ScriptRegion, 12, 5, 15, 40); symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Method)); - Assert.Equal("fn MyClassMethod", symbol.Id); + Assert.Equal("mtd MyClassMethod", symbol.Id); Assert.Equal("string MyClassMethod([MyNewLineEnum]$param1)", symbol.Name); AssertIsRegion(symbol.NameRegion, 20, 5, 20, 18); AssertIsRegion(symbol.ScriptRegion, 17, 5, 22, 6); From 6208b539053f4cc6e129f63b95298fc9464be7df Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 1 Feb 2023 10:28:24 -0800 Subject: [PATCH 232/327] Remove unused code from `Extensions.cs` --- .../Utility/Extensions.cs | 123 ------------------ 1 file changed, 123 deletions(-) diff --git a/src/PowerShellEditorServices/Utility/Extensions.cs b/src/PowerShellEditorServices/Utility/Extensions.cs index 22148e8b3..88a9fa4f3 100644 --- a/src/PowerShellEditorServices/Utility/Extensions.cs +++ b/src/PowerShellEditorServices/Utility/Extensions.cs @@ -2,9 +2,6 @@ // Licensed under the MIT License. using System; -using System.Linq; -using System.Collections.Generic; -using System.Management.Automation.Language; using System.Text; namespace Microsoft.PowerShell.EditorServices.Utility @@ -33,126 +30,6 @@ public static string SafeToString(this object obj) return str; } - /// - /// Get the maximum of the elements from the given enumerable. - /// - /// Type of object for which the enumerable is defined. - /// An enumerable object of type T - /// A comparer for ordering elements of type T. The comparer should handle null values. - /// An object of type T. If the enumerable is empty or has all null elements, then the method returns null. - public static T MaxElement(this IEnumerable elements, Func comparer) where T : class - { - if (elements == null) - { - throw new ArgumentNullException(nameof(elements)); - } - - if (comparer == null) - { - throw new ArgumentNullException(nameof(comparer)); - } - - if (!elements.Any()) - { - return null; - } - - T maxElement = elements.First(); - foreach (T element in elements.Skip(1)) - { - if (element != null && comparer(element, maxElement) > 0) - { - maxElement = element; - } - } - - return maxElement; - } - - /// - /// Get the minimum of the elements from the given enumerable. - /// - /// Type of object for which the enumerable is defined. - /// An enumerable object of type T - /// A comparer for ordering elements of type T. The comparer should handle null values. - /// An object of type T. If the enumerable is empty or has all null elements, then the method returns null. - public static T MinElement(this IEnumerable elements, Func comparer) where T : class => MaxElement(elements, (elementX, elementY) => -1 * comparer(elementX, elementY)); - - /// - /// Compare extents with respect to their widths. - /// - /// Width of an extent is defined as the difference between its EndOffset and StartOffest properties. - /// - /// Extent of type IScriptExtent. - /// Extent of type IScriptExtent. - /// 0 if extentX and extentY are equal in width. 1 if width of extent X is greater than that of extent Y. Otherwise, -1. - public static int ExtentWidthComparer(this IScriptExtent extentX, IScriptExtent extentY) - { - if (extentX == null && extentY == null) - { - return 0; - } - - if (extentX != null && extentY == null) - { - return 1; - } - - if (extentX == null) - { - return -1; - } - - int extentWidthX = extentX.EndOffset - extentX.StartOffset; - int extentWidthY = extentY.EndOffset - extentY.StartOffset; - if (extentWidthX > extentWidthY) - { - return 1; - } - else if (extentWidthX < extentWidthY) - { - return -1; - } - else - { - return 0; - } - } - - /// - /// Check if the given coordinates are wholly contained in the instance's extent. - /// - /// Extent of type IScriptExtent. - /// 1-based line number. - /// 1-based column number - /// True if the coordinates are wholly contained in the instance's extent, otherwise, false. - public static bool Contains(this IScriptExtent scriptExtent, int line, int column) - { - if (scriptExtent.StartLineNumber > line || scriptExtent.EndLineNumber < line) - { - return false; - } - - if (scriptExtent.StartLineNumber == line) - { - if (scriptExtent.StartLineNumber == scriptExtent.EndLineNumber) - { - return scriptExtent.StartColumnNumber <= column && scriptExtent.EndColumnNumber >= column; - } - else - { - return scriptExtent.StartColumnNumber <= column; - } - } - - if (scriptExtent.EndLineNumber == line) - { - return scriptExtent.EndColumnNumber >= column; - } - - return true; - } - /// /// Same as but never CRLF. Use this when building /// formatting for clients that may not render CRLF correctly. From 41d8769854611185f96353cfe1764c005da68228 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 2 Feb 2023 13:10:07 -0800 Subject: [PATCH 233/327] Update CHANGELOG for `v3.8.0` --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e826a0e3d..dd7f96d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # PowerShell Editor Services Release History +## v3.8.0 +### Thursday, February 02, 2023 + +In the PR below we rewrote all the symbol logic. Classes (and their properties and +methods) are now proper symbols. Instead of a dozen similar-yet-different Abstract Symbol +Tree (AST) PowerShell script visitors handling different parts of each symbol-related +request, we have a single visitor that builds a cached dictionary of symbols for each +file. This was a massive simplification of the code that also leads to huge performance +improvements across all the symbol related features: + +- [Go to Symbol in Workspace](https://code.visualstudio.com/Docs/editor/editingevolved#_open-symbol-by-name) +- [Go to Symbol in Editor](https://code.visualstudio.com/Docs/editor/editingevolved#_go-to-symbol) +- [Go to Definition](https://code.visualstudio.com/Docs/editor/editingevolved#_go-to-definition) +- [Go to References / CodeLens](https://code.visualstudio.com/Docs/editor/editingevolved#_reference-information) +- [Outline view](https://code.visualstudio.com/docs/getstarted/userinterface#_outline-view) + +Please try it out and give us feedback! There's plenty of room for more improvement, and +this will be much easier going forward. + +- ✨ 🙏 [PowerShellEditorServices #1984](https://github.com/PowerShell/PowerShellEditorServices/pull/1984) - Integrating class symbol support. + ## v3.7.3 ### Wednesday, January 04, 2023 From ec33127bc33e4124a887949aa6962d6852f28129 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 2 Feb 2023 13:10:07 -0800 Subject: [PATCH 234/327] Bump version to `v3.8.0` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index ca8404bd4..85b8f9d31 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.7.3 + 3.8.0 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 9fc35546b..4c4004459 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.7.3' +ModuleVersion = '3.8.0' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 368dcfa7853a6acf30981dae61af17bbd2c1a932 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:16:22 -0800 Subject: [PATCH 235/327] Keep only first assignment as declaration (#1989) --- .../Services/Symbols/ReferenceTable.cs | 9 +++++++++ .../References/SimpleFile.ps1 | 2 +- .../Language/SymbolsServiceTests.cs | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs index 620fe2ea1..6cb8e52c4 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs @@ -94,6 +94,15 @@ private AstVisitAction AddReference(SymbolReference symbol) _ => new ConcurrentBag { symbol }, (_, existing) => { + // Keep only the first variable encountered as a declaration marked as such. This + // keeps the first assignment without also counting every reassignment as a + // declaration (cleaning up e.g. Code's outline view). + if (symbol.Type is SymbolType.Variable && symbol.IsDeclaration + && existing.Any(i => i.IsDeclaration)) + { + symbol = symbol with { IsDeclaration = false }; + } + existing.Add(symbol); return existing; }); diff --git a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 index b60389c63..64f3d0f43 100644 --- a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 @@ -5,7 +5,7 @@ function My-Function ($myInput) $things = 4 -$things +$things = 3 My-Function $things diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 53993a484..6fe20e407 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -263,7 +263,8 @@ public async Task FindsFunctionDefinitionInWorkspace() [Fact] public async Task FindsVariableDefinition() { - SymbolReference symbol = await GetDefinition(FindsVariableDefinitionData.SourceDetails).ConfigureAwait(true); + IEnumerable definitions = await GetDefinitions(FindsVariableDefinitionData.SourceDetails).ConfigureAwait(true); + SymbolReference symbol = Assert.Single(definitions); // Even though it's re-assigned Assert.Equal("var things", symbol.Id); Assert.Equal("$things", symbol.Name); Assert.Equal(SymbolType.Variable, symbol.Type); From 9ee381f171778863fdf95d79f0cbcb8f51efe8ea Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Tue, 7 Feb 2023 22:27:53 +0100 Subject: [PATCH 236/327] Support Run/Debug tests in PSKoans files (#1988) Code Lens feature only depends on Pester symbols, so we only need to make sure they appear for `*.koans.ps1` files --- .../InvokePesterStub.ps1 | 16 ++++++++++++- .../Symbols/PesterDocumentSymbolProvider.cs | 5 ++-- .../Symbols/FindSymbolsInPSKoansFile.cs | 21 +++++++++++++++++ .../Symbols/PesterFile.Koans.ps1 | 23 +++++++++++++++++++ .../Language/SymbolsServiceTests.cs | 9 ++++++++ 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInPSKoansFile.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.Koans.ps1 diff --git a/module/PowerShellEditorServices/InvokePesterStub.ps1 b/module/PowerShellEditorServices/InvokePesterStub.ps1 index 6fc1e799f..10615063a 100755 --- a/module/PowerShellEditorServices/InvokePesterStub.ps1 +++ b/module/PowerShellEditorServices/InvokePesterStub.ps1 @@ -59,10 +59,24 @@ param( [string] $OutputPath ) -$pesterModule = Microsoft.PowerShell.Core\Get-Module Pester # add one line, so the subsequent output is not shifted to the side Write-Output '' +# checking and importing PSKoans first as it will import the required Pester-version (v4 vs v5) +if ($ScriptPath -match '\.Koans\.ps1$') { + $psKoansModule = Microsoft.PowerShell.Core\Get-Module PSKoans + if (!$psKoansModule) { + Write-Output "Importing PSKoans module..." + $psKoansModule = Microsoft.PowerShell.Core\Import-Module PSKoans -ErrorAction Ignore -PassThru + } + + if (!$psKoansModule) { + Write-Warning "Failed to import PSKoans. You must install PSKoans module to run or debug tests in *.Koans.ps1 files." + return + } +} + +$pesterModule = Microsoft.PowerShell.Core\Get-Module Pester if (!$pesterModule) { Write-Output "Importing Pester module..." if ($MinimumVersion5) { diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index f38e72372..7a77d7607 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -20,9 +20,8 @@ internal class PesterDocumentSymbolProvider : IDocumentSymbolProvider IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( ScriptFile scriptFile) { - if (!scriptFile.FilePath.EndsWith( - "tests.ps1", - StringComparison.OrdinalIgnoreCase)) + if (!scriptFile.FilePath.EndsWith(".tests.ps1", StringComparison.OrdinalIgnoreCase) && + !scriptFile.FilePath.EndsWith(".Koans.ps1", StringComparison.OrdinalIgnoreCase)) { return Enumerable.Empty(); } diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInPSKoansFile.cs b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInPSKoansFile.cs new file mode 100644 index 000000000..03d26d82f --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInPSKoansFile.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Test.Shared.Symbols +{ + public static class FindSymbolsInPSKoansFile + { + public static readonly ScriptRegion SourceDetails = + new( + file: TestUtilities.NormalizePath("Symbols/PesterFile.Koans.ps1"), + text: string.Empty, + startLineNumber: 0, + startColumnNumber: 0, + startOffset: 0, + endLineNumber: 0, + endColumnNumber: 0, + endOffset: 0); + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.Koans.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.Koans.ps1 new file mode 100644 index 000000000..1c745d0ac --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/PesterFile.Koans.ps1 @@ -0,0 +1,23 @@ +Describe "Testing Pester symbols in a PSKoans-file" { + Context "Simple demo" { + BeforeAll { + + } + + BeforeEach { + + } + + It "Should return Pester symbols" { + + } + + AfterEach { + + } + } + + AfterAll { + + } +} diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 6fe20e407..0a066583c 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -890,6 +890,15 @@ public void FindsSymbolsInPesterFile() Assert.Equal(5, symbol.ScriptRegion.StartColumnNumber); } + [Fact] + public void FindsSymbolsInPSKoansFile() + { + IEnumerable symbols = FindSymbolsInFile(FindSymbolsInPSKoansFile.SourceDetails).OfType(); + + // Pester symbols are properly tested in FindsSymbolsInPesterFile so only counting to make sure they appear + Assert.Equal(7, symbols.Count(i => i.Type == SymbolType.Function)); + } + [Fact] public void FindsSymbolsInPSDFile() { From 422e2cda25e8794f1912a494bf749e99b4125130 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Wed, 8 Feb 2023 11:06:50 -0800 Subject: [PATCH 237/327] Strip scope from function references (#1990) Like methods we just bucket them as overloads, which means that it works better than before despite not being perfect. --- .../Services/Symbols/Visitors/SymbolVisitor.cs | 4 ++-- .../Utility/VisitorUtils.cs | 14 ++++++++++++++ .../Symbols/MultipleSymbols.ps1 | 2 +- .../Language/SymbolsServiceTests.cs | 3 ++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index 1035f2557..08ab7db95 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -40,7 +40,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) return _action(new SymbolReference( SymbolType.Function, - "fn " + CommandHelpers.StripModuleQualification(commandName, out _), + "fn " + VisitorUtils.GetUnqualifiedFunctionName(CommandHelpers.StripModuleQualification(commandName, out _)), commandName, commandAst.CommandElements[0].Extent, commandAst.Extent, @@ -64,7 +64,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst); return _action(new SymbolReference( symbolType, - "fn " + functionDefinitionAst.Name, + "fn " + VisitorUtils.GetUnqualifiedFunctionName(functionDefinitionAst.Name), VisitorUtils.GetFunctionDisplayName(functionDefinitionAst), nameExtent, functionDefinitionAst.Extent, diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index fbfdd1413..a35f402bf 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -33,6 +33,20 @@ internal static class VisitorUtils return PSESSymbols.AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null; } + // Strip the qualification, if there is any, so script:my-function is a reference of my-function etc. + internal static string GetUnqualifiedFunctionName(string name) + { + foreach (string scope in new string[] { "private:", "script:", "global:", "local:" }) + { + if (name.StartsWith(scope, StringComparison.OrdinalIgnoreCase)) + { + return name.Substring(scope.Length); + } + } + + return name; + } + // Strip the qualification, if there is any, so $var is a reference of $script:var etc. internal static string GetUnqualifiedVariableName(VariablePath variablePath) { diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 index db53a6c1a..b4f54c329 100644 --- a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 @@ -4,7 +4,7 @@ $Script:ScriptVar2 = 2 "`$Script:ScriptVar2 is $Script:ScriptVar2" -function AFunction {} +function script:AFunction {} filter AFilter {$_} diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 0a066583c..257e36880 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -738,8 +738,9 @@ public void FindsSymbolsInFile() SymbolReference symbol = symbols.First(i => i.Type == SymbolType.Function); Assert.Equal("fn AFunction", symbol.Id); - Assert.Equal("function AFunction ()", symbol.Name); + Assert.Equal("function script:AFunction ()", symbol.Name); Assert.True(symbol.IsDeclaration); + Assert.Equal(2, GetOccurrences(symbol.NameRegion).Count()); symbol = symbols.First(i => i.Id == "fn AFilter"); Assert.Equal("filter AFilter ()", symbol.Name); From a13eff26a9e3a2cc3a994af68efe92b50f8069f6 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 10 Feb 2023 16:00:54 -0800 Subject: [PATCH 238/327] Declare scope prefixes array as static readonly (#1991) --- src/PowerShellEditorServices/Utility/VisitorUtils.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs index a35f402bf..7ce37c194 100644 --- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs +++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs @@ -33,10 +33,18 @@ internal static class VisitorUtils return PSESSymbols.AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null; } + private static readonly string[] s_scopes = new string[] + { + "private:", + "script:", + "global:", + "local:" + }; + // Strip the qualification, if there is any, so script:my-function is a reference of my-function etc. internal static string GetUnqualifiedFunctionName(string name) { - foreach (string scope in new string[] { "private:", "script:", "global:", "local:" }) + foreach (string scope in s_scopes) { if (name.StartsWith(scope, StringComparison.OrdinalIgnoreCase)) { From 4e651e057114488c9e6e572a2c238d8abfe75d0b Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 10 Feb 2023 16:01:07 -0800 Subject: [PATCH 239/327] Count `${Function:My-Function}` as a function reference (#1993) --- .../Services/Symbols/Visitors/SymbolVisitor.cs | 15 +++++++++++++++ .../References/SimpleFile.ps1 | 2 ++ .../Language/SymbolsServiceTests.cs | 17 ++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index 08ab7db95..fda5a53dc 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -95,6 +95,21 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var return AstVisitAction.Continue; } + // Count $Function:MyFunction as function references. + if (variableExpressionAst.VariablePath.IsDriveQualified + && variableExpressionAst.VariablePath.DriveName.Equals("Function", StringComparison.OrdinalIgnoreCase)) + { + return _action(new SymbolReference( + SymbolType.Function, + "fn " + VisitorUtils.GetUnqualifiedVariableName(variableExpressionAst.VariablePath), + "$" + variableExpressionAst.VariablePath.UserPath, + variableExpressionAst.Extent, + variableExpressionAst.Extent, + _file, + false + )); + } + // TODO: Consider tracking unscoped variable references only when they're declared within // the same function definition. return _action(new SymbolReference( diff --git a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 index 64f3d0f43..b5c1ef7ce 100644 --- a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 @@ -18,3 +18,5 @@ Write-Host Get-ChildItem My-Alias + +Invoke-Command -ScriptBlock ${Function:My-Function} diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 257e36880..3b69e07c7 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -223,6 +223,13 @@ public async Task FindsReferencesOnFunction() Assert.Equal("My-Function", i.Name); Assert.Equal(SymbolType.Function, i.Type); Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("fn My-Function", i.Id); + Assert.Equal("$Function:My-Function", i.Name); + Assert.Equal(SymbolType.Function, i.Type); + Assert.False(i.IsDeclaration); }); } @@ -246,7 +253,8 @@ await psesHost.ExecutePSCommandAsync( { AssertIsRegion(i.NameRegion, 20, 1, 20, 9); Assert.Equal("fn My-Alias", i.Id); - }); + }, + (i) => AssertIsRegion(i.NameRegion, 22, 29, 22, 52)); } [Fact] @@ -324,6 +332,13 @@ public void FindsOccurrencesOnFunction() Assert.Equal("fn My-Function", i.Id); Assert.Equal(SymbolType.Function, i.Type); Assert.False(i.IsDeclaration); + }, + (i) => + { + Assert.Equal("fn My-Function", i.Id); + Assert.Equal("$Function:My-Function", i.Name); + Assert.Equal(SymbolType.Function, i.Type); + Assert.False(i.IsDeclaration); }); } From 9aa50590ae4724b61a69b36afeca17bc59c50fc7 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Mon, 13 Feb 2023 11:37:50 -0800 Subject: [PATCH 240/327] Add `WorkspaceFolders` and use it when enumerating files (#1995) * Rename `WorkspacePath` to `InitialWorkingDirectory` And note where the API needs to be updated to get the workspace path for the current open editor (since there could be multiple workspaces). * Add `WorkspaceFolders` and use it when enumerating files * Add symbols across multi-root workspace unit test --- .../Extensions/Api/WorkspaceService.cs | 6 +- .../Extensions/EditorWorkspace.cs | 2 +- .../Server/PsesLanguageServer.cs | 25 +++---- .../Extension/EditorOperationsService.cs | 4 +- .../Services/Symbols/SymbolReference.cs | 14 ++++ .../Handlers/ConfigurationHandler.cs | 2 +- .../Services/Workspace/WorkspaceService.cs | 72 ++++++++++++------- .../Language/SymbolsServiceTests.cs | 26 ++++++- .../Session/WorkspaceTests.cs | 22 +++--- 9 files changed, 116 insertions(+), 57 deletions(-) diff --git a/src/PowerShellEditorServices/Extensions/Api/WorkspaceService.cs b/src/PowerShellEditorServices/Extensions/Api/WorkspaceService.cs index 443183f49..8e5472633 100644 --- a/src/PowerShellEditorServices/Extensions/Api/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Extensions/Api/WorkspaceService.cs @@ -45,7 +45,7 @@ public interface IEditorScriptFile public interface IWorkspaceService { /// - /// The root path of the workspace. + /// The root path of the workspace for the current editor. /// string WorkspacePath { get; } @@ -116,7 +116,9 @@ internal WorkspaceService( ExcludedFileGlobs = _workspaceService.ExcludeFilesGlob.AsReadOnly(); } - public string WorkspacePath => _workspaceService.WorkspacePath; + // TODO: This needs to use the associated EditorContext to get the workspace for the current + // editor instead of the initial working directory. + public string WorkspacePath => _workspaceService.InitialWorkingDirectory; public bool FollowSymlinks => _workspaceService.FollowSymlinks; diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs index 1a471b034..18f001d56 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs +++ b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs @@ -18,7 +18,7 @@ public sealed class EditorWorkspace #region Properties /// - /// Gets the current workspace path if there is one or null otherwise. + /// Gets the current workspace path if there is one for the open editor or null otherwise. /// public string Path => editorOperations.GetWorkspacePath(); diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 05099b36b..440e84a70 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.IO; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -13,7 +14,6 @@ using Microsoft.PowerShell.EditorServices.Services.Template; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Server; using Serilog; @@ -130,12 +130,7 @@ public async Task StartAsync() WorkspaceService workspaceService = languageServer.Services.GetService(); if (initializeParams.WorkspaceFolders is not null) { - // TODO: Support multi-workspace. - foreach (WorkspaceFolder workspaceFolder in initializeParams.WorkspaceFolders) - { - workspaceService.WorkspacePath = workspaceFolder.Uri.GetFileSystemPath(); - break; - } + workspaceService.WorkspaceFolders.AddRange(initializeParams.WorkspaceFolders); } // Parse initialization options. @@ -149,13 +144,19 @@ public async Task StartAsync() // // NOTE: The keys start with a lowercase because OmniSharp's client // (used for testing) forces it to be that way. - LoadProfiles = initializationOptions?.GetValue("enableProfileLoading")?.Value() ?? true, - // TODO: Consider deprecating the setting which sets this and - // instead use WorkspacePath exclusively. - InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value() ?? workspaceService.WorkspacePath, - ShellIntegrationEnabled = initializationOptions?.GetValue("shellIntegrationEnabled")?.Value() ?? false + LoadProfiles = initializationOptions?.GetValue("enableProfileLoading")?.Value() + ?? true, + // First check the setting, then use the first workspace folder, + // finally fall back to CWD. + InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value() + ?? workspaceService.WorkspaceFolders.FirstOrDefault()?.Uri.GetFileSystemPath() + ?? Directory.GetCurrentDirectory(), + ShellIntegrationEnabled = initializationOptions?.GetValue("shellIntegrationEnabled")?.Value() + ?? false }; + workspaceService.InitialWorkingDirectory = hostStartOptions.InitialWorkingDirectory; + _psesHost = languageServer.Services.GetService(); return _psesHost.TryStartAsync(hostStartOptions, cancellationToken); }); diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs index 0f0f2a01a..fa6da7f90 100644 --- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs @@ -191,7 +191,9 @@ public async Task SaveFileAsync(string currentPath, string newSavePath) }).ReturningVoid(CancellationToken.None).ConfigureAwait(false); } - public string GetWorkspacePath() => _workspaceService.WorkspacePath; + // TODO: This should get the current editor's context and use it to determine which + // workspace it's in. + public string GetWorkspacePath() => _workspaceService.InitialWorkingDirectory; public string GetWorkspaceRelativePath(string filePath) => _workspaceService.GetRelativePath(filePath); diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs index 2c6c6ef8f..5e36bf63b 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs @@ -66,5 +66,19 @@ public SymbolReference( } IsDeclaration = isDeclaration; } + + /// + /// This is only used for unit tests! + /// + internal SymbolReference(string id, SymbolType type) + { + Id = id; + Type = type; + Name = ""; + NameRegion = new("", "", 0, 0, 0, 0, 0, 0); + ScriptRegion = NameRegion; + SourceLine = ""; + FilePath = ""; + } } } diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index ea7214a0a..230a82d58 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -58,7 +58,7 @@ public override async Task Handle(DidChangeConfigurationParams request, Ca _configurationService.CurrentSettings.Update( incomingSettings.Powershell, - _workspaceService.WorkspacePath, + _workspaceService.InitialWorkingDirectory, _logger); // Run any events subscribed to configuration updates diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs index 002a757ad..f705101f8 100644 --- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Security; using System.Text; using Microsoft.Extensions.FileSystemGlobbing; @@ -13,6 +14,7 @@ using Microsoft.PowerShell.EditorServices.Services.Workspace; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace Microsoft.PowerShell.EditorServices.Services { @@ -58,9 +60,19 @@ internal class WorkspaceService #region Properties /// - /// Gets or sets the root path of the workspace. + /// Gets or sets the initial working directory. + /// + /// This is settable by the same key in the initialization options, and likely corresponds + /// to the root of the workspace if only one workspace folder is being used. However, in + /// multi-root workspaces this may be any workspace folder's root (or none if overridden). + /// /// - public string WorkspacePath { get; set; } + public string InitialWorkingDirectory { get; set; } + + /// + /// Gets or sets the folders of the workspace. + /// + public List WorkspaceFolders { get; set; } /// /// Gets or sets the default list of file globs to exclude during workspace searches. @@ -83,6 +95,7 @@ public WorkspaceService(ILoggerFactory factory) { powerShellVersion = VersionUtils.PSVersion; logger = factory.CreateLogger(); + WorkspaceFolders = new List(); ExcludeFilesGlob = new List(); FollowSymlinks = true; } @@ -299,9 +312,9 @@ public string GetRelativePath(string filePath) { string resolvedPath = filePath; - if (!IsPathInMemory(filePath) && !string.IsNullOrEmpty(WorkspacePath)) + if (!IsPathInMemory(filePath) && !string.IsNullOrEmpty(InitialWorkingDirectory)) { - Uri workspaceUri = new(WorkspacePath); + Uri workspaceUri = new(InitialWorkingDirectory); Uri fileUri = new(filePath); resolvedPath = workspaceUri.MakeRelativeUri(fileUri).ToString(); @@ -331,39 +344,46 @@ public IEnumerable EnumeratePSFiles() } /// - /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner. + /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace folders in a + /// recursive manner. Falls back to initial working directory if there are no workspace folders. /// /// An enumerator over the PowerShell files found in the workspace. public IEnumerable EnumeratePSFiles( string[] excludeGlobs, string[] includeGlobs, int maxDepth, - bool ignoreReparsePoints - ) + bool ignoreReparsePoints) { - if (WorkspacePath is null || !Directory.Exists(WorkspacePath)) - { - yield break; - } + IEnumerable rootPaths = WorkspaceFolders.Count == 0 + ? new List { InitialWorkingDirectory } + : WorkspaceFolders.Select(i => i.Uri.GetFileSystemPath()); Matcher matcher = new(); foreach (string pattern in includeGlobs) { matcher.AddInclude(pattern); } foreach (string pattern in excludeGlobs) { matcher.AddExclude(pattern); } - WorkspaceFileSystemWrapperFactory fsFactory = new( - WorkspacePath, - maxDepth, - VersionUtils.IsNetCore ? s_psFileExtensionsCoreFramework : s_psFileExtensionsFullFramework, - ignoreReparsePoints, - logger - ); - PatternMatchingResult fileMatchResult = matcher.Execute(fsFactory.RootDirectory); - foreach (FilePatternMatch item in fileMatchResult.Files) + foreach (string rootPath in rootPaths) { - // item.Path always contains forward slashes in paths when it should be backslashes on Windows. - // Since we're returning strings here, it's important to use the correct directory separator. - string path = VersionUtils.IsWindows ? item.Path.Replace('/', Path.DirectorySeparatorChar) : item.Path; - yield return Path.Combine(WorkspacePath, path); + if (!Directory.Exists(rootPath)) + { + continue; + } + + WorkspaceFileSystemWrapperFactory fsFactory = new( + rootPath, + maxDepth, + VersionUtils.IsNetCore ? s_psFileExtensionsCoreFramework : s_psFileExtensionsFullFramework, + ignoreReparsePoints, + logger); + + PatternMatchingResult fileMatchResult = matcher.Execute(fsFactory.RootDirectory); + foreach (FilePatternMatch item in fileMatchResult.Files) + { + // item.Path always contains forward slashes in paths when it should be backslashes on Windows. + // Since we're returning strings here, it's important to use the correct directory separator. + string path = VersionUtils.IsWindows ? item.Path.Replace('/', Path.DirectorySeparatorChar) : item.Path; + yield return Path.Combine(rootPath, path); + } } } @@ -423,7 +443,7 @@ internal static bool IsPathInMemory(string filePath) return isInMemory; } - internal string ResolveWorkspacePath(string path) => ResolveRelativeScriptPath(WorkspacePath, path); + internal string ResolveWorkspacePath(string path) => ResolveRelativeScriptPath(InitialWorkingDirectory, path); internal string ResolveRelativeScriptPath(string baseFilePath, string relativePath) { diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 3b69e07c7..dbe0f8ca6 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -23,6 +23,8 @@ using Microsoft.PowerShell.EditorServices.Test.Shared.SymbolDetails; using Microsoft.PowerShell.EditorServices.Test.Shared.Symbols; using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Xunit; namespace PowerShellEditorServices.Test.Language @@ -38,10 +40,11 @@ public class SymbolsServiceTests : IDisposable public SymbolsServiceTests() { psesHost = PsesHostFactory.Create(NullLoggerFactory.Instance); - workspace = new WorkspaceService(NullLoggerFactory.Instance) + workspace = new WorkspaceService(NullLoggerFactory.Instance); + workspace.WorkspaceFolders.Add(new WorkspaceFolder { - WorkspacePath = TestUtilities.GetSharedPath("References") - }; + Uri = DocumentUri.FromFileSystemPath(TestUtilities.GetSharedPath("References")) + }); symbolsService = new SymbolsService( NullLoggerFactory.Instance, psesHost, @@ -233,6 +236,23 @@ public async Task FindsReferencesOnFunction() }); } + [Fact] + public async Task FindsReferenceAcrossMultiRootWorkspace() + { + workspace.WorkspaceFolders = new[] { "Debugging", "ParameterHints", "SymbolDetails" } + .Select(i => new WorkspaceFolder + { + Uri = DocumentUri.FromFileSystemPath(TestUtilities.GetSharedPath(i)) + }).ToList(); + + SymbolReference symbol = new("fn Get-Process", SymbolType.Function); + IEnumerable symbols = await symbolsService.ScanForReferencesOfSymbolAsync(symbol).ConfigureAwait(true); + Assert.Collection(symbols.OrderBy(i => i.FilePath), + i => Assert.EndsWith("VariableTest.ps1", i.FilePath), + i => Assert.EndsWith("ParamHints.ps1", i.FilePath), + i => Assert.EndsWith("SymbolDetails.ps1", i.FilePath)); + } + [Fact] public async Task FindsReferencesOnFunctionIncludingAliases() { diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs index c76da5328..2c7a44279 100644 --- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -39,7 +39,7 @@ public void CanResolveWorkspaceRelativePath() string expectedOutsidePath = TestUtilities.NormalizePath("../PeerPath/FilePath.ps1"); // Test with a workspace path - workspace.WorkspacePath = workspacePath; + workspace.InitialWorkingDirectory = workspacePath; Assert.Equal(expectedInsidePath, workspace.GetRelativePath(testPathInside)); Assert.Equal(expectedOutsidePath, workspace.GetRelativePath(testPathOutside)); Assert.Equal(testPathAnotherDrive, workspace.GetRelativePath(testPathAnotherDrive)); @@ -49,7 +49,7 @@ internal static WorkspaceService FixturesWorkspace() { return new WorkspaceService(NullLoggerFactory.Instance) { - WorkspacePath = TestUtilities.NormalizePath("Fixtures/Workspace") + InitialWorkingDirectory = TestUtilities.NormalizePath("Fixtures/Workspace") }; } @@ -94,10 +94,10 @@ public void CanRecurseDirectoryTree() List expected = new() { - Path.Combine(workspace.WorkspacePath, "nested", "donotfind.ps1"), - Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), - Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psm1"), - Path.Combine(workspace.WorkspacePath, "rootfile.ps1") + Path.Combine(workspace.InitialWorkingDirectory, "nested", "donotfind.ps1"), + Path.Combine(workspace.InitialWorkingDirectory, "nested", "nestedmodule.psd1"), + Path.Combine(workspace.InitialWorkingDirectory, "nested", "nestedmodule.psm1"), + Path.Combine(workspace.InitialWorkingDirectory, "rootfile.ps1") }; // .NET Core doesn't appear to use the same three letter pattern matching rule although the docs @@ -105,7 +105,7 @@ public void CanRecurseDirectoryTree() // ref https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netcore-2.1#System_IO_Directory_GetFiles_System_String_System_String_System_IO_EnumerationOptions_ if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) { - expected.Insert(3, Path.Combine(workspace.WorkspacePath, "other", "other.ps1xml")); + expected.Insert(3, Path.Combine(workspace.InitialWorkingDirectory, "other", "other.ps1xml")); } Assert.Equal(expected, actual); @@ -122,7 +122,7 @@ public void CanRecurseDirectoryTreeWithLimit() maxDepth: 1, ignoreReparsePoints: s_defaultIgnoreReparsePoints ); - Assert.Equal(new[] { Path.Combine(workspace.WorkspacePath, "rootfile.ps1") }, actual); + Assert.Equal(new[] { Path.Combine(workspace.InitialWorkingDirectory, "rootfile.ps1") }, actual); } [Fact] @@ -138,8 +138,8 @@ public void CanRecurseDirectoryTreeWithGlobs() ); Assert.Equal(new[] { - Path.Combine(workspace.WorkspacePath, "nested", "nestedmodule.psd1"), - Path.Combine(workspace.WorkspacePath, "rootfile.ps1") + Path.Combine(workspace.InitialWorkingDirectory, "nested", "nestedmodule.psd1"), + Path.Combine(workspace.InitialWorkingDirectory, "rootfile.ps1") }, actual); } @@ -181,7 +181,7 @@ public void CanDetermineIsPathInMemory() public void CanOpenAndCloseFile() { WorkspaceService workspace = FixturesWorkspace(); - string filePath = Path.GetFullPath(Path.Combine(workspace.WorkspacePath, "rootfile.ps1")); + string filePath = Path.GetFullPath(Path.Combine(workspace.InitialWorkingDirectory, "rootfile.ps1")); ScriptFile file = workspace.GetFile(filePath); Assert.Equal(workspace.GetOpenedFiles(), new[] { file }); From 125bb5faa7fd29a799a4ff7f39ee66beb65bd063 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 13 Feb 2023 12:13:21 -0800 Subject: [PATCH 241/327] Update CHANGELOG for `v3.8.1` --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd7f96d18..7031ca04d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # PowerShell Editor Services Release History +## v3.8.1 +### Monday, February 13, 2023 + +- ✨ 📁 [vscode-powershell #2112](https://github.com/PowerShell/PowerShellEditorServices/pull/1995) - Add `WorkspaceFolders` and use it when enumerating files. +- ✨ 🙏 [vscode-powershell #1481](https://github.com/PowerShell/PowerShellEditorServices/pull/1993) - Count `${Function:My-Function}` as a function reference. +- 🐛 🙏 [vscode-powershell #1089](https://github.com/PowerShell/PowerShellEditorServices/pull/1990) - Strip scope from function references. +- 🐛 🙏 [PowerShellEditorServices #1989](https://github.com/PowerShell/PowerShellEditorServices/pull/1989) - Keep only first assignment as declaration. +- ✨ 🐢 [PowerShellEditorServices #1988](https://github.com/PowerShell/PowerShellEditorServices/pull/1988) - Support Run/Debug tests in PSKoans-files. (Thanks @fflaten!) + ## v3.8.0 ### Thursday, February 02, 2023 From 016f1300cb4230cf71536cf3d4f0a4096e4ad23e Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 13 Feb 2023 12:13:21 -0800 Subject: [PATCH 242/327] Bump version to `v3.8.1` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 85b8f9d31..348fa28da 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.8.0 + 3.8.1 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 4c4004459..d2140c741 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.8.0' +ModuleVersion = '3.8.1' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From adba47d00e79247bf76ef56e14984d9767ac72fc Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Mon, 20 Feb 2023 18:42:16 +0000 Subject: [PATCH 243/327] Fix New-EditorFile inserting text in wrong editor The object returned from GetEditorContext() always points to the original file in CurrentFile. We need to get an updated object after opening or creating a file to insert text in the right editor. Fix PowerShell/vscode-powershell#3192 --- .../Commands/Public/CmdletInterface.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 b/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 index 6fc9114e6..e54089d91 100644 --- a/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 +++ b/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 @@ -155,7 +155,7 @@ function New-EditorFile { } $psEditor.Workspace.OpenFile($fileName, $preview) - $editorContext.CurrentFile.InsertText(($valueList | Out-String)) + $psEditor.GetEditorContext().CurrentFile.InsertText(($valueList | Out-String)) } else { $PSCmdlet.WriteError( ( New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList @( @@ -167,7 +167,7 @@ function New-EditorFile { } } else { $psEditor.Workspace.NewFile() - $editorContext.CurrentFile.InsertText(($valueList | Out-String)) + $psEditor.GetEditorContext().CurrentFile.InsertText(($valueList | Out-String)) } } } From 8e4d14006d8895cc30e9fd3f914a97f81818731b Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Mon, 20 Feb 2023 18:45:02 +0000 Subject: [PATCH 244/327] Remove unintended output from Out-CurrentFile #869 forgot to assign a returned editorcontext, causing new output from the command. Assigning to null. --- .../Commands/Public/Out-CurrentFile.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/PowerShellEditorServices/Commands/Public/Out-CurrentFile.ps1 b/module/PowerShellEditorServices/Commands/Public/Out-CurrentFile.ps1 index a8d222e97..bc7bfcba1 100644 --- a/module/PowerShellEditorServices/Commands/Public/Out-CurrentFile.ps1 +++ b/module/PowerShellEditorServices/Commands/Public/Out-CurrentFile.ps1 @@ -26,7 +26,7 @@ function Out-CurrentFile { try { # If there is no file open - $psEditor.GetEditorContext() + $null = $psEditor.GetEditorContext() } catch { # create a new one From 021f95f5e8272155f24919934a9d41b35cdeb5d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:01:57 -0800 Subject: [PATCH 245/327] Bump Microsoft.NET.Test.Sdk from 17.4.1 to 17.5.0 (#1999) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.4.1 to 17.5.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.4.1...v17.5.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 044051e74..429720042 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 98c6e28f7..ec7863dd9 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From 97ae16a57f90c948b67775c0e2f9e62a19127079 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Wed, 22 Feb 2023 21:56:01 +0100 Subject: [PATCH 246/327] Support module-qualified calls for Pester keywords (#1998) Co-authored-by: Andy Jordan <2226434+andschwa@users.noreply.github.com> Co-authored-by: Patrick Meinecke --- .../Symbols/PesterDocumentSymbolProvider.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index 7a77d7607..dcf675975 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Management.Automation.Language; using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { @@ -42,8 +43,7 @@ IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols( private static bool IsNamedCommandWithArguments(Ast ast) { return ast is CommandAst commandAst && - commandAst.InvocationOperator != TokenKind.Dot && - PesterSymbolReference.GetCommandType(commandAst.GetCommandName()).HasValue && + commandAst.InvocationOperator is not (TokenKind.Dot or TokenKind.Ampersand) && commandAst.CommandElements.Count >= 2; } @@ -59,8 +59,10 @@ private static bool IsPesterCommand(CommandAst commandAst) return false; } - // Ensure the first word is a Pester keyword - if (!PesterSymbolReference.PesterKeywords.ContainsKey(commandAst.GetCommandName())) + // Ensure the first word is a Pester keyword and in Pester-module if using module-qualified call + string commandName = CommandHelpers.StripModuleQualification(commandAst.GetCommandName(), out ReadOnlyMemory module); + if (!PesterSymbolReference.PesterKeywords.ContainsKey(commandName) || + (!module.IsEmpty && !module.Span.Equals("pester".AsSpan(), StringComparison.OrdinalIgnoreCase))) { return false; } @@ -90,14 +92,16 @@ private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFil .TrimStart() .TrimEnd(DefinitionTrimChars); - PesterCommandType? commandName = PesterSymbolReference.GetCommandType(pesterCommandAst.GetCommandName()); - if (commandName == null) + string commandName = CommandHelpers.StripModuleQualification(pesterCommandAst.GetCommandName(), out _); + PesterCommandType? commandType = PesterSymbolReference.GetCommandType(commandName); + if (commandType == null) { return null; } string testName = null; - if (PesterSymbolReference.IsPesterTestCommand(commandName.Value)) { + if (PesterSymbolReference.IsPesterTestCommand(commandType.Value)) + { // Search for a name for the test // If the test has more than one argument for names, we set it to null bool alreadySawName = false; @@ -130,7 +134,7 @@ private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFil return new PesterSymbolReference( scriptFile, - commandName.Value, + commandType.Value, symbolName, testName, pesterCommandAst.Extent From b42b83ecdf8d1009130ca0f2941b42ea5d145c47 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 23 Feb 2023 14:38:59 -0800 Subject: [PATCH 247/327] Delete dead internal code --- .../Utility/IScriptExtentExtensions.cs | 28 --------------- .../Utility/PSCommandExtensions.cs | 34 ------------------- .../Utility/PathUtils.cs | 31 ++--------------- 3 files changed, 2 insertions(+), 91 deletions(-) delete mode 100644 src/PowerShellEditorServices/Utility/IScriptExtentExtensions.cs diff --git a/src/PowerShellEditorServices/Utility/IScriptExtentExtensions.cs b/src/PowerShellEditorServices/Utility/IScriptExtentExtensions.cs deleted file mode 100644 index 29f78dc98..000000000 --- a/src/PowerShellEditorServices/Utility/IScriptExtentExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Management.Automation.Language; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - internal static class IScriptExtentExtensions - { - public static Range ToRange(this IScriptExtent scriptExtent) - { - return new Range - { - Start = new Position - { - Line = scriptExtent.StartLineNumber - 1, - Character = scriptExtent.StartColumnNumber - 1 - }, - End = new Position - { - Line = scriptExtent.EndLineNumber - 1, - Character = scriptExtent.EndColumnNumber - 1 - } - }; - } - } -} diff --git a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs index 6a9390af1..b16129af9 100644 --- a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs +++ b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs @@ -4,48 +4,14 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq.Expressions; using System.Management.Automation; using System.Management.Automation.Runspaces; -using System.Reflection; using System.Text; namespace Microsoft.PowerShell.EditorServices.Utility { internal static class PSCommandHelpers { - private static readonly Func s_commandCtor; - - static PSCommandHelpers() - { - ConstructorInfo ctor = typeof(Command).GetConstructor( - BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, - binder: null, - new[] { typeof(CommandInfo) }, - modifiers: null); - - ParameterExpression commandInfo = Expression.Parameter(typeof(CommandInfo), nameof(commandInfo)); - - s_commandCtor = Expression.Lambda>( - Expression.New(ctor, commandInfo), - new[] { commandInfo }) - .Compile(); - } - - /// - /// PowerShell's missing an API for us to AddCommand using a CommandInfo. - /// An issue was filed here: https://github.com/PowerShell/PowerShell/issues/12295 - /// This works around this by creating a `Command` and passing it into PSCommand.AddCommand(Command command) - /// - /// - /// - /// - public static PSCommand AddCommand(this PSCommand command, CommandInfo commandInfo) - { - Command rsCommand = s_commandCtor(commandInfo); - return command.AddCommand(rsCommand); - } - public static PSCommand AddOutputCommand(this PSCommand psCommand) { return psCommand.MergePipelineResults() diff --git a/src/PowerShellEditorServices/Utility/PathUtils.cs b/src/PowerShellEditorServices/Utility/PathUtils.cs index 066bf5917..112e8b465 100644 --- a/src/PowerShellEditorServices/Utility/PathUtils.cs +++ b/src/PowerShellEditorServices/Utility/PathUtils.cs @@ -10,26 +10,6 @@ namespace Microsoft.PowerShell.EditorServices.Utility { internal static class PathUtils { - /// - /// 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. - /// - /// - internal static readonly char DefaultPathSeparator = Path.DirectorySeparatorChar; - - /// - /// 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. - /// - /// - internal static readonly char AlternatePathSeparator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? '/' : '\\'; - /// /// The value to be used when comparing paths. Will be /// for case sensitive file systems and @@ -39,13 +19,6 @@ internal static class PathUtils ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; - /// - /// Converts all alternate path separators to the current platform's main path separators. - /// - /// The path to normalize. - /// The normalized path. - public static string NormalizePathSeparators(string path) => string.IsNullOrWhiteSpace(path) ? path : path.Replace(AlternatePathSeparator, DefaultPathSeparator); - /// /// Determines whether two specified strings represent the same path. /// @@ -67,8 +40,8 @@ internal static bool IsPathEqual(string left, string right) return false; } - left = Path.GetFullPath(left).TrimEnd(DefaultPathSeparator); - right = Path.GetFullPath(right).TrimEnd(DefaultPathSeparator); + left = Path.GetFullPath(left).TrimEnd(Path.DirectorySeparatorChar); + right = Path.GetFullPath(right).TrimEnd(Path.DirectorySeparatorChar); return left.Equals(right, PathComparison); } From 7e5d8e37b41a0e353bedf9770ab4905ef7183aaa Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 23 Feb 2023 15:13:32 -0800 Subject: [PATCH 248/327] Reduce warnings --- .editorconfig | 2 +- .../Extensions/EditorContext.cs | 19 +++---------- .../Extensions/EditorFileRanges.cs | 8 +++--- .../Extensions/EditorObject.cs | 2 ++ .../Extensions/EditorWindow.cs | 2 ++ .../Extensions/EditorWorkspace.cs | 2 ++ .../Extensions/FileContext.cs | 6 +++- .../Hosting/EditorServicesServerFactory.cs | 2 +- .../Logging/LoggerExtensions.cs | 1 + .../Services/DebugAdapter/DebugService.cs | 13 --------- .../InvalidPowerShellExpressionException.cs | 9 +++--- .../Handlers/StackTraceHandler.cs | 22 ++++++--------- .../Debugging/DscBreakpointCapability.cs | 4 +-- .../PowerShell/Handlers/GetVersionHandler.cs | 6 ++-- .../PSHostProcessAndRunspaceHandlers.cs | 5 +--- .../PowerShell/Utility/CommandHelpers.cs | 3 +- .../Handlers/CodeActionHandler.cs | 11 +------- .../Services/TextDocument/TokenOperations.cs | 2 +- .../Workspace/RemoteFileManagerService.cs | 28 +++++++++---------- .../Language/TokenOperationsTests.cs | 2 +- .../PsesHostFactory.cs | 3 +- 21 files changed, 61 insertions(+), 91 deletions(-) diff --git a/.editorconfig b/.editorconfig index 48f4d1c5a..1cb572ef7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -39,7 +39,7 @@ dotnet_diagnostic.CS0649.severity = error # CS1570: Parameter has no matching param tag in the XML comment dotnet_diagnostic.CS1570.severity = silent # CS1574: XML comment has cref attribute that could not be resolved. -dotnet_diagnostic.CS1574.severity = suggestion +dotnet_diagnostic.CS1574.severity = silent # CS1591: Missing XML comment for publicly visible type or member dotnet_diagnostic.CS1591.severity = silent # CS1998: This async method lacks 'await' operators and will run synchronously diff --git a/src/PowerShellEditorServices/Extensions/EditorContext.cs b/src/PowerShellEditorServices/Extensions/EditorContext.cs index fef641a28..f2ab42e8c 100644 --- a/src/PowerShellEditorServices/Extensions/EditorContext.cs +++ b/src/PowerShellEditorServices/Extensions/EditorContext.cs @@ -86,26 +86,15 @@ public void SetSelection( /// /// The starting position of the selection. /// The ending position of the selection. - public void SetSelection( - FilePosition startPosition, - FilePosition endPosition) - { - SetSelection( - new FileRange( - startPosition, - endPosition)); - } + public void SetSelection(FilePosition startPosition, FilePosition endPosition) => SetSelection(new FileRange(startPosition, endPosition)); /// /// Sets a selection in the host editor's active buffer. /// /// The range of the selection. - public void SetSelection(FileRange selectionRange) - { - editorOperations - .SetSelectionAsync(selectionRange.ToBufferRange()) - .Wait(); - } + #pragma warning disable VSTHRD002 + public void SetSelection(FileRange selectionRange) => editorOperations.SetSelectionAsync(selectionRange.ToBufferRange()).Wait(); + #pragma warning restore VSTHRD002 #endregion } diff --git a/src/PowerShellEditorServices/Extensions/EditorFileRanges.cs b/src/PowerShellEditorServices/Extensions/EditorFileRanges.cs index ed6ae9de5..901307a73 100644 --- a/src/PowerShellEditorServices/Extensions/EditorFileRanges.cs +++ b/src/PowerShellEditorServices/Extensions/EditorFileRanges.cs @@ -274,7 +274,7 @@ public interface ILspCurrentFileContext : IFileContext ILspFileRange SelectionRange { get; } } - internal struct OmnisharpLspPosition : ILspFilePosition, IEquatable + internal readonly struct OmnisharpLspPosition : ILspFilePosition, IEquatable { private readonly Position _position; @@ -287,7 +287,7 @@ internal struct OmnisharpLspPosition : ILspFilePosition, IEquatable _position == other._position; } - internal struct OmnisharpLspRange : ILspFileRange, IEquatable + internal readonly struct OmnisharpLspRange : ILspFileRange, IEquatable { private readonly Range _range; @@ -300,7 +300,7 @@ internal struct OmnisharpLspRange : ILspFileRange, IEquatable public bool Equals(OmnisharpLspRange other) => _range == other._range; } - internal struct BufferFilePosition : IFilePosition, IEquatable + internal readonly struct BufferFilePosition : IFilePosition, IEquatable { private readonly BufferPosition _position; @@ -317,7 +317,7 @@ public bool Equals(BufferFilePosition other) } } - internal struct BufferFileRange : IFileRange, IEquatable + internal readonly struct BufferFileRange : IFileRange, IEquatable { private readonly BufferRange _range; diff --git a/src/PowerShellEditorServices/Extensions/EditorObject.cs b/src/PowerShellEditorServices/Extensions/EditorObject.cs index cfbd97254..17006baf2 100644 --- a/src/PowerShellEditorServices/Extensions/EditorObject.cs +++ b/src/PowerShellEditorServices/Extensions/EditorObject.cs @@ -115,7 +115,9 @@ internal EditorObject( /// at the time this method is invoked. /// /// A instance of the EditorContext class. + #pragma warning disable VSTHRD002, VSTHRD104 public EditorContext GetEditorContext() => _editorOperations.GetEditorContextAsync().Result; + #pragma warning restore VSTHRD002, VSTHRD104 internal void SetAsStaticInstance() { diff --git a/src/PowerShellEditorServices/Extensions/EditorWindow.cs b/src/PowerShellEditorServices/Extensions/EditorWindow.cs index ac986308a..428c192ea 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWindow.cs +++ b/src/PowerShellEditorServices/Extensions/EditorWindow.cs @@ -39,6 +39,7 @@ internal EditorWindow(IEditorOperations editorOperations) #endregion #region Public Methods + #pragma warning disable VSTHRD002 // These are public APIs that use async internal methods. /// /// Shows an informational message to the user. @@ -71,6 +72,7 @@ internal EditorWindow(IEditorOperations editorOperations) /// A timeout in milliseconds for how long the message should remain visible. public void SetStatusBarMessage(string message, int timeout) => editorOperations.SetStatusBarMessageAsync(message, timeout).Wait(); + #pragma warning restore VSTHRD002 #endregion } } diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs index 18f001d56..aa5802564 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs +++ b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs @@ -31,6 +31,7 @@ public sealed class EditorWorkspace #endregion #region Public Methods + #pragma warning disable VSTHRD002 // These are public APIs that use async internal methods. /// /// Creates a new file in the editor @@ -53,6 +54,7 @@ public sealed class EditorWorkspace /// Determines wether the file is opened as a preview or as a durable editor. public void OpenFile(string filePath, bool preview) => editorOperations.OpenFileAsync(filePath, preview).Wait(); + #pragma warning restore VSTHRD002 #endregion } } diff --git a/src/PowerShellEditorServices/Extensions/FileContext.cs b/src/PowerShellEditorServices/Extensions/FileContext.cs index ddd84029d..770cc33f7 100644 --- a/src/PowerShellEditorServices/Extensions/FileContext.cs +++ b/src/PowerShellEditorServices/Extensions/FileContext.cs @@ -209,12 +209,14 @@ public void InsertText( /// /// The text string to insert. /// The buffer range which will be replaced by the string. + #pragma warning disable VSTHRD002 public void InsertText(string textToInsert, IFileRange insertRange) { editorOperations .InsertTextAsync(scriptFile.DocumentUri.ToString(), textToInsert, insertRange.ToBufferRange()) .Wait(); } + #pragma warning restore VSTHRD002 #endregion @@ -232,6 +234,7 @@ public void InsertText(string textToInsert, IFileRange insertRange) /// the path where the file should be saved, /// including the file name with extension as the leaf /// + #pragma warning disable VSTHRD002 public void SaveAs(string newFilePath) { // Do some validation here so that we can provide a helpful error if the path won't work @@ -244,8 +247,9 @@ public void SaveAs(string newFilePath) throw new IOException(string.Format("The file '{0}' already exists", absolutePath)); } - editorOperations.SaveFileAsync(scriptFile.FilePath, newFilePath); + editorOperations.SaveFileAsync(scriptFile.FilePath, newFilePath).Wait(); } + #pragma warning restore VSTHRD002 #endregion } diff --git a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs index 2ff417029..be599b27f 100644 --- a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs +++ b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs @@ -83,7 +83,7 @@ public static EditorServicesServerFactory Create(string logPath, int minimumLogL /// The protocol transport input stream. /// The protocol transport output stream. /// The host details configuration for Editor Services - /// instantation. + /// instantiation. /// A new, unstarted language server instance. public PsesLanguageServer CreateLanguageServer( Stream inputStream, diff --git a/src/PowerShellEditorServices/Logging/LoggerExtensions.cs b/src/PowerShellEditorServices/Logging/LoggerExtensions.cs index a580c7802..94c821e42 100644 --- a/src/PowerShellEditorServices/Logging/LoggerExtensions.cs +++ b/src/PowerShellEditorServices/Logging/LoggerExtensions.cs @@ -9,6 +9,7 @@ namespace Microsoft.PowerShell.EditorServices.Logging { internal static class LoggerExtensions { + // TODO: These need to be fixed (and used consistently). public static void LogException( this ILogger logger, string message, diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 4c9ef8735..7ea6ad435 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -559,19 +559,6 @@ public StackFrameDetails[] GetStackFrames() } } - internal StackFrameDetails[] GetStackFrames(CancellationToken cancellationToken) - { - debugInfoHandle.Wait(cancellationToken); - try - { - return stackFrameDetails; - } - finally - { - debugInfoHandle.Release(); - } - } - internal async Task GetStackFramesAsync() { await debugInfoHandle.WaitAsync().ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/InvalidPowerShellExpressionException.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/InvalidPowerShellExpressionException.cs index 38c95f1ae..e04478639 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/InvalidPowerShellExpressionException.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/InvalidPowerShellExpressionException.cs @@ -14,9 +14,10 @@ public class InvalidPowerShellExpressionException : Exception /// Initializes a new instance of the SetVariableExpressionException class. /// /// Message indicating why the expression is invalid. - public InvalidPowerShellExpressionException(string message) - : base(message) - { - } + public InvalidPowerShellExpressionException(string message) : base(message) { } + + public InvalidPowerShellExpressionException() { } + + public InvalidPowerShellExpressionException(string message, Exception innerException) : base(message, innerException) { } } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/StackTraceHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/StackTraceHandler.cs index fb3b5e17e..f53e094e7 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/StackTraceHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/StackTraceHandler.cs @@ -19,20 +19,19 @@ internal class StackTraceHandler : IStackTraceHandler public StackTraceHandler(DebugService debugService) => _debugService = debugService; - public Task Handle(StackTraceArguments request, CancellationToken cancellationToken) + public async Task Handle(StackTraceArguments request, CancellationToken cancellationToken) { - StackFrameDetails[] stackFrameDetails = - _debugService.GetStackFrames(cancellationToken); + StackFrameDetails[] stackFrameDetails = await _debugService.GetStackFramesAsync(cancellationToken).ConfigureAwait(false); // Handle a rare race condition where the adapter requests stack frames before they've // begun building. - if (stackFrameDetails == null) + if (stackFrameDetails is null) { - return Task.FromResult(new StackTraceResponse + return new StackTraceResponse { StackFrames = Array.Empty(), TotalFrames = 0 - }); + }; } List newStackFrames = new(); @@ -52,19 +51,14 @@ public Task Handle(StackTraceArguments request, Cancellation { // Create the new StackFrame object with an ID that can // be referenced back to the current list of stack frames - //newStackFrames.Add( - // StackFrame.Create( - // stackFrameDetails[i], - // i)); - newStackFrames.Add( - LspDebugUtils.CreateStackFrame(stackFrameDetails[i], id: i)); + newStackFrames.Add(LspDebugUtils.CreateStackFrame(stackFrameDetails[i], id: i)); } - return Task.FromResult(new StackTraceResponse + return new StackTraceResponse { StackFrames = newStackFrames, TotalFrames = newStackFrames.Count - }); + }; } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 3f6f05b24..644eec763 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -97,7 +97,7 @@ public static Task GetDscCapabilityAsync( return Task.FromResult(null); } - Func getDscBreakpointCapabilityFunc = (pwsh, _) => + DscBreakpointCapability getDscBreakpointCapabilityFunc(SMA.PowerShell pwsh, CancellationToken _) { PSInvocationSettings invocationSettings = new() { @@ -159,7 +159,7 @@ public static Task GetDscCapabilityAsync( logger.LogTrace($"DSC resources found: {resourcePaths.Count}"); return capability; - }; + } return psesHost.ExecuteDelegateAsync( nameof(getDscBreakpointCapabilityFunc), diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index 025f92f5a..f6aaf1779 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -10,15 +10,15 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class GetVersionHandler : IGetVersionHandler { - public async Task Handle(GetVersionParams request, CancellationToken cancellationToken) + public Task Handle(GetVersionParams request, CancellationToken cancellationToken) { - return new PowerShellVersion + return Task.FromResult(new PowerShellVersion { Version = VersionUtils.PSVersionString, Edition = VersionUtils.PSEdition, Commit = VersionUtils.GitCommitId, Architecture = VersionUtils.Architecture - }; + }); } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs index ed0b37b04..fcf58cce3 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -62,10 +62,7 @@ public async Task Handle(GetRunspaceParams request, Cancella { IEnumerable runspaces = null; - if (request.ProcessId == null) - { - request.ProcessId = "current"; - } + request.ProcessId ??= "current"; // If the processId is a valid int, we need to run Get-Runspace within that process // otherwise just use the current runspace. diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index da4a89a34..7750aa042 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Management.Automation; using System.Threading; using System.Threading.Tasks; @@ -241,7 +242,7 @@ public static async Task GetAliasesAsync( .AddParameter("CommandType", CommandTypes.Alias), cancellationToken).ConfigureAwait(false); - foreach (AliasInfo aliasInfo in aliases) + foreach (AliasInfo aliasInfo in aliases.Cast()) { // TODO: When we move to netstandard2.1, we can use another overload which generates // static delegates and thus reduces allocations. diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs index 895c804ec..2861fe27f 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs @@ -35,16 +35,7 @@ public PsesCodeActionHandler(ILoggerFactory factory, AnalysisService analysisSer CodeActionKinds = new CodeActionKind[] { CodeActionKind.QuickFix } }; - // TODO: Either fix or ignore "method lacks 'await'" warning. - public override async Task Handle(CodeAction request, CancellationToken cancellationToken) - { - // TODO: How on earth do we handle a CodeAction? This is new... - if (cancellationToken.IsCancellationRequested) - { - _logger.LogDebug("CodeAction request canceled for: {Title}", request.Title); - } - return request; - } + public override Task Handle(CodeAction request, CancellationToken cancellationToken) => Task.FromResult(request); public override async Task Handle(CodeActionParams request, CancellationToken cancellationToken) { diff --git a/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs b/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs index d1a3ca5e5..4299993f7 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs @@ -136,7 +136,7 @@ internal static FoldingReferenceList FoldableReferences(Token[] tokens) refList.SafeAdd(CreateFoldingReference(blockStartToken, blockNextLine - 1, FoldingRangeKind.Comment)); blockStartToken = token; } - if (blockStartToken == null) { blockStartToken = token; } + blockStartToken ??= token; blockNextLine = thisLine + 1; } diff --git a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index 1229951de..0a7ed4c74 100644 --- a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -528,24 +528,22 @@ private void HandleRunspaceChanged(object sender, RunspaceChangedEventArgs e) } // Close any remote files that were opened - if (ShouldTearDownRemoteFiles(e)) + if (ShouldTearDownRemoteFiles(e) + && filesPerComputer.TryGetValue(e.PreviousRunspace.SessionDetails.ComputerName, out RemotePathMappings remotePathMappings)) { - if (filesPerComputer.TryGetValue(e.PreviousRunspace.SessionDetails.ComputerName, out RemotePathMappings remotePathMappings)) + List fileCloseTasks = new(); + foreach (string remotePath in remotePathMappings.OpenedPaths) { - List fileCloseTasks = new(); - foreach (string remotePath in remotePathMappings.OpenedPaths) - { - fileCloseTasks.Add(editorOperations?.CloseFileAsync(remotePath)); - } + fileCloseTasks.Add(editorOperations?.CloseFileAsync(remotePath)); + } - try - { - Task.WaitAll(fileCloseTasks.ToArray()); - } - catch (Exception ex) - { - logger.LogError(ex, "Unable to close all files in closed runspace"); - } + try + { + Task.WaitAll(fileCloseTasks.ToArray()); + } + catch (Exception ex) + { + logger.LogError(ex, "Unable to close all files in closed runspace"); } } diff --git a/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs b/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs index 325384d05..a47d765e1 100644 --- a/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs @@ -174,7 +174,7 @@ public void LanguageServiceFindsFoldablRegionsWithLF() // Remove and CR characters string testString = allInOneScript.Replace("\r", ""); // Ensure that there are no CR characters in the string - Assert.True(testString.IndexOf("\r\n") == -1, "CRLF should not be present in the test string"); + Assert.False(testString.Contains("\r\n"), "CRLF should not be present in the test string"); FoldingReference[] result = GetRegions(testString); AssertFoldingReferenceArrays(expectedAllInOneScriptFolds, result); } diff --git a/test/PowerShellEditorServices.Test/PsesHostFactory.cs b/test/PowerShellEditorServices.Test/PsesHostFactory.cs index 8d535b260..b2d4ceb7c 100644 --- a/test/PowerShellEditorServices.Test/PsesHostFactory.cs +++ b/test/PowerShellEditorServices.Test/PsesHostFactory.cs @@ -61,8 +61,9 @@ public static PsesInternalHost Create(ILoggerFactory loggerFactory, bool loadPro PsesInternalHost psesHost = new(loggerFactory, null, testHostDetails); - // NOTE: Because this is used by constructors it can't use await. + #pragma warning disable VSTHRD002 // Because this is used by constructors it can't use await. if (psesHost.TryStartAsync(new HostStartOptions { LoadProfiles = loadProfiles }, CancellationToken.None).GetAwaiter().GetResult()) + #pragma warning restore VSTHRD002 { return psesHost; } From 4989ddd5a99651322c30cb8e2d39912a11efb3a2 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 23 Feb 2023 15:13:57 -0800 Subject: [PATCH 249/327] Re-enable accidentally disabled end-to-end tests --- PowerShellEditorServices.build.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 34a1e7590..bf37c108b 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -203,7 +203,7 @@ Task TestE2E Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test.E2E\ $env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" } - Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS73 } if (!$script:IsNix) { if (-not [Security.Principal.WindowsIdentity]::GetCurrent().Owner.IsWellKnown("BuiltInAdministratorsSid")) { @@ -214,7 +214,7 @@ Task TestE2E Build, SetupHelpForTests, { try { Write-Host "Running end-to-end tests in Constrained Language Mode." [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine); - Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS72 } + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS73 } } finally { [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine); } From 50ee6ad4dc3cc888a30a120e27068ab0a244e0c4 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 23 Feb 2023 15:38:00 -0800 Subject: [PATCH 250/327] Fix end-to-end tests --- .../Handlers/WorkspaceSymbolsHandler.cs | 3 +- .../LanguageServerProtocolMessageTests.cs | 30 +++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 7f9a43049..3efe4edcf 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -39,6 +39,7 @@ public override async Task> Handle(WorkspaceSymbolP foreach (ScriptFile scriptFile in _workspaceService.GetOpenedFiles()) { + _logger.LogDebug($"Handling workspace symbols request for: {request.Query}"); IEnumerable foundSymbols = _symbolsService.FindSymbolsInFile(scriptFile); // TODO: Need to compute a relative path that is based on common path for all workspace files @@ -89,7 +90,7 @@ public override async Task> Handle(WorkspaceSymbolP }); } } - _logger.LogWarning("Logging in a handler works now."); + return new Container(symbols); } diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index d990ebe09..fd4854f04 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -151,7 +151,7 @@ function CanSendWorkspaceSymbolRequest { .Returning>(CancellationToken.None).ConfigureAwait(true); SymbolInformation symbol = Assert.Single(symbols); - Assert.Equal("CanSendWorkspaceSymbolRequest { }", symbol.Name); + Assert.Equal("function CanSendWorkspaceSymbolRequest ()", symbol.Name); } [SkippableFact] @@ -518,7 +518,7 @@ await PsesLanguageClient }) .Returning(CancellationToken.None).ConfigureAwait(true); - Assert.Collection(documentHighlights, + Assert.Collection(documentHighlights.OrderBy(i => i.Range.Start.Line), documentHighlight1 => { Range range = documentHighlight1.Range; @@ -868,8 +868,8 @@ function CanSendReferencesCodeLensRequest { Range range = codeLens.Range; Assert.Equal(1, range.Start.Line); Assert.Equal(9, range.Start.Character); - Assert.Equal(3, range.End.Line); - Assert.Equal(1, range.End.Character); + Assert.Equal(1, range.End.Line); + Assert.Equal(41, range.End.Character); CodeLens codeLensResolveResult = await PsesLanguageClient .SendRequest("codeLens/resolve", codeLens) @@ -910,26 +910,26 @@ class ChildClass : MyBaseClass, System.IDisposable { }) .Returning(CancellationToken.None).ConfigureAwait(true); - Assert.Collection(codeLenses, + Assert.Collection(codeLenses.OrderBy(i => i.Range.Start.Line), codeLens => { Range range = codeLens.Range; Assert.Equal(5, range.Start.Line); Assert.Equal(6, range.Start.Character); - Assert.Equal(7, range.End.Line); - Assert.Equal(1, range.End.Character); + Assert.Equal(5, range.End.Line); + Assert.Equal(17, range.End.Character); }, codeLens => { Range range = codeLens.Range; Assert.Equal(9, range.Start.Line); Assert.Equal(6, range.Start.Character); - Assert.Equal(11, range.End.Line); - Assert.Equal(1, range.End.Character); + Assert.Equal(9, range.End.Line); + Assert.Equal(16, range.End.Character); } ); - CodeLens baseClassCodeLens = codeLenses.First(); + CodeLens baseClassCodeLens = codeLenses.OrderBy(i => i.Range.Start.Line).First(); CodeLens codeLensResolveResult = await PsesLanguageClient .SendRequest("codeLens/resolve", baseClassCodeLens) .Returning(CancellationToken.None).ConfigureAwait(true); @@ -972,8 +972,8 @@ enum MyEnum { Range range = codeLens.Range; Assert.Equal(5, range.Start.Line); Assert.Equal(5, range.Start.Character); - Assert.Equal(9, range.End.Line); - Assert.Equal(1, range.End.Character); + Assert.Equal(5, range.End.Line); + Assert.Equal(11, range.End.Character); CodeLens codeLensResolveResult = await PsesLanguageClient .SendRequest("codeLens/resolve", codeLens) @@ -1146,7 +1146,7 @@ public async Task CanSendHoverRequestAsync() Assert.True(hover.Contents.HasMarkedStrings); Assert.Collection(hover.Contents.MarkedStrings, - str1 => Assert.Equal("function Write-Host", str1.Value), + str1 => Assert.Equal("Write-Host", str1.Value), str2 => { Assert.Equal("markdown", str2.Language); @@ -1157,7 +1157,7 @@ public async Task CanSendHoverRequestAsync() [Fact] public async Task CanSendSignatureHelpRequestAsync() { - string filePath = NewTestFile("Get-Date "); + string filePath = NewTestFile("Get-Date -"); SignatureHelp signatureHelp = await PsesLanguageClient .SendRequest( @@ -1171,7 +1171,7 @@ public async Task CanSendSignatureHelpRequestAsync() Position = new Position { Line = 0, - Character = 9 + Character = 10 } }) .Returning(CancellationToken.None).ConfigureAwait(true); From e5ca68d69452b4713ff7ba74a39bd8b152ddef27 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Feb 2023 21:54:01 +0000 Subject: [PATCH 251/327] Bump Microsoft.PowerShell.SDK from 7.2.9 to 7.3.3 (#2002) Bumps [Microsoft.PowerShell.SDK](https://github.com/PowerShell/PowerShell) from 7.2.9 to 7.3.3. - [Release notes](https://github.com/PowerShell/PowerShell/releases) - [Commits](https://github.com/PowerShell/PowerShell/compare/v7.2.9...v7.3.3) --- updated-dependencies: - dependency-name: Microsoft.PowerShell.SDK dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index ec7863dd9..9449fc9cd 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -14,12 +14,12 @@ - + - + From 216727a22dee78f427554d8c47d11ef70d05e2c5 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Fri, 24 Feb 2023 10:47:25 -0800 Subject: [PATCH 252/327] Add `SymbolType.Region` --- src/PowerShellEditorServices/Services/Symbols/SymbolType.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs index 6533e7726..02e34e6f8 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs @@ -79,6 +79,11 @@ internal enum SymbolType /// The symbol is a type reference /// Type, + + /// + /// The symbol is a region. Only used for navigation-features. + /// + Region } internal static class SymbolTypeUtils @@ -97,6 +102,7 @@ internal static SymbolKind GetSymbolKind(SymbolType symbolType) SymbolType.Variable or SymbolType.Parameter => SymbolKind.Variable, SymbolType.HashtableKey => SymbolKind.Key, SymbolType.Type => SymbolKind.TypeParameter, + SymbolType.Region => SymbolKind.String, SymbolType.Unknown or _ => SymbolKind.Object, }; } From 085187f2140c68623be299e3878f2566446cf6d9 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Fri, 24 Feb 2023 10:48:23 -0800 Subject: [PATCH 253/327] Add `RegionDocumentSymbolProvider.cs` Co-authored-by: Andy Jordan --- .../Symbols/RegionDocumentSymbolProvider.cs | 79 +++++++++++++++++++ .../Services/Symbols/SymbolsService.cs | 4 +- .../Handlers/DocumentSymbolHandler.cs | 5 +- .../Services/TextDocument/TokenOperations.cs | 6 +- .../Symbols/MultipleSymbols.ps1 | 13 +++ .../Language/SymbolsServiceTests.cs | 29 +++++++ 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/Symbols/RegionDocumentSymbolProvider.cs diff --git a/src/PowerShellEditorServices/Services/Symbols/RegionDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/RegionDocumentSymbolProvider.cs new file mode 100644 index 000000000..e3b33b076 --- /dev/null +++ b/src/PowerShellEditorServices/Services/Symbols/RegionDocumentSymbolProvider.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; + +namespace Microsoft.PowerShell.EditorServices.Services.Symbols +{ + /// + /// Provides an IDocumentSymbolProvider implementation for + /// enumerating regions as symbols in script (.psd1, .psm1) files. + /// + internal class RegionDocumentSymbolProvider : IDocumentSymbolProvider + { + string IDocumentSymbolProvider.ProviderId => nameof(RegionDocumentSymbolProvider); + + IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols(ScriptFile scriptFile) + { + Stack tokenCommentRegionStack = new(); + Token[] tokens = scriptFile.ScriptTokens; + + for (int i = 0; i < tokens.Length; i++) + { + Token token = tokens[i]; + + // Exclude everything but single-line comments + if (token.Kind != TokenKind.Comment || + token.Extent.StartLineNumber != token.Extent.EndLineNumber || + !TokenOperations.IsBlockComment(i, tokens)) + { + continue; + } + + // Processing for #region -> #endregion + if (TokenOperations.s_startRegionTextRegex.IsMatch(token.Text)) + { + tokenCommentRegionStack.Push(token); + continue; + } + + if (TokenOperations.s_endRegionTextRegex.IsMatch(token.Text)) + { + // Mismatched regions in the script can cause bad stacks. + if (tokenCommentRegionStack.Count > 0) + { + Token regionStart = tokenCommentRegionStack.Pop(); + Token regionEnd = token; + + BufferRange regionRange = new( + regionStart.Extent.StartLineNumber, + regionStart.Extent.StartColumnNumber, + regionEnd.Extent.EndLineNumber, + regionEnd.Extent.EndColumnNumber); + + yield return new SymbolReference( + SymbolType.Region, + regionStart.Extent.Text.Trim().TrimStart('#'), + regionStart.Extent.Text.Trim(), + regionStart.Extent, + new ScriptExtent() + { + Text = string.Join(System.Environment.NewLine, scriptFile.GetLinesInRange(regionRange)), + StartLineNumber = regionStart.Extent.StartLineNumber, + StartColumnNumber = regionStart.Extent.StartColumnNumber, + StartOffset = regionStart.Extent.StartOffset, + EndLineNumber = regionEnd.Extent.EndLineNumber, + EndColumnNumber = regionEnd.Extent.EndColumnNumber, + EndOffset = regionEnd.Extent.EndOffset, + File = regionStart.Extent.File + }, + scriptFile, + isDeclaration: true); + } + } + } + } + } +} diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index cf4040642..2d316cb13 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -77,13 +77,13 @@ public SymbolsService( PesterCodeLensProvider pesterProvider = new(configurationService); _ = _codeLensProviders.TryAdd(pesterProvider.ProviderId, pesterProvider); - // TODO: Is this complication so necessary? _documentSymbolProviders = new ConcurrentDictionary(); IDocumentSymbolProvider[] documentSymbolProviders = new IDocumentSymbolProvider[] { new ScriptDocumentSymbolProvider(), new PsdDocumentSymbolProvider(), - new PesterDocumentSymbolProvider(), + new PesterDocumentSymbolProvider() + // NOTE: This specifically does not include RegionDocumentSymbolProvider. }; foreach (IDocumentSymbolProvider documentSymbolProvider in documentSymbolProviders) diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index 823d01f26..25953b3cb 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -35,7 +35,8 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp { new ScriptDocumentSymbolProvider(), new PsdDocumentSymbolProvider(), - new PesterDocumentSymbolProvider() + new PesterDocumentSymbolProvider(), + new RegionDocumentSymbolProvider() }; } diff --git a/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs b/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs index 4299993f7..6a6f789ff 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs @@ -17,9 +17,9 @@ internal static class TokenOperations // script. They are based on the defaults in the VS Code Language Configuration at; // https://github.com/Microsoft/vscode/blob/64186b0a26/extensions/powershell/language-configuration.json#L26-L31 // https://github.com/Microsoft/vscode/issues/49070 - private static readonly Regex s_startRegionTextRegex = new( + internal static readonly Regex s_startRegionTextRegex = new( @"^\s*#[rR]egion\b", RegexOptions.Compiled); - private static readonly Regex s_endRegionTextRegex = new( + internal static readonly Regex s_endRegionTextRegex = new( @"^\s*#[eE]nd[rR]egion\b", RegexOptions.Compiled); /// @@ -199,7 +199,7 @@ private static FoldingReference CreateFoldingReference( /// - Token text must start with a '#'.false This is because comment regions /// start with '<#' but have the same TokenKind /// - private static bool IsBlockComment(int index, Token[] tokens) + internal static bool IsBlockComment(int index, Token[] tokens) { Token thisToken = tokens[index]; if (thisToken.Kind != TokenKind.Comment) { return false; } diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 index b4f54c329..2831ea332 100644 --- a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 @@ -41,3 +41,16 @@ enum AEnum { AFunction 1..3 | AFilter AnAdvancedFunction + +<# +#region don't find me inside comment block +abc +#endregion +#> + +#region find me outer +#region find me inner + +#endregion +#endregion +#region ignore this unclosed region diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index dbe0f8ca6..fa5d905d9 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -821,6 +821,35 @@ public void FindsSymbolsInFile() Assert.Equal("prop AValue", symbol.Id); Assert.Equal("AValue", symbol.Name); Assert.True(symbol.IsDeclaration); + + // There should be no region symbols unless the provider has been registered. + Assert.Empty(symbols.Where(i => i.Type == SymbolType.Region)); + } + + [Fact] + public void FindsRegionsInFile() + { + symbolsService.TryRegisterDocumentSymbolProvider(new RegionDocumentSymbolProvider()); + IEnumerable symbols = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails); + Assert.Collection(symbols.Where(i => i.Type == SymbolType.Region), + (i) => + { + Assert.Equal("region find me outer", i.Id); + Assert.Equal("#region find me outer", i.Name); + Assert.Equal(SymbolType.Region, i.Type); + Assert.True(i.IsDeclaration); + AssertIsRegion(i.NameRegion, 51, 1, 51, 22); + AssertIsRegion(i.ScriptRegion, 51, 1, 55, 11); + }, + (i) => + { + Assert.Equal("region find me inner", i.Id); + Assert.Equal("#region find me inner", i.Name); + Assert.Equal(SymbolType.Region, i.Type); + Assert.True(i.IsDeclaration); + AssertIsRegion(i.NameRegion, 52, 1, 52, 22); + AssertIsRegion(i.ScriptRegion, 52, 1, 54, 11); + }); } [Fact] From fdde391cded35f0ef25cc175d243a1790a6ae282 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 24 Feb 2023 12:40:38 -0800 Subject: [PATCH 254/327] Optimize `CodeLens` providers Removing the timer and unnecessary allocations. --- .../Services/CodeLens/ICodeLensProvider.cs | 5 +- .../CodeLens/PesterCodeLensProvider.cs | 18 +++--- .../CodeLens/ReferencesCodeLensProvider.cs | 17 +++--- .../TextDocument/Handlers/CodeLensHandlers.cs | 56 ++++++------------- 4 files changed, 40 insertions(+), 56 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs index a6255a41d..982fb1f45 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -27,8 +28,8 @@ internal interface ICodeLensProvider /// The document for which CodeLenses should be provided. /// /// - /// An array of CodeLenses. - CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken); + /// An IEnumerable of CodeLenses. + IEnumerable ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken); /// /// Resolves a CodeLens that was created without a Command. diff --git a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs index 50ddcfc22..398e73f89 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -99,18 +98,20 @@ private static CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, Scri /// The script file to get Pester CodeLenses for. /// /// All Pester CodeLenses for the given script file. - public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) + public IEnumerable ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) { // Don't return anything if codelens setting is disabled if (!_configurationService.CurrentSettings.Pester.CodeLens) { - return Array.Empty(); + yield break; } - List lenses = new(); foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile)) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + yield break; + } if (symbol is not PesterSymbolReference pesterSymbol) { @@ -129,10 +130,11 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken can continue; } - lenses.AddRange(GetPesterLens(pesterSymbol, scriptFile)); + foreach (CodeLens codeLens in GetPesterLens(pesterSymbol, scriptFile)) + { + yield return codeLens; + } } - - return lenses.ToArray(); } /// diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index 6e1e4cc81..b3a1f6f97 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -53,13 +53,16 @@ public ReferencesCodeLensProvider(WorkspaceService workspaceService, SymbolsServ /// /// The PowerShell script file to get code lenses for. /// - /// An array of CodeLenses describing all functions, classes and enums in the given script file. - public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) + /// An IEnumerable of CodeLenses describing all functions, classes and enums in the given script file. + public IEnumerable ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) { - List acc = new(); foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile)) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + yield break; + } + // TODO: Can we support more here? if (symbol.IsDeclaration && symbol.Type is @@ -67,7 +70,7 @@ SymbolType.Function or SymbolType.Class or SymbolType.Enum) { - acc.Add(new CodeLens + yield return new CodeLens { Data = JToken.FromObject(new { @@ -75,11 +78,9 @@ SymbolType.Class or ProviderId = nameof(ReferencesCodeLensProvider) }, LspSerializer.Instance.JsonSerializer), Range = symbol.NameRegion.ToRange(), - }); + }; } } - - return acc.ToArray(); } /// diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs index 6ac18df8e..00cd9d968 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs @@ -3,13 +3,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.CodeLenses; -using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Utility; @@ -21,6 +19,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class PsesCodeLensHandlers : CodeLensHandlerBase { + private static readonly CodeLensContainer s_emptyCodeLensContainer = new(); private readonly ILogger _logger; private readonly SymbolsService _symbolsService; private readonly WorkspaceService _workspaceService; @@ -40,12 +39,17 @@ public PsesCodeLensHandlers(ILoggerFactory factory, SymbolsService symbolsServic public override Task Handle(CodeLensParams request, CancellationToken cancellationToken) { + _logger.LogDebug($"Handling code lens request for {request.TextDocument.Uri}"); + ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); - CodeLens[] codeLensResults = ProvideCodeLenses(scriptFile, cancellationToken); - return Task.FromResult(new CodeLensContainer(codeLensResults)); + IEnumerable codeLensResults = ProvideCodeLenses(scriptFile, cancellationToken); + + return !codeLensResults.Any() + ? Task.FromResult(s_emptyCodeLensContainer) + : Task.FromResult(new CodeLensContainer(codeLensResults)); } - public override async Task Handle(CodeLens request, CancellationToken cancellationToken) + public override Task Handle(CodeLens request, CancellationToken cancellationToken) { // TODO: Catch deserialization exception on bad object CodeLensData codeLensData = request.Data.ToObject(); @@ -55,8 +59,7 @@ public override async Task Handle(CodeLens request, CancellationToken .FirstOrDefault(provider => provider.ProviderId.Equals(codeLensData.ProviderId, StringComparison.Ordinal)); ScriptFile scriptFile = _workspaceService.GetFile(codeLensData.Uri); - return await originalProvider.ResolveCodeLens(request, scriptFile, cancellationToken) - .ConfigureAwait(false); + return originalProvider.ResolveCodeLens(request, scriptFile, cancellationToken); } /// @@ -65,43 +68,20 @@ public override async Task Handle(CodeLens request, CancellationToken /// The PowerShell script file to get CodeLenses for. /// /// All generated CodeLenses for the given script file. - private CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) - { - return InvokeProviders(provider => provider.ProvideCodeLenses(scriptFile, cancellationToken)) - .SelectMany(codeLens => codeLens) - .ToArray(); - } - - /// - /// Invokes the given function synchronously against all - /// registered providers. - /// - /// The function to be invoked. - /// - /// An IEnumerable containing the results of all providers - /// that were invoked successfully. - /// - private IEnumerable InvokeProviders(Func invokeFunc) + private IEnumerable ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) { - Stopwatch invokeTimer = new(); - List providerResults = new(); - foreach (ICodeLensProvider provider in _symbolsService.GetCodeLensProviders()) { - try - { - invokeTimer.Restart(); - providerResults.Add(invokeFunc(provider)); - invokeTimer.Stop(); - _logger.LogTrace($"Invocation of provider '{provider.GetType().Name}' completed in {invokeTimer.ElapsedMilliseconds}ms."); - } - catch (Exception e) + foreach (CodeLens codeLens in provider.ProvideCodeLenses(scriptFile, cancellationToken)) { - _logger.LogException($"Exception caught while invoking provider {provider.GetType().Name}:", e); + if (cancellationToken.IsCancellationRequested) + { + yield break; + } + + yield return codeLens; } } - - return providerResults; } } } From b4f252448cd9633e3d531788cf048027bc925f65 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 24 Feb 2023 13:12:37 -0800 Subject: [PATCH 255/327] Miscellaneous clean-ups --- src/PowerShellEditorServices/Server/PsesLanguageServer.cs | 2 ++ .../Services/Analysis/PssaCmdletAnalysisEngine.cs | 1 + .../Services/PowerShell/Utility/CommandHelpers.cs | 5 +++++ .../Services/Symbols/PesterDocumentSymbolProvider.cs | 7 ++++--- .../Services/Symbols/SymbolDetails.cs | 1 + .../Services/Symbols/SymbolsService.cs | 6 +++++- 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 440e84a70..74319e828 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -70,7 +70,9 @@ public PsesLanguageServer( /// cref="PsesServiceCollectionExtensions.AddPsesLanguageServices"/>. /// /// A task that completes when the server is ready and listening. +#pragma warning disable CA1506 // Coupling complexity we don't care about public async Task StartAsync() +#pragma warning restore CA1506 { LanguageServer = await OmniSharp.Extensions.LanguageServer.Server.LanguageServer.From(options => { diff --git a/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs b/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs index 371950bc0..05f4de8b9 100644 --- a/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs +++ b/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs @@ -123,6 +123,7 @@ private PssaCmdletAnalysisEngine( /// /// Format a script given its contents. + /// TODO: This needs to be cancellable. /// /// The full text of a script. /// The formatter settings to use. diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index 7750aa042..38a581bec 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -244,6 +244,11 @@ public static async Task GetAliasesAsync( foreach (AliasInfo aliasInfo in aliases.Cast()) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + // TODO: When we move to netstandard2.1, we can use another overload which generates // static delegates and thus reduces allocations. s_cmdletToAliasCache.AddOrUpdate( diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs index dcf675975..ebc2e720f 100644 --- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs @@ -54,7 +54,7 @@ commandAst.InvocationOperator is not (TokenKind.Dot or TokenKind.Ampersand) && /// true if the CommandAst represents a Pester command, false otherwise private static bool IsPesterCommand(CommandAst commandAst) { - if (commandAst == null) + if (commandAst is null) { return false; } @@ -94,7 +94,7 @@ private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFil string commandName = CommandHelpers.StripModuleQualification(pesterCommandAst.GetCommandName(), out _); PesterCommandType? commandType = PesterSymbolReference.GetCommandType(commandName); - if (commandType == null) + if (commandType is null) { return null; } @@ -247,10 +247,11 @@ internal PesterSymbolReference( internal static PesterCommandType? GetCommandType(string commandName) { - if (commandName == null || !PesterKeywords.TryGetValue(commandName, out PesterCommandType pesterCommandType)) + if (commandName is null || !PesterKeywords.TryGetValue(commandName, out PesterCommandType pesterCommandType)) { return null; } + return pesterCommandType; } diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index ddca0f0a7..f8527edb3 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -34,6 +34,7 @@ internal class SymbolDetails #region Constructors + // TODO: This should take a cancellation token! internal static async Task CreateAsync( SymbolReference symbolReference, IRunspaceInfo currentRunspace, diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 2d316cb13..af39bcf21 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -187,7 +187,11 @@ public async Task> ScanForReferencesOfSymbolAsync( foreach (string targetIdentifier in allIdentifiers) { await Task.Yield(); - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + break; + } + symbols.AddRange(file.References.TryGetReferences(symbol with { Id = targetIdentifier })); } } From 05acee2d4c986eb8fe87d6621a0902afad375652 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 24 Feb 2023 13:17:03 -0800 Subject: [PATCH 256/327] Reduce unnecessary allocations and exceptions in handlers By consistently using a static empty container, and breaking instead of throwing on cancellation. --- .../Handlers/CodeActionHandler.cs | 20 ++++-- .../Handlers/DefinitionHandler.cs | 22 +++--- .../Handlers/DocumentHighlightHandler.cs | 7 +- .../Handlers/DocumentSymbolHandler.cs | 70 +++++-------------- .../Handlers/FoldingRangeHandler.cs | 30 +++++--- .../Handlers/FormattingHandlers.cs | 32 ++++++--- .../Handlers/ReferencesHandler.cs | 15 +++- .../Handlers/WorkspaceSymbolsHandler.cs | 20 +++--- 8 files changed, 118 insertions(+), 98 deletions(-) diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs index 2861fe27f..5606c80fa 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs @@ -19,6 +19,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class PsesCodeActionHandler : CodeActionHandlerBase { + private static readonly CommandOrCodeActionContainer s_emptyCommandOrCodeActionContainer = new(); private readonly ILogger _logger; private readonly AnalysisService _analysisService; @@ -42,16 +43,17 @@ public override async Task Handle(CodeActionParams if (cancellationToken.IsCancellationRequested) { _logger.LogDebug($"CodeAction request canceled at range: {request.Range}"); - return Array.Empty(); + return s_emptyCommandOrCodeActionContainer; } IReadOnlyDictionary> corrections = await _analysisService.GetMostRecentCodeActionsForFileAsync( request.TextDocument.Uri) .ConfigureAwait(false); - if (corrections == null) + // GetMostRecentCodeActionsForFileAsync actually returns null if there's no corrections. + if (corrections is null) { - return Array.Empty(); + return s_emptyCommandOrCodeActionContainer; } List codeActions = new(); @@ -59,6 +61,11 @@ public override async Task Handle(CodeActionParams // If there are any code fixes, send these commands first so they appear at top of "Code Fix" menu in the client UI. foreach (Diagnostic diagnostic in request.Context.Diagnostics) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + if (string.IsNullOrEmpty(diagnostic.Code?.String)) { _logger.LogWarning( @@ -100,8 +107,7 @@ public override async Task Handle(CodeActionParams HashSet ruleNamesProcessed = new(); foreach (Diagnostic diagnostic in request.Context.Diagnostics) { - if ( - !diagnostic.Code.HasValue || + if (!diagnostic.Code.HasValue || !diagnostic.Code.Value.IsString || string.IsNullOrEmpty(diagnostic.Code?.String)) { @@ -134,7 +140,9 @@ public override async Task Handle(CodeActionParams } } - return codeActions; + return codeActions.Count == 0 + ? s_emptyCommandOrCodeActionContainer + : codeActions; } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs index f39ca9917..2519891f5 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs @@ -17,6 +17,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class PsesDefinitionHandler : DefinitionHandlerBase { + private static readonly LocationOrLocationLinks s_emptyLocationOrLocationLinks = new(); private readonly SymbolsService _symbolsService; private readonly WorkspaceService _workspaceService; @@ -45,20 +46,19 @@ public override async Task Handle(DefinitionParams requ if (foundSymbol is null) { - return new LocationOrLocationLinks(); + return s_emptyLocationOrLocationLinks; } // Short-circuit if we're already on the definition. if (foundSymbol.IsDeclaration) { - return new LocationOrLocationLinks( - new LocationOrLocationLink[] { - new LocationOrLocationLink( - new Location - { - Uri = DocumentUri.From(foundSymbol.FilePath), - Range = foundSymbol.NameRegion.ToRange() - })}); + return new LocationOrLocationLink[] { + new LocationOrLocationLink( + new Location + { + Uri = DocumentUri.From(foundSymbol.FilePath), + Range = foundSymbol.NameRegion.ToRange() + })}; } List definitionLocations = new(); @@ -74,7 +74,9 @@ public override async Task Handle(DefinitionParams requ })); } - return new LocationOrLocationLinks(definitionLocations); + return definitionLocations.Count == 0 + ? s_emptyLocationOrLocationLinks + : definitionLocations; } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs index 5758e59a1..6be52e961 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -45,7 +46,7 @@ public override Task Handle( request.Position.Line + 1, request.Position.Character + 1); - if (occurrences is null) + if (!occurrences.Any()) { return Task.FromResult(s_emptyHighlightContainer); } @@ -62,7 +63,9 @@ public override Task Handle( _logger.LogDebug("Highlights: " + highlights); - return Task.FromResult(new DocumentHighlightContainer(highlights)); + return highlights.Count == 0 + ? Task.FromResult(s_emptyHighlightContainer) + : Task.FromResult(new DocumentHighlightContainer(highlights)); } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs index 25953b3cb..c6a24bc1b 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs @@ -1,15 +1,11 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -23,6 +19,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class PsesDocumentSymbolHandler : DocumentSymbolHandlerBase { + private static readonly SymbolInformationOrDocumentSymbolContainer s_emptySymbolInformationOrDocumentSymbolContainer = new(); private readonly ILogger _logger; private readonly WorkspaceService _workspaceService; private readonly IDocumentSymbolProvider[] _providers; @@ -48,23 +45,21 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp // AKA the outline feature public override async Task Handle(DocumentSymbolParams request, CancellationToken cancellationToken) { - ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); - - IEnumerable foundSymbols = ProvideDocumentSymbols(scriptFile); - if (foundSymbols is null) - { - return null; - } + _logger.LogDebug($"Handling document symbols for {request.TextDocument.Uri}"); + ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); - List symbols = new(); - foreach (SymbolReference r in foundSymbols) + + foreach (SymbolReference r in ProvideDocumentSymbols(scriptFile)) { // This async method is pretty dense with synchronous code // so it's helpful to add some yields. await Task.Yield(); - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + break; + } // Outline view should only include declarations. // @@ -93,58 +88,27 @@ public override async Task Handle(Do Location = new Location { Uri = DocumentUri.From(r.FilePath), - Range = new Range(r.NameRegion.ToRange().Start, r.ScriptRegion.ToRange().End) // Jump to name start, but keep whole range to support symbol tree in outline + // Jump to name start, but keep whole range to support symbol tree in outline + Range = new Range(r.NameRegion.ToRange().Start, r.ScriptRegion.ToRange().End) }, Name = r.Name })); } - return new SymbolInformationOrDocumentSymbolContainer(symbols); + return symbols.Count == 0 + ? s_emptySymbolInformationOrDocumentSymbolContainer + : new SymbolInformationOrDocumentSymbolContainer(symbols); } private IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile) { - return InvokeProviders(p => p.ProvideDocumentSymbols(scriptFile)) - .SelectMany(r => r); - } - - /// - /// Invokes the given function synchronously against all - /// registered providers. - /// - /// The function to be invoked. - /// - /// An IEnumerable containing the results of all providers - /// that were invoked successfully. - /// - protected IEnumerable InvokeProviders( - Func invokeFunc) - { - Stopwatch invokeTimer = new(); - List providerResults = new(); - foreach (IDocumentSymbolProvider provider in _providers) { - try + foreach (SymbolReference symbol in provider.ProvideDocumentSymbols(scriptFile)) { - invokeTimer.Restart(); - - providerResults.Add(invokeFunc(provider)); - - invokeTimer.Stop(); - - _logger.LogTrace( - $"Invocation of provider '{provider.GetType().Name}' completed in {invokeTimer.ElapsedMilliseconds}ms."); - } - catch (Exception e) - { - _logger.LogException( - $"Exception caught while invoking provider {provider.GetType().Name}:", - e); + yield return symbol; } } - - return providerResults; } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/FoldingRangeHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FoldingRangeHandler.cs index fbf5ad9a5..85593447c 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/FoldingRangeHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FoldingRangeHandler.cs @@ -16,6 +16,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class PsesFoldingRangeHandler : FoldingRangeHandlerBase { + private static readonly Container s_emptyFoldingRangeContainer = new(); private readonly ILogger _logger; private readonly ConfigurationService _configurationService; private readonly WorkspaceService _workspaceService; @@ -37,27 +38,36 @@ public override Task> Handle(FoldingRangeRequestParam re if (cancellationToken.IsCancellationRequested) { _logger.LogDebug("FoldingRange request canceled for file: {Uri}", request.TextDocument.Uri); - return Task.FromResult(new Container()); + return Task.FromResult(s_emptyFoldingRangeContainer); } - // TODO Should be using dynamic registrations - if (!_configurationService.CurrentSettings.CodeFolding.Enable) { return Task.FromResult(new Container()); } + // TODO: Should be using dynamic registrations + if (!_configurationService.CurrentSettings.CodeFolding.Enable) + { + return Task.FromResult(s_emptyFoldingRangeContainer); + } // Avoid crash when using untitled: scheme or any other scheme where the document doesn't // have a backing file. https://github.com/PowerShell/vscode-powershell/issues/1676 // Perhaps a better option would be to parse the contents of the document as a string // as opposed to reading a file but the scenario of "no backing file" probably doesn't // warrant the extra effort. - if (!_workspaceService.TryGetFile(request.TextDocument.Uri, out ScriptFile scriptFile)) { return Task.FromResult(new Container()); } - - List result = new(); + if (!_workspaceService.TryGetFile(request.TextDocument.Uri, out ScriptFile scriptFile)) + { + return Task.FromResult(s_emptyFoldingRangeContainer); + } // If we're showing the last line, decrement the Endline of all regions by one. int endLineOffset = _configurationService.CurrentSettings.CodeFolding.ShowLastLine ? -1 : 0; - + List folds = new(); foreach (FoldingReference fold in TokenOperations.FoldableReferences(scriptFile.ScriptTokens).References) { - result.Add(new FoldingRange + if (cancellationToken.IsCancellationRequested) + { + break; + } + + folds.Add(new FoldingRange { EndCharacter = fold.EndCharacter, EndLine = fold.EndLine + endLineOffset, @@ -67,7 +77,9 @@ public override Task> Handle(FoldingRangeRequestParam re }); } - return Task.FromResult(new Container(result)); + return folds.Count == 0 + ? Task.FromResult(s_emptyFoldingRangeContainer) + : Task.FromResult(new Container(folds)); } } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs index 5b72f25cd..64ccb3156 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs @@ -15,6 +15,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers // TODO: Add IDocumentOnTypeFormatHandler to support on-type formatting. internal class PsesDocumentFormattingHandler : DocumentFormattingHandlerBase { + private static readonly TextEditContainer s_emptyTextEditContainer = new(); private readonly ILogger _logger; private readonly AnalysisService _analysisService; private readonly ConfigurationService _configurationService; @@ -39,6 +40,12 @@ public PsesDocumentFormattingHandler( public override async Task Handle(DocumentFormattingParams request, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + _logger.LogDebug($"Formatting request canceled for: {request.TextDocument.Uri}"); + return s_emptyTextEditContainer; + } + Services.TextDocument.ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); System.Collections.Hashtable pssaSettings = _configurationService.CurrentSettings.CodeFormatting.GetPSSASettingsHashtable( request.Options.TabSize, @@ -72,14 +79,15 @@ public override async Task Handle(DocumentFormattingParams re if (formattedScript is null) { - _logger.LogWarning($"Formatting returned null. Not formatting: {scriptFile.DocumentUri}"); - return null; + _logger.LogDebug($"Formatting returned null. Not formatting: {scriptFile.DocumentUri}"); + return s_emptyTextEditContainer; } + // Just in case the user really requested a cancellation. if (cancellationToken.IsCancellationRequested) { - _logger.LogWarning($"Formatting request canceled for: {scriptFile.DocumentUri}"); - return null; + _logger.LogDebug($"Formatting request canceled for: {scriptFile.DocumentUri}"); + return s_emptyTextEditContainer; } return new TextEditContainer(new TextEdit @@ -92,6 +100,7 @@ public override async Task Handle(DocumentFormattingParams re internal class PsesDocumentRangeFormattingHandler : DocumentRangeFormattingHandlerBase { + private static readonly TextEditContainer s_emptyTextEditContainer = new(); private readonly ILogger _logger; private readonly AnalysisService _analysisService; private readonly ConfigurationService _configurationService; @@ -116,6 +125,12 @@ public PsesDocumentRangeFormattingHandler( public override async Task Handle(DocumentRangeFormattingParams request, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + _logger.LogDebug($"Formatting request canceled for: {request.TextDocument.Uri}"); + return s_emptyTextEditContainer; + } + Services.TextDocument.ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); System.Collections.Hashtable pssaSettings = _configurationService.CurrentSettings.CodeFormatting.GetPSSASettingsHashtable( request.Options.TabSize, @@ -158,14 +173,15 @@ public override async Task Handle(DocumentRangeFormattingPara if (formattedScript is null) { - _logger.LogWarning($"Formatting returned null. Not formatting: {scriptFile.DocumentUri}"); - return null; + _logger.LogDebug($"Formatting returned null. Not formatting: {scriptFile.DocumentUri}"); + return s_emptyTextEditContainer; } + // Just in case the user really requested a cancellation. if (cancellationToken.IsCancellationRequested) { - _logger.LogWarning($"Formatting request canceled for: {scriptFile.DocumentUri}"); - return null; + _logger.LogDebug($"Formatting request canceled for: {scriptFile.DocumentUri}"); + return s_emptyTextEditContainer; } return new TextEditContainer(new TextEdit diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs index 1d41f3571..c7297e16f 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs @@ -17,6 +17,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class PsesReferencesHandler : ReferencesHandlerBase { + private static readonly LocationContainer s_emptyLocationContainer = new(); private readonly SymbolsService _symbolsService; private readonly WorkspaceService _workspaceService; @@ -33,6 +34,11 @@ public PsesReferencesHandler(SymbolsService symbolsService, WorkspaceService wor public override async Task Handle(ReferenceParams request, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + return s_emptyLocationContainer; + } + ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); SymbolReference foundSymbol = @@ -45,6 +51,11 @@ public override async Task Handle(ReferenceParams request, Ca foreach (SymbolReference foundReference in await _symbolsService.ScanForReferencesOfSymbolAsync( foundSymbol, cancellationToken).ConfigureAwait(false)) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + // Respect the request's setting to include declarations. if (!request.Context.IncludeDeclaration && foundReference.IsDeclaration) { @@ -58,7 +69,9 @@ public override async Task Handle(ReferenceParams request, Ca }); } - return new LocationContainer(locations); + return locations.Count == 0 + ? s_emptyLocationContainer + : locations; } } } diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs index 3efe4edcf..fa7f1d49a 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs @@ -19,6 +19,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class PsesWorkspaceSymbolsHandler : WorkspaceSymbolsHandlerBase { + private static readonly Container s_emptySymbolInformationContainer = new(); private readonly ILogger _logger; private readonly SymbolsService _symbolsService; private readonly WorkspaceService _workspaceService; @@ -34,23 +35,26 @@ public PsesWorkspaceSymbolsHandler(ILoggerFactory loggerFactory, SymbolsService public override async Task> Handle(WorkspaceSymbolParams request, CancellationToken cancellationToken) { + _logger.LogDebug($"Handling workspace symbols request for query {request.Query}"); + await _symbolsService.ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false); List symbols = new(); foreach (ScriptFile scriptFile in _workspaceService.GetOpenedFiles()) { _logger.LogDebug($"Handling workspace symbols request for: {request.Query}"); - IEnumerable foundSymbols = _symbolsService.FindSymbolsInFile(scriptFile); - // TODO: Need to compute a relative path that is based on common path for all workspace files string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath); - foreach (SymbolReference symbol in foundSymbols) + foreach (SymbolReference symbol in _symbolsService.FindSymbolsInFile(scriptFile)) { // This async method is pretty dense with synchronous code // so it's helpful to add some yields. await Task.Yield(); - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + break; + } if (!symbol.IsDeclaration) { @@ -91,13 +95,11 @@ public override async Task> Handle(WorkspaceSymbolP } } - return new Container(symbols); + return symbols.Count == 0 + ? s_emptySymbolInformationContainer + : symbols; } - #region private Methods - private static bool IsQueryMatch(string query, string symbolName) => symbolName.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; - - #endregion } } From a4bc2a1a776d7b784c0cc2b9dff5e366f2ffef05 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 28 Feb 2023 13:02:13 -0800 Subject: [PATCH 257/327] Fix `ProvideCodeLenses` to not take useless `CancellationToken` --- .../Services/CodeLens/ICodeLensProvider.cs | 3 +-- .../Services/CodeLens/PesterCodeLensProvider.cs | 8 +------- .../CodeLens/ReferencesCodeLensProvider.cs | 8 +------- .../TextDocument/Handlers/CodeLensHandlers.cs | 14 ++++---------- .../Handlers/DocumentHighlightHandler.cs | 8 +------- 5 files changed, 8 insertions(+), 33 deletions(-) diff --git a/src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs index 982fb1f45..8ee6bc8b7 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs @@ -27,9 +27,8 @@ internal interface ICodeLensProvider /// /// The document for which CodeLenses should be provided. /// - /// /// An IEnumerable of CodeLenses. - IEnumerable ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken); + IEnumerable ProvideCodeLenses(ScriptFile scriptFile); /// /// Resolves a CodeLens that was created without a Command. diff --git a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs index 398e73f89..22b563b1d 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs @@ -96,9 +96,8 @@ private static CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, Scri /// Get all Pester CodeLenses for a given script file. /// /// The script file to get Pester CodeLenses for. - /// /// All Pester CodeLenses for the given script file. - public IEnumerable ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) + public IEnumerable ProvideCodeLenses(ScriptFile scriptFile) { // Don't return anything if codelens setting is disabled if (!_configurationService.CurrentSettings.Pester.CodeLens) @@ -108,11 +107,6 @@ public IEnumerable ProvideCodeLenses(ScriptFile scriptFile, Cancellati foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile)) { - if (cancellationToken.IsCancellationRequested) - { - yield break; - } - if (symbol is not PesterSymbolReference pesterSymbol) { continue; diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs index b3a1f6f97..0307163cc 100644 --- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs @@ -52,17 +52,11 @@ public ReferencesCodeLensProvider(WorkspaceService workspaceService, SymbolsServ /// Get all reference code lenses for a given script file. /// /// The PowerShell script file to get code lenses for. - /// /// An IEnumerable of CodeLenses describing all functions, classes and enums in the given script file. - public IEnumerable ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) + public IEnumerable ProvideCodeLenses(ScriptFile scriptFile) { foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile)) { - if (cancellationToken.IsCancellationRequested) - { - yield break; - } - // TODO: Can we support more here? if (symbol.IsDeclaration && symbol.Type is diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs index 00cd9d968..407b0d3a0 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs @@ -42,9 +42,9 @@ public override Task Handle(CodeLensParams request, Cancellat _logger.LogDebug($"Handling code lens request for {request.TextDocument.Uri}"); ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); - IEnumerable codeLensResults = ProvideCodeLenses(scriptFile, cancellationToken); + IEnumerable codeLensResults = ProvideCodeLenses(scriptFile); - return !codeLensResults.Any() + return cancellationToken.IsCancellationRequested ? Task.FromResult(s_emptyCodeLensContainer) : Task.FromResult(new CodeLensContainer(codeLensResults)); } @@ -66,19 +66,13 @@ public override Task Handle(CodeLens request, CancellationToken cancel /// Get all the CodeLenses for a given script file. /// /// The PowerShell script file to get CodeLenses for. - /// /// All generated CodeLenses for the given script file. - private IEnumerable ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken) + private IEnumerable ProvideCodeLenses(ScriptFile scriptFile) { foreach (ICodeLensProvider provider in _symbolsService.GetCodeLensProviders()) { - foreach (CodeLens codeLens in provider.ProvideCodeLenses(scriptFile, cancellationToken)) + foreach (CodeLens codeLens in provider.ProvideCodeLenses(scriptFile)) { - if (cancellationToken.IsCancellationRequested) - { - yield break; - } - yield return codeLens; } } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs index 6be52e961..0e470d46f 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -46,11 +45,6 @@ public override Task Handle( request.Position.Line + 1, request.Position.Character + 1); - if (!occurrences.Any()) - { - return Task.FromResult(s_emptyHighlightContainer); - } - List highlights = new(); foreach (SymbolReference occurrence in occurrences) { @@ -63,7 +57,7 @@ public override Task Handle( _logger.LogDebug("Highlights: " + highlights); - return highlights.Count == 0 + return cancellationToken.IsCancellationRequested || highlights.Count == 0 ? Task.FromResult(s_emptyHighlightContainer) : Task.FromResult(new DocumentHighlightContainer(highlights)); } From e16ca88195f917b56f1edaebd78eb50ce1745253 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 28 Feb 2023 13:57:20 -0800 Subject: [PATCH 258/327] Pass `CancellationToken` through `SymbolDetails.CreateAsync()` --- .../Services/Symbols/SymbolDetails.cs | 11 +++++++---- .../Services/Symbols/SymbolsService.cs | 8 ++++++-- .../Services/TextDocument/Handlers/HoverHandler.cs | 3 ++- .../Language/SymbolsServiceTests.cs | 3 ++- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index f8527edb3..dc0a57fcd 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Management.Automation; +using System.Threading; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; @@ -34,11 +35,11 @@ internal class SymbolDetails #region Constructors - // TODO: This should take a cancellation token! internal static async Task CreateAsync( SymbolReference symbolReference, IRunspaceInfo currentRunspace, - IInternalPowerShellExecutionService executionService) + IInternalPowerShellExecutionService executionService, + CancellationToken cancellationToken) { SymbolDetails symbolDetails = new() { @@ -50,14 +51,16 @@ internal static async Task CreateAsync( CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( symbolReference.Id, currentRunspace, - executionService).ConfigureAwait(false); + executionService, + cancellationToken).ConfigureAwait(false); if (commandInfo is not null) { symbolDetails.Documentation = await CommandHelpers.GetCommandSynopsisAsync( commandInfo, - executionService).ConfigureAwait(false); + executionService, + cancellationToken).ConfigureAwait(false); if (commandInfo.CommandType == CommandTypes.Application) { diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index af39bcf21..457b1d651 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -222,12 +222,16 @@ public static IEnumerable FindOccurrencesInFile( /// Finds the details of the symbol at the given script file location. /// public Task FindSymbolDetailsAtLocationAsync( - ScriptFile scriptFile, int line, int column) + ScriptFile scriptFile, int line, int column, CancellationToken cancellationToken) { SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column); return symbol is null ? Task.FromResult(null) - : SymbolDetails.CreateAsync(symbol, _runspaceContext.CurrentRunspace, _executionService); + : SymbolDetails.CreateAsync( + symbol, + _runspaceContext.CurrentRunspace, + _executionService, + cancellationToken); } /// diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs index 567da0159..9da05490a 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs @@ -50,7 +50,8 @@ public override async Task Handle(HoverParams request, CancellationToken await _symbolsService.FindSymbolDetailsAtLocationAsync( scriptFile, request.Position.Line + 1, - request.Position.Character + 1).ConfigureAwait(false); + request.Position.Character + 1, + cancellationToken).ConfigureAwait(false); if (symbolDetails is null) { diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index fa5d905d9..109a3667f 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -755,7 +755,8 @@ public async Task FindsDetailsForBuiltInCommand() SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync( GetScriptFile(FindsDetailsForBuiltInCommandData.SourceDetails), FindsDetailsForBuiltInCommandData.SourceDetails.StartLineNumber, - FindsDetailsForBuiltInCommandData.SourceDetails.StartColumnNumber).ConfigureAwait(true); + FindsDetailsForBuiltInCommandData.SourceDetails.StartColumnNumber, + CancellationToken.None).ConfigureAwait(true); Assert.Equal("Gets the processes that are running on the local computer.", symbolDetails.Documentation); } From 824fd4f2f88eeec5defd19db6a23093206bfb5e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Mar 2023 21:23:17 +0000 Subject: [PATCH 259/327] Bump purcell/setup-emacs from 3.0 to 4.0 (#2005) Bumps [purcell/setup-emacs](https://github.com/purcell/setup-emacs) from 3.0 to 4.0. - [Release notes](https://github.com/purcell/setup-emacs/releases) - [Commits](https://github.com/purcell/setup-emacs/compare/v3.0...v4.0) --- updated-dependencies: - dependency-name: purcell/setup-emacs dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/emacs-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/emacs-test.yml b/.github/workflows/emacs-test.yml index ddbcb7887..6074058da 100644 --- a/.github/workflows/emacs-test.yml +++ b/.github/workflows/emacs-test.yml @@ -25,7 +25,7 @@ jobs: run: tools/azurePipelinesBuild.ps1 - name: Install Emacs - uses: purcell/setup-emacs@v3.0 + uses: purcell/setup-emacs@v4.0 with: version: '28.1' From 98a97c26fd513a3c94ecd2817aaf58b3e380d50e Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Thu, 9 Mar 2023 09:56:34 -0800 Subject: [PATCH 260/327] Fix declaration detection for variables with type constraints (#2006) --- .../Symbols/Visitors/SymbolVisitor.cs | 23 ++++++++++++++++++- .../FindsTypedVariableDefinition.cs | 20 ++++++++++++++++ .../References/SimpleFile.ps1 | 3 +++ .../Language/SymbolsServiceTests.cs | 12 ++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 test/PowerShellEditorServices.Test.Shared/Definition/FindsTypedVariableDefinition.cs diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs index fda5a53dc..58d7f581a 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs @@ -110,6 +110,27 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var )); } + // Traverse the parents to determine if this is a declaration. + static bool isDeclaration(Ast current) + { + Ast next = current.Parent; + while (true) + { + // Should come from an assignment statement. + if (next is AssignmentStatementAst assignment) + { + return assignment.Left == current; + } + // Or we might have type constraints or attributes to traverse first. + if (next is not ConvertExpressionAst or not AttributedExpressionAst) + { + return false; + } + current = next; + next = next.Parent; + } + } + // TODO: Consider tracking unscoped variable references only when they're declared within // the same function definition. return _action(new SymbolReference( @@ -119,7 +140,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var variableExpressionAst.Extent, variableExpressionAst.Extent, // TODO: Maybe parent? _file, - isDeclaration: variableExpressionAst.Parent is AssignmentStatementAst or ParameterAst)); + isDeclaration(variableExpressionAst))); } public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypedVariableDefinition.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypedVariableDefinition.cs new file mode 100644 index 000000000..98892dfab --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypedVariableDefinition.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 FindsTypedVariableDefinitionData + { + public static readonly ScriptRegion SourceDetails = new( + file: TestUtilities.NormalizePath("References/SimpleFile.ps1"), + text: string.Empty, + startLineNumber: 25, + startColumnNumber: 13, + 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 b5c1ef7ce..819f2a2c6 100644 --- a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 @@ -20,3 +20,6 @@ Get-ChildItem My-Alias Invoke-Command -ScriptBlock ${Function:My-Function} + +[string]$hello = "test" +Write-Host $hello diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs index 109a3667f..100b1ee8e 100644 --- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs @@ -300,6 +300,18 @@ public async Task FindsVariableDefinition() AssertIsRegion(symbol.NameRegion, 6, 1, 6, 8); } + [Fact] + public async Task FindsTypedVariableDefinition() + { + IEnumerable definitions = await GetDefinitions(FindsTypedVariableDefinitionData.SourceDetails).ConfigureAwait(true); + SymbolReference symbol = Assert.Single(definitions); + Assert.Equal("var hello", symbol.Id); + Assert.Equal("$hello", symbol.Name); + Assert.Equal(SymbolType.Variable, symbol.Type); + Assert.True(symbol.IsDeclaration); + AssertIsRegion(symbol.NameRegion, 24, 9, 24, 15); + } + [Fact] public async Task FindsReferencesOnVariable() { From 3c0d8366f209bafe96f11af05483099921c3e327 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 9 Mar 2023 10:02:53 -0800 Subject: [PATCH 261/327] Update CHANGELOG for `v3.8.2` --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7031ca04d..e89d11b0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # PowerShell Editor Services Release History +## v3.8.2 +### Thursday, March 09, 2023 + +- 🐛 🙏 [vscode-powershell #4443](https://github.com/PowerShell/PowerShellEditorServices/pull/2006) - Fix declaration detection for variables with type constraints. +- ✨ 🙏 [vscode-powershell #3604](https://github.com/PowerShell/PowerShellEditorServices/pull/2003) - Add document symbols for `#region`. +- ✨ 🙏 [PowerShellEditorServices #2000](https://github.com/PowerShell/PowerShellEditorServices/pull/2000) - Code clean-up and fixing end-to-end tests. +- 🐛 🐢 [PowerShellEditorServices #1998](https://github.com/PowerShell/PowerShellEditorServices/pull/1998) - Support module-qualified calls for Pester keywords. (Thanks @fflaten!) +- 🐛 🙏 [vscode-powershell #3192](https://github.com/PowerShell/PowerShellEditorServices/pull/1997) - Fix New-EditorFile adding content in current file. (Thanks @fflaten!) + ## v3.8.1 ### Monday, February 13, 2023 From 7fc0400a610f12fddc6dfa2f50df89974c701c91 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 9 Mar 2023 10:02:53 -0800 Subject: [PATCH 262/327] Bump version to `v3.8.2` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 348fa28da..418a23c71 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.8.1 + 3.8.2 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index d2140c741..70f260531 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.8.1' +ModuleVersion = '3.8.2' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 22fed1ac28787c7a80d1364f475b74877278cf77 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 21 Mar 2023 14:12:36 -0700 Subject: [PATCH 263/327] Update build documentation to include platyPS requirement Since it's always used post-build to generate the documentation, we have a `#requires` for it but forgot to add it to the documentation. Also suppress stderr for sysctl when checking if macOS is an Intel or Arm processor. On the former, it doesn't know what field we're querying. --- PowerShellEditorServices.build.ps1 | 4 ++-- README.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index bf37c108b..be8b5247c 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -42,7 +42,8 @@ $script:dotnetTestArgs = @("test") + $script:dotnetBuildArgs + $TestArgs + @( $script:IsNix = $IsLinux -or $IsMacOS # For Apple M1, pwsh might be getting emulated, in which case we need to check # for the proc_translated flag, otherwise we can check the architecture. -$script:IsAppleM1 = $IsMacOS -and ((sysctl -n sysctl.proc_translated) -eq 1 -or (uname -m) -eq "arm64") +$script:IsAppleM1 = $IsMacOS -and ((sysctl -n sysctl.proc_translated 2> $null) -eq 1 -or + [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq "Arm64") $script:IsArm64 = -not $script:IsNix -and @("ARM64") -contains $env:PROCESSOR_ARCHITECTURE $script:BuildInfoPath = [System.IO.Path]::Combine($PSScriptRoot, "src", "PowerShellEditorServices.Hosting", "BuildInfo.cs") $script:PsesCommonProps = [xml](Get-Content -Raw "$PSScriptRoot/PowerShellEditorServices.Common.props") @@ -272,7 +273,6 @@ Task LayoutModule -After Build { } # Assemble the PowerShellEditorServices.VSCode module - foreach ($vscodeComponent in Get-ChildItem $script:VSCodeOutput) { if (-not $includedDlls.Contains($vscodeComponent.Name)) { Copy-Item -Path $vscodeComponent.FullName -Destination $psesVSCodeBinOutputPath -Force diff --git a/README.md b/README.md index 9ddbd3aa1..1148636fb 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ git clone https://github.com/PowerShell/PowerShellEditorServices.git ```powershell Install-Module InvokeBuild -Scope CurrentUser +Install-Module platyPS -Scope CurrentUser ``` Now you're ready to build the code. You can do so in one of two ways: From 92e681dec92ff155db6af90cdf970cca13676a02 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 22 Mar 2023 15:39:21 -0700 Subject: [PATCH 264/327] Bump Emacs version in GitHub action and install updated packages Now using `use-package` to better install them, and setting `load-prefer-newer` which was probably the bug. --- .github/workflows/emacs-test.yml | 8 +++++--- .gitignore | 1 + test/emacs-test.el | 8 +++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/emacs-test.yml b/.github/workflows/emacs-test.yml index 6074058da..d244a16a2 100644 --- a/.github/workflows/emacs-test.yml +++ b/.github/workflows/emacs-test.yml @@ -25,9 +25,11 @@ jobs: run: tools/azurePipelinesBuild.ps1 - name: Install Emacs - uses: purcell/setup-emacs@v4.0 + uses: purcell/setup-emacs@master with: - version: '28.1' + version: '28.2' - name: Run ERT - run: emacs -batch -l ert -l test/emacs-test.el -f ert-run-tests-batch-and-exit + run: | + emacs -Q --batch -f package-refresh-contents --eval "(package-install 'eglot)" + emacs -Q --batch -l test/emacs-test.el -f ert-run-tests-batch-and-exit diff --git a/.gitignore b/.gitignore index 219315a5b..7d3bccc3b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ App_Data *.sln.cache *.suo TestResults +test/emacs-session.json [Tt]humbs.db buildd.* release/ diff --git a/test/emacs-test.el b/test/emacs-test.el index 9ed0dc330..d703ff22d 100644 --- a/test/emacs-test.el +++ b/test/emacs-test.el @@ -8,7 +8,8 @@ ;;; Code: -(require 'ert) +;; Avoid using old packages. +(setq load-prefer-newer t) ;; Improved TLS Security. (with-eval-after-load 'gnutls @@ -21,16 +22,17 @@ (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) +(package-refresh-contents) + +(require 'ert) (require 'flymake) (unless (package-installed-p 'powershell) - (package-refresh-contents) (package-install 'powershell)) (require 'powershell) (unless (package-installed-p 'eglot) - (package-refresh-contents) (package-install 'eglot)) (require 'eglot) From 8acee0e61813a6da6d1bf73c169a6716f297f3f1 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 27 Mar 2023 13:42:02 -0700 Subject: [PATCH 265/327] Update CHANGELOG for `v3.8.3` --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e89d11b0b..ab2bf1151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # PowerShell Editor Services Release History +## v3.8.3 +### Monday, March 27, 2023 + +- Documentation update and CI fix. + ## v3.8.2 ### Thursday, March 09, 2023 From 98e28e9638a9f355aaed861527c251542e456f4f Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 27 Mar 2023 13:42:03 -0700 Subject: [PATCH 266/327] Bump version to `v3.8.3` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 418a23c71..ea1c83bff 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.8.2 + 3.8.3 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 70f260531..2695c1ee4 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.8.2' +ModuleVersion = '3.8.3' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 9ec753dac009bc83e49e8fee5df690a9626ea137 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Fri, 7 Apr 2023 10:45:39 -0700 Subject: [PATCH 267/327] Add Git mailmap for Andy Jordan (#2011) This makes Git transparently remap commits under my prior name and email to my new name and Microsoft email. --- .mailmap | 1 + 1 file changed, 1 insertion(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..0bc786ac4 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Andy Jordan From b8c2817a6047ef0414d3ec8ff87d54e104e1715d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 17:08:28 +0000 Subject: [PATCH 268/327] Bump Microsoft.PowerShell.SDK from 7.2.10 to 7.3.4 (#2013) Bumps [Microsoft.PowerShell.SDK](https://github.com/PowerShell/PowerShell) from 7.2.10 to 7.3.4. - [Release notes](https://github.com/PowerShell/PowerShell/releases) - [Commits](https://github.com/PowerShell/PowerShell/compare/v7.2.10...v7.3.4) --- updated-dependencies: - dependency-name: Microsoft.PowerShell.SDK dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 9449fc9cd..de4666633 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -1,4 +1,4 @@ - + @@ -14,12 +14,12 @@ - + - + From 5e0b65e85e9e6300804286602b87929dfb7b121f Mon Sep 17 00:00:00 2001 From: csc027 Date: Tue, 18 Apr 2023 11:52:18 -0700 Subject: [PATCH 269/327] Add guide to configure Neovim (#2016) Added a getting started guide to setup and configure PowerShell Editor Services with Neovim. There is scaffolding in place to extend this guide to more languages in the future. --- docs/guide/getting_started.md | 124 ++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 docs/guide/getting_started.md diff --git a/docs/guide/getting_started.md b/docs/guide/getting_started.md new file mode 100644 index 000000000..696e102af --- /dev/null +++ b/docs/guide/getting_started.md @@ -0,0 +1,124 @@ +# Getting Started +The PowerShell Editor Services project provides a Language Server Protocol (LSP) +HTTP server that runs outside the editor. The server supplies rich editor +functionality like code completion, syntax highlighting, and code annotation. +This document will guide you through getting a minimal setup working with +several editors. + +## Editors +1. [Neovim](#neovim) + +## Neovim + +### Install the Server +Download and extract the PowerShell Editor Services server from the +[releases page](https://github.com/PowerShell/PowerShellEditorServices/releases) +into a directory of your choice. Remember the path that you extract the +project into. +```powershell +$DownloadUrl = 'https://github.com/PowerShell/PowerShellEditorServices/releases/latest/download/PowerShellEditorServices.zip'; +$ZipPath = "$HOME/Desktop/PowerShellEditorServices.zip"; +$InstallPath = "$HOME/Desktop/PowerShellEditorServices"; +Invoke-WebRequest -Method 'GET' -Uri $DownloadUrl -OutFile $ZipPath; +Expand-Archive -Path $ZipPath -DestinationPath $InstallPath; +``` + +### Install Neovim's Quickstart LSP Configurations +Neovim has a repository of quickstart LSP configurations for a number of +languages, including PowerShell. Install the quickstart LSP configuration into +one of the package directories inside `$XDG_CONFIG_HOME`. The path +`$XDG_CONFIG_HOME` will vary depending on which operating system you are on: + +| OS | Path | +| ---------- | -------------------------- | +| Windows | `$HOME/AppData/local/nvim` | +| *nix/macOS | `$HOME/.config/nvim` | + +The easiest way is to install the quickstart configuration is to clone the +repository using git: +```powershell +git clone https://github.com/neovim/nvim-lspconfig.git "$HOME/AppData/local/nvim/pack/complete/start/nvim-lspconfig" +``` + +Alternatively, you can extract the zip file into the same place: +```powershell +$DownloadUrl = 'https://github.com/neovim/nvim-lspconfig/archive/refs/heads/master.zip'; +$ZipPath = "$HOME/AppData/local/nvim/nvim-lspconfig.zip"; +$InstallPath = "$HOME/AppData/local/nvim/pack/complete/start/nvim-lspconfig"; +Invoke-WebRequest -Method 'GET' Uri $DownloadUrl -OutFile $ZipPath; +Expand-Archive -Path $ZipPath -DestinationPath $InstallPath; +``` + +> NOTE: If the corresponding neovim configuration and package directories have +> not been created yet, create them before installing the quickstart LSP +> configuration repository. + +### Configure the Server + +#### Setup Keybindings and Path Information +Once the basic language configurations have been installed, add this to your +`init.lua` located in `$XDG_CONFIG_HOME`: +```lua +local on_attach = function(client, bufnr) + -- Enable completion triggered by + vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc') + + local bufopts = { noremap = true, silent = true, buffer = bufnr } + vim.keymap.set('n', '', vim.lsp.buf.signature_help, bufopts) + vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, bufopts) + vim.keymap.set('n', 'gd', vim.lsp.buf.definition, bufopts) + vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, bufopts) + vim.keymap.set('n', 'gr', vim.lsp.buf.references, bufopts) + vim.keymap.set('n', 'K', vim.lsp.buf.hover, bufopts) + vim.keymap.set('n', 'ca', vim.lsp.buf.code_action, bufopts) + vim.keymap.set('n', 'f', function() vim.lsp.buf.format { async = true } end, bufopts) + vim.keymap.set('n', 'rn', vim.lsp.buf.rename, bufopts) + vim.keymap.set('n', 'td', vim.lsp.buf.type_definition, bufopts) +end + +local home_directory = os.getenv('HOME') +if home_directory == nil then + home_directory = os.getenv('USERPROFILE') +end + +-- The bundle_path is where PowerShell Editor Services was installed +local bundle_path = home_directory .. '/Desktop/PowerShellEditorServices' + +require('lspconfig')['powershell_es'].setup { + bundle_path = bundle_path, + on_attach = on_attach +} +``` + +> NOTE: Be sure to set the bundle_path variable to the correct location, +> otherwise the server will not know the path to start the server. + +If you use an `init.vim` file, you may put the keybinding and path configuration +in your `init.vim` with the `lua` heredoc syntax instead. +```vim +lua << EOF +-- lua keybindings and path configuration here +EOF +``` + +#### Configure Additional Settings +To further configure the server, you can supply settings to the setup table. +For example, you can set the code formatting preset to one true brace style +(OTBS). +```lua +require('lspconfig')['powershell_es'].setup { + bundle_path = bundle_path, + on_attach = on_attach, + settings = { powershell = { codeFormatting = { Preset = 'OTBS' } } } +} +``` + +You can also set the bundled PSScriptAnalyzer's custom rule path like so: +```lua +local custom_settings_path = home_directory .. '/PSScriptAnalyzerSettings.psd1' +require('lspconfig')['powershell_es'].setup { + bundle_path = bundle_path, + on_attach = on_attach, + settings = { powershell = { scriptAnalysis = { settingsPath = custom_settings_path } } } +} +``` From 708759b48d6dad7bb98ace90426a16031584c0b1 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Mon, 24 Apr 2023 12:03:39 -0700 Subject: [PATCH 270/327] Fix Dependabot pull requests limit (#2017) There seems to be a bug in the backend that thinks we're hitting the default limit of 5 even when no PRs are open. Also switch to weekly like the client. --- .github/dependabot.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5672e2cca..bbe4db5db 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,8 +3,9 @@ updates: - package-ecosystem: nuget directory: "/" schedule: - interval: daily + interval: weekly + open-pull-requests-limit: 10 - package-ecosystem: github-actions directory: "/" schedule: - interval: daily + interval: weekly From b1e468fecd27803b525c2b8e00d9565e087df792 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Apr 2023 11:49:51 -0700 Subject: [PATCH 271/327] Update minimum supported .NET check to 4.8 This is not updating our requirement, that apparently already changed to 4.8 as determined by testing. --- .../EditorServicesLoader.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index 2f9b8f8a6..b9d4097cc 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -33,7 +33,7 @@ public sealed class EditorServicesLoader : IDisposable { #if !CoreCLR // See https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed - private const int Net462Version = 394802; + private const int Net48Version = 528040; private static readonly string s_psesBaseDirPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); #endif @@ -244,7 +244,7 @@ private static void LoadEditorServices() => #if !CoreCLR private void CheckNetFxVersion() { - _logger.Log(PsesLogLevel.Diagnostic, "Checking that .NET Framework version is at least 4.6.2"); + _logger.Log(PsesLogLevel.Diagnostic, "Checking that .NET Framework version is at least 4.8"); using RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full"); object netFxValue = key?.GetValue("Release"); if (netFxValue == null || netFxValue is not int netFxVersion) @@ -254,9 +254,9 @@ private void CheckNetFxVersion() _logger.Log(PsesLogLevel.Verbose, $".NET registry version: {netFxVersion}"); - if (netFxVersion < Net462Version) + if (netFxVersion < Net48Version) { - _logger.Log(PsesLogLevel.Warning, $".NET Framework version {netFxVersion} lower than .NET 4.6.2. This runtime is not supported and you may experience errors. Please update your .NET runtime version."); + _logger.Log(PsesLogLevel.Warning, $".NET Framework version {netFxVersion} lower than .NET 4.8. This runtime is not supported and you may experience errors. Please update your .NET runtime version."); } } #endif From 4223eecd1b2892235daa4455989d0a63c1ea300e Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Apr 2023 11:51:14 -0700 Subject: [PATCH 272/327] Remove `details` from `WriteSessionFailure` Which is currently unused. It must have been in use previously because the client is expecting specific reasons. Since we need to redo this, let's remove the excess. --- .../Configuration/SessionFileWriter.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs b/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs index 207ef67ab..e7765331a 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs @@ -20,8 +20,7 @@ public interface ISessionFileWriter /// Write a session file describing a failed startup. /// /// The reason for the startup failure. - /// Any details to accompany the reason. - void WriteSessionFailure(string reason, object details); + void WriteSessionFailure(string reason); /// /// Write a session file describing a successful startup. @@ -58,8 +57,7 @@ public SessionFileWriter(HostLogger logger, string sessionFilePath) /// Write a startup failure to the session file. /// /// The reason for the startup failure. - /// Any extra details, which will be serialized as JSON. - public void WriteSessionFailure(string reason, object details) + public void WriteSessionFailure(string reason) { _logger.Log(PsesLogLevel.Diagnostic, "Writing session failure"); @@ -69,11 +67,6 @@ public void WriteSessionFailure(string reason, object details) { "reason", reason }, }; - if (details != null) - { - sessionObject["details"] = details; - } - WriteSessionObject(sessionObject); } From 9c0bea28e9d28e6ea3d1b49e7c8a9738c835f822 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Apr 2023 13:07:28 -0700 Subject: [PATCH 273/327] Set session failure with reason when applicable --- .../Commands/StartEditorServicesCommand.cs | 7 +- .../Configuration/SessionFileWriter.cs | 14 ++- .../EditorServicesLoader.cs | 99 +++++++++---------- 3 files changed, 61 insertions(+), 59 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 743b6aac9..10de11254 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -224,10 +224,7 @@ protected override void EndProcessing() // Create the configuration from parameters EditorServicesConfig editorServicesConfig = CreateConfigObject(); - SessionFileWriter sessionFileWriter = new(_logger, SessionDetailsPath); - _logger.Log(PsesLogLevel.Diagnostic, "Session file writer created"); - - using EditorServicesLoader psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, sessionFileWriter, _loggerUnsubscribers); + using EditorServicesLoader psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, SessionDetailsPath, _loggerUnsubscribers); _logger.Log(PsesLogLevel.Verbose, "Loading EditorServices"); // Synchronously start editor services and wait here until it shuts down. #pragma warning disable VSTHRD002 @@ -394,7 +391,7 @@ private string GetProfilePathFromProfileObject(PSObject profileObject, ProfileUs $"{HostProfileId}_profile.ps1"); } - // We should only use PSReadLine if we specificied that we want a console repl + // We should only use PSReadLine if we specified that we want a console repl // and we have not explicitly said to use the legacy ReadLine. // We also want it if we are either: // * On Windows on any version OR diff --git a/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs b/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs index e7765331a..c5f351f91 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs @@ -42,15 +42,19 @@ public sealed class SessionFileWriter : ISessionFileWriter private readonly string _sessionFilePath; + private readonly Version _powerShellVersion; + /// /// Construct a new session file writer for the given session file path. /// /// The logger to log actions with. /// The path to write the session file path to. - public SessionFileWriter(HostLogger logger, string sessionFilePath) + /// The process's PowerShell version object. + public SessionFileWriter(HostLogger logger, string sessionFilePath, Version powerShellVersion) { _logger = logger; _sessionFilePath = sessionFilePath; + _powerShellVersion = powerShellVersion; } /// @@ -84,11 +88,11 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran { "status", "started" }, }; - if (languageServiceTransport != null) + if (languageServiceTransport is not null) { sessionObject["languageServiceTransport"] = languageServiceTransport.SessionFileTransportName; - if (languageServiceTransport.SessionFileEntries != null) + if (languageServiceTransport.SessionFileEntries is not null) { foreach (KeyValuePair sessionEntry in languageServiceTransport.SessionFileEntries) { @@ -97,7 +101,7 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran } } - if (debugAdapterTransport != null) + if (debugAdapterTransport is not null) { sessionObject["debugServiceTransport"] = debugAdapterTransport.SessionFileTransportName; @@ -119,6 +123,8 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran /// The dictionary representing the session file. private void WriteSessionObject(Dictionary sessionObject) { + sessionObject["powerShellVersion"] = _powerShellVersion.ToString(); + string psModulePath = Environment.GetEnvironmentVariable("PSModulePath"); string content = null; using (SMA.PowerShell pwsh = SMA.PowerShell.Create(RunspaceMode.NewRunspace)) diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index b9d4097cc..d5772bb59 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -49,37 +49,28 @@ public sealed class EditorServicesLoader : IDisposable /// /// The host logger to use. /// The host configuration to start editor services with. - /// The session file writer to write the session file with. - /// - public static EditorServicesLoader Create( - HostLogger logger, - EditorServicesConfig hostConfig, - ISessionFileWriter sessionFileWriter) => Create(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe: null); - - /// - /// Create a new Editor Services loader. - /// - /// The host logger to use. - /// The host configuration to start editor services with. - /// The session file writer to write the session file with. + /// Path to the session file to create on startup or startup failure. /// The loggers to unsubscribe form writing to the terminal. - /// public static EditorServicesLoader Create( HostLogger logger, EditorServicesConfig hostConfig, - ISessionFileWriter sessionFileWriter, + string sessionDetailsPath, IReadOnlyCollection loggersToUnsubscribe) { - if (logger == null) + if (logger is null) { throw new ArgumentNullException(nameof(logger)); } - if (hostConfig == null) + if (hostConfig is null) { throw new ArgumentNullException(nameof(hostConfig)); } + Version powerShellVersion = GetPSVersion(); + SessionFileWriter sessionFileWriter = new(logger, sessionDetailsPath, powerShellVersion); + logger.Log(PsesLogLevel.Diagnostic, "Session file writer created"); + #if CoreCLR // In .NET Core, we add an event here to redirect dependency loading to the new AssemblyLoadContext we load PSES' dependencies into logger.Log(PsesLogLevel.Verbose, "Adding AssemblyResolve event handler for new AssemblyLoadContext dependency loading"); @@ -167,7 +158,7 @@ public static EditorServicesLoader Create( }; #endif - return new EditorServicesLoader(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe); + return new EditorServicesLoader(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe, powerShellVersion); } private readonly EditorServicesConfig _hostConfig; @@ -178,33 +169,38 @@ public static EditorServicesLoader Create( private readonly IReadOnlyCollection _loggersToUnsubscribe; + private readonly Version _powerShellVersion; + private EditorServicesRunner _editorServicesRunner; private EditorServicesLoader( HostLogger logger, EditorServicesConfig hostConfig, ISessionFileWriter sessionFileWriter, - IReadOnlyCollection loggersToUnsubscribe) + IReadOnlyCollection loggersToUnsubscribe, + Version powerShellVersion) { _logger = logger; _hostConfig = hostConfig; _sessionFileWriter = sessionFileWriter; _loggersToUnsubscribe = loggersToUnsubscribe; + _powerShellVersion = powerShellVersion; } /// /// Load Editor Services and its dependencies in an isolated way and start it. /// This method's returned task will end when Editor Services shuts down. /// - /// public Task LoadAndRunEditorServicesAsync() { // Log important host information here LogHostInformation(); + CheckPowerShellVersion(); + #if !CoreCLR // Make sure the .NET Framework version supports .NET Standard 2.0 - CheckNetFxVersion(); + CheckDotNetVersion(); #endif // Add the bundled modules to the PSModulePath @@ -241,10 +237,30 @@ private static void LoadEditorServices() => // The call within this method is therefore a total no-op EditorServicesLoading.LoadEditorServicesForHost(); + private void CheckPowerShellVersion() + { + PSLanguageMode languageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode; + + _logger.Log(PsesLogLevel.Verbose, $@" +== PowerShell Details == +- PowerShell version: {_powerShellVersion} +- Language mode: {languageMode} +"); + + if ((_powerShellVersion < new Version(5, 1)) + || (_powerShellVersion >= new Version(6, 0) && _powerShellVersion < new Version(7, 2))) + { + _logger.Log(PsesLogLevel.Error, $"PowerShell {_powerShellVersion} is not supported, please update!"); + _sessionFileWriter.WriteSessionFailure("powerShellVersion"); + } + + // TODO: Check if language mode still matters for support. + } + #if !CoreCLR - private void CheckNetFxVersion() + private void CheckDotNetVersion() { - _logger.Log(PsesLogLevel.Diagnostic, "Checking that .NET Framework version is at least 4.8"); + _logger.Log(PsesLogLevel.Verbose, "Checking that .NET Framework version is at least 4.8"); using RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full"); object netFxValue = key?.GetValue("Release"); if (netFxValue == null || netFxValue is not int netFxVersion) @@ -256,7 +272,8 @@ private void CheckNetFxVersion() if (netFxVersion < Net48Version) { - _logger.Log(PsesLogLevel.Warning, $".NET Framework version {netFxVersion} lower than .NET 4.8. This runtime is not supported and you may experience errors. Please update your .NET runtime version."); + _logger.Log(PsesLogLevel.Error, $".NET Framework {netFxVersion} is out-of-date, please install at least 4.8: https://dotnet.microsoft.com/en-us/download/dotnet-framework"); + _sessionFileWriter.WriteSessionFailure("dotNetVersion"); } } #endif @@ -322,30 +339,6 @@ private void LogHostInformation() - PowerShell output encoding: {GetPSOutputEncoding()} "); - LogPowerShellDetails(); - - LogOperatingSystemDetails(); - } - - private static string GetPSOutputEncoding() - { - using SMA.PowerShell pwsh = SMA.PowerShell.Create(); - return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke()[0]; - } - - private void LogPowerShellDetails() - { - PSLanguageMode languageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode; - - _logger.Log(PsesLogLevel.Verbose, $@" -== PowerShell Details == -- PowerShell version: {GetPSVersion()} -- Language mode: {languageMode} -"); - } - - private void LogOperatingSystemDetails() - { _logger.Log(PsesLogLevel.Verbose, $@" == Environment Details == - OS description: {RuntimeInformation.OSDescription} @@ -354,6 +347,12 @@ private void LogOperatingSystemDetails() "); } + private static string GetPSOutputEncoding() + { + using SMA.PowerShell pwsh = SMA.PowerShell.Create(); + return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke()[0]; + } + // TODO: Deduplicate this with VersionUtils. private static string GetOSArchitecture() { @@ -401,7 +400,7 @@ private void ValidateConfiguration() } } - private static object GetPSVersion() + private static Version GetPSVersion() { // In order to read the $PSVersionTable variable, // we are forced to create a new runspace to avoid concurrency issues, @@ -412,7 +411,7 @@ private static object GetPSVersion() return typeof(PSObject).Assembly .GetType("System.Management.Automation.PSVersionInfo") .GetMethod("get_PSVersion", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .Invoke(null, new object[0] /* Cannot use Array.Empty, since it must work in net452 */); + .Invoke(null, new object[0] /* Cannot use Array.Empty, since it must work in net452 */) as Version; #pragma warning restore CA1825 } } From e2d0e0e8901d2f285167d07d01bb0fdeea6445a3 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 25 Apr 2023 12:46:00 -0700 Subject: [PATCH 274/327] Update CHANGELOG for `v3.8.4` --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab2bf1151..1a83988a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # PowerShell Editor Services Release History +## v3.8.4 +### Tuesday, April 25, 2023 + +- 🐛 🛫 [vscode-powershell #4509](https://github.com/PowerShell/PowerShellEditorServices/pull/2018) - Set session failure with reason when applicable. +- ✨ 📖 [PowerShellEditorServices #2016](https://github.com/PowerShell/PowerShellEditorServices/pull/2016) - Add guide to configure Neovim. (Thanks @csc027!) + ## v3.8.3 ### Monday, March 27, 2023 From de4d117d37dd1ffa955e6231bb41dc8b95a766ff Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 25 Apr 2023 12:46:01 -0700 Subject: [PATCH 275/327] Bump version to `v3.8.4` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index ea1c83bff..59dbac420 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.8.3 + 3.8.4 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 2695c1ee4..da23ef4bf 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.8.3' +ModuleVersion = '3.8.4' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From ec06e5755d9c2a962142e34ee0addb26cf974c24 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 4 May 2023 17:17:09 -0700 Subject: [PATCH 276/327] Remove useless `ToArray` of `BreakpointDetails` This can just be an `IEnumerable` throughout instead. --- .../Services/DebugAdapter/DebugService.cs | 10 ++-- .../Handlers/BreakpointHandlers.cs | 8 +-- .../Debugging/DscBreakpointCapability.cs | 17 +++--- .../Debugging/DebugServiceTests.cs | 52 +++++++++---------- 4 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 7ea6ad435..63d8c961c 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -130,9 +130,9 @@ public DebugService( /// BreakpointDetails for each breakpoint that will be set. /// If true, causes all existing breakpoints to be cleared before setting new ones. /// An awaitable Task that will provide details about the breakpoints that were set. - public async Task SetLineBreakpointsAsync( + public async Task> SetLineBreakpointsAsync( ScriptFile scriptFile, - BreakpointDetails[] breakpoints, + IEnumerable breakpoints, bool clearExisting = true) { DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync(CancellationToken.None).ConfigureAwait(false); @@ -146,7 +146,7 @@ public async Task SetLineBreakpointsAsync( if (!_remoteFileManager.IsUnderRemoteTempPath(scriptPath)) { _logger.LogTrace($"Could not set breakpoints for local path '{scriptPath}' in a remote session."); - return Array.Empty(); + return Enumerable.Empty(); } scriptPath = _remoteFileManager.GetMappedPath(scriptPath, _psesHost.CurrentRunspace); @@ -154,7 +154,7 @@ public async Task SetLineBreakpointsAsync( else if (temporaryScriptListingPath?.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase) == true) { _logger.LogTrace($"Could not set breakpoint on temporary script listing path '{scriptPath}'."); - return Array.Empty(); + return Enumerable.Empty(); } // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to @@ -168,7 +168,7 @@ public async Task SetLineBreakpointsAsync( await _breakpointService.RemoveAllBreakpointsAsync(scriptFile.FilePath).ConfigureAwait(false); } - return (await _breakpointService.SetBreakpointsAsync(escapedScriptPath, breakpoints).ConfigureAwait(false)).ToArray(); + return await _breakpointService.SetBreakpointsAsync(escapedScriptPath, breakpoints).ConfigureAwait(false); } return await dscBreakpoints diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs index 03f2841f2..0e3da3942 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -82,18 +83,17 @@ public async Task Handle(SetBreakpointsArguments request } // At this point, the source file has been verified as a PowerShell script. - BreakpointDetails[] breakpointDetails = request.Breakpoints + IEnumerable breakpointDetails = request.Breakpoints .Select((srcBreakpoint) => BreakpointDetails.Create( scriptFile.FilePath, srcBreakpoint.Line, srcBreakpoint.Column, srcBreakpoint.Condition, srcBreakpoint.HitCondition, - srcBreakpoint.LogMessage)) - .ToArray(); + srcBreakpoint.LogMessage)); // If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints. - BreakpointDetails[] updatedBreakpointDetails = breakpointDetails; + IEnumerable updatedBreakpointDetails = breakpointDetails; if (!_debugStateService.NoDebug) { await _debugStateService.WaitForSetBreakpointHandleAsync().ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 644eec763..7121e6d75 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -22,24 +22,19 @@ internal class DscBreakpointCapability { private string[] dscResourceRootPaths = Array.Empty(); - private readonly Dictionary breakpointsPerFile = - new(); + private readonly Dictionary breakpointsPerFile = new(); - public async Task SetLineBreakpointsAsync( + public async Task> SetLineBreakpointsAsync( IInternalPowerShellExecutionService executionService, string scriptPath, - BreakpointDetails[] breakpoints) + IEnumerable breakpoints) { - List resultBreakpointDetails = - new(); - // We always get the latest array of breakpoint line numbers // so store that for future use - if (breakpoints.Length > 0) + if (breakpoints.Any()) { // Set the breakpoints for this scriptPath - breakpointsPerFile[scriptPath] = - breakpoints.Select(b => b.LineNumber).ToArray(); + breakpointsPerFile[scriptPath] = breakpoints.Select(b => b.LineNumber).ToArray(); } else { @@ -72,7 +67,7 @@ await executionService.ExecutePSCommandAsync( breakpoint.Verified = true; } - return breakpoints.ToArray(); + return breakpoints; } public bool IsDscResourcePath(string scriptPath) diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index fa1ed7be8..0e58a0025 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -203,7 +203,7 @@ await debugService.SetCommandBreakpointsAsync( [MemberData(nameof(DebuggerAcceptsScriptArgsTestData))] public async Task DebuggerAcceptsScriptArgs(string[] args) { - BreakpointDetails[] breakpoints = await debugService.SetLineBreakpointsAsync( + IEnumerable breakpoints = await debugService.SetLineBreakpointsAsync( oddPathScriptFile, new[] { BreakpointDetails.Create(oddPathScriptFile.FilePath, 3) }).ConfigureAwait(true); @@ -264,8 +264,8 @@ public async Task DebuggerSetsAndClearsFunctionBreakpoints() }).ConfigureAwait(true); Assert.Equal(2, breakpoints.Length); - Assert.Equal("Write-Host", breakpoints[0].Name); - Assert.Equal("Get-Date", breakpoints[1].Name); + Assert.Equal("Write-Host", breakpoints.ElementAt(0).Name); + Assert.Equal("Get-Date", breakpoints.ElementAt(1).Name); breakpoints = await debugService.SetCommandBreakpointsAsync( new[] { CommandBreakpointDetails.Create("Get-Host") }).ConfigureAwait(true); @@ -311,7 +311,7 @@ public async Task DebuggerStopsOnFunctionBreakpoints() [Fact] public async Task DebuggerSetsAndClearsLineBreakpoints() { - BreakpointDetails[] breakpoints = + IEnumerable breakpoints = await debugService.SetLineBreakpointsAsync( debugScriptFile, new[] { @@ -322,8 +322,8 @@ await debugService.SetLineBreakpointsAsync( IReadOnlyList confirmedBreakpoints = await GetConfirmedBreakpoints(debugScriptFile).ConfigureAwait(true); Assert.Equal(2, confirmedBreakpoints.Count); - Assert.Equal(5, breakpoints[0].LineNumber); - Assert.Equal(10, breakpoints[1].LineNumber); + Assert.Equal(5, breakpoints.ElementAt(0).LineNumber); + Assert.Equal(10, breakpoints.ElementAt(1).LineNumber); breakpoints = await debugService.SetLineBreakpointsAsync( debugScriptFile, @@ -331,7 +331,7 @@ await debugService.SetLineBreakpointsAsync( confirmedBreakpoints = await GetConfirmedBreakpoints(debugScriptFile).ConfigureAwait(true); Assert.Single(confirmedBreakpoints); - Assert.Equal(2, breakpoints[0].LineNumber); + Assert.Equal(2, breakpoints.ElementAt(0).LineNumber); await debugService.SetLineBreakpointsAsync( debugScriptFile, @@ -442,7 +442,7 @@ await debugService.SetLineBreakpointsAsync( [Fact] public async Task DebuggerProvidesMessageForInvalidConditionalBreakpoint() { - BreakpointDetails[] breakpoints = + IEnumerable breakpoints = await debugService.SetLineBreakpointsAsync( debugScriptFile, new[] { @@ -457,20 +457,20 @@ await debugService.SetLineBreakpointsAsync( }).ConfigureAwait(true); Assert.Single(breakpoints); - // Assert.Equal(5, breakpoints[0].LineNumber); - // Assert.True(breakpoints[0].Verified); - // Assert.Null(breakpoints[0].Message); - - Assert.Equal(10, breakpoints[0].LineNumber); - Assert.False(breakpoints[0].Verified); - Assert.NotNull(breakpoints[0].Message); - Assert.Contains("Unexpected token '-ez'", breakpoints[0].Message); + // Assert.Equal(5, breakpoints.ElementAt(0).LineNumber); + // Assert.True(breakpoints.ElementAt(0).Verified); + // Assert.Null(breakpoints.ElementAt(0).Message); + + Assert.Equal(10, breakpoints.ElementAt(0).LineNumber); + Assert.False(breakpoints.ElementAt(0).Verified); + Assert.NotNull(breakpoints.ElementAt(0).Message); + Assert.Contains("Unexpected token '-ez'", breakpoints.ElementAt(0).Message); } [Fact] public async Task DebuggerFindsParsableButInvalidSimpleBreakpointConditions() { - BreakpointDetails[] breakpoints = + IEnumerable breakpoints = await debugService.SetLineBreakpointsAsync( debugScriptFile, new[] { @@ -478,15 +478,15 @@ await debugService.SetLineBreakpointsAsync( BreakpointDetails.Create(debugScriptFile.FilePath, 7, column: null, condition: "$i > 100") }).ConfigureAwait(true); - Assert.Equal(2, breakpoints.Length); - Assert.Equal(5, breakpoints[0].LineNumber); - Assert.False(breakpoints[0].Verified); - Assert.Contains("Use '-eq' instead of '=='", breakpoints[0].Message); - - Assert.Equal(7, breakpoints[1].LineNumber); - Assert.False(breakpoints[1].Verified); - Assert.NotNull(breakpoints[1].Message); - Assert.Contains("Use '-gt' instead of '>'", breakpoints[1].Message); + Assert.Equal(2, breakpoints.Count()); + Assert.Equal(5, breakpoints.ElementAt(0).LineNumber); + Assert.False(breakpoints.ElementAt(0).Verified); + Assert.Contains("Use '-eq' instead of '=='", breakpoints.ElementAt(0).Message); + + Assert.Equal(7, breakpoints.ElementAt(1).LineNumber); + Assert.False(breakpoints.ElementAt(1).Verified); + Assert.NotNull(breakpoints.ElementAt(1).Message); + Assert.Contains("Use '-gt' instead of '>'", breakpoints.ElementAt(1).Message); } [Fact] From 11837b9e4051541276b58be46026cfd585201f3a Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 4 May 2023 17:53:46 -0700 Subject: [PATCH 277/327] Only try to load DSC module once Instead of trying repeatedly every time we set a breakpoint. --- .../Services/DebugAdapter/DebugService.cs | 2 +- .../Handlers/BreakpointHandlers.cs | 4 +- .../Debugging/DscBreakpointCapability.cs | 120 +++++++----------- .../Debugging/IPowerShellDebugContext.cs | 3 +- .../Debugging/PowerShellDebugContext.cs | 5 +- .../PowerShell/Runspace/RunspaceInfo.cs | 7 +- .../Services/Template/TemplateService.cs | 20 +-- 7 files changed, 58 insertions(+), 103 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 63d8c961c..a02775cc5 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -135,7 +135,7 @@ public async Task> SetLineBreakpointsAsync( IEnumerable breakpoints, bool clearExisting = true) { - DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync(CancellationToken.None).ConfigureAwait(false); + DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync().ConfigureAwait(false); string scriptPath = scriptFile.FilePath; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs index 0e3da3942..4efe9e6f2 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs @@ -52,7 +52,7 @@ public async Task Handle(SetBreakpointsArguments request if (!_workspaceService.TryGetFile(request.Source.Path, out ScriptFile scriptFile)) { string message = _debugStateService.NoDebug ? string.Empty : "Source file could not be accessed, breakpoint not set."; - System.Collections.Generic.IEnumerable srcBreakpoints = request.Breakpoints + IEnumerable srcBreakpoints = request.Breakpoints .Select(srcBkpt => LspDebugUtils.CreateBreakpoint( srcBkpt, request.Source.Path, message, verified: _debugStateService.NoDebug)); @@ -71,7 +71,7 @@ public async Task Handle(SetBreakpointsArguments request string message = _debugStateService.NoDebug ? string.Empty : "Source is not a PowerShell script, breakpoint not set."; - System.Collections.Generic.IEnumerable srcBreakpoints = request.Breakpoints + IEnumerable srcBreakpoints = request.Breakpoints .Select(srcBkpt => LspDebugUtils.CreateBreakpoint( srcBkpt, request.Source.Path, message, verified: _debugStateService.NoDebug)); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 7121e6d75..189fb35b3 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -1,27 +1,24 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Logging; -using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Linq; using System.Management.Automation; using System.Threading; -using SMA = System.Management.Automation; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging { internal class DscBreakpointCapability { + private static bool? isDscInstalled; private string[] dscResourceRootPaths = Array.Empty(); - private readonly Dictionary breakpointsPerFile = new(); public async Task> SetLineBreakpointsAsync( @@ -79,88 +76,57 @@ public bool IsDscResourcePath(string scriptPath) StringComparison.CurrentCultureIgnoreCase)); } - public static Task GetDscCapabilityAsync( + public static async Task GetDscCapabilityAsync( ILogger logger, IRunspaceInfo currentRunspace, - PsesInternalHost psesHost, - CancellationToken cancellationToken) + PsesInternalHost psesHost) { // DSC support is enabled only for Windows PowerShell. if ((currentRunspace.PowerShellVersionDetails.Version.Major >= 6) && (currentRunspace.RunspaceOrigin != RunspaceOrigin.DebuggedRunspace)) { - return Task.FromResult(null); + return null; } - DscBreakpointCapability getDscBreakpointCapabilityFunc(SMA.PowerShell pwsh, CancellationToken _) + if (!isDscInstalled.HasValue) { - PSInvocationSettings invocationSettings = new() - { - AddToHistory = false, - ErrorActionPreference = ActionPreference.Stop - }; - - PSModuleInfo dscModule = null; - try - { - dscModule = pwsh.AddCommand("Import-Module") - .AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1") - .AddParameter("PassThru") - .InvokeAndClear(invocationSettings) - .FirstOrDefault(); - } - catch (RuntimeException e) - { - logger.LogException("Could not load the DSC module!", e); - } - - if (dscModule == null) - { - logger.LogTrace("Side-by-side DSC module was not found."); - return null; - } - - logger.LogTrace("Side-by-side DSC module found, gathering DSC resource paths..."); - - // The module was loaded, add the breakpoint capability - DscBreakpointCapability capability = new(); - - pwsh.AddCommand("Microsoft.PowerShell.Utility\\Write-Host") - .AddArgument("Gathering DSC resource paths, this may take a while...") - .InvokeAndClear(invocationSettings); - - Collection resourcePaths = null; - try - { - // Get the list of DSC resource paths - resourcePaths = pwsh.AddCommand("Get-DscResource") - .AddCommand("Select-Object") - .AddParameter("ExpandProperty", "ParentPath") - .InvokeAndClear(invocationSettings); - } - catch (CmdletInvocationException e) - { - logger.LogException("Get-DscResource failed!", e); - } - - if (resourcePaths == null) - { - logger.LogTrace("No DSC resources found."); - return null; - } + PSCommand psCommand = new PSCommand() + .AddCommand("Import-Module") + .AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1") + .AddParameter("PassThru"); + + IReadOnlyList dscModule = + await psesHost.ExecutePSCommandAsync( + psCommand, + CancellationToken.None, + new PowerShellExecutionOptions { ThrowOnError = false }).ConfigureAwait(false); + + isDscInstalled = dscModule.Count > 0; + logger.LogTrace("Side-by-side DSC module found: " + isDscInstalled.Value); + } - capability.dscResourceRootPaths = resourcePaths.ToArray(); + if (isDscInstalled.Value) + { + PSCommand psCommand = new PSCommand() + .AddCommand("Get-DscResource") + .AddCommand("Select-Object") + .AddParameter("ExpandProperty", "ParentPath"); + + IReadOnlyList resourcePaths = + await psesHost.ExecutePSCommandAsync( + psCommand, + CancellationToken.None, + new PowerShellExecutionOptions { ThrowOnError = false } + ).ConfigureAwait(false); logger.LogTrace($"DSC resources found: {resourcePaths.Count}"); - - return capability; + return new DscBreakpointCapability + { + dscResourceRootPaths = resourcePaths.ToArray() + }; } - return psesHost.ExecuteDelegateAsync( - nameof(getDscBreakpointCapabilityFunc), - executionOptions: null, - getDscBreakpointCapabilityFunc, - cancellationToken); + return null; } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs index 173e5992b..506109b7d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs @@ -3,7 +3,6 @@ using System; using System.Management.Automation; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging @@ -34,6 +33,6 @@ internal interface IPowerShellDebugContext void Abort(); - Task GetDscBreakpointCapabilityAsync(CancellationToken cancellationToken); + Task GetDscBreakpointCapabilityAsync(); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 1d45c435d..f88aa38d5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -3,7 +3,6 @@ using System; using System.Management.Automation; -using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; @@ -81,10 +80,10 @@ public PowerShellDebugContext( public event Action DebuggerResuming; public event Action BreakpointUpdated; - public Task GetDscBreakpointCapabilityAsync(CancellationToken cancellationToken) + public Task GetDscBreakpointCapabilityAsync() { _psesHost.Runspace.ThrowCancelledIfUnusable(); - return _psesHost.CurrentRunspace.GetDscBreakpointCapabilityAsync(_logger, _psesHost, cancellationToken); + return _psesHost.CurrentRunspace.GetDscBreakpointCapabilityAsync(_logger, _psesHost); } // This is required by the PowerShell API so that remote debugging works. Without it, a diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs index d35d0f8a7..c955bac4c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs @@ -5,7 +5,6 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.Extensions.Logging; using System.Threading.Tasks; -using System.Threading; using System; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; @@ -86,14 +85,12 @@ public RunspaceInfo( public async Task GetDscBreakpointCapabilityAsync( ILogger logger, - PsesInternalHost psesHost, - CancellationToken cancellationToken) + PsesInternalHost psesHost) { return _dscBreakpointCapability ??= await DscBreakpointCapability.GetDscCapabilityAsync( logger, this, - psesHost, - cancellationToken) + psesHost) .ConfigureAwait(false); } } diff --git a/src/PowerShellEditorServices/Services/Template/TemplateService.cs b/src/PowerShellEditorServices/Services/Template/TemplateService.cs index f6dc76fa6..4de0de520 100644 --- a/src/PowerShellEditorServices/Services/Template/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/Template/TemplateService.cs @@ -72,14 +72,12 @@ public async Task ImportPlasterIfInstalledAsync() _logger.LogTrace("Checking if Plaster is installed..."); - PSObject moduleObject = (await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false))[0]; + IReadOnlyList moduleObject = + await _executionService.ExecutePSCommandAsync( + psCommand, CancellationToken.None).ConfigureAwait(false); - isPlasterInstalled = moduleObject != null; - string installedQualifier = - isPlasterInstalled.Value - ? string.Empty : "not "; - - _logger.LogTrace($"Plaster is {installedQualifier}installed!"); + isPlasterInstalled = moduleObject.Count > 0; + _logger.LogTrace("Plaster installed: " + isPlasterInstalled.Value); // Attempt to load plaster if (isPlasterInstalled.Value && !isPlasterLoaded) @@ -89,17 +87,13 @@ public async Task ImportPlasterIfInstalledAsync() psCommand = new PSCommand(); psCommand .AddCommand("Import-Module") - .AddParameter("ModuleInfo", (PSModuleInfo)moduleObject.ImmediateBaseObject) + .AddParameter("ModuleInfo", (PSModuleInfo)moduleObject[0].ImmediateBaseObject) .AddParameter("PassThru"); IReadOnlyList importResult = await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); isPlasterLoaded = importResult.Count > 0; - string loadedQualifier = - isPlasterInstalled.Value - ? "was" : "could not be"; - - _logger.LogTrace($"Plaster {loadedQualifier} loaded successfully!"); + _logger.LogTrace("Plaster loaded: " + isPlasterLoaded); } } From 67905945b9459f0fcffd0fe619e85b5a60933a1c Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 5 May 2023 12:32:59 -0700 Subject: [PATCH 278/327] Fix Emacs end-to-end test The way the class properties are accessed changed. --- test/emacs-test.el | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/emacs-test.el b/test/emacs-test.el index d703ff22d..378c792fc 100644 --- a/test/emacs-test.el +++ b/test/emacs-test.el @@ -59,9 +59,8 @@ (should (apply #'eglot--connect (eglot--guess-contact))) (should (eglot-current-server)) (let ((lsp (eglot-current-server))) - (should (string= (oref lsp project-nickname) "PowerShellEditorServices")) - (should (member 'powershell-mode (oref lsp major-modes))) - (should (string= (oref lsp language-id) "powershell"))) + (should (string= (eglot--project-nickname lsp) "PowerShellEditorServices")) + (should (member (cons 'powershell-mode "powershell") (eglot--languages lsp)))) (sleep-for 5) ; TODO: Wait for "textDocument/publishDiagnostics" instead (flymake-start) (goto-char (point-min)) From aa73a2162564e5533f0b53dde5686e9b00f5b871 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 8 May 2023 12:06:08 -0700 Subject: [PATCH 279/327] Move to `IReadOnlyList` from `IEnumerable` to prevent double enumerations Seems to be the easiest "clean and safe" way to handle this. --- .../DebugAdapter/BreakpointService.cs | 12 ++-- .../Services/DebugAdapter/DebugService.cs | 25 ++++----- .../Handlers/BreakpointHandlers.cs | 21 +++---- .../Debugging/DscBreakpointCapability.cs | 9 +-- .../Debugging/DebugServiceTests.cs | 56 +++++++++---------- 5 files changed, 56 insertions(+), 67 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs index 4f806ce39..748e21c6d 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs @@ -40,7 +40,7 @@ public BreakpointService( _debugStateService = debugStateService; } - public async Task> GetBreakpointsAsync() + public async Task> GetBreakpointsAsync() { if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace)) { @@ -52,14 +52,12 @@ public async Task> GetBreakpointsAsync() // Legacy behavior PSCommand psCommand = new PSCommand().AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint"); - IEnumerable breakpoints = await _executionService + return await _executionService .ExecutePSCommandAsync(psCommand, CancellationToken.None) .ConfigureAwait(false); - - return breakpoints.ToList(); } - public async Task> SetBreakpointsAsync(string escapedScriptPath, IEnumerable breakpoints) + public async Task> SetBreakpointsAsync(string escapedScriptPath, IReadOnlyList breakpoints) { if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace)) { @@ -147,7 +145,7 @@ public async Task> SetBreakpointsAsync(string esc return configuredBreakpoints; } - public async Task> SetCommandBreakpointsAsync(IEnumerable breakpoints) + public async Task> SetCommandBreakpointsAsync(IReadOnlyList breakpoints) { if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace)) { @@ -216,7 +214,7 @@ public async Task> SetCommandBreakpointsAs // If no PSCommand was created then there are no breakpoints to set. if (psCommand is not null) { - IEnumerable setBreakpoints = await _executionService + IReadOnlyList setBreakpoints = await _executionService .ExecutePSCommandAsync(psCommand, CancellationToken.None) .ConfigureAwait(false); configuredBreakpoints.AddRange(setBreakpoints.Select(CommandBreakpointDetails.Create)); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index a02775cc5..a92f05eed 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -130,9 +130,9 @@ public DebugService( /// BreakpointDetails for each breakpoint that will be set. /// If true, causes all existing breakpoints to be cleared before setting new ones. /// An awaitable Task that will provide details about the breakpoints that were set. - public async Task> SetLineBreakpointsAsync( + public async Task> SetLineBreakpointsAsync( ScriptFile scriptFile, - IEnumerable breakpoints, + IReadOnlyList breakpoints, bool clearExisting = true) { DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync().ConfigureAwait(false); @@ -146,7 +146,7 @@ public async Task> SetLineBreakpointsAsync( if (!_remoteFileManager.IsUnderRemoteTempPath(scriptPath)) { _logger.LogTrace($"Could not set breakpoints for local path '{scriptPath}' in a remote session."); - return Enumerable.Empty(); + return Array.Empty(); } scriptPath = _remoteFileManager.GetMappedPath(scriptPath, _psesHost.CurrentRunspace); @@ -154,7 +154,7 @@ public async Task> SetLineBreakpointsAsync( else if (temporaryScriptListingPath?.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase) == true) { _logger.LogTrace($"Could not set breakpoint on temporary script listing path '{scriptPath}'."); - return Enumerable.Empty(); + return Array.Empty(); } // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to @@ -182,25 +182,20 @@ public async Task> SetLineBreakpointsAsync( /// CommandBreakpointDetails for each command breakpoint that will be set. /// If true, causes all existing function breakpoints to be cleared before setting new ones. /// An awaitable Task that will provide details about the breakpoints that were set. - public async Task SetCommandBreakpointsAsync( - CommandBreakpointDetails[] breakpoints, + public async Task> SetCommandBreakpointsAsync( + IReadOnlyList breakpoints, bool clearExisting = true) { - CommandBreakpointDetails[] resultBreakpointDetails = null; - if (clearExisting) { // Flatten dictionary values into one list and remove them all. - IEnumerable existingBreakpoints = await _breakpointService.GetBreakpointsAsync().ConfigureAwait(false); + IReadOnlyList existingBreakpoints = await _breakpointService.GetBreakpointsAsync().ConfigureAwait(false); await _breakpointService.RemoveBreakpointsAsync(existingBreakpoints.OfType()).ConfigureAwait(false); } - if (breakpoints.Length > 0) - { - resultBreakpointDetails = (await _breakpointService.SetCommandBreakpointsAsync(breakpoints).ConfigureAwait(false)).ToArray(); - } - - return resultBreakpointDetails ?? Array.Empty(); + return breakpoints.Count > 0 + ? await _breakpointService.SetCommandBreakpointsAsync(breakpoints).ConfigureAwait(false) + : Array.Empty(); } /// diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs index 4efe9e6f2..4c99ff747 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs @@ -83,17 +83,17 @@ public async Task Handle(SetBreakpointsArguments request } // At this point, the source file has been verified as a PowerShell script. - IEnumerable breakpointDetails = request.Breakpoints + IReadOnlyList breakpointDetails = request.Breakpoints .Select((srcBreakpoint) => BreakpointDetails.Create( scriptFile.FilePath, srcBreakpoint.Line, srcBreakpoint.Column, srcBreakpoint.Condition, srcBreakpoint.HitCondition, - srcBreakpoint.LogMessage)); + srcBreakpoint.LogMessage)).ToList(); // If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints. - IEnumerable updatedBreakpointDetails = breakpointDetails; + IReadOnlyList updatedBreakpointDetails = breakpointDetails; if (!_debugStateService.NoDebug) { await _debugStateService.WaitForSetBreakpointHandleAsync().ConfigureAwait(false); @@ -125,23 +125,20 @@ await _debugService.SetLineBreakpointsAsync( public async Task Handle(SetFunctionBreakpointsArguments request, CancellationToken cancellationToken) { - CommandBreakpointDetails[] breakpointDetails = request.Breakpoints + IReadOnlyList breakpointDetails = request.Breakpoints .Select((funcBreakpoint) => CommandBreakpointDetails.Create( funcBreakpoint.Name, - funcBreakpoint.Condition)) - .ToArray(); + funcBreakpoint.Condition)).ToList(); // If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints. - CommandBreakpointDetails[] updatedBreakpointDetails = breakpointDetails; + IReadOnlyList updatedBreakpointDetails = breakpointDetails; if (!_debugStateService.NoDebug) { await _debugStateService.WaitForSetBreakpointHandleAsync().ConfigureAwait(false); try { - updatedBreakpointDetails = - await _debugService.SetCommandBreakpointsAsync( - breakpointDetails).ConfigureAwait(false); + updatedBreakpointDetails = await _debugService.SetCommandBreakpointsAsync(breakpointDetails).ConfigureAwait(false); } catch (Exception e) { @@ -156,9 +153,7 @@ await _debugService.SetCommandBreakpointsAsync( return new SetFunctionBreakpointsResponse { - Breakpoints = updatedBreakpointDetails - .Select(LspDebugUtils.CreateBreakpoint) - .ToArray() + Breakpoints = updatedBreakpointDetails.Select(LspDebugUtils.CreateBreakpoint).ToList() }; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 189fb35b3..d75274fa6 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -21,17 +21,18 @@ internal class DscBreakpointCapability private string[] dscResourceRootPaths = Array.Empty(); private readonly Dictionary breakpointsPerFile = new(); - public async Task> SetLineBreakpointsAsync( + public async Task> SetLineBreakpointsAsync( IInternalPowerShellExecutionService executionService, string scriptPath, - IEnumerable breakpoints) + IReadOnlyList breakpoints) { // We always get the latest array of breakpoint line numbers // so store that for future use - if (breakpoints.Any()) + int[] lineNumbers = breakpoints.Select(b => b.LineNumber).ToArray(); + if (lineNumbers.Length > 0) { // Set the breakpoints for this scriptPath - breakpointsPerFile[scriptPath] = breakpoints.Select(b => b.LineNumber).ToArray(); + breakpointsPerFile[scriptPath] = lineNumbers; } else { diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 0e58a0025..68db85f36 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -203,7 +203,7 @@ await debugService.SetCommandBreakpointsAsync( [MemberData(nameof(DebuggerAcceptsScriptArgsTestData))] public async Task DebuggerAcceptsScriptArgs(string[] args) { - IEnumerable breakpoints = await debugService.SetLineBreakpointsAsync( + IReadOnlyList breakpoints = await debugService.SetLineBreakpointsAsync( oddPathScriptFile, new[] { BreakpointDetails.Create(oddPathScriptFile.FilePath, 3) }).ConfigureAwait(true); @@ -257,15 +257,15 @@ public async Task DebuggerAcceptsScriptArgs(string[] args) [Fact] public async Task DebuggerSetsAndClearsFunctionBreakpoints() { - CommandBreakpointDetails[] breakpoints = await debugService.SetCommandBreakpointsAsync( + IReadOnlyList breakpoints = await debugService.SetCommandBreakpointsAsync( new[] { CommandBreakpointDetails.Create("Write-Host"), CommandBreakpointDetails.Create("Get-Date") }).ConfigureAwait(true); - Assert.Equal(2, breakpoints.Length); - Assert.Equal("Write-Host", breakpoints.ElementAt(0).Name); - Assert.Equal("Get-Date", breakpoints.ElementAt(1).Name); + Assert.Equal(2, breakpoints.Count); + Assert.Equal("Write-Host", breakpoints[0].Name); + Assert.Equal("Get-Date", breakpoints[1].Name); breakpoints = await debugService.SetCommandBreakpointsAsync( new[] { CommandBreakpointDetails.Create("Get-Host") }).ConfigureAwait(true); @@ -281,7 +281,7 @@ public async Task DebuggerSetsAndClearsFunctionBreakpoints() [Fact] public async Task DebuggerStopsOnFunctionBreakpoints() { - CommandBreakpointDetails[] breakpoints = await debugService.SetCommandBreakpointsAsync( + IReadOnlyList breakpoints = await debugService.SetCommandBreakpointsAsync( new[] { CommandBreakpointDetails.Create("Write-Host") }).ConfigureAwait(true); Task _ = ExecuteDebugFileAsync(); @@ -311,7 +311,7 @@ public async Task DebuggerStopsOnFunctionBreakpoints() [Fact] public async Task DebuggerSetsAndClearsLineBreakpoints() { - IEnumerable breakpoints = + IReadOnlyList breakpoints = await debugService.SetLineBreakpointsAsync( debugScriptFile, new[] { @@ -322,8 +322,8 @@ await debugService.SetLineBreakpointsAsync( IReadOnlyList confirmedBreakpoints = await GetConfirmedBreakpoints(debugScriptFile).ConfigureAwait(true); Assert.Equal(2, confirmedBreakpoints.Count); - Assert.Equal(5, breakpoints.ElementAt(0).LineNumber); - Assert.Equal(10, breakpoints.ElementAt(1).LineNumber); + Assert.Equal(5, breakpoints[0].LineNumber); + Assert.Equal(10, breakpoints[1].LineNumber); breakpoints = await debugService.SetLineBreakpointsAsync( debugScriptFile, @@ -331,7 +331,7 @@ await debugService.SetLineBreakpointsAsync( confirmedBreakpoints = await GetConfirmedBreakpoints(debugScriptFile).ConfigureAwait(true); Assert.Single(confirmedBreakpoints); - Assert.Equal(2, breakpoints.ElementAt(0).LineNumber); + Assert.Equal(2, breakpoints[0].LineNumber); await debugService.SetLineBreakpointsAsync( debugScriptFile, @@ -442,7 +442,7 @@ await debugService.SetLineBreakpointsAsync( [Fact] public async Task DebuggerProvidesMessageForInvalidConditionalBreakpoint() { - IEnumerable breakpoints = + IReadOnlyList breakpoints = await debugService.SetLineBreakpointsAsync( debugScriptFile, new[] { @@ -457,20 +457,20 @@ await debugService.SetLineBreakpointsAsync( }).ConfigureAwait(true); Assert.Single(breakpoints); - // Assert.Equal(5, breakpoints.ElementAt(0).LineNumber); - // Assert.True(breakpoints.ElementAt(0).Verified); - // Assert.Null(breakpoints.ElementAt(0).Message); - - Assert.Equal(10, breakpoints.ElementAt(0).LineNumber); - Assert.False(breakpoints.ElementAt(0).Verified); - Assert.NotNull(breakpoints.ElementAt(0).Message); - Assert.Contains("Unexpected token '-ez'", breakpoints.ElementAt(0).Message); + // Assert.Equal(5, breakpoints[0].LineNumber); + // Assert.True(breakpoints[0].Verified); + // Assert.Null(breakpoints[0].Message); + + Assert.Equal(10, breakpoints[0].LineNumber); + Assert.False(breakpoints[0].Verified); + Assert.NotNull(breakpoints[0].Message); + Assert.Contains("Unexpected token '-ez'", breakpoints[0].Message); } [Fact] public async Task DebuggerFindsParsableButInvalidSimpleBreakpointConditions() { - IEnumerable breakpoints = + IReadOnlyList breakpoints = await debugService.SetLineBreakpointsAsync( debugScriptFile, new[] { @@ -478,15 +478,15 @@ await debugService.SetLineBreakpointsAsync( BreakpointDetails.Create(debugScriptFile.FilePath, 7, column: null, condition: "$i > 100") }).ConfigureAwait(true); - Assert.Equal(2, breakpoints.Count()); - Assert.Equal(5, breakpoints.ElementAt(0).LineNumber); - Assert.False(breakpoints.ElementAt(0).Verified); - Assert.Contains("Use '-eq' instead of '=='", breakpoints.ElementAt(0).Message); + Assert.Equal(2, breakpoints.Count); + Assert.Equal(5, breakpoints[0].LineNumber); + Assert.False(breakpoints[0].Verified); + Assert.Contains("Use '-eq' instead of '=='", breakpoints[0].Message); - Assert.Equal(7, breakpoints.ElementAt(1).LineNumber); - Assert.False(breakpoints.ElementAt(1).Verified); - Assert.NotNull(breakpoints.ElementAt(1).Message); - Assert.Contains("Use '-gt' instead of '>'", breakpoints.ElementAt(1).Message); + Assert.Equal(7, breakpoints[1].LineNumber); + Assert.False(breakpoints[1].Verified); + Assert.NotNull(breakpoints[1].Message); + Assert.Contains("Use '-gt' instead of '>'", breakpoints[1].Message); } [Fact] From 6dca0bdd618dd2f8d0520e260f8f61b5d4799f69 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 10 May 2023 11:39:30 -0700 Subject: [PATCH 280/327] Only get aliases when looking at function symbols Instead of every symbol reference lookup. Though we should probably still do this less often, like in the `prompt`. --- .../Services/Symbols/SymbolsService.cs | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 457b1d651..99f8bbbc8 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -137,11 +137,6 @@ public IEnumerable FindSymbolsInFile(ScriptFile scriptFile) // asserting we should use a giant nested ternary. private static string[] GetIdentifiers(string symbolName, SymbolType symbolType, CommandHelpers.AliasMap aliases) { - if (symbolType is not SymbolType.Function) - { - return new[] { symbolName }; - } - if (!aliases.CmdletToAliases.TryGetValue(symbolName, out List foundAliasList)) { return new[] { symbolName }; @@ -165,22 +160,31 @@ public async Task> ScanForReferencesOfSymbolAsync( return Enumerable.Empty(); } - // TODO: Should we handle aliases at a lower level? - CommandHelpers.AliasMap aliases = await CommandHelpers.GetAliasesAsync( - _executionService, - cancellationToken).ConfigureAwait(false); + // We want to handle aliases for functions, but we only want to do the work of getting + // the aliases when we must. We can't cache the alias list on first run else we won't + // support newly defined aliases. + string[] allIdentifiers; + if (symbol.Type is SymbolType.Function) + { + CommandHelpers.AliasMap aliases = await CommandHelpers.GetAliasesAsync( + _executionService, + cancellationToken).ConfigureAwait(false); - string targetName = symbol.Id; - if (symbol.Type is SymbolType.Function - && aliases.AliasToCmdlets.TryGetValue(symbol.Id, out string aliasDefinition)) + string targetName = symbol.Id; + if (aliases.AliasToCmdlets.TryGetValue(symbol.Id, out string aliasDefinition)) + { + targetName = aliasDefinition; + } + allIdentifiers = GetIdentifiers(targetName, symbol.Type, aliases); + } + else { - targetName = aliasDefinition; + allIdentifiers = new[] { symbol.Id }; } await ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false); List symbols = new(); - string[] allIdentifiers = GetIdentifiers(targetName, symbol.Type, aliases); foreach (ScriptFile file in _workspaceService.GetOpenedFiles()) { From 95715ed389ebf704627167ea1a846aee85b1fafa Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 12 May 2023 10:48:03 -0700 Subject: [PATCH 281/327] Hide artificial pipeline from debugger Same fix as applied in https://github.com/PowerShell/PSReadLine/pull/3629 --- .../PowerShell/Host/PsesInternalHost.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 419aebf8c..ef8385bd3 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -1207,7 +1207,18 @@ private Runspace CreateInitialRunspace(InitialSessionState initialSessionState) return runspace; } - // NOTE: This token is received from PSReadLine, and it _is_ the ReadKey cancellation token! + /// + /// This delegate is handed to PSReadLine and overrides similar logic within its `ReadKey` + /// method. Essentially we're replacing PowerShell's `OnIdle` handler since the PowerShell + /// engine isn't idle when we're sitting in PSReadLine's `ReadKey` loop. In our case we also + /// use this idle time to process queued tasks by executing those that can run in the + /// background, and canceling the foreground task if a queued tasks requires the foreground. + /// Finally, if and only if we have to, we run an artificial pipeline to force PowerShell's + /// own event processing. + /// + /// + /// This token is received from PSReadLine, and it is the ReadKey cancellation token! + /// internal void OnPowerShellIdle(CancellationToken idleCancellationToken) { IReadOnlyList eventSubscribers = _mainRunspaceEngineIntrinsics.Events.Subscribers; @@ -1257,10 +1268,15 @@ internal void OnPowerShellIdle(CancellationToken idleCancellationToken) // We didn't end up executing anything in the background, // so we need to run a small artificial pipeline instead - // to force event processing + // to force event processing. if (runPipelineForEventProcessing) { - InvokePSCommand(new PSCommand().AddScript("0", useLocalScope: true), executionOptions: null, CancellationToken.None); + InvokePSCommand( + new PSCommand().AddScript( + "[System.Diagnostics.DebuggerHidden()]param() 0", + useLocalScope: true), + executionOptions: null, + CancellationToken.None); } } From 9974c7dcca06899baf6693655897827f384fc0d7 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 12 May 2023 10:49:31 -0700 Subject: [PATCH 282/327] Fix bug where artificial pipeline was erroneously run The previous logic could accidentally set the boolean flag to `true`, causing the pipeline to be run when it was not necessary. --- .../Services/PowerShell/Host/PsesInternalHost.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index ef8385bd3..018803983 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -1261,7 +1261,12 @@ internal void OnPowerShellIdle(CancellationToken idleCancellationToken) // If we're executing a PowerShell task, we don't need to run an extra pipeline // later for events. - runPipelineForEventProcessing = task is not ISynchronousPowerShellTask; + if (task is ISynchronousPowerShellTask) + { + // We don't ever want to set this to true here, just skip if it had + // previously been set true. + runPipelineForEventProcessing = false; + } ExecuteTaskSynchronously(task, cancellationScope.CancellationToken); } } From f4faebe9a4f91dc357d19b4209366028d0dd19d6 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 12 May 2023 10:57:49 -0700 Subject: [PATCH 283/327] Cache the `PSCommand` for the prompt Since it doesn't change and we're always using it. --- .../Services/PowerShell/Host/PsesInternalHost.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 018803983..4b03e1749 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -35,6 +35,8 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns { internal const string DefaultPrompt = "> "; + private static readonly PSCommand s_promptCommand = new PSCommand().AddCommand("prompt"); + private static readonly PropertyInfo s_scriptDebuggerTriggerObjectProperty; private readonly ILoggerFactory _loggerFactory; @@ -1026,10 +1028,8 @@ internal string GetPrompt(CancellationToken cancellationToken) string prompt = DefaultPrompt; try { - // TODO: Should we cache PSCommands like this as static members? - PSCommand command = new PSCommand().AddCommand("prompt"); IReadOnlyList results = InvokePSCommand( - command, + s_promptCommand, executionOptions: new PowerShellExecutionOptions { ThrowOnError = false }, cancellationToken); From ec81e75f35967983d5c41b4de13270011f3bb1b7 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 12 May 2023 11:12:48 -0700 Subject: [PATCH 284/327] Hide more internal PowerShell scripts from debugger --- .../EditorServicesLoader.cs | 4 +++- .../Services/DebugAdapter/DebugService.cs | 6 +++--- .../Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs | 2 +- .../Services/PowerShell/Context/PowerShellVersionDetails.cs | 2 +- .../Services/PowerShell/Handlers/ExpandAliasHandler.cs | 2 +- .../Services/PowerShell/Handlers/ShowHelpHandler.cs | 2 ++ .../Services/PowerShell/Runspace/SessionDetails.cs | 2 +- 7 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index d5772bb59..5a7166eef 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -350,7 +350,9 @@ private void LogHostInformation() private static string GetPSOutputEncoding() { using SMA.PowerShell pwsh = SMA.PowerShell.Create(); - return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke()[0]; + return pwsh.AddScript( + "[System.Diagnostics.DebuggerHidden()]param() $OutputEncoding.EncodingName", + useLocalScope: true).Invoke()[0]; } // TODO: Deduplicate this with VersionUtils. diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index a92f05eed..0439f4484 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -367,7 +367,7 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str // Evaluate the expression to get back a PowerShell object from the expression string. // This may throw, in which case the exception is propagated to the caller - PSCommand evaluateExpressionCommand = new PSCommand().AddScript(value); + PSCommand evaluateExpressionCommand = new PSCommand().AddScript($"[System.Diagnostics.DebuggerHidden()]param() {value}"); IReadOnlyList expressionResults = await _executionService.ExecutePSCommandAsync(evaluateExpressionCommand, CancellationToken.None).ConfigureAwait(false); if (expressionResults.Count == 0) { @@ -500,7 +500,7 @@ public async Task EvaluateExpressionAsync( bool writeResultAsOutput, CancellationToken cancellationToken) { - PSCommand command = new PSCommand().AddScript(expressionString); + PSCommand command = new PSCommand().AddScript($"[System.Diagnostics.DebuggerHidden()]param() {expressionString}"); IReadOnlyList results; try { @@ -799,7 +799,7 @@ private async Task FetchStackFramesAsync(string scriptNameOverride) // PSObject is used here instead of the specific type because we get deserialized // objects from remote sessions and want a common interface. - PSCommand psCommand = new PSCommand().AddScript($"[Collections.ArrayList]{callStackVarName} = @(); {getPSCallStack}; {returnSerializedIfInRemoteRunspace}"); + PSCommand psCommand = new PSCommand().AddScript($"[System.Diagnostics.DebuggerHidden()]param() [Collections.ArrayList]{callStackVarName} = @(); {getPSCallStack}; {returnSerializedIfInRemoteRunspace}"); IReadOnlyList results = await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); IEnumerable callStack = isRemoteRunspace diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs index 2c6c47364..cd91809fd 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs @@ -48,7 +48,7 @@ public async Task Handle(EvaluateRequestArguments request, if (isFromRepl) { await _executionService.ExecutePSCommandAsync( - new PSCommand().AddScript(request.Expression), + new PSCommand().AddScript($"[System.Diagnostics.DebuggerHidden()]param() {request.Expression}"), cancellationToken, new PowerShellExecutionOptions { WriteOutputToHost = true, ThrowOnError = false, AddToHistory = true }).HandleErrorsAsync(_logger).ConfigureAwait(false); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs index ec5b8711f..d236f2327 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs @@ -55,7 +55,7 @@ public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerSh try { Hashtable psVersionTable = pwsh - .AddScript("$PSVersionTable", useLocalScope: true) + .AddScript("[System.Diagnostics.DebuggerHidden()]param() $PSVersionTable", useLocalScope: true) .InvokeAndClear() .FirstOrDefault(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs index 69c39450d..8390bbdf5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs @@ -33,7 +33,7 @@ public async Task Handle(ExpandAliasParams request, Cancellat { const string script = @" function __Expand-Alias { - + [System.Diagnostics.DebuggerHidden()] param($targetScript) [ref]$errors=$null diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs index ea18de73c..43fcdfca2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs @@ -27,7 +27,9 @@ internal class ShowHelpHandler : IShowHelpHandler public async Task Handle(ShowHelpParams request, CancellationToken cancellationToken) { + // TODO: Refactor to not rerun the function definition every time. const string CheckHelpScript = @" + [System.Diagnostics.DebuggerHidden()] [CmdletBinding()] param ( [String]$CommandName diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs index 4eeaf165c..53be32e13 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs @@ -27,7 +27,7 @@ public static SessionDetails GetFromPowerShell(PowerShell pwsh) { Hashtable detailsObject = pwsh .AddScript( - $"@{{ '{Property_ComputerName}' = if ([Environment]::MachineName) {{[Environment]::MachineName}} else {{'localhost'}}; '{Property_ProcessId}' = $PID; '{Property_InstanceId}' = $host.InstanceId }}", + $"[System.Diagnostics.DebuggerHidden()]param() @{{ '{Property_ComputerName}' = if ([Environment]::MachineName) {{[Environment]::MachineName}} else {{'localhost'}}; '{Property_ProcessId}' = $PID; '{Property_InstanceId}' = $host.InstanceId }}", useLocalScope: true) .InvokeAndClear() .FirstOrDefault(); From 9d2a151f121554b03ab7e60f482b352888355eaf Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 12 May 2023 12:07:57 -0700 Subject: [PATCH 285/327] Fix disappearing output bug by resetting `TranscribeOnly` What a bug! In PowerShell 5.1 our artificial pipeline for event handling (among other possible PowerShell tasks) could cause the output to just disappear. Turns out a bug in 5.1 with the transcript was being triggered. Scripts without `Out-Default` would get it appended with `-TranscribeOnly True` in order for the transcription to happen...but the `TranscribeOnly` field could erroneously not be set back to `false`! This would cause all output to just "disappear." It was fixed in PowerShell 7, but we still have to work around it for PowerShell 5.1 by resetting it. --- ...ditorServicesConsolePSHostUserInterface.cs | 31 ++++++++++++++++++- .../PowerShell/Host/PsesInternalHost.cs | 14 ++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs index fb6d2c861..badcaef6b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs @@ -7,8 +7,10 @@ using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Host; +using System.Reflection; using System.Security; using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { @@ -16,12 +18,28 @@ internal class EditorServicesConsolePSHostUserInterface : PSHostUserInterface, I { private readonly PSHostUserInterface _underlyingHostUI; + private static readonly Action s_setTranscribeOnlyDelegate; + /// /// We use a ConcurrentDictionary because ConcurrentHashSet does not exist, hence the value /// is never actually used, and `WriteProgress` must be thread-safe. /// private readonly ConcurrentDictionary<(long, int), object> _currentProgressRecords = new(); + static EditorServicesConsolePSHostUserInterface() + { + if (VersionUtils.IsPS5) + { + PropertyInfo transcribeOnlyProperty = typeof(PSHostUserInterface) + .GetProperty("TranscribeOnly", BindingFlags.NonPublic | BindingFlags.Instance); + + MethodInfo transcribeOnlySetMethod = transcribeOnlyProperty.GetSetMethod(nonPublic: true); + + s_setTranscribeOnlyDelegate = (Action)Delegate.CreateDelegate( + typeof(Action), transcribeOnlySetMethod); + } + } + public EditorServicesConsolePSHostUserInterface( ILoggerFactory loggerFactory, PSHostUserInterface underlyingHostUI) @@ -70,7 +88,7 @@ public override void WriteProgress(long sourceId, ProgressRecord record) _underlyingHostUI.WriteProgress(sourceId, record); } - public void ResetProgress() + internal void ResetProgress() { // Mark all processed progress records as completed. foreach ((long sourceId, int activityId) in _currentProgressRecords.Keys) @@ -87,6 +105,17 @@ public void ResetProgress() // TODO: Maybe send the OSC sequence to turn off progress indicator. } + // This works around a bug in PowerShell 5.1 (that was later fixed) where a running + // transcription could cause output to disappear since the `TranscribeOnly` property was + // accidentally not reset to false. + internal void DisableTranscribeOnly() + { + if (VersionUtils.IsPS5) + { + s_setTranscribeOnlyDelegate(_underlyingHostUI, false); + } + } + public override void WriteVerboseLine(string message) => _underlyingHostUI.WriteVerboseLine(message); public override void WriteWarningLine(string message) => _underlyingHostUI.WriteWarningLine(message); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 4b03e1749..32a728291 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -476,7 +476,19 @@ public void InvokeDelegate(string representation, ExecutionOptions executionOpti public IReadOnlyList InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) { SynchronousPowerShellTask task = new(_logger, this, psCommand, executionOptions, cancellationToken); - return task.ExecuteAndGetResult(cancellationToken); + try + { + return task.ExecuteAndGetResult(cancellationToken); + } + finally + { + // At the end of each PowerShell command we need to reset PowerShell 5.1's + // `TranscribeOnly` property to avoid a bug where output disappears. + if (UI is EditorServicesConsolePSHostUserInterface ui) + { + ui.DisableTranscribeOnly(); + } + } } public void InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) => InvokePSCommand(psCommand, executionOptions, cancellationToken); From 526f3aa3760f87b0e50301ba266c8d4c2fa6210d Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 12 May 2023 13:08:26 -0700 Subject: [PATCH 286/327] Update CHANGELOG for `v3.8.5` --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a83988a8..7c0268a5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # PowerShell Editor Services Release History +## v3.8.5 +### Friday, May 12, 2023 + +- 🐛 📟 [vscode-powershell #3991](https://github.com/PowerShell/PowerShellEditorServices/pull/2023) - Fix disappearing output in PowerShell 5.1. +- 🐛 🔍 [PowerShellEditorServices #2020](https://github.com/PowerShell/PowerShellEditorServices/pull/2020) - Fix repeated failure to load DSC module. + ## v3.8.4 ### Tuesday, April 25, 2023 From b9a3d373802f9828d91f397e2cbeb916adf0ebbd Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 12 May 2023 13:08:26 -0700 Subject: [PATCH 287/327] Bump version to `v3.8.5` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 59dbac420..db9df9e48 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.8.4 + 3.8.5 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index da23ef4bf..51617a1e3 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.8.4' +ModuleVersion = '3.8.5' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 2c3e42ca0cddfcecd8bd5fd64ee221e532786a42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 16:12:54 -0700 Subject: [PATCH 288/327] Bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.0 (#2027) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.5.0 to 17.6.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.5.0...v17.6.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 429720042..222250cf6 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index de4666633..8e5a09a3a 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From 4ff3078a2df40f4edb62a14665d61b9487e7d9c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 16:26:13 -0700 Subject: [PATCH 289/327] Bump Serilog.Extensions.Logging from 3.1.0 to 7.0.0 (#2024) Bumps [Serilog.Extensions.Logging](https://github.com/serilog/serilog-extensions-logging) from 3.1.0 to 7.0.0. - [Release notes](https://github.com/serilog/serilog-extensions-logging/releases) - [Commits](https://github.com/serilog/serilog-extensions-logging/compare/v3.1.0...v7.0.0) --- updated-dependencies: - dependency-name: Serilog.Extensions.Logging dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index ef9b2696b..99cb61559 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -1,4 +1,4 @@ - + @@ -30,7 +30,7 @@ - + From 759a88fc373731ab52da94dc72ed30457eed39ae Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Mon, 22 May 2023 17:03:32 -0700 Subject: [PATCH 290/327] Fix the `TranscribeOnly` bug (take two) (#2026) We were using our own UI, not the byzantine internal UI where it actually needed to be fixed. Whole lot of reflection. Also had to fix our `CoreCLR` compiler constant. Co-authored-by: Patrick Meinecke --- .editorconfig | 2 + .../PowerShellEditorServices.Hosting.csproj | 2 +- .../PowerShellEditorServices.VSCode.csproj | 6 +- .../PowerShellEditorServices.csproj | 4 + .../Execution/SynchronousPowerShellTask.cs | 12 ++- ...ditorServicesConsolePSHostUserInterface.cs | 29 ------- .../PowerShell/Host/PsesInternalHost.cs | 82 ++++++++++++++++--- .../PowerShellEditorServices.Test.E2E.csproj | 4 + .../PowerShellEditorServices.Test.csproj | 8 +- 9 files changed, 97 insertions(+), 52 deletions(-) diff --git a/.editorconfig b/.editorconfig index 1cb572ef7..79e4abe9a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -205,6 +205,8 @@ dotnet_diagnostic.IDE0052.severity = error dotnet_diagnostic.IDE0053.severity = error # IDE0054: Use compound assignment dotnet_diagnostic.IDE0054.severity = error +# IDE0059: Unnecessary assignment of a value +dotnet_diagnostic.IDE0059.severity = error # IDE0063: Use simple 'using' statement dotnet_diagnostic.IDE0063.severity = error # IDE0066: Use switch expression diff --git a/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj b/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj index 118b25d73..5c31f0758 100644 --- a/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj +++ b/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj @@ -6,7 +6,7 @@ Microsoft.PowerShell.EditorServices.Hosting - + $(DefineConstants);CoreCLR diff --git a/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj b/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj index b6022679f..2624f1ddf 100644 --- a/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj +++ b/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj @@ -6,7 +6,11 @@ Provides added functionality to PowerShell Editor Services for the Visual Studio Code editor. netstandard2.0 Microsoft.PowerShell.EditorServices.VSCode - Debug;Release;CoreCLR + Debug;Release + + + + $(DefineConstants);CoreCLR diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 99cb61559..315bf696e 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -9,6 +9,10 @@ Debug;Release + + $(DefineConstants);CoreCLR + + <_Parameter1>Microsoft.PowerShell.EditorServices.Hosting diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 5256f5ad9..b96beae9e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -105,6 +105,12 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok if (PowerShellExecutionOptions.WriteOutputToHost) { _psCommand.AddOutputCommand(); + + // Fix the transcription bug! + if (!_pwsh.Runspace.RunspaceIsRemote) + { + _psesHost.DisableTranscribeOnly(); + } } cancellationToken.Register(CancelNormalExecution); @@ -148,7 +154,7 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok if (e is PSRemotingTransportException) { _ = System.Threading.Tasks.Task.Run( - () => _psesHost.UnwindCallStack(), + _psesHost.UnwindCallStack, CancellationToken.None) .HandleErrorsAsync(_logger); @@ -189,8 +195,6 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationToken) { - // TODO: How much of this method can we remove now that it only processes PowerShell's - // intrinsic debugger commands? cancellationToken.Register(CancelDebugExecution); PSDataCollection outputCollection = new(); @@ -247,7 +251,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT if (e is PSRemotingTransportException) { _ = System.Threading.Tasks.Task.Run( - () => _psesHost.UnwindCallStack(), + _psesHost.UnwindCallStack, CancellationToken.None) .HandleErrorsAsync(_logger); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs index badcaef6b..55ab29e7c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs @@ -7,10 +7,8 @@ using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Host; -using System.Reflection; using System.Security; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { @@ -18,28 +16,12 @@ internal class EditorServicesConsolePSHostUserInterface : PSHostUserInterface, I { private readonly PSHostUserInterface _underlyingHostUI; - private static readonly Action s_setTranscribeOnlyDelegate; - /// /// We use a ConcurrentDictionary because ConcurrentHashSet does not exist, hence the value /// is never actually used, and `WriteProgress` must be thread-safe. /// private readonly ConcurrentDictionary<(long, int), object> _currentProgressRecords = new(); - static EditorServicesConsolePSHostUserInterface() - { - if (VersionUtils.IsPS5) - { - PropertyInfo transcribeOnlyProperty = typeof(PSHostUserInterface) - .GetProperty("TranscribeOnly", BindingFlags.NonPublic | BindingFlags.Instance); - - MethodInfo transcribeOnlySetMethod = transcribeOnlyProperty.GetSetMethod(nonPublic: true); - - s_setTranscribeOnlyDelegate = (Action)Delegate.CreateDelegate( - typeof(Action), transcribeOnlySetMethod); - } - } - public EditorServicesConsolePSHostUserInterface( ILoggerFactory loggerFactory, PSHostUserInterface underlyingHostUI) @@ -105,17 +87,6 @@ internal void ResetProgress() // TODO: Maybe send the OSC sequence to turn off progress indicator. } - // This works around a bug in PowerShell 5.1 (that was later fixed) where a running - // transcription could cause output to disappear since the `TranscribeOnly` property was - // accidentally not reset to false. - internal void DisableTranscribeOnly() - { - if (VersionUtils.IsPS5) - { - s_setTranscribeOnlyDelegate(_underlyingHostUI, false); - } - } - public override void WriteVerboseLine(string message) => _underlyingHostUI.WriteVerboseLine(message); public override void WriteWarningLine(string message) => _underlyingHostUI.WriteWarningLine(message); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 32a728291..a94406e4c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -39,6 +39,31 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns private static readonly PropertyInfo s_scriptDebuggerTriggerObjectProperty; +#if !CoreCLR + /// + /// To workaround a horrid bug where the `TranscribeOnly` field of the PSHostUserInterface + /// can accidentally remain true, we have to use a bunch of reflection so that can reset it to false. (This was fixed in PowerShell + /// 7.) Note that it must be the internal UI instance, not our own UI instance, otherwise + /// this would be easier. Because of the amount of reflection involved, we contain it to + /// only PowerShell 5.1 at compile-time, and we have to set this up in this class, not because that's templated, making statics practically + /// useless. method calls when necessary. + /// See: https://github.com/PowerShell/PowerShell/pull/3436 + /// + [ThreadStatic] // Because we can re-use it, but only once per instance of PSES. + private static PSHostUserInterface s_internalPSHostUserInterface; + + private static readonly Func s_getTranscribeOnlyDelegate; + + private static readonly Action s_setTranscribeOnlyDelegate; + + private static readonly PropertyInfo s_executionContextProperty; + + private static readonly PropertyInfo s_internalHostProperty; +#endif + private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; @@ -104,6 +129,27 @@ static PsesInternalHost() s_scriptDebuggerTriggerObjectProperty = scriptDebuggerType.GetProperty( "TriggerObject", BindingFlags.Instance | BindingFlags.NonPublic); + +#if !CoreCLR + PropertyInfo transcribeOnlyProperty = typeof(PSHostUserInterface) + .GetProperty("TranscribeOnly", BindingFlags.NonPublic | BindingFlags.Instance); + + MethodInfo transcribeOnlyGetMethod = transcribeOnlyProperty.GetGetMethod(nonPublic: true); + + s_getTranscribeOnlyDelegate = (Func)Delegate.CreateDelegate( + typeof(Func), transcribeOnlyGetMethod); + + MethodInfo transcribeOnlySetMethod = transcribeOnlyProperty.GetSetMethod(nonPublic: true); + + s_setTranscribeOnlyDelegate = (Action)Delegate.CreateDelegate( + typeof(Action), transcribeOnlySetMethod); + + s_executionContextProperty = typeof(System.Management.Automation.Runspaces.Runspace) + .GetProperty("ExecutionContext", BindingFlags.NonPublic | BindingFlags.Instance); + + s_internalHostProperty = s_executionContextProperty.PropertyType + .GetProperty("InternalHost", BindingFlags.NonPublic | BindingFlags.Instance); +#endif } public PsesInternalHost( @@ -476,19 +522,7 @@ public void InvokeDelegate(string representation, ExecutionOptions executionOpti public IReadOnlyList InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) { SynchronousPowerShellTask task = new(_logger, this, psCommand, executionOptions, cancellationToken); - try - { - return task.ExecuteAndGetResult(cancellationToken); - } - finally - { - // At the end of each PowerShell command we need to reset PowerShell 5.1's - // `TranscribeOnly` property to avoid a bug where output disappears. - if (UI is EditorServicesConsolePSHostUserInterface ui) - { - ui.DisableTranscribeOnly(); - } - } + return task.ExecuteAndGetResult(cancellationToken); } public void InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) => InvokePSCommand(psCommand, executionOptions, cancellationToken); @@ -507,6 +541,28 @@ public void InvokePSDelegate(string representation, ExecutionOptions executionOp internal void AddToHistory(string historyEntry) => _readLineProvider.ReadLine.AddToHistory(historyEntry); + // This works around a bug in PowerShell 5.1 (that was later fixed) where a running + // transcription could cause output to disappear since the `TranscribeOnly` property was + // accidentally not reset to false. +#pragma warning disable CA1822 // Warning to make it static when it's empty for CoreCLR. + internal void DisableTranscribeOnly() +#pragma warning restore CA1822 + { +#if !CoreCLR + // To fix the TranscribeOnly bug, we have to get the internal UI, which involves a lot + // of reflection since we can't always just use PowerShell to execute `$Host.UI`. + s_internalPSHostUserInterface ??= + (s_internalHostProperty.GetValue( + s_executionContextProperty.GetValue(CurrentPowerShell.Runspace)) + as PSHost).UI; + + if (s_getTranscribeOnlyDelegate(s_internalPSHostUserInterface)) + { + s_setTranscribeOnlyDelegate(s_internalPSHostUserInterface, false); + } +#endif + } + internal Task LoadHostProfilesAsync(CancellationToken cancellationToken) { // NOTE: This is a special task run on startup! diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 222250cf6..a39126dd7 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -6,6 +6,10 @@ false + + $(DefineConstants);CoreCLR + + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 8e5a09a3a..4674803f5 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -7,6 +7,10 @@ x64 + + $(DefineConstants);CoreCLR + + @@ -27,10 +31,6 @@ - - $(DefineConstants);CoreCLR - - From c43c4a9bb5ff8103e9398f2aebfe33440cfcd319 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Tue, 23 May 2023 13:37:20 -0700 Subject: [PATCH 291/327] Ignore cancellation of completion requests (#2028) Resolves #4582. --- .../Services/TextDocument/Handlers/CompletionHandler.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs index a8144d360..29e36ce25 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs @@ -82,6 +82,11 @@ public override async Task Handle(CompletionParams request, Canc // (typically files) returned by the space completion return new CompletionList(completionResults, isIncomplete || request?.Context?.TriggerCharacter is " "); } + // Ignore canceled requests (logging will pollute the output). + catch (TaskCanceledException) + { + return new CompletionList(isIncomplete: true); + } // We can't do anything about completions failing. catch (Exception e) { From 976c5ae0b415b34f7fe14b63463ec525c3fc3a76 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 23 May 2023 15:06:59 -0700 Subject: [PATCH 292/327] Update CHANGELOG for `v3.8.6` --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c0268a5b..1472d7d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # PowerShell Editor Services Release History +## v3.8.6 +### Tuesday, May 23, 2023 + +- 🐛 💭 [vscode-powershell #4582](https://github.com/PowerShell/PowerShellEditorServices/pull/2028) - Ignore cancellation of completion requests. +- 🐛 📟 [vscode-powershell #3991](https://github.com/PowerShell/PowerShellEditorServices/pull/2026) - Fix the `TranscribeOnly` bug (take two). + ## v3.8.5 ### Friday, May 12, 2023 From e04d5a919265f3064764cc5a2c6eef0ed02c57ab Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 23 May 2023 15:06:59 -0700 Subject: [PATCH 293/327] Bump version to `v3.8.6` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index db9df9e48..6917f6ddf 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.8.5 + 3.8.6 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 51617a1e3..42986d423 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.8.5' +ModuleVersion = '3.8.6' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 3451004ca97ddedf9cd4a42f50892a2876d82020 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Tue, 30 May 2023 14:34:16 -0700 Subject: [PATCH 294/327] Fix the `TranscribeOnly` bug (take three) (#2031) --- .../EditorServicesLoader.cs | 24 ++-------------- .../PowerShellEditorServices.VSCode.csproj | 4 --- .../PowerShellEditorServices.csproj | 9 ++++-- .../Execution/SynchronousPowerShellTask.cs | 11 +++++++- .../PowerShell/Host/PsesInternalHost.cs | 28 ++++++++++++------- .../Utility/VersionUtils.cs | 26 +---------------- .../PowerShellEditorServices.Test.E2E.csproj | 4 --- 7 files changed, 37 insertions(+), 69 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index 5a7166eef..f2da0eb4e 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -32,6 +32,7 @@ namespace Microsoft.PowerShell.EditorServices.Hosting public sealed class EditorServicesLoader : IDisposable { #if !CoreCLR + // TODO: Well, we're saying we need 4.8 here but we're building for 4.6.2... // See https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed private const int Net48Version = 528040; @@ -342,7 +343,7 @@ private void LogHostInformation() _logger.Log(PsesLogLevel.Verbose, $@" == Environment Details == - OS description: {RuntimeInformation.OSDescription} - - OS architecture: {GetOSArchitecture()} + - OS architecture: {RuntimeInformation.OSArchitecture} - Process bitness: {(Environment.Is64BitProcess ? "64" : "32")} "); } @@ -355,27 +356,6 @@ private static string GetPSOutputEncoding() useLocalScope: true).Invoke()[0]; } - // TODO: Deduplicate this with VersionUtils. - private static string GetOSArchitecture() - { -#if CoreCLR - if (Environment.OSVersion.Platform != PlatformID.Win32NT) - { - return RuntimeInformation.OSArchitecture.ToString(); - } -#endif - - // If on win7 (version 6.1.x), avoid System.Runtime.InteropServices.RuntimeInformation - if (Environment.OSVersion.Version < new Version(6, 2)) - { - return Environment.Is64BitProcess - ? "X64" - : "X86"; - } - - return RuntimeInformation.OSArchitecture.ToString(); - } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "Checking user-defined configuration")] private void ValidateConfiguration() { diff --git a/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj b/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj index 2624f1ddf..bea365c3b 100644 --- a/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj +++ b/src/PowerShellEditorServices.VSCode/PowerShellEditorServices.VSCode.csproj @@ -9,10 +9,6 @@ Debug;Release - - $(DefineConstants);CoreCLR - - 1591,1573,1572 diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 315bf696e..a344b3401 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -9,9 +9,12 @@ Debug;Release - - $(DefineConstants);CoreCLR - + diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index b96beae9e..c7a983bd8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -106,7 +106,8 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok { _psCommand.AddOutputCommand(); - // Fix the transcription bug! + // Fix the transcription bug! Here we're fixing immediately before the invocation of + // our command, that has had `Out-Default` added to it. if (!_pwsh.Runspace.RunspaceIsRemote) { _psesHost.DisableTranscribeOnly(); @@ -282,6 +283,14 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT { _pwsh.Streams.Error.Clear(); } + + // Fix the transcription bug! Since we don't depend on `Out-Default` for + // `ExecuteDebugger`, we fix the bug here so the original invocation (before the + // script is executed) is good to go. + if (!_pwsh.Runspace.RunspaceIsRemote) + { + _psesHost.DisableTranscribeOnly(); + } } _psesHost.DebugContext.ProcessDebuggerResult(debuggerResult); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index a94406e4c..a737cf1d9 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -39,7 +39,6 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns private static readonly PropertyInfo s_scriptDebuggerTriggerObjectProperty; -#if !CoreCLR /// /// To workaround a horrid bug where the `TranscribeOnly` field of the PSHostUserInterface /// can accidentally remain true, we have to use a bunch of reflection so that )Delegate.CreateDelegate( typeof(Action), transcribeOnlySetMethod); - s_executionContextProperty = typeof(System.Management.Automation.Runspaces.Runspace) + s_executionContextProperty = typeof(Runspace) .GetProperty("ExecutionContext", BindingFlags.NonPublic | BindingFlags.Instance); s_internalHostProperty = s_executionContextProperty.PropertyType .GetProperty("InternalHost", BindingFlags.NonPublic | BindingFlags.Instance); -#endif } public PsesInternalHost( @@ -544,23 +546,29 @@ public void InvokePSDelegate(string representation, ExecutionOptions executionOp // This works around a bug in PowerShell 5.1 (that was later fixed) where a running // transcription could cause output to disappear since the `TranscribeOnly` property was // accidentally not reset to false. -#pragma warning disable CA1822 // Warning to make it static when it's empty for CoreCLR. internal void DisableTranscribeOnly() -#pragma warning restore CA1822 { -#if !CoreCLR + if (VersionUtils.IsNetCore) + { + return; + } + // To fix the TranscribeOnly bug, we have to get the internal UI, which involves a lot // of reflection since we can't always just use PowerShell to execute `$Host.UI`. s_internalPSHostUserInterface ??= (s_internalHostProperty.GetValue( s_executionContextProperty.GetValue(CurrentPowerShell.Runspace)) - as PSHost).UI; + as PSHost)?.UI; + + if (s_internalPSHostUserInterface is null) + { + return; + } if (s_getTranscribeOnlyDelegate(s_internalPSHostUserInterface)) { s_setTranscribeOnlyDelegate(s_internalPSHostUserInterface, false); } -#endif } internal Task LoadHostProfilesAsync(CancellationToken cancellationToken) diff --git a/src/PowerShellEditorServices/Utility/VersionUtils.cs b/src/PowerShellEditorServices/Utility/VersionUtils.cs index f130039ee..85d6bb66b 100644 --- a/src/PowerShellEditorServices/Utility/VersionUtils.cs +++ b/src/PowerShellEditorServices/Utility/VersionUtils.cs @@ -42,11 +42,6 @@ internal static class VersionUtils /// public static bool IsPS5 { get; } = PSVersion.Major == 5; - /// - /// True if we are running in PowerShell Core 6, false otherwise. - /// - public static bool IsPS6 { get; } = PSVersion.Major == 6; - /// /// True if we are running in PowerShell 7, false otherwise. /// @@ -70,7 +65,7 @@ internal static class VersionUtils /// /// The .NET Architecture as a string. /// - public static string Architecture { get; } = PowerShellReflectionUtils.GetOSArchitecture(); + public static string Architecture { get; } = RuntimeInformation.OSArchitecture.ToString(); } internal static class PowerShellReflectionUtils @@ -117,24 +112,5 @@ internal static class PowerShellReflectionUtils public static string PSVersionString { get; } = s_psCurrentVersionProperty != null ? s_psCurrentVersionProperty.GetValue(null).ToString() : PSVersion.ToString(3); - - public static string GetOSArchitecture() - { -#if CoreCLR - if (Environment.OSVersion.Platform != PlatformID.Win32NT) - { - return RuntimeInformation.OSArchitecture.ToString(); - } -#endif - // If on win7 (version 6.1.x), avoid System.Runtime.InteropServices.RuntimeInformation - if (Environment.OSVersion.Version < new Version(6, 2)) - { - return Environment.Is64BitProcess - ? "X64" - : "X86"; - } - - return RuntimeInformation.OSArchitecture.ToString(); - } } } diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index a39126dd7..222250cf6 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -6,10 +6,6 @@ false - - $(DefineConstants);CoreCLR - - From d78cebdac85aec522acad1698e9cd69bad609a95 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 30 May 2023 15:26:32 -0700 Subject: [PATCH 295/327] Update CHANGELOG for `v3.8.7` --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1472d7d34..7a3eb3c6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # PowerShell Editor Services Release History +## v3.8.7 +### Tuesday, May 30, 2023 + +- 🐛 📟 [PowerShellEditorServices #2031](https://github.com/PowerShell/PowerShellEditorServices/pull/2031) - Fix the `TranscribeOnly` bug (take three). + ## v3.8.6 ### Tuesday, May 23, 2023 From 128b6090474400ad77e9c68902ab703a87c6ae3b Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Tue, 30 May 2023 15:26:32 -0700 Subject: [PATCH 296/327] Bump version to `v3.8.7` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 6917f6ddf..86cfb1038 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.8.6 + 3.8.7 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 42986d423..f618c1d15 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.8.6' +ModuleVersion = '3.8.7' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 75cdf0b05e3cdb720c196afe80ca4d159866a32f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 10:30:00 -0700 Subject: [PATCH 297/327] Bump Microsoft.NET.Test.Sdk from 17.6.0 to 17.6.1 (#2035) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.0 to 17.6.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.6.0...v17.6.1) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 222250cf6..982fd1961 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 4674803f5..9a014459f 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From 6b94e33fba3c5a3221ed8bcbf2da3c9ff9b5b90c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:58:04 -0700 Subject: [PATCH 298/327] Bump Microsoft.NET.Test.Sdk from 17.6.1 to 17.6.2 (#2036) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.1 to 17.6.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.6.1...v17.6.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 982fd1961..919015e50 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 9a014459f..0df8cb091 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From de8036dc3dcc7357b50f3038f3fb6a40fba8b049 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jul 2023 17:01:45 +0000 Subject: [PATCH 299/327] Bump Microsoft.NET.Test.Sdk from 17.6.2 to 17.6.3 (#2040) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.2 to 17.6.3. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.6.2...v17.6.3) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 919015e50..5e53552f3 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 0df8cb091..188d8d60c 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From 36854aaaeeb6e1afedfd89970f53d90d4560498b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jul 2023 21:34:27 +0000 Subject: [PATCH 300/327] Bump Microsoft.PowerShell.SDK from 7.3.4 to 7.3.5 (#2039) And from 7.2.11 to 7.2.12. Bumps [Microsoft.PowerShell.SDK](https://github.com/PowerShell/PowerShell) from 7.3.4 to 7.3.5. - [Release notes](https://github.com/PowerShell/PowerShell/releases) - [Commits](https://github.com/PowerShell/PowerShell/compare/v7.3.4...v7.3.5) --- updated-dependencies: - dependency-name: Microsoft.PowerShell.SDK dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 188d8d60c..97a18e28a 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -16,14 +16,14 @@ - + - + - + - + From 712c0f8c640895899c78ad18cf7b02d7bc2a2f0a Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andschwa@users.noreply.github.com> Date: Wed, 12 Jul 2023 11:07:34 -0700 Subject: [PATCH 301/327] Setup GitHub merge queue (#2045) --- .github/workflows/codeql-analysis.yml | 2 ++ .github/workflows/emacs-test.yml | 2 ++ .github/workflows/vim-test.yml | 2 ++ .vsts-ci/azure-pipelines-ci.yml | 9 ++++++++- .vsts-ci/misc-analysis.yml | 11 +++++------ 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fcc259678..4f1d2b042 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -8,6 +8,8 @@ on: # The branches below must be a subset of the branches above branches: [ main ] paths-ignore: [ '**/*.md' ] + merge_group: + types: [ checks_requested ] schedule: - cron: '00 14 * * *' # Every morning at 7:00am PDT diff --git a/.github/workflows/emacs-test.yml b/.github/workflows/emacs-test.yml index d244a16a2..338b7c1b7 100644 --- a/.github/workflows/emacs-test.yml +++ b/.github/workflows/emacs-test.yml @@ -8,6 +8,8 @@ on: # The branches below must be a subset of the branches above branches: [ main ] paths-ignore: [ '**/*.md' ] + merge_group: + types: [ checks_requested ] jobs: test: diff --git a/.github/workflows/vim-test.yml b/.github/workflows/vim-test.yml index 387a98e00..e529f3b14 100644 --- a/.github/workflows/vim-test.yml +++ b/.github/workflows/vim-test.yml @@ -8,6 +8,8 @@ on: # The branches below must be a subset of the branches above branches: [ main ] paths-ignore: [ '**/*.md' ] + merge_group: + types: [ checks_requested ] jobs: test: diff --git a/.vsts-ci/azure-pipelines-ci.yml b/.vsts-ci/azure-pipelines-ci.yml index c76a32ba3..64f10d89f 100644 --- a/.vsts-ci/azure-pipelines-ci.yml +++ b/.vsts-ci/azure-pipelines-ci.yml @@ -1,4 +1,11 @@ -name: PR-$(System.PullRequest.PullRequestNumber)-$(Date:yyyyMMdd)$(Rev:.rr) +name: CI-$(Build.SourceBranchName)-$(Date:yyyyMMdd)$(Rev:.rr) + +trigger: + - gh-readonly-queue/main/* +pr: + paths: + exclude: + - '**/*.md' variables: # Don't download unneeded packages diff --git a/.vsts-ci/misc-analysis.yml b/.vsts-ci/misc-analysis.yml index 86b0b5840..3c79fceec 100644 --- a/.vsts-ci/misc-analysis.yml +++ b/.vsts-ci/misc-analysis.yml @@ -1,12 +1,11 @@ -name: PR-$(System.PullRequest.PullRequestNumber)-$(Date:yyyyMMdd)$(Rev:.rr) +name: Misc-$(Build.SourceBranchName)-$(Date:yyyyMMdd)$(Rev:.rr) trigger: - branches: - include: - - main - + - gh-readonly-queue/main/* pr: -- main + paths: + exclude: + - '**/*.md' resources: repositories: From 439581d60363e8c08abd32ec4587e1a329bf43cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 20:34:55 +0000 Subject: [PATCH 302/327] Bump xunit from 2.4.2 to 2.5.0 Bumps [xunit](https://github.com/xunit/xunit) from 2.4.2 to 2.5.0. - [Commits](https://github.com/xunit/xunit/compare/2.4.2...2.5.0) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../Debugging/DebugServiceTests.cs | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 5e53552f3..7da949d81 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -10,7 +10,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 68db85f36..bfdac2774 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -493,7 +493,7 @@ await debugService.SetLineBreakpointsAsync( public async Task DebuggerBreaksWhenRequested() { IReadOnlyList confirmedBreakpoints = await GetConfirmedBreakpoints(debugScriptFile).ConfigureAwait(true); - Assert.Equal(0, confirmedBreakpoints.Count); + Assert.Empty(confirmedBreakpoints); Task _ = ExecuteDebugFileAsync(); // NOTE: This must be run on a separate thread so the async event handlers can fire. await Task.Run(debugService.Break).ConfigureAwait(true); diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 97a18e28a..5416d2e2d 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -33,7 +33,7 @@ - + From 391ad94193e18b94ad160a4cbbc5a6533245737e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 01:51:15 +0000 Subject: [PATCH 303/327] Bump xunit.runner.visualstudio from 2.4.5 to 2.5.0 Bumps [xunit.runner.visualstudio](https://github.com/xunit/visualstudio.xunit) from 2.4.5 to 2.5.0. - [Release notes](https://github.com/xunit/visualstudio.xunit/releases) - [Commits](https://github.com/xunit/visualstudio.xunit/compare/v2.4.5...2.5.0) --- updated-dependencies: - dependency-name: xunit.runner.visualstudio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 7da949d81..3b6468423 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -11,7 +11,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 5416d2e2d..52989014a 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -34,7 +34,7 @@ - + From e8021bfddf871ce9cfb97881a2f4f5863ac531b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 01:31:30 +0000 Subject: [PATCH 304/327] Bump Serilog from 2.12.0 to 3.0.1 Bumps [Serilog](https://github.com/serilog/serilog) from 2.12.0 to 3.0.1. - [Release notes](https://github.com/serilog/serilog/releases) - [Changelog](https://github.com/serilog/serilog/blob/dev/CHANGES.md) - [Commits](https://github.com/serilog/serilog/compare/v2.12.0...v3.0.1) --- updated-dependencies: - dependency-name: Serilog dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- src/PowerShellEditorServices/PowerShellEditorServices.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index a344b3401..68111e381 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -36,7 +36,7 @@ - + From bfe334df7e4bdd4b92c5c0319655e40444e185ff Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 27 Jul 2023 12:23:30 -0700 Subject: [PATCH 305/327] Bump PSReadLine to beta for extension preview --- modules.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules.json b/modules.json index 488d74b74..8f9d84b92 100644 --- a/modules.json +++ b/modules.json @@ -6,6 +6,7 @@ "Version": "1.1.3" }, "PSReadLine": { - "Version": "2.2.6" + "Version": "2.3.1-beta1", + "AllowPrerelease": true } } From f042eef2f32f14a618a0090d49c55637292165a5 Mon Sep 17 00:00:00 2001 From: "microsoft-github-policy-service[bot]" <77245923+microsoft-github-policy-service[bot]@users.noreply.github.com> Date: Fri, 7 Jul 2023 17:56:53 +0000 Subject: [PATCH 306/327] Add prIssueManagement.yml to onboard repo to GitOps.ResourceManagement as FabricBot replacement Owners of the FabricBot configuration should have received email notification. The same information contained in the email is published internally at: https://aka.ms/gim/fabricbot. Details on the replacement service and the syntax of the new yaml configuration file is available publicly at: https://microsoft.github.io/GitOps/policies/resource-management.html Please review and merge this PR to complete the process of onboarding to the new service. --- .github/policies/resourceManagement.yml | 150 ++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 .github/policies/resourceManagement.yml diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml new file mode 100644 index 000000000..0028f166f --- /dev/null +++ b/.github/policies/resourceManagement.yml @@ -0,0 +1,150 @@ +id: +name: GitOps.PullRequestIssueManagement +description: GitOps.PullRequestIssueManagement primitive +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isIssue + - isOpen + - hasLabel: + label: "Needs: Author Feedback \U0001F442" + - noActivitySince: + days: 8 + actions: + - closeIssue + - addReply: + reply: This issue was closed automatically as author feedback was indicated as needed, but there has been no activity in over a week. Please feel free to reopen with any available information! + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isIssue + - isOpen + - hasLabel: + label: Resolution-Duplicate + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as duplicate and has not had any activity in a day. It has been closed for housekeeping purposes. + - closeIssue + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isOpen + - hasLabel: + label: Resolution-Answered + - noActivitySince: + days: 1 + actions: + - closeIssue + - addReply: + reply: This issue has been marked as answered and has not had any activity in a day. It has been automatically closed for housekeeping purposes. + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isOpen + - hasLabel: + label: Needs-Repro-Info + - noActivitySince: + days: 8 + actions: + - addReply: + reply: This issue was closed automatically as repro info was indicated as needed, but there has been no activity in over a week. Please feel free to reopen with any available information! + - closeIssue + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isOpen + - hasLabel: + label: Resolution-External + actions: + - closeIssue + - addReply: + reply: This issue has been marked as external. It has been automatically closed for housekeeping purposes. + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isOpen + - hasLabel: + label: 'Status: Fixed' + actions: + - closeIssue + - addReply: + reply: This issue has been marked as fixed. It has been automatically closed for housekeeping purposes. + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isOpen + - hasLabel: + label: Resolution-Inactive + actions: + - addReply: + reply: This issue has been marked as inactive. It has been automatically closed for housekeeping purposes. + - closeIssue + eventResponderTasks: + - if: + - payloadType: Issue_Comment + - isAction: + action: Created + - hasLabel: + label: "Needs: Author Feedback \U0001F442" + - isActivitySender: + issueAuthor: True + then: + - addLabel: + label: 'Needs: Attention :wave:' + - removeLabel: + label: "Needs: Author Feedback \U0001F442" + description: + - if: + - payloadType: Pull_Request + then: + - labelSync: + pattern: Issue-* + - labelSync: + pattern: Area-* + description: + - if: + - payloadType: Issue_Comment + - not: isOpen + - isAction: + action: Created + - hasLabel: + label: "Needs: Author Feedback \U0001F442" + - isActivitySender: + issueAuthor: True + then: + - reopenIssue + - removeLabel: + label: "Needs: Author Feedback \U0001F442" + - addLabel: + label: 'Needs: Attention :wave:' + description: + - if: + - payloadType: Issue_Comment + then: + - cleanEmailReply + description: +onFailure: +onSuccess: From ad8dd14000b30595fd0c38ae80aac7dda6744167 Mon Sep 17 00:00:00 2001 From: "microsoft-github-policy-service[bot]" <77245923+microsoft-github-policy-service[bot]@users.noreply.github.com> Date: Fri, 7 Jul 2023 17:56:54 +0000 Subject: [PATCH 307/327] Deleting fabricbot.json --- .github/fabricbot.json | 1252 ---------------------------------------- 1 file changed, 1252 deletions(-) delete mode 100644 .github/fabricbot.json diff --git a/.github/fabricbot.json b/.github/fabricbot.json deleted file mode 100644 index 34290cbab..000000000 --- a/.github/fabricbot.json +++ /dev/null @@ -1,1252 +0,0 @@ -{ - "version": "1.0", - "tasks": [ - { - "taskType": "trigger", - "capabilityId": "IssueResponder", - "subCapability": "IssuesOnlyResponder", - "version": "1.0", - "config": { - "taskName": "Add Needs: Triage to new issues", - "conditions": { - "operator": "and", - "operands": [ - { - "name": "isAction", - "parameters": { - "action": "opened" - } - }, - { - "operator": "not", - "operands": [ - { - "name": "isPartOfProject", - "parameters": {} - } - ] - }, - { - "operator": "not", - "operands": [ - { - "name": "addedToMilestone", - "parameters": { - "permissions": "admin" - } - } - ] - } - ] - }, - "actions": [ - { - "name": "addLabel", - "parameters": { - "label": "Needs: Triage :mag:" - } - } - ], - "eventType": "issue", - "eventNames": [ - "issues", - "project_card" - ] - }, - "id": "bZ69BOc_NY22JqGoYuRVf" - }, - { - "taskType": "trigger", - "capabilityId": "IssueResponder", - "subCapability": "IssueCommentResponder", - "version": "1.0", - "config": { - "taskName": "Switch Needs: Author Feedback with Needs: Attention", - "conditions": { - "operator": "and", - "operands": [ - { - "name": "isAction", - "parameters": { - "action": "created" - } - }, - { - "name": "hasLabel", - "parameters": { - "label": "Needs: Author Feedback 👂" - } - }, - { - "name": "isActivitySender", - "parameters": { - "permissions": "write", - "association": "MEMBER", - "user": { - "type": "author" - } - } - } - ] - }, - "actions": [ - { - "name": "addLabel", - "parameters": { - "label": "Needs: Attention :wave:" - } - }, - { - "name": "removeLabel", - "parameters": { - "label": "Needs: Author Feedback 👂" - } - } - ], - "eventType": "issue", - "eventNames": [ - "issue_comment" - ] - }, - "id": "5PCaGHNU2ezWdoBvKw7-B" - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "config": { - "taskName": "Close inactive and Needs: Author Feedback", - "frequency": [ - { - "weekDay": 0, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 1, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 2, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 3, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 4, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 5, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 6, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -8 - } - ], - "searchTerms": [ - { - "name": "isIssue", - "parameters": {} - }, - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Needs: Author Feedback 👂" - } - }, - { - "name": "noActivitySince", - "parameters": { - "days": 8 - } - } - ], - "actions": [ - { - "name": "closeIssue", - "parameters": {} - }, - { - "name": "addReply", - "parameters": { - "comment": "This issue was closed automatically as author feedback was indicated as needed, but there has been no activity in over a week. Please feel free to reopen with any available information!" - } - } - ] - }, - "id": "jDdWWtEU5532BUAdD5UHM" - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "config": { - "taskName": "Close Resolution-Duplicate", - "frequency": [ - { - "weekDay": 0, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 1, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 2, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 3, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 4, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 5, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 6, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - } - ], - "searchTerms": [ - { - "name": "isIssue", - "parameters": {} - }, - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Resolution-Duplicate" - } - }, - { - "name": "noActivitySince", - "parameters": { - "days": 1 - } - } - ], - "actions": [ - { - "name": "addReply", - "parameters": { - "comment": "This issue has been marked as duplicate and has not had any activity in a day. It has been closed for housekeeping purposes." - } - }, - { - "name": "closeIssue", - "parameters": {} - } - ] - }, - "id": "9GI_9-AuXXQFt-WPIw2Bw" - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "config": { - "frequency": [ - { - "weekDay": 0, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 1, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 2, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 3, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 4, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 5, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 6, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - } - ], - "searchTerms": [ - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Resolution-Answered" - } - }, - { - "name": "noActivitySince", - "parameters": { - "days": 1 - } - } - ], - "actions": [ - { - "name": "closeIssue", - "parameters": {} - }, - { - "name": "addReply", - "parameters": { - "comment": "This issue has been marked as answered and has not had any activity in a day. It has been automatically closed for housekeeping purposes." - } - } - ], - "taskName": "Close Resolution-Answered" - }, - "id": "vVb4ljkjGUfwB83agn9W2" - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "config": { - "frequency": [ - { - "weekDay": 0, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 1, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 2, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 3, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 4, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 5, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 6, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - } - ], - "searchTerms": [ - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Needs-Repro-Info" - } - }, - { - "name": "noActivitySince", - "parameters": { - "days": 8 - } - } - ], - "actions": [ - { - "name": "addReply", - "parameters": { - "comment": "This issue was closed automatically as repro info was indicated as needed, but there has been no activity in over a week. Please feel free to reopen with any available information!" - } - }, - { - "name": "closeIssue", - "parameters": {} - } - ], - "taskName": "Close inactive and Needs: Repro Info" - }, - "id": "zRqoXPqgN-Zw8Ea5Vdrm4" - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "config": { - "frequency": [ - { - "weekDay": 0, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 1, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 2, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 3, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 4, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 5, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 6, - "hours": [ - 1, - 4, - 7, - 10, - 13, - 16, - 19, - 22 - ], - "timezoneOffset": -7 - } - ], - "searchTerms": [ - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Resolution-External" - } - } - ], - "taskName": "Close Resolution-External", - "actions": [ - { - "name": "closeIssue", - "parameters": {} - }, - { - "name": "addReply", - "parameters": { - "comment": "This issue has been marked as external. It has been automatically closed for housekeeping purposes." - } - } - ] - }, - "id": "bP-71eDgdx-vLsGuDERz4" - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "config": { - "frequency": [ - { - "weekDay": 0, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 1, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 2, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 3, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 4, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 5, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 6, - "hours": [ - 0, - 3, - 6, - 9, - 12, - 15, - 18, - 21 - ], - "timezoneOffset": -7 - } - ], - "searchTerms": [ - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Status: Fixed" - } - } - ], - "taskName": "Close Resolution-Fixed", - "actions": [ - { - "name": "closeIssue", - "parameters": {} - }, - { - "name": "addReply", - "parameters": { - "comment": "This issue has been marked as fixed. It has been automatically closed for housekeeping purposes." - } - } - ] - }, - "id": "-qbCtQh10GaxPmxfPg9my" - }, - { - "taskType": "trigger", - "capabilityId": "LabelSync", - "subCapability": "LabelSync", - "version": "1.0", - "config": { - "taskName": "Sync 'Issue' and 'Area' labels", - "labelPatterns": [ - { - "pattern": "Issue-*" - }, - { - "pattern": "Area-*" - } - ] - }, - "id": "RK6zFqXCNgU-moUu1GGvG" - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "config": { - "frequency": [ - { - "weekDay": 0, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 1, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 2, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 3, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 4, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 5, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - }, - { - "weekDay": 6, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -7 - } - ], - "searchTerms": [ - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Resolution-Inactive" - } - } - ], - "taskName": "Close Resolution-Inactive", - "actions": [ - { - "name": "addReply", - "parameters": { - "comment": "This issue has been marked as inactive. It has been automatically closed for housekeeping purposes." - } - }, - { - "name": "closeIssue", - "parameters": {} - } - ] - }, - "id": "M_Vcycw86e5EPDRg5YIV_" - }, - { - "taskType": "trigger", - "capabilityId": "IssueResponder", - "subCapability": "IssueCommentResponder", - "version": "1.0", - "config": { - "conditions": { - "operator": "and", - "operands": [ - { - "operator": "not", - "operands": [ - { - "name": "isOpen", - "parameters": {} - } - ] - }, - { - "name": "isAction", - "parameters": { - "action": "created" - } - }, - { - "name": "hasLabel", - "parameters": { - "label": "Needs: Author Feedback 👂" - } - }, - { - "name": "isActivitySender", - "parameters": { - "user": { - "type": "author" - }, - "association": "MEMBER" - } - } - ] - }, - "eventType": "issue", - "eventNames": [ - "issue_comment" - ], - "taskName": "For issues closed due to inactivity, re-open an issue if issue author posts a reply", - "actions": [ - { - "name": "reopenIssue", - "parameters": {} - }, - { - "name": "removeLabel", - "parameters": { - "label": "Needs: Author Feedback 👂" - } - }, - { - "name": "addLabel", - "parameters": { - "label": "Needs: Attention :wave:" - } - } - ] - }, - "id": "bTryBbM9TBHP65Apwhl_X" - }, - { - "taskType": "trigger", - "capabilityId": "IssueResponder", - "subCapability": "IssueCommentResponder", - "version": "1.0", - "config": { - "conditions": { - "operator": "and", - "operands": [ - { - "name": "isAction", - "parameters": { - "action": "created" - } - }, - { - "operator": "not", - "operands": [ - { - "name": "isOpen", - "parameters": {} - } - ] - }, - { - "name": "noActivitySince", - "parameters": { - "days": 8 - } - }, - { - "operator": "not", - "operands": [ - { - "name": "isActivitySender", - "parameters": { - "user": { - "type": "author" - } - } - } - ] - }, - { - "operator": "not", - "operands": [ - { - "name": "activitySenderHasAssociation", - "parameters": { - "association": "COLLABORATOR" - } - } - ] - } - ] - }, - "eventType": "issue", - "eventNames": [ - "issue_comment" - ], - "taskName": "For issues closed with no activity over a week, ask non-collaborator to consider opening a new issue instead.", - "actions": [ - { - "name": "addReply", - "parameters": { - "comment": "Thank you for your comment, but please note that this issue has been closed for over a week. For better visibility, consider opening a new issue with a link to this instead." - } - } - ] - }, - "id": "uscYG6BhmFB66YtDHQvCd" - }, - { - "taskType": "trigger", - "capabilityId": "EmailCleanser", - "subCapability": "EmailCleanser", - "version": "1.0", - "config": { - "taskName": "Clean emails" - }, - "id": "B7xR1x_vo-NjhnYhgfCKE" - } - ], - "userGroups": [] -} From 489dc89a4306f49bda4663689b0f08213281a809 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:19:37 -0700 Subject: [PATCH 308/327] Bump Microsoft.NET.Test.Sdk from 17.6.3 to 17.7.0 (#2049) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.3 to 17.7.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.6.3...v17.7.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 3b6468423..20d997566 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 52989014a..76b293f74 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From b8b6cbb9f79e0558348fd9bc84ddd62eb8a9a793 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:41:59 -0700 Subject: [PATCH 309/327] Update CHANGELOG for `v3.9.0` --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a3eb3c6a..d69929bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # PowerShell Editor Services Release History +## v3.9.0 +### Wednesday, August 09, 2023 + +- ✨ 📟 [PowerShellEditorServices #2046](https://github.com/PowerShell/PowerShellEditorServices/pull/2046) - Bump PSReadLine to beta for extension preview. + ## v3.8.7 ### Tuesday, May 30, 2023 From 89ce0867c6b119bef8af83ab21c249e10d0e77a2 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:42:00 -0700 Subject: [PATCH 310/327] Bump version to `v3.9.0` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 86cfb1038..9bbe7c543 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.8.7 + 3.9.0 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index f618c1d15..d59c6ccf9 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.8.7' +ModuleVersion = '3.9.0' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 9da8f055d768aa589ed2c9609ff8052a0199b152 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:19:57 -0700 Subject: [PATCH 311/327] Update PSReadLine to `v2.3.2-beta2` --- modules.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules.json b/modules.json index 8f9d84b92..b6da5f290 100644 --- a/modules.json +++ b/modules.json @@ -6,7 +6,7 @@ "Version": "1.1.3" }, "PSReadLine": { - "Version": "2.3.1-beta1", + "Version": "2.3.2-beta2", "AllowPrerelease": true } } From b9ae76b0f8db7c3f75dea2a6305bd7b9b112c165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 20:08:36 +0000 Subject: [PATCH 312/327] Bump Microsoft.NET.Test.Sdk from 17.7.0 to 17.7.1 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.7.0 to 17.7.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.7.0...v17.7.1) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 20d997566..7267cf110 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 76b293f74..e3e8ae1d7 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From 975ed6c5c2b8151d096ef5008b1d12c400ded13b Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 10 Aug 2023 17:25:50 -0700 Subject: [PATCH 313/327] Fix shell integration for PowerShell 5.1 with strict mode Since `$IsWindows` doesn't exist in Windows PowerShell, with strict mode enabled and the `ErrorActionPreference` set to `Stop`, shell integration would cause the startup to crash. This was reported and fixed upstream too. --- .../Services/PowerShell/Host/PsesInternalHost.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index a737cf1d9..e6f85ca78 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -663,7 +663,11 @@ private Task EnableShellIntegrationAsync(CancellationToken cancellationToken) } # Set IsWindows property -Write-Host -NoNewLine ""$([char]0x1b)]633;P;IsWindows=$($IsWindows)`a"" +if ($PSVersionTable.PSVersion -lt ""6.0"") { + [Console]::Write(""$([char]0x1b)]633;P;IsWindows=$true`a"") +} else { + [Console]::Write(""$([char]0x1b)]633;P;IsWindows=$IsWindows`a"") +} # Set always on key handlers which map to default VS Code keybindings function Set-MappedKeyHandler { From f96e537be77778c85d630ab6c7238ee3bf71615d Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:32:18 -0700 Subject: [PATCH 314/327] Move `ConstrainedLanguageMode` tests to separate task --- PowerShellEditorServices.build.ps1 | 48 ++++++++++++++++-------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index be8b5247c..1de501a29 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -175,7 +175,7 @@ Task Build FindDotNet, CreateBuildInfo, { Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj -f $script:NetRuntime.Standard } } -Task Test TestServer, TestE2E +Task Test TestServer, TestE2E, TestConstrainedLanguageMode Task TestServer TestServerWinPS, TestServerPS72, TestServerPS73 @@ -202,23 +202,25 @@ Task TestServerPS73 -If ($PSVersionTable.PSEdition -eq "Core") Build, SetupHelpF Task TestE2E Build, SetupHelpForTests, { Set-Location .\test\PowerShellEditorServices.Test.E2E\ - $env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" } Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS73 } +} - if (!$script:IsNix) { - if (-not [Security.Principal.WindowsIdentity]::GetCurrent().Owner.IsWellKnown("BuiltInAdministratorsSid")) { - Write-Warning "Skipping Constrained Language Mode tests as they must be ran in an elevated process." - return - } +Task TestConstrainedLanguageMode -If (-not $script:IsNix) Build, SetupHelpForTests, { + Set-Location .\test\PowerShellEditorServices.Test.E2E\ + $env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" } - try { - Write-Host "Running end-to-end tests in Constrained Language Mode." - [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine); - Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS73 } - } finally { - [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine); - } + if (-not [Security.Principal.WindowsIdentity]::GetCurrent().Owner.IsWellKnown("BuiltInAdministratorsSid")) { + Write-Warning "Skipping Constrained Language Mode tests as they must be ran in an elevated process." + return + } + + try { + Write-Host "Running end-to-end tests in Constrained Language Mode." + [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine) + Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetRuntime.PS73 } + } finally { + [System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine) } } @@ -290,11 +292,11 @@ task RestorePsesModules -After Build { (Get-Content -Raw $ModulesJsonPath | ConvertFrom-Json).PSObject.Properties | ForEach-Object { $name = $_.Name $body = @{ - Name = $name - Version = $_.Value.Version + Name = $name + Version = $_.Value.Version AllowPrerelease = $_.Value.AllowPrerelease - Repository = if ($_.Value.Repository) { $_.Value.Repository } else { $DefaultModuleRepository } - Path = $submodulePath + Repository = if ($_.Value.Repository) { $_.Value.Repository } else { $DefaultModuleRepository } + Path = $submodulePath } if (-not $name) { @@ -321,11 +323,11 @@ task RestorePsesModules -After Build { $moduleInstallDetails = $moduleInfos[$moduleName] $splatParameters = @{ - Name = $moduleName - RequiredVersion = $moduleInstallDetails.Version - AllowPrerelease = $moduleInstallDetails.AllowPrerelease - Repository = if ($moduleInstallDetails.Repository) { $moduleInstallDetails.Repository } else { $DefaultModuleRepository } - Path = $submodulePath + Name = $moduleName + RequiredVersion = $moduleInstallDetails.Version + AllowPrerelease = $moduleInstallDetails.AllowPrerelease + Repository = if ($moduleInstallDetails.Repository) { $moduleInstallDetails.Repository } else { $DefaultModuleRepository } + Path = $submodulePath } Write-Host "`tInstalling module: ${moduleName} with arguments $(ConvertTo-Json $splatParameters)" From 2ba484d157d9c06dd57e6b486a3b25dbb7bd6390 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Wed, 16 Aug 2023 18:53:48 -0700 Subject: [PATCH 315/327] Fix up extension API Receive a `EditorOperationResponse` (not yet otherwise used). Delete dead code. Add optional `content` parameter to `NewFile`. Expose `CloseFile` and `SaveFile`. Fix an outdated warning message. --- .../Extensions/EditorRequests.cs | 6 +-- .../Extensions/EditorWindow.cs | 3 -- .../Extensions/EditorWorkspace.cs | 34 +++++++++++++---- .../Extensions/IEditorOperations.cs | 8 ++-- .../Extension/EditorOperationsService.cs | 37 +++++++++---------- 5 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/PowerShellEditorServices/Extensions/EditorRequests.cs b/src/PowerShellEditorServices/Extensions/EditorRequests.cs index 446cd2dfc..34dca2928 100644 --- a/src/PowerShellEditorServices/Extensions/EditorRequests.cs +++ b/src/PowerShellEditorServices/Extensions/EditorRequests.cs @@ -25,10 +25,10 @@ internal class ExtensionCommandRemovedNotification internal class GetEditorContextRequest { } - internal enum EditorCommandResponse + internal enum EditorOperationResponse { - Unsupported, - OK + Completed, + Failed } internal class InsertTextRequest diff --git a/src/PowerShellEditorServices/Extensions/EditorWindow.cs b/src/PowerShellEditorServices/Extensions/EditorWindow.cs index 428c192ea..8c9048c3c 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWindow.cs +++ b/src/PowerShellEditorServices/Extensions/EditorWindow.cs @@ -39,8 +39,6 @@ internal EditorWindow(IEditorOperations editorOperations) #endregion #region Public Methods - #pragma warning disable VSTHRD002 // These are public APIs that use async internal methods. - /// /// Shows an informational message to the user. /// @@ -72,7 +70,6 @@ internal EditorWindow(IEditorOperations editorOperations) /// A timeout in milliseconds for how long the message should remain visible. public void SetStatusBarMessage(string message, int timeout) => editorOperations.SetStatusBarMessageAsync(message, timeout).Wait(); - #pragma warning restore VSTHRD002 #endregion } } diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs index aa5802564..d12de06e3 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs +++ b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs @@ -18,7 +18,8 @@ public sealed class EditorWorkspace #region Properties /// - /// Gets the current workspace path if there is one for the open editor or null otherwise. + /// Gets the server's initial working directory, since the extension API doesn't have a + /// multi-root workspace concept. /// public string Path => editorOperations.GetWorkspacePath(); @@ -31,22 +32,23 @@ public sealed class EditorWorkspace #endregion #region Public Methods - #pragma warning disable VSTHRD002 // These are public APIs that use async internal methods. + // TODO: Consider returning bool instead of void to indicate success? /// - /// Creates a new file in the editor + /// Creates a new file in the editor. /// - public void NewFile() => editorOperations.NewFileAsync().Wait(); + /// The content to place in the new file. + public void NewFile(string content = "") => editorOperations.NewFileAsync(content).Wait(); /// - /// Opens a file in the workspace. If the file is already open + /// Opens a file in the workspace. If the file is already open /// its buffer will be made active. /// /// The path to the file to be opened. public void OpenFile(string filePath) => editorOperations.OpenFileAsync(filePath).Wait(); /// - /// Opens a file in the workspace. If the file is already open + /// Opens a file in the workspace. If the file is already open /// its buffer will be made active. /// You can specify whether the file opens as a preview or as a durable editor. /// @@ -54,7 +56,25 @@ public sealed class EditorWorkspace /// Determines wether the file is opened as a preview or as a durable editor. public void OpenFile(string filePath, bool preview) => editorOperations.OpenFileAsync(filePath, preview).Wait(); - #pragma warning restore VSTHRD002 + /// + /// Closes a file in the workspace. + /// + /// The path to the file to be closed. + public void CloseFile(string filePath) => editorOperations.CloseFileAsync(filePath).Wait(); + + /// + /// Saves an open file in the workspace. + /// + /// The path to the file to be saved. + public void SaveFile(string filePath) => editorOperations.SaveFileAsync(filePath).Wait(); + + /// + /// Saves a file with a new name AKA a copy. + /// + /// The file to copy. + /// The file to create. + public void SaveFile(string oldFilePath, string newFilePath) => editorOperations.SaveFileAsync(oldFilePath, newFilePath).Wait(); + #endregion } } diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs index 6f282eeea..07472a487 100644 --- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs +++ b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs @@ -20,9 +20,10 @@ internal interface IEditorOperations Task GetEditorContextAsync(); /// - /// Gets the path to the editor's active workspace. + /// Gets the server's initial working directory, since the extension API doesn't have a + /// multi-root workspace concept. /// - /// The workspace path or null if there isn't one. + /// The server's initial working directory. string GetWorkspacePath(); /// @@ -35,8 +36,9 @@ internal interface IEditorOperations /// /// Causes a new untitled file to be created in the editor. /// + /// The content to insert into the new file. /// A task that can be awaited for completion. - Task NewFileAsync(); + Task NewFileAsync(string content = ""); /// /// Causes a file to be opened in the editor. If the file is diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs index fa6da7f90..949c2321e 100644 --- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs @@ -13,11 +13,8 @@ namespace Microsoft.PowerShell.EditorServices.Services.Extension { internal class EditorOperationsService : IEditorOperations { - private const bool DefaultPreviewSetting = true; - private readonly PsesInternalHost _psesHost; private readonly WorkspaceService _workspaceService; - private readonly ILanguageServerFacade _languageServer; public EditorOperationsService( @@ -72,7 +69,7 @@ public async Task InsertTextAsync(string filePath, string text, BufferRange inse Character = insertRange.End.Column - 1 } } - }).ReturningVoid(CancellationToken.None).ConfigureAwait(false); + }).Returning(CancellationToken.None).ConfigureAwait(false); } public async Task SetSelectionAsync(BufferRange selectionRange) @@ -98,7 +95,7 @@ public async Task SetSelectionAsync(BufferRange selectionRange) Character = selectionRange.End.Column - 1 } } - }).ReturningVoid(CancellationToken.None).ConfigureAwait(false); + }).Returning(CancellationToken.None).ConfigureAwait(false); } public EditorContext ConvertClientEditorContext( @@ -123,15 +120,15 @@ public EditorContext ConvertClientEditorContext( clientContext.CurrentFileLanguage); } - public async Task NewFileAsync() + public async Task NewFileAsync(string content = "") { if (!TestHasLanguageServer()) { return; } - await _languageServer.SendRequest("editor/newFile", null) - .ReturningVoid(CancellationToken.None) + await _languageServer.SendRequest("editor/newFile", content) + .Returning(CancellationToken.None) .ConfigureAwait(false); } @@ -145,8 +142,8 @@ public async Task OpenFileAsync(string filePath) await _languageServer.SendRequest("editor/openFile", new OpenFileDetails { FilePath = filePath, - Preview = DefaultPreviewSetting - }).ReturningVoid(CancellationToken.None).ConfigureAwait(false); + Preview = true + }).Returning(CancellationToken.None).ConfigureAwait(false); } public async Task OpenFileAsync(string filePath, bool preview) @@ -160,7 +157,7 @@ public async Task OpenFileAsync(string filePath, bool preview) { FilePath = filePath, Preview = preview - }).ReturningVoid(CancellationToken.None).ConfigureAwait(false); + }).Returning(CancellationToken.None).ConfigureAwait(false); } public async Task CloseFileAsync(string filePath) @@ -171,7 +168,7 @@ public async Task CloseFileAsync(string filePath) } await _languageServer.SendRequest("editor/closeFile", filePath) - .ReturningVoid(CancellationToken.None) + .Returning(CancellationToken.None) .ConfigureAwait(false); } @@ -188,11 +185,11 @@ public async Task SaveFileAsync(string currentPath, string newSavePath) { FilePath = currentPath, NewPath = newSavePath - }).ReturningVoid(CancellationToken.None).ConfigureAwait(false); + }).Returning(CancellationToken.None).ConfigureAwait(false); } - // TODO: This should get the current editor's context and use it to determine which - // workspace it's in. + // NOTE: This name is now outdated since we don't have a way to distinguish one workspace + // from another for the extension API. public string GetWorkspacePath() => _workspaceService.InitialWorkingDirectory; public string GetWorkspaceRelativePath(string filePath) => _workspaceService.GetRelativePath(filePath); @@ -205,7 +202,7 @@ public async Task ShowInformationMessageAsync(string message) } await _languageServer.SendRequest("editor/showInformationMessage", message) - .ReturningVoid(CancellationToken.None) + .Returning(CancellationToken.None) .ConfigureAwait(false); } @@ -217,7 +214,7 @@ public async Task ShowErrorMessageAsync(string message) } await _languageServer.SendRequest("editor/showErrorMessage", message) - .ReturningVoid(CancellationToken.None) + .Returning(CancellationToken.None) .ConfigureAwait(false); } @@ -229,7 +226,7 @@ public async Task ShowWarningMessageAsync(string message) } await _languageServer.SendRequest("editor/showWarningMessage", message) - .ReturningVoid(CancellationToken.None) + .Returning(CancellationToken.None) .ConfigureAwait(false); } @@ -244,7 +241,7 @@ public async Task SetStatusBarMessageAsync(string message, int? timeout) { Message = message, Timeout = timeout - }).ReturningVoid(CancellationToken.None).ConfigureAwait(false); + }).Returning(CancellationToken.None).ConfigureAwait(false); } public void ClearTerminal() @@ -267,7 +264,7 @@ private bool TestHasLanguageServer(bool warnUser = true) if (warnUser) { _psesHost.UI.WriteWarningLine( - "Editor operations are not supported in temporary consoles. Re-run the command in the main PowerShell Intergrated Console."); + "Editor operations are not supported in temporary consoles. Re-run the command in the main Extension Terminal."); } return false; From a47a077426067d296ddb9dcf66d33db6b9c6485b Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 17 Aug 2023 12:56:30 -0700 Subject: [PATCH 316/327] Support multi-root workspaces for `CurrentFile.RelativePath` --- .../Extensions/FileContext.cs | 3 +- .../Extensions/IEditorOperations.cs | 3 +- .../Extension/EditorOperationsService.cs | 2 +- .../Handlers/GetCommentHelpHandler.cs | 4 +- .../Services/TextDocument/ScriptFile.cs | 18 +--- .../Services/Workspace/WorkspaceService.cs | 68 ++++----------- .../Session/WorkspaceTests.cs | 83 +++++++++---------- 7 files changed, 63 insertions(+), 118 deletions(-) diff --git a/src/PowerShellEditorServices/Extensions/FileContext.cs b/src/PowerShellEditorServices/Extensions/FileContext.cs index 770cc33f7..c8c32e58e 100644 --- a/src/PowerShellEditorServices/Extensions/FileContext.cs +++ b/src/PowerShellEditorServices/Extensions/FileContext.cs @@ -58,8 +58,7 @@ public sealed class FileContext /// /// Gets the workspace-relative path of the file. /// - public string WorkspacePath => editorOperations.GetWorkspaceRelativePath( - scriptFile.FilePath); + public string WorkspacePath => editorOperations.GetWorkspaceRelativePath(scriptFile); #endregion diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs index 07472a487..63bae4574 100644 --- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs +++ b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs @@ -29,9 +29,8 @@ internal interface IEditorOperations /// /// Resolves the given file path relative to the current workspace path. /// - /// The file path to be resolved. /// The resolved file path. - string GetWorkspaceRelativePath(string filePath); + string GetWorkspaceRelativePath(ScriptFile scriptFile); /// /// Causes a new untitled file to be created in the editor. diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs index 949c2321e..a187533c5 100644 --- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs @@ -192,7 +192,7 @@ public async Task SaveFileAsync(string currentPath, string newSavePath) // from another for the extension API. public string GetWorkspacePath() => _workspaceService.InitialWorkingDirectory; - public string GetWorkspaceRelativePath(string filePath) => _workspaceService.GetRelativePath(filePath); + public string GetWorkspaceRelativePath(ScriptFile scriptFile) => _workspaceService.GetRelativePath(scriptFile); public async Task ShowInformationMessageAsync(string message) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs index 27288ce90..10013a09b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs @@ -51,7 +51,7 @@ public async Task Handle(CommentHelpRequestParams requ { // check if the previous character is `<` because it invalidates // the param block the follows it. - IList lines = ScriptFile.GetLinesInternal(funcText); + IList lines = ScriptFile.GetLines(funcText); int relativeTriggerLine0b = triggerLine - funcExtent.StartLineNumber; if (relativeTriggerLine0b > 0 && lines[relativeTriggerLine0b].IndexOf("<", StringComparison.OrdinalIgnoreCase) > -1) { @@ -68,7 +68,7 @@ public async Task Handle(CommentHelpRequestParams requ return result; } - List helpLines = ScriptFile.GetLinesInternal(helpText); + List helpLines = ScriptFile.GetLines(helpText); if (helpLocation?.Equals("before", StringComparison.OrdinalIgnoreCase) == false) { diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs index 3e52e4938..4909d1020 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs @@ -31,13 +31,6 @@ internal sealed class ScriptFile #region Properties - /// - /// Gets a unique string that identifies this file. At this time, - /// this property returns a normalized version of the value stored - /// in the FilePath property. - /// - public string Id => FilePath.ToLower(); - /// /// Gets the path at which this file resides. /// @@ -173,14 +166,7 @@ internal ScriptFile( /// /// Input string to be split up into lines. /// The lines in the string. - internal static IList GetLines(string text) => GetLinesInternal(text); - - /// - /// Get the lines in a string. - /// - /// Input string to be split up into lines. - /// The lines in the string. - internal static List GetLinesInternal(string text) + internal static List GetLines(string text) { if (text == null) { @@ -520,7 +506,7 @@ internal void SetFileContents(string fileContents) { // Split the file contents into lines and trim // any carriage returns from the strings. - FileLines = GetLinesInternal(fileContents); + FileLines = GetLines(fileContents); // Parse the contents to get syntax tree and errors ParseFileContents(); diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs index f705101f8..fce8cab20 100644 --- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs @@ -306,27 +306,31 @@ public void CloseFile(ScriptFile scriptFile) /// /// Gets the workspace-relative path of the given file path. /// - /// The original full file path. /// A relative file path - public string GetRelativePath(string filePath) + public string GetRelativePath(ScriptFile scriptFile) { - string resolvedPath = filePath; - - if (!IsPathInMemory(filePath) && !string.IsNullOrEmpty(InitialWorkingDirectory)) + Uri fileUri = scriptFile.DocumentUri.ToUri(); + if (!scriptFile.IsInMemory) { - Uri workspaceUri = new(InitialWorkingDirectory); - Uri fileUri = new(filePath); - - resolvedPath = workspaceUri.MakeRelativeUri(fileUri).ToString(); - - // Convert the directory separators if necessary - if (Path.DirectorySeparatorChar == '\\') + // Support calculating out-of-workspace relative paths in the common case of a + // single workspace folder. Otherwise try to get the matching folder. + foreach (WorkspaceFolder workspaceFolder in WorkspaceFolders) { - resolvedPath = resolvedPath.Replace('/', '\\'); + Uri workspaceUri = workspaceFolder.Uri.ToUri(); + if (WorkspaceFolders.Count == 1 || workspaceUri.IsBaseOf(fileUri)) + { + return workspaceUri.MakeRelativeUri(fileUri).ToString(); + } } } - return resolvedPath; + // Default to the absolute file path if possible, otherwise just return the URI. This + // removes the scheme and initial slash when possible. + if (fileUri.IsAbsoluteUri) + { + return fileUri.AbsolutePath; + } + return fileUri.ToString(); } /// @@ -407,42 +411,6 @@ internal static string ReadFileContents(DocumentUri uri) return reader.ReadToEnd(); } - internal static bool IsPathInMemory(string filePath) - { - bool isInMemory = false; - - // In cases where a "virtual" file is displayed in the editor, - // we need to treat the file differently than one that exists - // on disk. A virtual file could be something like a diff - // view of the current file or an untitled file. - try - { - // File system absolute paths will have a URI scheme of file:. - // Other schemes like "untitled:" and "gitlens-git:" will return false for IsFile. - Uri uri = new(filePath); - isInMemory = !uri.IsFile; - } - catch (UriFormatException) - { - // Relative file paths cause a UriFormatException. - // In this case, fallback to using Path.GetFullPath(). - try - { - Path.GetFullPath(filePath); - } - catch (Exception ex) when (ex is ArgumentException or NotSupportedException) - { - isInMemory = true; - } - catch (PathTooLongException) - { - // If we ever get here, it should be an actual file so, not in memory - } - } - - return isInMemory; - } - internal string ResolveWorkspacePath(string path) => ResolveRelativeScriptPath(InitialWorkingDirectory, path); internal string ResolveRelativeScriptPath(string baseFilePath, string relativePath) diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs index 2c7a44279..1fff5eb3a 100644 --- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -10,6 +10,9 @@ using Microsoft.PowerShell.EditorServices.Test.Shared; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Xunit; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol; namespace PowerShellEditorServices.Test.Session { @@ -22,27 +25,51 @@ public class WorkspaceTests ? s_lazyDriveLetter.Value : string.Empty; + internal static ScriptFile CreateScriptFile(string path) => new(path, "", VersionUtils.PSVersion); + + [Fact] public void CanResolveWorkspaceRelativePath() { - string workspacePath = TestUtilities.NormalizePath("c:/Test/Workspace/"); - string testPathInside = TestUtilities.NormalizePath("c:/Test/Workspace/SubFolder/FilePath.ps1"); - string testPathOutside = TestUtilities.NormalizePath("c:/Test/PeerPath/FilePath.ps1"); - string testPathAnotherDrive = TestUtilities.NormalizePath("z:/TryAndFindMe/FilePath.ps1"); + string workspacePath = "c:/Test/Workspace/"; + ScriptFile testPathInside = CreateScriptFile("c:/Test/Workspace/SubFolder/FilePath.ps1"); + ScriptFile testPathOutside = CreateScriptFile("c:/Test/PeerPath/FilePath.ps1"); + ScriptFile testPathAnotherDrive = CreateScriptFile("z:/TryAndFindMe/FilePath.ps1"); WorkspaceService workspace = new(NullLoggerFactory.Instance); - // Test without a workspace path - Assert.Equal(testPathOutside, workspace.GetRelativePath(testPathOutside)); + // Test with zero workspace folders + Assert.Equal( + testPathOutside.DocumentUri.ToUri().AbsolutePath, + workspace.GetRelativePath(testPathOutside)); - string expectedInsidePath = TestUtilities.NormalizePath("SubFolder/FilePath.ps1"); - string expectedOutsidePath = TestUtilities.NormalizePath("../PeerPath/FilePath.ps1"); + string expectedInsidePath = "SubFolder/FilePath.ps1"; + string expectedOutsidePath = "../PeerPath/FilePath.ps1"; + + // Test with a single workspace folder + workspace.WorkspaceFolders.Add(new WorkspaceFolder + { + Uri = DocumentUri.FromFileSystemPath(workspacePath) + }); - // Test with a workspace path - workspace.InitialWorkingDirectory = workspacePath; Assert.Equal(expectedInsidePath, workspace.GetRelativePath(testPathInside)); Assert.Equal(expectedOutsidePath, workspace.GetRelativePath(testPathOutside)); - Assert.Equal(testPathAnotherDrive, workspace.GetRelativePath(testPathAnotherDrive)); + Assert.Equal( + testPathAnotherDrive.DocumentUri.ToUri().AbsolutePath, + workspace.GetRelativePath(testPathAnotherDrive)); + + // Test with two workspace folders + string anotherWorkspacePath = "c:/Test/AnotherWorkspace/"; + ScriptFile anotherTestPathInside = CreateScriptFile("c:/Test/AnotherWorkspace/DifferentFolder/FilePath.ps1"); + string anotherExpectedInsidePath = "DifferentFolder/FilePath.ps1"; + + workspace.WorkspaceFolders.Add(new WorkspaceFolder + { + Uri = DocumentUri.FromFileSystemPath(anotherWorkspacePath) + }); + + Assert.Equal(expectedInsidePath, workspace.GetRelativePath(testPathInside)); + Assert.Equal(anotherExpectedInsidePath, workspace.GetRelativePath(anotherTestPathInside)); } internal static WorkspaceService FixturesWorkspace() @@ -143,40 +170,6 @@ public void CanRecurseDirectoryTreeWithGlobs() }, actual); } - [Fact] - public void CanDetermineIsPathInMemory() - { - string tempDir = Path.GetTempPath(); - string shortDirPath = Path.Combine(tempDir, "GitHub", "PowerShellEditorServices"); - string shortFilePath = Path.Combine(shortDirPath, "foo.ps1"); - const string shortUriForm = "git:/c%3A/Users/Keith/GitHub/dahlbyk/posh-git/src/PoshGitTypes.ps1?%7B%22path%22%3A%22c%3A%5C%5CUsers%5C%5CKeith%5C%5CGitHub%5C%5Cdahlbyk%5C%5Cposh-git%5C%5Csrc%5C%5CPoshGitTypes.ps1%22%2C%22ref%22%3A%22~%22%7D"; - const string longUriForm = "gitlens-git:c%3A%5CUsers%5CKeith%5CGitHub%5Cdahlbyk%5Cposh-git%5Csrc%5CPoshGitTypes%3Ae0022701.ps1?%7B%22fileName%22%3A%22src%2FPoshGitTypes.ps1%22%2C%22repoPath%22%3A%22c%3A%2FUsers%2FKeith%2FGitHub%2Fdahlbyk%2Fposh-git%22%2C%22sha%22%3A%22e0022701fa12e0bc22d0458673d6443c942b974a%22%7D"; - - string[] inMemoryPaths = new[] { - // Test short non-file paths - "untitled:untitled-1", - shortUriForm, - "inmemory://foo.ps1", - // Test long non-file path - longUriForm - }; - - Assert.All(inMemoryPaths, (p) => Assert.True(WorkspaceService.IsPathInMemory(p))); - - string[] notInMemoryPaths = new[] { - // Test short file absolute paths - shortDirPath, - shortFilePath, - new Uri(shortDirPath).ToString(), - new Uri(shortFilePath).ToString(), - // Test short file relative paths - "foo.ps1", - Path.Combine(new[] { "..", "foo.ps1" }) - }; - - Assert.All(notInMemoryPaths, (p) => Assert.False(WorkspaceService.IsPathInMemory(p))); - } - [Fact] public void CanOpenAndCloseFile() { From 4be31b58632def4534e68ad630a0fb6d7a6f0ba4 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 17 Aug 2023 13:17:14 -0700 Subject: [PATCH 317/327] Add `Paths` to workspace API for multi-root workspaces Since `Path` now refers to initial working directory. --- .../Extensions/EditorWorkspace.cs | 5 +++++ .../Extensions/IEditorOperations.cs | 6 ++++++ .../Services/Extension/EditorOperationsService.cs | 3 +++ .../Services/Workspace/WorkspaceService.cs | 10 +++++----- .../Session/WorkspaceTests.cs | 8 ++++++++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs index d12de06e3..46930cda6 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs +++ b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs @@ -23,6 +23,11 @@ public sealed class EditorWorkspace /// public string Path => editorOperations.GetWorkspacePath(); + /// + /// Get all the workspace folders' paths. + /// + public string[] Paths => editorOperations.GetWorkspacePaths(); + #endregion #region Constructors diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs index 63bae4574..c349bcae1 100644 --- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs +++ b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs @@ -26,6 +26,12 @@ internal interface IEditorOperations /// The server's initial working directory. string GetWorkspacePath(); + /// + /// Get all the workspace folders' paths. + /// + /// + string[] GetWorkspacePaths(); + /// /// Resolves the given file path relative to the current workspace path. /// diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs index a187533c5..4ff235d94 100644 --- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs @@ -6,6 +6,7 @@ using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -192,6 +193,8 @@ public async Task SaveFileAsync(string currentPath, string newSavePath) // from another for the extension API. public string GetWorkspacePath() => _workspaceService.InitialWorkingDirectory; + public string[] GetWorkspacePaths() => _workspaceService.WorkspacePaths.ToArray(); + public string GetWorkspaceRelativePath(ScriptFile scriptFile) => _workspaceService.GetRelativePath(scriptFile); public async Task ShowInformationMessageAsync(string message) diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs index fce8cab20..e8f702321 100644 --- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs @@ -104,6 +104,10 @@ public WorkspaceService(ILoggerFactory factory) #region Public Methods + public IEnumerable WorkspacePaths => WorkspaceFolders.Count == 0 + ? new List { InitialWorkingDirectory } + : WorkspaceFolders.Select(i => i.Uri.GetFileSystemPath()); + /// /// Gets an open file in the workspace. If the file isn't open but exists on the filesystem, load and return it. /// IMPORTANT: Not all documents have a backing file e.g. untitled: scheme documents. Consider using @@ -358,15 +362,11 @@ public IEnumerable EnumeratePSFiles( int maxDepth, bool ignoreReparsePoints) { - IEnumerable rootPaths = WorkspaceFolders.Count == 0 - ? new List { InitialWorkingDirectory } - : WorkspaceFolders.Select(i => i.Uri.GetFileSystemPath()); - Matcher matcher = new(); foreach (string pattern in includeGlobs) { matcher.AddInclude(pattern); } foreach (string pattern in excludeGlobs) { matcher.AddExclude(pattern); } - foreach (string rootPath in rootPaths) + foreach (string rootPath in WorkspacePaths) { if (!Directory.Exists(rootPath)) { diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs index 1fff5eb3a..a25e4e8db 100644 --- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -80,6 +80,14 @@ internal static WorkspaceService FixturesWorkspace() }; } + [Fact] + public void HasDefaultForWorkspacePaths() + { + WorkspaceService workspace = FixturesWorkspace(); + string actual = Assert.Single(workspace.WorkspacePaths); + Assert.Equal(workspace.InitialWorkingDirectory, actual); + } + // These are the default values for the EnumeratePSFiles() method // in Microsoft.PowerShell.EditorServices.Workspace class private static readonly string[] s_defaultExcludeGlobs = Array.Empty(); From 85fd5beb2ce6e148673286aabbccec948a5b82d2 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Tue, 22 Aug 2023 12:05:23 -0700 Subject: [PATCH 318/327] Use separate overload instead of optional argument So as to now add a binary breaking change. Co-authored-by: Patrick Meinecke --- .../Extensions/EditorWorkspace.cs | 7 ++++++- .../Extensions/IEditorOperations.cs | 8 +++++++- .../Services/Extension/EditorOperationsService.cs | 4 +++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs index 46930cda6..b01c6eca7 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs +++ b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs @@ -39,11 +39,16 @@ public sealed class EditorWorkspace #region Public Methods // TODO: Consider returning bool instead of void to indicate success? + /// + /// Creates a new file in the editor. + /// + public void NewFile() => editorOperations.NewFileAsync(string.Empty).Wait(); + /// /// Creates a new file in the editor. /// /// The content to place in the new file. - public void NewFile(string content = "") => editorOperations.NewFileAsync(content).Wait(); + public void NewFile(string content) => editorOperations.NewFileAsync(content).Wait(); /// /// Opens a file in the workspace. If the file is already open diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs index c349bcae1..3ec33ebc6 100644 --- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs +++ b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs @@ -38,12 +38,18 @@ internal interface IEditorOperations /// The resolved file path. string GetWorkspaceRelativePath(ScriptFile scriptFile); + /// + /// Causes a new untitled file to be created in the editor. + /// + /// A task that can be awaited for completion. + Task NewFileAsync(); + /// /// Causes a new untitled file to be created in the editor. /// /// The content to insert into the new file. /// A task that can be awaited for completion. - Task NewFileAsync(string content = ""); + Task NewFileAsync(string content); /// /// Causes a file to be opened in the editor. If the file is diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs index 4ff235d94..7a7c6e6e7 100644 --- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs @@ -121,7 +121,9 @@ public EditorContext ConvertClientEditorContext( clientContext.CurrentFileLanguage); } - public async Task NewFileAsync(string content = "") + public async Task NewFileAsync() => await NewFileAsync(string.Empty).ConfigureAwait(false); + + public async Task NewFileAsync(string content) { if (!TestHasLanguageServer()) { From 4badbbc259df1fa8222c7266754167c24e38ccbf Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:37:31 -0700 Subject: [PATCH 319/327] Ignore not finding DSC module When the user has set `ErrorActionPreference` this could cause a crash, so we have to override it. Resolves #2037. --- .../Services/PowerShell/Debugging/DscBreakpointCapability.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index d75274fa6..3bbd166ed 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -94,7 +94,8 @@ public static async Task GetDscCapabilityAsync( PSCommand psCommand = new PSCommand() .AddCommand("Import-Module") .AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1") - .AddParameter("PassThru"); + .AddParameter("PassThru") + .AddParameter("ErrorAction", ActionPreference.Ignore); IReadOnlyList dscModule = await psesHost.ExecutePSCommandAsync( From ad619a7519a7937a84b7e0d8d23fd93e4d2165a9 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Fri, 25 Aug 2023 12:53:43 -0700 Subject: [PATCH 320/327] Update CHANGELOG for `v3.10.0` --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d69929bb8..4fa58c805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # PowerShell Editor Services Release History +## v3.10.0 +### Friday, August 25, 2023 + +- 🐛 🔍 [PowerShellEditorServices #2037](https://github.com/PowerShell/PowerShellEditorServices/pull/2058) - Ignore not finding DSC module. +- ✨ 🚨 [vscode-powershell #3484](https://github.com/PowerShell/PowerShellEditorServices/pull/2055) - Move `ConstrainedLanguageMode` tests to separate task. +- ✨ 📟 [PowerShellEditorServices #2054](https://github.com/PowerShell/PowerShellEditorServices/pull/2054) - Update PSReadLine to `v2.3.2-beta2`. +- ✨ 🙏 [PowerShellEditorServices #2053](https://github.com/PowerShell/PowerShellEditorServices/pull/2053) - Fix up extension API. +- 🐛 📟 [PowerShellEditorServices #2050](https://github.com/PowerShell/PowerShellEditorServices/pull/2052) - Fix shell integration for PowerShell 5.1 with strict mode. + ## v3.9.0 ### Wednesday, August 09, 2023 From 46f866428518f5703057bdd067d1c3df52e1d9dc Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Fri, 25 Aug 2023 12:53:44 -0700 Subject: [PATCH 321/327] Bump version to `v3.10.0` --- PowerShellEditorServices.Common.props | 2 +- module/PowerShellEditorServices/PowerShellEditorServices.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index 9bbe7c543..d381bfc84 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.9.0 + 3.10.0 Microsoft © Microsoft Corporation. diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index d59c6ccf9..c6a713b40 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.9.0' +ModuleVersion = '3.10.0' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' From 7d2c1b7f01f6159c695bbe6500c82eeb59df5165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 20:55:07 +0000 Subject: [PATCH 322/327] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/emacs-test.yml | 2 +- .github/workflows/vim-test.yml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4f1d2b042..8f174da10 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dotnet uses: actions/setup-dotnet@v3 diff --git a/.github/workflows/emacs-test.yml b/.github/workflows/emacs-test.yml index 338b7c1b7..5aa591e24 100644 --- a/.github/workflows/emacs-test.yml +++ b/.github/workflows/emacs-test.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dotnet uses: actions/setup-dotnet@v3 diff --git a/.github/workflows/vim-test.yml b/.github/workflows/vim-test.yml index e529f3b14..fdeefd348 100644 --- a/.github/workflows/vim-test.yml +++ b/.github/workflows/vim-test.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dotnet uses: actions/setup-dotnet@v3 @@ -31,13 +31,13 @@ jobs: id: vim - name: Checkout vim-ps1 - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: PProvost/vim-ps1 path: vim-ps1 - name: Checkout LanguageClient-neovim - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: autozimu/LanguageClient-neovim path: LanguageClient-neovim @@ -47,7 +47,7 @@ jobs: working-directory: LanguageClient-neovim - name: Checkout Themis - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: thinca/vim-themis path: vim-themis From ae4abd761143c2969429f67549fdd1ddbedf7e64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 20:11:47 +0000 Subject: [PATCH 323/327] Bump Microsoft.NET.Test.Sdk from 17.7.1 to 17.7.2 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.7.1 to 17.7.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.7.1...v17.7.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../PowerShellEditorServices.Test.E2E.csproj | 2 +- .../PowerShellEditorServices.Test.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 7267cf110..5f4d99362 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index e3e8ae1d7..8a8a21be2 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -32,7 +32,7 @@ - + From cb0487cb9968513646c978711191fd7832b16020 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 7 Sep 2023 13:11:36 -0700 Subject: [PATCH 324/327] Import `PSDesiredStateConfiguration` by name Instead of a hard-coded path which doesn't always work. Also apply `ErrorAction -Ignore` fix to Plaster module. --- .../Debugging/DscBreakpointCapability.cs | 7 ++++--- .../Services/Template/TemplateService.cs | 19 ++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 3bbd166ed..52cef2c72 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -92,8 +92,8 @@ public static async Task GetDscCapabilityAsync( if (!isDscInstalled.HasValue) { PSCommand psCommand = new PSCommand() - .AddCommand("Import-Module") - .AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1") + .AddCommand("Microsoft.PowerShell.Core\\Import-Module") + .AddParameter("-Name", "PSDesiredStateConfiguration") .AddParameter("PassThru") .AddParameter("ErrorAction", ActionPreference.Ignore); @@ -101,7 +101,8 @@ public static async Task GetDscCapabilityAsync( await psesHost.ExecutePSCommandAsync( psCommand, CancellationToken.None, - new PowerShellExecutionOptions { ThrowOnError = false }).ConfigureAwait(false); + new PowerShellExecutionOptions { ThrowOnError = false }) + .ConfigureAwait(false); isDscInstalled = dscModule.Count > 0; logger.LogTrace("Side-by-side DSC module found: " + isDscInstalled.Value); diff --git a/src/PowerShellEditorServices/Services/Template/TemplateService.cs b/src/PowerShellEditorServices/Services/Template/TemplateService.cs index 4de0de520..7accae402 100644 --- a/src/PowerShellEditorServices/Services/Template/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/Template/TemplateService.cs @@ -74,7 +74,10 @@ public async Task ImportPlasterIfInstalledAsync() IReadOnlyList moduleObject = await _executionService.ExecutePSCommandAsync( - psCommand, CancellationToken.None).ConfigureAwait(false); + psCommand, + CancellationToken.None, + new PowerShellExecutionOptions { ThrowOnError = false }) + .ConfigureAwait(false); isPlasterInstalled = moduleObject.Count > 0; _logger.LogTrace("Plaster installed: " + isPlasterInstalled.Value); @@ -86,13 +89,19 @@ await _executionService.ExecutePSCommandAsync( psCommand = new PSCommand(); psCommand - .AddCommand("Import-Module") + .AddCommand("Microsoft.PowerShell.Core\\Import-Module") .AddParameter("ModuleInfo", (PSModuleInfo)moduleObject[0].ImmediateBaseObject) - .AddParameter("PassThru"); + .AddParameter("PassThru") + .AddParameter("ErrorAction", ActionPreference.Ignore); - IReadOnlyList importResult = await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); + IReadOnlyList plasterModule = + await _executionService.ExecutePSCommandAsync( + psCommand, + CancellationToken.None, + new PowerShellExecutionOptions { ThrowOnError = false }) + .ConfigureAwait(false); - isPlasterLoaded = importResult.Count > 0; + isPlasterLoaded = plasterModule.Count > 0; _logger.LogTrace("Plaster loaded: " + isPlasterLoaded); } } From c60d8d4165de938e1838c762a81ce3313767a149 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 7 Sep 2023 13:22:08 -0700 Subject: [PATCH 325/327] Use verbatim strings consistently for qualified modules --- .../Commands/StartEditorServicesCommand.cs | 4 ++-- .../DebugAdapter/Handlers/LaunchAndAttachHandler.cs | 4 ++-- .../PowerShell/Debugging/DscBreakpointCapability.cs | 2 +- .../Services/PowerShell/Handlers/GetCommandHandler.cs | 4 ++-- .../PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs | 4 ++-- .../Services/PowerShell/Utility/CommandHelpers.cs | 2 +- .../Services/PowerShell/Utility/PowerShellExtensions.cs | 6 +++--- .../Services/Template/TemplateService.cs | 2 +- .../Services/Workspace/RemoteFileManagerService.cs | 2 +- .../Debugging/DebugServiceTests.cs | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 10de11254..4589b9a44 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -301,7 +301,7 @@ private void RemovePSReadLineForStartup() { _logger.Log(PsesLogLevel.Verbose, "Removing PSReadLine"); using SMA.PowerShell pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); - bool hasPSReadLine = pwsh.AddCommand(new CmdletInfo("Microsoft.PowerShell.Core\\Get-Module", typeof(GetModuleCommand))) + bool hasPSReadLine = pwsh.AddCommand(new CmdletInfo(@"Microsoft.PowerShell.Core\Get-Module", typeof(GetModuleCommand))) .AddParameter("Name", "PSReadLine") .Invoke() .Count > 0; @@ -310,7 +310,7 @@ private void RemovePSReadLineForStartup() { pwsh.Commands.Clear(); - pwsh.AddCommand(new CmdletInfo("Microsoft.PowerShell.Core\\Remove-Module", typeof(RemoveModuleCommand))) + pwsh.AddCommand(new CmdletInfo(@"Microsoft.PowerShell.Core\Remove-Module", typeof(RemoveModuleCommand))) .AddParameter("Name", "PSReadLine") .AddParameter("ErrorAction", "SilentlyContinue"); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 8b5b4dee6..ac6490c7b 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -378,9 +378,9 @@ await _executionService.ExecutePSCommandAsync( if (request.RunspaceName != null) { PSCommand getRunspaceIdCommand = new PSCommand() - .AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace") + .AddCommand(@"Microsoft.PowerShell.Utility\Get-Runspace") .AddParameter("Name", request.RunspaceName) - .AddCommand("Microsoft.PowerShell.Utility\\Select-Object") + .AddCommand(@"Microsoft.PowerShell.Utility\Select-Object") .AddParameter("ExpandProperty", "Id"); try diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 52cef2c72..da486ccbc 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -92,7 +92,7 @@ public static async Task GetDscCapabilityAsync( if (!isDscInstalled.HasValue) { PSCommand psCommand = new PSCommand() - .AddCommand("Microsoft.PowerShell.Core\\Import-Module") + .AddCommand(@"Microsoft.PowerShell.Core\Import-Module") .AddParameter("-Name", "PSDesiredStateConfiguration") .AddParameter("PassThru") .AddParameter("ErrorAction", ActionPreference.Ignore); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs index 12df83713..a9f48ce52 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs @@ -42,9 +42,9 @@ public async Task> Handle(GetCommandParams request, Cance // Executes the following: // Get-Command -CommandType Function,Cmdlet,ExternalScript | Sort-Object -Property Name psCommand - .AddCommand("Microsoft.PowerShell.Core\\Get-Command") + .AddCommand(@"Microsoft.PowerShell.Core\Get-Command") .AddParameter("CommandType", new[] { "Function", "Cmdlet", "ExternalScript" }) - .AddCommand("Microsoft.PowerShell.Utility\\Sort-Object") + .AddCommand(@"Microsoft.PowerShell.Utility\Sort-Object") .AddParameter("Property", "Name"); IEnumerable result = await _executionService.ExecutePSCommandAsync(psCommand, cancellationToken).ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs index fcf58cce3..bff586d44 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -74,11 +74,11 @@ public async Task Handle(GetRunspaceParams request, Cancella rs.Open(); ps.Runspace = rs; // Returns deserialized Runspaces. For simpler code, we use PSObject and rely on dynamic later. - runspaces = ps.AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace").Invoke(); + runspaces = ps.AddCommand(@"Microsoft.PowerShell.Utility\Get-Runspace").Invoke(); } else { - PSCommand psCommand = new PSCommand().AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace"); + PSCommand psCommand = new PSCommand().AddCommand(@"Microsoft.PowerShell.Utility\Get-Runspace"); // returns (not deserialized) Runspaces. For simpler code, we use PSObject and rely on dynamic later. runspaces = await _executionService.ExecutePSCommandAsync(psCommand, cancellationToken).ConfigureAwait(false); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index 38a581bec..6532c531a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -237,7 +237,7 @@ public static async Task GetAliasesAsync( // our PSRL on idle handler. IReadOnlyList aliases = await executionService.ExecutePSCommandAsync( new PSCommand() - .AddCommand("Microsoft.PowerShell.Core\\Get-Command") + .AddCommand(@"Microsoft.PowerShell.Core\Get-Command") .AddParameter("ListImported", true) .AddParameter("CommandType", CommandTypes.Alias), cancellationToken).ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index d7e85f3e4..1032e70f5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -139,7 +139,7 @@ public static void SetCorrectExecutionPolicy(this PowerShell pwsh, ILogger logge // We want to get the list hierarchy of execution policies // Calling the cmdlet is the simplest way to do that IReadOnlyList policies = pwsh - .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") + .AddCommand(@"Microsoft.PowerShell.Security\Get-ExecutionPolicy") .AddParameter("-List") .InvokeAndClear(); @@ -181,7 +181,7 @@ public static void SetCorrectExecutionPolicy(this PowerShell pwsh, ILogger logge logger.LogTrace("Setting execution policy to {Policy}", policyToSet); try { - pwsh.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") + pwsh.AddCommand(@"Microsoft.PowerShell.Security\Set-ExecutionPolicy") .AddParameter("Scope", ExecutionPolicyScope.Process) .AddParameter("ExecutionPolicy", policyToSet) .AddParameter("Force") @@ -222,7 +222,7 @@ public static void LoadProfiles(this PowerShell pwsh, ProfilePathInfo profilePat public static void ImportModule(this PowerShell pwsh, string moduleNameOrPath) { - pwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module") + pwsh.AddCommand(@"Microsoft.PowerShell.Core\Import-Module") .AddParameter("-Name", moduleNameOrPath) .InvokeAndClear(); } diff --git a/src/PowerShellEditorServices/Services/Template/TemplateService.cs b/src/PowerShellEditorServices/Services/Template/TemplateService.cs index 7accae402..e7295ec8c 100644 --- a/src/PowerShellEditorServices/Services/Template/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/Template/TemplateService.cs @@ -89,7 +89,7 @@ await _executionService.ExecutePSCommandAsync( psCommand = new PSCommand(); psCommand - .AddCommand("Microsoft.PowerShell.Core\\Import-Module") + .AddCommand(@"Microsoft.PowerShell.Core\Import-Module") .AddParameter("ModuleInfo", (PSModuleInfo)moduleObject[0].ImmediateBaseObject) .AddParameter("PassThru") .AddParameter("ErrorAction", ActionPreference.Ignore); diff --git a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index 0a7ed4c74..a3e46f4f1 100644 --- a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -316,7 +316,7 @@ public async Task FetchRemoteFileAsync( { // Load the file contents from the remote machine and create the buffer PSCommand command = new PSCommand() - .AddCommand("Microsoft.PowerShell.Management\\Get-Content") + .AddCommand(@"Microsoft.PowerShell.Management\Get-Content") .AddParameter("Path", remoteFilePath) .AddParameter("Raw"); diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index bfdac2774..294901362 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -1140,7 +1140,7 @@ await debugService.SetCommandBreakpointsAsync( VariableDetailsBase var = Array.Find(variables, v => v.Name == "$file"); VariableDetailsBase[] childVars = await debugService.GetVariables(var.Id, CancellationToken.None).ConfigureAwait(true); Assert.Contains(childVars, i => i.Name is "PSPath"); - Assert.Contains(childVars, i => i.Name is "PSProvider" && i.ValueString is "Microsoft.PowerShell.Core\\FileSystem"); + Assert.Contains(childVars, i => i.Name is "PSProvider" && i.ValueString is @"Microsoft.PowerShell.Core\FileSystem"); Assert.Contains(childVars, i => i.Name is "Exists" && i.ValueString is "$true"); Assert.Contains(childVars, i => i.Name is "LastAccessTime"); } From 9e19755dd639b89b85d0af94f8dd5f1e1ec9fd7c Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 7 Sep 2023 13:26:47 -0700 Subject: [PATCH 326/327] Use verbatim strings in tests' file paths --- .../Extensions/ExtensionCommandTests.cs | 6 +++--- .../Session/PathEscapingTests.cs | 20 +++++++++---------- .../Session/PsesInternalHostTests.cs | 2 +- .../Session/ScriptFileTests.cs | 7 ++++--- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs index 430687f93..613202094 100644 --- a/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs +++ b/test/PowerShellEditorServices.Test/Extensions/ExtensionCommandTests.cs @@ -51,7 +51,7 @@ public void Dispose() [Fact] public async Task CanRegisterAndInvokeCommandWithCmdletName() { - string filePath = TestUtilities.NormalizePath("C:\\Temp\\Test.ps1"); + string filePath = TestUtilities.NormalizePath(@"C:\Temp\Test.ps1"); ScriptFile currentFile = new(new Uri(filePath), "This is a test file", new Version("7.0")); EditorContext editorContext = new( editorOperations: null, @@ -87,7 +87,7 @@ await psesHost.ExecutePSCommandAsync( [Fact] public async Task CanRegisterAndInvokeCommandWithScriptBlock() { - string filePath = TestUtilities.NormalizePath("C:\\Temp\\Test.ps1"); + string filePath = TestUtilities.NormalizePath(@"C:\Temp\Test.ps1"); ScriptFile currentFile = new(new Uri(filePath), "This is a test file", new Version("7.0")); EditorContext editorContext = new( editorOperations: null, @@ -149,7 +149,7 @@ await psesHost.ExecutePSCommandAsync( [Fact] public async Task CanUnregisterCommand() { - string filePath = TestUtilities.NormalizePath("C:\\Temp\\Test.ps1"); + string filePath = TestUtilities.NormalizePath(@"C:\Temp\Test.ps1"); ScriptFile currentFile = new(new Uri(filePath), "This is a test file", new Version("7.0")); EditorContext editorContext = new( editorOperations: null, diff --git a/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs b/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs index ed591a395..3d4f0d479 100644 --- a/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs @@ -12,17 +12,17 @@ public class PathEscapingTests [Theory] [InlineData("DebugTest.ps1", "DebugTest.ps1")] [InlineData("../../DebugTest.ps1", "../../DebugTest.ps1")] - [InlineData("C:\\Users\\me\\Documents\\DebugTest.ps1", "C:\\Users\\me\\Documents\\DebugTest.ps1")] + [InlineData(@"C:\Users\me\Documents\DebugTest.ps1", @"C:\Users\me\Documents\DebugTest.ps1")] [InlineData("/home/me/Documents/weird&folder/script.ps1", "/home/me/Documents/weird&folder/script.ps1")] [InlineData("./path/with some/spaces", "./path/with some/spaces")] - [InlineData("C:\\path\\with[some]brackets\\file.ps1", "C:\\path\\with`[some`]brackets\\file.ps1")] - [InlineData("C:\\look\\an*\\here.ps1", "C:\\look\\an`*\\here.ps1")] + [InlineData(@"C:\path\with[some]brackets\file.ps1", @"C:\path\with`[some`]brackets\file.ps1")] + [InlineData(@"C:\look\an*\here.ps1", @"C:\look\an`*\here.ps1")] [InlineData("/Users/me/Documents/?here.ps1", "/Users/me/Documents/`?here.ps1")] [InlineData("/Brackets [and s]paces/path.ps1", "/Brackets `[and s`]paces/path.ps1")] [InlineData("/CJK.chars/脚本/hello.ps1", "/CJK.chars/脚本/hello.ps1")] [InlineData("/CJK.chars/脚本/[hello].ps1", "/CJK.chars/脚本/`[hello`].ps1")] - [InlineData("C:\\Animals\\утка\\quack.ps1", "C:\\Animals\\утка\\quack.ps1")] - [InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "C:\\&nimals\\утка\\qu`*ck`?.ps1")] + [InlineData(@"C:\Animals\утка\quack.ps1", @"C:\Animals\утка\quack.ps1")] + [InlineData(@"C:\&nimals\утка\qu*ck?.ps1", @"C:\&nimals\утка\qu`*ck`?.ps1")] public void CorrectlyWildcardEscapesPathsNoSpaces(string unescapedPath, string escapedPath) { string extensionEscapedPath = PathUtils.WildcardEscapePath(unescapedPath); @@ -33,17 +33,17 @@ public void CorrectlyWildcardEscapesPathsNoSpaces(string unescapedPath, string e [Theory] [InlineData("DebugTest.ps1", "DebugTest.ps1")] [InlineData("../../DebugTest.ps1", "../../DebugTest.ps1")] - [InlineData("C:\\Users\\me\\Documents\\DebugTest.ps1", "C:\\Users\\me\\Documents\\DebugTest.ps1")] + [InlineData(@"C:\Users\me\Documents\DebugTest.ps1", @"C:\Users\me\Documents\DebugTest.ps1")] [InlineData("/home/me/Documents/weird&folder/script.ps1", "/home/me/Documents/weird&folder/script.ps1")] [InlineData("./path/with some/spaces", "./path/with` some/spaces")] - [InlineData("C:\\path\\with[some]brackets\\file.ps1", "C:\\path\\with`[some`]brackets\\file.ps1")] - [InlineData("C:\\look\\an*\\here.ps1", "C:\\look\\an`*\\here.ps1")] + [InlineData(@"C:\path\with[some]brackets\file.ps1", @"C:\path\with`[some`]brackets\file.ps1")] + [InlineData(@"C:\look\an*\here.ps1", @"C:\look\an`*\here.ps1")] [InlineData("/Users/me/Documents/?here.ps1", "/Users/me/Documents/`?here.ps1")] [InlineData("/Brackets [and s]paces/path.ps1", "/Brackets` `[and` s`]paces/path.ps1")] [InlineData("/CJK chars/脚本/hello.ps1", "/CJK` chars/脚本/hello.ps1")] [InlineData("/CJK chars/脚本/[hello].ps1", "/CJK` chars/脚本/`[hello`].ps1")] - [InlineData("C:\\Animal s\\утка\\quack.ps1", "C:\\Animal` s\\утка\\quack.ps1")] - [InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "C:\\&nimals\\утка\\qu`*ck`?.ps1")] + [InlineData(@"C:\Animal s\утка\quack.ps1", @"C:\Animal` s\утка\quack.ps1")] + [InlineData(@"C:\&nimals\утка\qu*ck?.ps1", @"C:\&nimals\утка\qu`*ck`?.ps1")] public void CorrectlyWildcardEscapesPathsSpaces(string unescapedPath, string escapedPath) { string extensionEscapedPath = PathUtils.WildcardEscapePath(unescapedPath, escapeSpaces: true); diff --git a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs index 76f303681..1e141bbc9 100644 --- a/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs @@ -223,7 +223,7 @@ await psesHost.ExecutePSCommandAsync( [Theory] [InlineData("")] // Regression test for "unset" path. - [InlineData("C:\\Some\\Bad\\Directory")] // Non-existent directory. + [InlineData(@"C:\Some\Bad\Directory")] // Non-existent directory. [InlineData("testhost.dll")] // Existent file. public async Task CanHandleBadInitialWorkingDirectory(string path) { diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index 4839eacfa..11c27c4cf 100644 --- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -654,7 +654,7 @@ public void DocumentUriReturnsCorrectStringForAbsolutePath() scriptFile = new ScriptFile(DocumentUri.FromFileSystemPath(path), emptyStringReader, PowerShellVersion); Assert.Equal("file:///home/NaomiNagata/projects/Rocinate/Proto%3AMole%3Acule.ps1", scriptFile.DocumentUri); - path = "/home/JamesHolden/projects/Rocinate/Proto:Mole\\cule.ps1"; + path = @"/home/JamesHolden/projects/Rocinate/Proto:Mole\cule.ps1"; scriptFile = new ScriptFile(DocumentUri.FromFileSystemPath(path), emptyStringReader, PowerShellVersion); Assert.Equal("file:///home/JamesHolden/projects/Rocinate/Proto%3AMole%5Ccule.ps1", scriptFile.DocumentUri); } @@ -662,12 +662,13 @@ public void DocumentUriReturnsCorrectStringForAbsolutePath() [Trait("Category", "ScriptFile")] [Theory] - [InlineData("C:\\Users\\me\\Documents\\test.ps1", false)] + [InlineData(@"C:\Users\me\Documents\test.ps1", false)] [InlineData("/Users/me/Documents/test.ps1", false)] [InlineData("vscode-notebook-cell:/Users/me/Documents/test.ps1#0001", true)] [InlineData("https://microsoft.com", true)] [InlineData("Untitled:Untitled-1", true)] - [InlineData("'a log statement' > 'c:\\Users\\me\\Documents\\test.txt'\r\n", false)] + [InlineData(@"'a log statement' > 'c:\Users\me\Documents\test.txt' +", false)] public void IsUntitledFileIsCorrect(string path, bool expected) => Assert.Equal(expected, ScriptFile.IsUntitledPath(path)); } } From 0fcb641ad76e10b795c5d06f841802be901372dd Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:57:25 -0700 Subject: [PATCH 327/327] Fix debugging script blocks that aren't in files The code relied on running `list 1 ` but that wasn't being run in the debug context because we allow-list commands to prevent pollution of the history, and missed it. Like the `prompt` and interactive commands (which `list` could be but is not when we run it) we need to check for this exact `list` command and run it under the debugger. Moreover, we also weren't locking the `debugInfoHandle`, nor were we correctly checking if `scriptListingLines` was empty (it was never null), and our shortcut to skip allocation was broken. Actually we can't skip allocation, but we can at least skip superfluous conversions. --- .../Services/DebugAdapter/DebugService.cs | 21 ++++++++++++++----- .../Execution/SynchronousPowerShellTask.cs | 19 ++++++++++------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 0439f4484..9328e5440 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -930,13 +930,24 @@ internal async void OnDebuggerStopAsync(object sender, DebuggerStopEventArgs e) if (_remoteFileManager is not null && string.IsNullOrEmpty(localScriptPath)) { // Get the current script listing and create the buffer - PSCommand command = new PSCommand().AddScript($"list 1 {int.MaxValue}"); + IReadOnlyList scriptListingLines; + await debugInfoHandle.WaitAsync().ConfigureAwait(false); + try + { + // This command must be run through `ExecuteInDebugger`! + PSCommand psCommand = new PSCommand().AddScript($"list 1 {int.MaxValue}"); - IReadOnlyList scriptListingLines = - await _executionService.ExecutePSCommandAsync( - command, CancellationToken.None).ConfigureAwait(false); + scriptListingLines = + await _executionService.ExecutePSCommandAsync( + psCommand, + CancellationToken.None).ConfigureAwait(false); + } + finally + { + debugInfoHandle.Release(); + } - if (scriptListingLines is not null) + if (scriptListingLines.Count > 0) { int linePrefixLength = 0; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index c7a983bd8..7c05b3da2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -73,9 +73,12 @@ public override IReadOnlyList Run(CancellationToken cancellationToken) // state that we sync with the LSP debugger. The former commands we want to send // through PowerShell's `Debugger.ProcessCommand` so that they work as expected, but // the latter we must not send through it else they pollute the history as this - // PowerShell API does not let us exclude them from it. + // PowerShell API does not let us exclude them from it. Notably we also need to send + // the `prompt` command and our special `list 1 ` through the debugger too. + // The former needs the context in order to show `DBG 1>` etc., and the latter is + // used to gather the lines when debugging a script that isn't in a file. return _pwsh.Runspace.Debugger.InBreakpoint - && (PowerShellExecutionOptions.AddToHistory || IsPromptCommand(_psCommand) || _pwsh.Runspace.RunspaceIsRemote) + && (PowerShellExecutionOptions.AddToHistory || IsPromptOrListCommand(_psCommand) || _pwsh.Runspace.RunspaceIsRemote) ? ExecuteInDebugger(cancellationToken) : ExecuteNormally(cancellationToken); } @@ -87,7 +90,7 @@ public override IReadOnlyList Run(CancellationToken cancellationToken) public override string ToString() => _psCommand.GetInvocationText(); - private static bool IsPromptCommand(PSCommand command) + private static bool IsPromptOrListCommand(PSCommand command) { if (command.Commands.Count is not 1 || command.Commands[0] is { IsScript: false } or { Parameters.Count: > 0 }) @@ -96,7 +99,8 @@ private static bool IsPromptCommand(PSCommand command) } string commandText = command.Commands[0].CommandText; - return commandText.Equals("prompt", StringComparison.OrdinalIgnoreCase); + return commandText.Equals("prompt", StringComparison.OrdinalIgnoreCase) + || commandText.Equals($"list 1 {int.MaxValue}", StringComparison.OrdinalIgnoreCase); } private IReadOnlyList ExecuteNormally(CancellationToken cancellationToken) @@ -301,11 +305,10 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT return Array.Empty(); } - // If we've been asked for a PSObject, no need to allocate a new collection - if (typeof(TResult) == typeof(PSObject) - && outputCollection is IReadOnlyList resultCollection) + // If we've been asked for a PSObject, no need to convert + if (typeof(TResult) == typeof(PSObject)) { - return resultCollection; + return new List(outputCollection) as IReadOnlyList; } // Otherwise, convert things over