18
18
using Microsoft . Windows . PowerShell . ScriptAnalyzer . Hosting ;
19
19
using Microsoft . Windows . PowerShell . ScriptAnalyzer ;
20
20
using Microsoft . PowerShell . EditorServices . Utility ;
21
+ using Microsoft . PowerShell . EditorServices . Services . Analysis ;
22
+ using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
21
23
22
24
namespace Microsoft . PowerShell . EditorServices . Services
23
25
{
@@ -99,42 +101,31 @@ public AnalysisService(ConfigurationService configurationService, ILanguageServe
99
101
#region Public Methods
100
102
101
103
/// <summary>
102
- /// Get PSScriptAnalyzer settings hashtable for PSProvideCommentHelp rule.
104
+ /// Get PSScriptAnalyzer settings for PSProvideCommentHelp rule.
103
105
/// </summary>
104
106
/// <param name="enable">Enable the rule.</param>
105
107
/// <param name="exportedOnly">Analyze only exported functions/cmdlets.</param>
106
108
/// <param name="blockComment">Use block comment or line comment.</param>
107
109
/// <param name="vscodeSnippetCorrection">Return a vscode snipped correction should be returned.</param>
108
110
/// <param name="placement">Place comment help at the given location relative to the function definition.</param>
109
- /// <returns>A PSScriptAnalyzer settings hashtable .</returns>
110
- public static Hashtable GetCommentHelpRuleSettings (
111
+ /// <returns>A PSScriptAnalyzer settings.</returns>
112
+ public Settings GetCommentHelpRuleSettings (
111
113
bool enable ,
112
114
bool exportedOnly ,
113
115
bool blockComment ,
114
116
bool vscodeSnippetCorrection ,
115
117
string placement )
116
118
{
117
- var settings = new Dictionary < string , Hashtable > ( ) ;
118
- var ruleSettings = new Hashtable ( ) ;
119
- ruleSettings . Add ( "Enable" , enable ) ;
120
- ruleSettings . Add ( "ExportedOnly" , exportedOnly ) ;
121
- ruleSettings . Add ( "BlockComment" , blockComment ) ;
122
- ruleSettings . Add ( "VSCodeSnippetCorrection" , vscodeSnippetCorrection ) ;
123
- ruleSettings . Add ( "Placement" , placement ) ;
124
- settings . Add ( "PSProvideCommentHelp" , ruleSettings ) ;
125
-
126
- var hashtable = new Hashtable ( ) ;
127
- var ruleSettingsHashtable = new Hashtable ( ) ;
128
-
129
- hashtable [ "IncludeRules" ] = settings . Keys . ToArray < object > ( ) ;
130
- hashtable [ "Rules" ] = ruleSettingsHashtable ;
131
-
132
- foreach ( var kvp in settings )
133
- {
134
- ruleSettingsHashtable . Add ( kvp . Key , kvp . Value ) ;
135
- }
119
+ var pssaSettings = _analyzer . CreateSettings ( ) ;
120
+ pssaSettings . AddRuleArgument ( "PSProvideCommentHelp" , new Dictionary < string , object > {
121
+ { "Enable" , enable } ,
122
+ { "ExportedOnly" , exportedOnly } ,
123
+ { "BlockComment" , blockComment } ,
124
+ { "VSCodeSnippetCorrection" , vscodeSnippetCorrection } ,
125
+ { "Placement" , placement }
126
+ } ) ;
136
127
137
- return hashtable ;
128
+ return pssaSettings ;
138
129
}
139
130
140
131
/// <summary>
@@ -143,15 +134,15 @@ public static Hashtable GetCommentHelpRuleSettings(
143
134
/// <param name="scriptContent">The script content to be analyzed.</param>
144
135
/// <param name="settings">ScriptAnalyzer settings</param>
145
136
/// <returns></returns>
146
- public async Task < List < ScriptFileMarker > > GetSemanticMarkersAsync (
137
+ public async Task < List < Diagnostic > > GetSemanticMarkersAsync (
147
138
string scriptContent ,
148
- Hashtable settings )
139
+ Settings settings )
149
140
{
150
141
AnalyzerResult analyzerResult = await _analyzer . AnalyzeAsync (
151
142
scriptContent ,
152
- _analyzer . CreateSettings ( settings ) ) ;
143
+ settings ) ;
153
144
154
- return analyzerResult . Result . Select ( ScriptFileMarker . FromDiagnosticRecord ) . ToList ( ) ;
145
+ return analyzerResult . Result . Select ( DiagnosticCreationHelper . FromDiagnosticRecord ) . ToList ( ) ;
155
146
}
156
147
157
148
/// <summary>
@@ -228,6 +219,11 @@ public async Task RunScriptDiagnosticsAsync(
228
219
229
220
foreach ( ScriptFile file in filesToAnalyze )
230
221
{
222
+ if ( ! ct . CanBeCanceled || ct . IsCancellationRequested )
223
+ {
224
+ break ;
225
+ }
226
+
231
227
AnalyzerResult analyzerResult = await _analyzer . AnalyzeAsync (
232
228
file . ScriptAst ,
233
229
file . ScriptTokens ,
@@ -239,8 +235,77 @@ public async Task RunScriptDiagnosticsAsync(
239
235
break ;
240
236
}
241
237
242
- IEnumerable < ScriptFileMarker > scriptFileMarkers = analyzerResult . Result . Select ( ScriptFileMarker . FromDiagnosticRecord ) ;
243
- file . DiagnosticMarkers . AddRange ( scriptFileMarkers ) ;
238
+ // Create the entry for this file if it does not already exist
239
+ SemaphoreSlim fileLock ;
240
+ Dictionary < string , MarkerCorrection > fileCorrections ;
241
+ bool newEntryNeeded = false ;
242
+ if ( _mostRecentCorrectionsByFile . TryGetValue ( file . DocumentUri , out ( SemaphoreSlim , Dictionary < string , MarkerCorrection > ) fileCorrectionsEntry ) )
243
+ {
244
+ fileLock = fileCorrectionsEntry . Item1 ;
245
+ fileCorrections = fileCorrectionsEntry . Item2 ;
246
+ }
247
+ else
248
+ {
249
+ fileLock = AsyncUtils . CreateSimpleLockingSemaphore ( ) ;
250
+ fileCorrections = new Dictionary < string , MarkerCorrection > ( ) ;
251
+ newEntryNeeded = true ;
252
+ }
253
+
254
+ await fileLock . WaitAsync ( ) ;
255
+ try
256
+ {
257
+ if ( newEntryNeeded )
258
+ {
259
+ // If we create a new entry, we should do it after acquiring the lock we just created
260
+ // to ensure a competing thread can never acquire it first and read invalid information from it
261
+ _mostRecentCorrectionsByFile [ file . DocumentUri ] = ( fileLock , fileCorrections ) ;
262
+ }
263
+ else
264
+ {
265
+ // Otherwise we need to clear the stale corrections
266
+ fileCorrections . Clear ( ) ;
267
+ }
268
+
269
+ foreach ( DiagnosticRecord diagnosticRecord in analyzerResult . Result )
270
+ {
271
+ var diagnostic = DiagnosticCreationHelper . FromDiagnosticRecord ( diagnosticRecord ) ;
272
+ file . DiagnosticMarkers . Add ( diagnostic ) ;
273
+
274
+ // Does the marker contain a correction?
275
+ //Diagnostic markerDiagnostic = GetDiagnosticFromMarker(marker);
276
+ if ( diagnosticRecord . SuggestedCorrections != null )
277
+ {
278
+ var editRegions = new List < ScriptRegion > ( ) ;
279
+ string correctionMessage = null ;
280
+ foreach ( dynamic suggestedCorrection in diagnosticRecord . SuggestedCorrections )
281
+ {
282
+ editRegions . Add (
283
+ new ScriptRegion (
284
+ diagnosticRecord . ScriptPath ,
285
+ suggestedCorrection . Text ,
286
+ startLineNumber : suggestedCorrection . StartLineNumber ,
287
+ startColumnNumber : suggestedCorrection . StartColumnNumber ,
288
+ endLineNumber : suggestedCorrection . EndLineNumber ,
289
+ endColumnNumber : suggestedCorrection . EndColumnNumber ,
290
+ startOffset : - 1 ,
291
+ endOffset : - 1 ) ) ;
292
+
293
+ correctionMessage = suggestedCorrection . Description ;
294
+ }
295
+
296
+ string diagnosticId = GetUniqueIdFromDiagnostic ( diagnostic ) ;
297
+ fileCorrections [ diagnosticId ] = new MarkerCorrection
298
+ {
299
+ Name = correctionMessage == null ? diagnosticRecord . Message : correctionMessage ,
300
+ Edits = editRegions . ToArray ( )
301
+ } ;
302
+ }
303
+ }
304
+ }
305
+ finally
306
+ {
307
+ fileLock . Release ( ) ;
308
+ }
244
309
}
245
310
}
246
311
catch ( TaskCanceledException )
@@ -256,13 +321,8 @@ public async Task RunScriptDiagnosticsAsync(
256
321
}
257
322
}
258
323
259
- public void ClearMarkers ( ScriptFile scriptFile )
260
- {
261
- // send empty diagnostic markers to clear any markers associated with the given file.
262
- PublishScriptDiagnostics (
263
- scriptFile ,
264
- new List < ScriptFileMarker > ( ) ) ;
265
- }
324
+ // send empty diagnostic markers to clear any markers associated with the given file.
325
+ public void ClearMarkers ( ScriptFile scriptFile ) => PublishScriptDiagnostics ( scriptFile , new List < Diagnostic > ( ) ) ;
266
326
267
327
public async Task < IReadOnlyDictionary < string , MarkerCorrection > > GetMostRecentCodeActionsForFileAsync ( string documentUri )
268
328
{
@@ -313,63 +373,34 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
313
373
return id ;
314
374
}
315
375
376
+ /// <summary>
377
+ /// Uses the PSScriptAnalyzer rule 'PSProvideCommentHelp' to get the comment-based help for a function string passed in.
378
+ /// </summary>
379
+ /// <param name="funcText">The string representation of the function we will get help for.</param>
380
+ /// <param name="blockComment">Use block comment or line comment.</param>
381
+ /// <param name="placement">Place comment help at the given location relative to the function definition.</param>
382
+ /// <returns>A PSScriptAnalyzer settings.</returns>
383
+ internal async Task < string > GetCommentHelpCorrectionTextAsync ( string funcText , bool blockComment , string placement )
384
+ {
385
+ Settings commentHelpSettings = GetCommentHelpRuleSettings (
386
+ enable : true ,
387
+ exportedOnly : false ,
388
+ blockComment : blockComment ,
389
+ vscodeSnippetCorrection : true ,
390
+ placement : placement ) ;
391
+
392
+ AnalyzerResult analyzerResult = await _analyzer . AnalyzeAsync ( funcText , commentHelpSettings ) ;
393
+ return analyzerResult . Result
394
+ . Single ( record => record . RuleName == "PSProvideCommentHelp" )
395
+ . SuggestedCorrections . Single ( ) . Text ;
396
+ }
397
+
316
398
#endregion
317
399
318
400
private void PublishScriptDiagnostics (
319
401
ScriptFile scriptFile ,
320
- List < ScriptFileMarker > markers )
402
+ List < Diagnostic > diagnostics )
321
403
{
322
- var diagnostics = new List < Diagnostic > ( ) ;
323
-
324
- // Create the entry for this file if it does not already exist
325
- SemaphoreSlim fileLock ;
326
- Dictionary < string , MarkerCorrection > fileCorrections ;
327
- bool newEntryNeeded = false ;
328
- if ( _mostRecentCorrectionsByFile . TryGetValue ( scriptFile . DocumentUri , out ( SemaphoreSlim , Dictionary < string , MarkerCorrection > ) fileCorrectionsEntry ) )
329
- {
330
- fileLock = fileCorrectionsEntry . Item1 ;
331
- fileCorrections = fileCorrectionsEntry . Item2 ;
332
- }
333
- else
334
- {
335
- fileLock = new SemaphoreSlim ( initialCount : 1 , maxCount : 1 ) ;
336
- fileCorrections = new Dictionary < string , MarkerCorrection > ( ) ;
337
- newEntryNeeded = true ;
338
- }
339
-
340
- fileLock . Wait ( ) ;
341
- try
342
- {
343
- if ( newEntryNeeded )
344
- {
345
- // If we create a new entry, we should do it after acquiring the lock we just created
346
- // to ensure a competing thread can never acquire it first and read invalid information from it
347
- _mostRecentCorrectionsByFile [ scriptFile . DocumentUri ] = ( fileLock , fileCorrections ) ;
348
- }
349
- else
350
- {
351
- // Otherwise we need to clear the stale corrections
352
- fileCorrections . Clear ( ) ;
353
- }
354
-
355
- foreach ( ScriptFileMarker marker in markers )
356
- {
357
- // Does the marker contain a correction?
358
- Diagnostic markerDiagnostic = GetDiagnosticFromMarker ( marker ) ;
359
- if ( marker . Correction != null )
360
- {
361
- string diagnosticId = GetUniqueIdFromDiagnostic ( markerDiagnostic ) ;
362
- fileCorrections [ diagnosticId ] = marker . Correction ;
363
- }
364
-
365
- diagnostics . Add ( markerDiagnostic ) ;
366
- }
367
- }
368
- finally
369
- {
370
- fileLock . Release ( ) ;
371
- }
372
-
373
404
// Always send syntax and semantic errors. We want to
374
405
// make sure no out-of-date markers are being displayed.
375
406
_languageServer . Document . PublishDiagnostics ( new PublishDiagnosticsParams ( )
@@ -378,47 +409,5 @@ private void PublishScriptDiagnostics(
378
409
Diagnostics = new Container < Diagnostic > ( diagnostics ) ,
379
410
} ) ;
380
411
}
381
-
382
- private static Diagnostic GetDiagnosticFromMarker ( ScriptFileMarker scriptFileMarker )
383
- {
384
- return new Diagnostic
385
- {
386
- Severity = MapDiagnosticSeverity ( scriptFileMarker . Level ) ,
387
- Message = scriptFileMarker . Message ,
388
- Code = scriptFileMarker . RuleName ,
389
- Source = scriptFileMarker . Source ,
390
- Range = new OmniSharp . Extensions . LanguageServer . Protocol . Models . Range
391
- {
392
- Start = new OmniSharp . Extensions . LanguageServer . Protocol . Models . Position
393
- {
394
- Line = scriptFileMarker . ScriptRegion . StartLineNumber - 1 ,
395
- Character = scriptFileMarker . ScriptRegion . StartColumnNumber - 1
396
- } ,
397
- End = new OmniSharp . Extensions . LanguageServer . Protocol . Models . Position
398
- {
399
- Line = scriptFileMarker . ScriptRegion . EndLineNumber - 1 ,
400
- Character = scriptFileMarker . ScriptRegion . EndColumnNumber - 1
401
- }
402
- }
403
- } ;
404
- }
405
-
406
- private static DiagnosticSeverity MapDiagnosticSeverity ( ScriptFileMarkerLevel markerLevel )
407
- {
408
- switch ( markerLevel )
409
- {
410
- case ScriptFileMarkerLevel . Error :
411
- return DiagnosticSeverity . Error ;
412
-
413
- case ScriptFileMarkerLevel . Warning :
414
- return DiagnosticSeverity . Warning ;
415
-
416
- case ScriptFileMarkerLevel . Information :
417
- return DiagnosticSeverity . Information ;
418
-
419
- default :
420
- return DiagnosticSeverity . Error ;
421
- }
422
- }
423
412
}
424
413
}
0 commit comments