From 00c45232bf2b33f34b3b7f3399eacee6248060a6 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Thu, 28 Jul 2022 10:54:49 -0700 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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); + }); + } + } +}