7
7
using System . Collections ;
8
8
using System . Collections . Concurrent ;
9
9
using System . Collections . Generic ;
10
+ using System . IO ;
10
11
using System . Linq ;
12
+ using System . Runtime . InteropServices ;
11
13
using System . Text ;
12
14
using System . Threading ;
13
15
using System . Threading . Tasks ;
@@ -91,10 +93,12 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
91
93
92
94
private readonly ConcurrentDictionary < ScriptFile , CorrectionTableEntry > _mostRecentCorrectionsByFile ;
93
95
94
- private Lazy < PssaCmdletAnalysisEngine > _analysisEngine ;
96
+ private Lazy < PssaCmdletAnalysisEngine > _analysisEngineLazy ;
95
97
96
98
private CancellationTokenSource _diagnosticsCancellationTokenSource ;
97
99
100
+ private string _pssaSettingsFilePath ;
101
+
98
102
/// <summary>
99
103
/// Construct a new AnalysisService.
100
104
/// </summary>
@@ -115,13 +119,14 @@ public AnalysisService(
115
119
_workplaceService = workspaceService ;
116
120
_analysisDelayMillis = 750 ;
117
121
_mostRecentCorrectionsByFile = new ConcurrentDictionary < ScriptFile , CorrectionTableEntry > ( ) ;
118
- _analysisEngine = new Lazy < PssaCmdletAnalysisEngine > ( InstantiateAnalysisEngine ) ;
122
+ _analysisEngineLazy = new Lazy < PssaCmdletAnalysisEngine > ( InstantiateAnalysisEngine ) ;
123
+ _pssaSettingsFilePath = null ;
119
124
}
120
125
121
126
/// <summary>
122
127
/// The analysis engine to use for running script analysis.
123
128
/// </summary>
124
- private PssaCmdletAnalysisEngine AnalysisEngine => _analysisEngine . Value ;
129
+ private PssaCmdletAnalysisEngine AnalysisEngine => _analysisEngineLazy ? . Value ;
125
130
126
131
/// <summary>
127
132
/// Sets up a script analysis run, eventually returning the result.
@@ -133,6 +138,8 @@ public void RunScriptDiagnostics(
133
138
ScriptFile [ ] filesToAnalyze ,
134
139
CancellationToken cancellationToken )
135
140
{
141
+ EnsureEngineSettingsCurrent ( ) ;
142
+
136
143
if ( AnalysisEngine == null )
137
144
{
138
145
return ;
@@ -183,6 +190,8 @@ public void RunScriptDiagnostics(
183
190
/// <returns>The text of the formatted PowerShell script.</returns>
184
191
public Task < string > FormatAsync ( string scriptFileContents , Hashtable formatSettings , int [ ] formatRange = null )
185
192
{
193
+ EnsureEngineSettingsCurrent ( ) ;
194
+
186
195
if ( AnalysisEngine == null )
187
196
{
188
197
return null ;
@@ -254,23 +263,61 @@ public void ClearMarkers(ScriptFile file)
254
263
/// <param name="settings">The new language server settings.</param>
255
264
public void OnConfigurationUpdated ( object sender , LanguageServerSettings settings )
256
265
{
257
- ClearOpenFileMarkers ( ) ;
258
- _analysisEngine = new Lazy < PssaCmdletAnalysisEngine > ( InstantiateAnalysisEngine ) ;
266
+ InitializeAnalysisEngineToCurrentSettings ( ) ;
259
267
}
260
268
261
- private PssaCmdletAnalysisEngine InstantiateAnalysisEngine ( )
269
+ private void EnsureEngineSettingsCurrent ( )
262
270
{
263
- if ( ! ( _configurationService . CurrentSettings . ScriptAnalysis . Enable ?? false ) )
271
+ if ( _pssaSettingsFilePath != null
272
+ && ! File . Exists ( _pssaSettingsFilePath ) )
264
273
{
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 ;
266
302
}
267
303
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
+ {
268
314
var pssaCmdletEngineBuilder = new PssaCmdletAnalysisEngine . Builder ( _loggerFactory ) ;
269
315
270
316
// If there's a settings file use that
271
317
if ( TryFindSettingsFile ( out string settingsFilePath ) )
272
318
{
273
319
_logger . LogInformation ( $ "Configuring PSScriptAnalyzer with rules at '{ settingsFilePath } '") ;
320
+ _pssaSettingsFilePath = settingsFilePath ;
274
321
pssaCmdletEngineBuilder . WithSettingsFile ( settingsFilePath ) ;
275
322
}
276
323
else
@@ -282,26 +329,40 @@ private PssaCmdletAnalysisEngine InstantiateAnalysisEngine()
282
329
return pssaCmdletEngineBuilder . Build ( ) ;
283
330
}
284
331
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
+
285
345
private bool TryFindSettingsFile ( out string settingsFilePath )
286
346
{
287
347
string configuredPath = _configurationService . CurrentSettings . ScriptAnalysis . SettingsPath ;
288
348
289
- if ( ! string . IsNullOrEmpty ( configuredPath ) )
349
+ if ( string . IsNullOrEmpty ( configuredPath ) )
290
350
{
291
- settingsFilePath = _workplaceService . ResolveWorkspacePath ( configuredPath ) ;
351
+ settingsFilePath = null ;
352
+ return false ;
353
+ }
292
354
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 ) ;
297
356
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 ;
299
363
}
300
364
301
- // TODO: Could search for a default here
302
-
303
- settingsFilePath = null ;
304
- return false ;
365
+ return true ;
305
366
}
306
367
307
368
private void ClearOpenFileMarkers ( )
@@ -442,7 +503,12 @@ protected virtual void Dispose(bool disposing)
442
503
{
443
504
if ( disposing )
444
505
{
445
- if ( _analysisEngine . IsValueCreated ) { _analysisEngine . Value . Dispose ( ) ; }
506
+ if ( _analysisEngineLazy != null
507
+ && _analysisEngineLazy . IsValueCreated )
508
+ {
509
+ _analysisEngineLazy . Value . Dispose ( ) ;
510
+ }
511
+
446
512
_diagnosticsCancellationTokenSource ? . Dispose ( ) ;
447
513
}
448
514
0 commit comments