77using System . Collections ;
88using System . Collections . Concurrent ;
99using System . Collections . Generic ;
10+ using System . IO ;
1011using System . Linq ;
12+ using System . Runtime . InteropServices ;
1113using System . Text ;
1214using System . Threading ;
1315using System . Threading . Tasks ;
@@ -91,10 +93,12 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
9193
9294 private readonly ConcurrentDictionary < ScriptFile , CorrectionTableEntry > _mostRecentCorrectionsByFile ;
9395
94- private Lazy < PssaCmdletAnalysisEngine > _analysisEngine ;
96+ private Lazy < PssaCmdletAnalysisEngine > _analysisEngineLazy ;
9597
9698 private CancellationTokenSource _diagnosticsCancellationTokenSource ;
9799
100+ private string _pssaSettingsFilePath ;
101+
98102 /// <summary>
99103 /// Construct a new AnalysisService.
100104 /// </summary>
@@ -115,13 +119,14 @@ public AnalysisService(
115119 _workplaceService = workspaceService ;
116120 _analysisDelayMillis = 750 ;
117121 _mostRecentCorrectionsByFile = new ConcurrentDictionary < ScriptFile , CorrectionTableEntry > ( ) ;
118- _analysisEngine = new Lazy < PssaCmdletAnalysisEngine > ( InstantiateAnalysisEngine ) ;
122+ _analysisEngineLazy = new Lazy < PssaCmdletAnalysisEngine > ( InstantiateAnalysisEngine ) ;
123+ _pssaSettingsFilePath = null ;
119124 }
120125
121126 /// <summary>
122127 /// The analysis engine to use for running script analysis.
123128 /// </summary>
124- private PssaCmdletAnalysisEngine AnalysisEngine => _analysisEngine . Value ;
129+ private PssaCmdletAnalysisEngine AnalysisEngine => _analysisEngineLazy ? . Value ;
125130
126131 /// <summary>
127132 /// Sets up a script analysis run, eventually returning the result.
@@ -133,6 +138,8 @@ public void RunScriptDiagnostics(
133138 ScriptFile [ ] filesToAnalyze ,
134139 CancellationToken cancellationToken )
135140 {
141+ EnsureEngineSettingsCurrent ( ) ;
142+
136143 if ( AnalysisEngine == null )
137144 {
138145 return ;
@@ -183,6 +190,8 @@ public void RunScriptDiagnostics(
183190 /// <returns>The text of the formatted PowerShell script.</returns>
184191 public Task < string > FormatAsync ( string scriptFileContents , Hashtable formatSettings , int [ ] formatRange = null )
185192 {
193+ EnsureEngineSettingsCurrent ( ) ;
194+
186195 if ( AnalysisEngine == null )
187196 {
188197 return null ;
@@ -254,23 +263,61 @@ public void ClearMarkers(ScriptFile file)
254263 /// <param name="settings">The new language server settings.</param>
255264 public void OnConfigurationUpdated ( object sender , LanguageServerSettings settings )
256265 {
257- ClearOpenFileMarkers ( ) ;
258- _analysisEngine = new Lazy < PssaCmdletAnalysisEngine > ( InstantiateAnalysisEngine ) ;
266+ InitializeAnalysisEngineToCurrentSettings ( ) ;
259267 }
260268
261- private PssaCmdletAnalysisEngine InstantiateAnalysisEngine ( )
269+ private void EnsureEngineSettingsCurrent ( )
262270 {
263- if ( ! ( _configurationService . CurrentSettings . ScriptAnalysis . Enable ?? false ) )
271+ if ( _pssaSettingsFilePath != null
272+ && ! File . Exists ( _pssaSettingsFilePath ) )
264273 {
265- return null ;
274+ InitializeAnalysisEngineToCurrentSettings ( ) ;
275+ }
276+ }
277+
278+ private void InitializeAnalysisEngineToCurrentSettings ( )
279+ {
280+ // If script analysis has been disabled, just return null
281+ if ( _configurationService . CurrentSettings . ScriptAnalysis . Enable != true )
282+ {
283+ if ( _analysisEngineLazy != null && _analysisEngineLazy . IsValueCreated )
284+ {
285+ _analysisEngineLazy . Value . Dispose ( ) ;
286+ }
287+
288+ _analysisEngineLazy = null ;
289+ return ;
290+ }
291+
292+ // We may be triggered after the lazy factory is set,
293+ // but before it's been able to instantiate
294+ if ( _analysisEngineLazy == null )
295+ {
296+ _analysisEngineLazy = new Lazy < PssaCmdletAnalysisEngine > ( InstantiateAnalysisEngine ) ;
297+ return ;
298+ }
299+ else if ( ! _analysisEngineLazy . IsValueCreated )
300+ {
301+ return ;
266302 }
267303
304+ // Retrieve the current script analysis engine so we can recreate it after we've overridden it
305+ PssaCmdletAnalysisEngine currentAnalysisEngine = AnalysisEngine ;
306+
307+ // Clear the open file markers and set the new engine factory
308+ ClearOpenFileMarkers ( ) ;
309+ _analysisEngineLazy = new Lazy < PssaCmdletAnalysisEngine > ( ( ) => RecreateAnalysisEngine ( currentAnalysisEngine ) ) ;
310+ }
311+
312+ private PssaCmdletAnalysisEngine InstantiateAnalysisEngine ( )
313+ {
268314 var pssaCmdletEngineBuilder = new PssaCmdletAnalysisEngine . Builder ( _loggerFactory ) ;
269315
270316 // If there's a settings file use that
271317 if ( TryFindSettingsFile ( out string settingsFilePath ) )
272318 {
273319 _logger . LogInformation ( $ "Configuring PSScriptAnalyzer with rules at '{ settingsFilePath } '") ;
320+ _pssaSettingsFilePath = settingsFilePath ;
274321 pssaCmdletEngineBuilder . WithSettingsFile ( settingsFilePath ) ;
275322 }
276323 else
@@ -282,26 +329,40 @@ private PssaCmdletAnalysisEngine InstantiateAnalysisEngine()
282329 return pssaCmdletEngineBuilder . Build ( ) ;
283330 }
284331
332+ private PssaCmdletAnalysisEngine RecreateAnalysisEngine ( PssaCmdletAnalysisEngine oldAnalysisEngine )
333+ {
334+ if ( TryFindSettingsFile ( out string settingsFilePath ) )
335+ {
336+ _logger . LogInformation ( $ "Recreating analysis engine with rules at '{ settingsFilePath } '") ;
337+ _pssaSettingsFilePath = settingsFilePath ;
338+ return oldAnalysisEngine . RecreateWithNewSettings ( settingsFilePath ) ;
339+ }
340+
341+ _logger . LogInformation ( "PSScriptAnalyzer settings file not found. Falling back to default rules" ) ;
342+ return oldAnalysisEngine . RecreateWithRules ( s_defaultRules ) ;
343+ }
344+
285345 private bool TryFindSettingsFile ( out string settingsFilePath )
286346 {
287347 string configuredPath = _configurationService . CurrentSettings . ScriptAnalysis . SettingsPath ;
288348
289- if ( ! string . IsNullOrEmpty ( configuredPath ) )
349+ if ( string . IsNullOrEmpty ( configuredPath ) )
290350 {
291- settingsFilePath = _workplaceService . ResolveWorkspacePath ( configuredPath ) ;
351+ settingsFilePath = null ;
352+ return false ;
353+ }
292354
293- if ( settingsFilePath == null )
294- {
295- _logger . LogError ( $ "Unable to find PSSA settings file at '{ configuredPath } '. Loading default rules.") ;
296- }
355+ settingsFilePath = _workplaceService . ResolveWorkspacePath ( configuredPath ) ;
297356
298- return settingsFilePath != null ;
357+ if ( settingsFilePath == null
358+ || ! File . Exists ( settingsFilePath ) )
359+ {
360+ _logger . LogWarning ( $ "Unable to find PSSA settings file at '{ configuredPath } '. Loading default rules.") ;
361+ settingsFilePath = null ;
362+ return false ;
299363 }
300364
301- // TODO: Could search for a default here
302-
303- settingsFilePath = null ;
304- return false ;
365+ return true ;
305366 }
306367
307368 private void ClearOpenFileMarkers ( )
@@ -442,7 +503,12 @@ protected virtual void Dispose(bool disposing)
442503 {
443504 if ( disposing )
444505 {
445- if ( _analysisEngine . IsValueCreated ) { _analysisEngine . Value . Dispose ( ) ; }
506+ if ( _analysisEngineLazy != null
507+ && _analysisEngineLazy . IsValueCreated )
508+ {
509+ _analysisEngineLazy . Value . Dispose ( ) ;
510+ }
511+
446512 _diagnosticsCancellationTokenSource ? . Dispose ( ) ;
447513 }
448514
0 commit comments