diff --git a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index 5fd9f5ef0..873a269f9 100644 --- a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -7,7 +7,9 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -91,10 +93,12 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic) private readonly ConcurrentDictionary _mostRecentCorrectionsByFile; - private Lazy _analysisEngine; + private Lazy _analysisEngineLazy; private CancellationTokenSource _diagnosticsCancellationTokenSource; + private string _pssaSettingsFilePath; + /// /// Construct a new AnalysisService. /// @@ -115,13 +119,14 @@ public AnalysisService( _workplaceService = workspaceService; _analysisDelayMillis = 750; _mostRecentCorrectionsByFile = new ConcurrentDictionary(); - _analysisEngine = new Lazy(InstantiateAnalysisEngine); + _analysisEngineLazy = new Lazy(InstantiateAnalysisEngine); + _pssaSettingsFilePath = null; } /// /// The analysis engine to use for running script analysis. /// - private PssaCmdletAnalysisEngine AnalysisEngine => _analysisEngine.Value; + private PssaCmdletAnalysisEngine AnalysisEngine => _analysisEngineLazy?.Value; /// /// Sets up a script analysis run, eventually returning the result. @@ -133,6 +138,8 @@ public void RunScriptDiagnostics( ScriptFile[] filesToAnalyze, CancellationToken cancellationToken) { + EnsureEngineSettingsCurrent(); + if (AnalysisEngine == null) { return; @@ -183,6 +190,8 @@ public void RunScriptDiagnostics( /// The text of the formatted PowerShell script. public Task FormatAsync(string scriptFileContents, Hashtable formatSettings, int[] formatRange = null) { + EnsureEngineSettingsCurrent(); + if (AnalysisEngine == null) { return null; @@ -254,23 +263,61 @@ public void ClearMarkers(ScriptFile file) /// The new language server settings. public void OnConfigurationUpdated(object sender, LanguageServerSettings settings) { - ClearOpenFileMarkers(); - _analysisEngine = new Lazy(InstantiateAnalysisEngine); + InitializeAnalysisEngineToCurrentSettings(); } - private PssaCmdletAnalysisEngine InstantiateAnalysisEngine() + private void EnsureEngineSettingsCurrent() { - if (!(_configurationService.CurrentSettings.ScriptAnalysis.Enable ?? false)) + if (_pssaSettingsFilePath != null + && !File.Exists(_pssaSettingsFilePath)) { - return null; + InitializeAnalysisEngineToCurrentSettings(); + } + } + + private void InitializeAnalysisEngineToCurrentSettings() + { + // If script analysis has been disabled, just return null + if (_configurationService.CurrentSettings.ScriptAnalysis.Enable != true) + { + if (_analysisEngineLazy != null && _analysisEngineLazy.IsValueCreated) + { + _analysisEngineLazy.Value.Dispose(); + } + + _analysisEngineLazy = null; + return; + } + + // We may be triggered after the lazy factory is set, + // but before it's been able to instantiate + if (_analysisEngineLazy == null) + { + _analysisEngineLazy = new Lazy(InstantiateAnalysisEngine); + return; + } + else if (!_analysisEngineLazy.IsValueCreated) + { + return; } + // Retrieve the current script analysis engine so we can recreate it after we've overridden it + PssaCmdletAnalysisEngine currentAnalysisEngine = AnalysisEngine; + + // Clear the open file markers and set the new engine factory + ClearOpenFileMarkers(); + _analysisEngineLazy = new Lazy(() => RecreateAnalysisEngine(currentAnalysisEngine)); + } + + private PssaCmdletAnalysisEngine InstantiateAnalysisEngine() + { var pssaCmdletEngineBuilder = new PssaCmdletAnalysisEngine.Builder(_loggerFactory); // If there's a settings file use that if (TryFindSettingsFile(out string settingsFilePath)) { _logger.LogInformation($"Configuring PSScriptAnalyzer with rules at '{settingsFilePath}'"); + _pssaSettingsFilePath = settingsFilePath; pssaCmdletEngineBuilder.WithSettingsFile(settingsFilePath); } else @@ -282,26 +329,40 @@ private PssaCmdletAnalysisEngine InstantiateAnalysisEngine() return pssaCmdletEngineBuilder.Build(); } + private PssaCmdletAnalysisEngine RecreateAnalysisEngine(PssaCmdletAnalysisEngine oldAnalysisEngine) + { + if (TryFindSettingsFile(out string settingsFilePath)) + { + _logger.LogInformation($"Recreating analysis engine with rules at '{settingsFilePath}'"); + _pssaSettingsFilePath = settingsFilePath; + return oldAnalysisEngine.RecreateWithNewSettings(settingsFilePath); + } + + _logger.LogInformation("PSScriptAnalyzer settings file not found. Falling back to default rules"); + return oldAnalysisEngine.RecreateWithRules(s_defaultRules); + } + private bool TryFindSettingsFile(out string settingsFilePath) { string configuredPath = _configurationService.CurrentSettings.ScriptAnalysis.SettingsPath; - if (!string.IsNullOrEmpty(configuredPath)) + if (string.IsNullOrEmpty(configuredPath)) { - settingsFilePath = _workplaceService.ResolveWorkspacePath(configuredPath); + settingsFilePath = null; + return false; + } - if (settingsFilePath == null) - { - _logger.LogError($"Unable to find PSSA settings file at '{configuredPath}'. Loading default rules."); - } + settingsFilePath = _workplaceService.ResolveWorkspacePath(configuredPath); - return settingsFilePath != null; + if (settingsFilePath == null + || !File.Exists(settingsFilePath)) + { + _logger.LogWarning($"Unable to find PSSA settings file at '{configuredPath}'. Loading default rules."); + settingsFilePath = null; + return false; } - // TODO: Could search for a default here - - settingsFilePath = null; - return false; + return true; } private void ClearOpenFileMarkers() @@ -442,7 +503,12 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - if (_analysisEngine.IsValueCreated) { _analysisEngine.Value.Dispose(); } + if (_analysisEngineLazy != null + && _analysisEngineLazy.IsValueCreated) + { + _analysisEngineLazy.Value.Dispose(); + } + _diagnosticsCancellationTokenSource?.Dispose(); } diff --git a/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs b/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs index 7eb7e9c02..eb1617c7d 100644 --- a/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs +++ b/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs @@ -261,6 +261,21 @@ public Task AnalyzeScriptAsync(string scriptContent, Hashtab return GetSemanticMarkersFromCommandAsync(command); } + public PssaCmdletAnalysisEngine RecreateWithNewSettings(string settingsPath) + { + return new PssaCmdletAnalysisEngine(_logger, _analysisRunspacePool, _pssaModuleInfo, settingsPath); + } + + public PssaCmdletAnalysisEngine RecreateWithNewSettings(Hashtable settingsHashtable) + { + return new PssaCmdletAnalysisEngine(_logger, _analysisRunspacePool, _pssaModuleInfo, settingsHashtable); + } + + public PssaCmdletAnalysisEngine RecreateWithRules(string[] rules) + { + return new PssaCmdletAnalysisEngine(_logger, _analysisRunspacePool, _pssaModuleInfo, rules); + } + #region IDisposable Support private bool disposedValue = false; // To detect redundant calls