1818using Microsoft . Windows . PowerShell . ScriptAnalyzer . Hosting ;
1919using Microsoft . Windows . PowerShell . ScriptAnalyzer ;
2020using Microsoft . PowerShell . EditorServices . Utility ;
21+ using Microsoft . PowerShell . EditorServices . Services . Analysis ;
22+ using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
2123
2224namespace Microsoft . PowerShell . EditorServices . Services
2325{
@@ -99,42 +101,31 @@ public AnalysisService(ConfigurationService configurationService, ILanguageServe
99101 #region Public Methods
100102
101103 /// <summary>
102- /// Get PSScriptAnalyzer settings hashtable for PSProvideCommentHelp rule.
104+ /// Get PSScriptAnalyzer settings for PSProvideCommentHelp rule.
103105 /// </summary>
104106 /// <param name="enable">Enable the rule.</param>
105107 /// <param name="exportedOnly">Analyze only exported functions/cmdlets.</param>
106108 /// <param name="blockComment">Use block comment or line comment.</param>
107109 /// <param name="vscodeSnippetCorrection">Return a vscode snipped correction should be returned.</param>
108110 /// <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 (
111113 bool enable ,
112114 bool exportedOnly ,
113115 bool blockComment ,
114116 bool vscodeSnippetCorrection ,
115117 string placement )
116118 {
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+ } ) ;
136127
137- return hashtable ;
128+ return pssaSettings ;
138129 }
139130
140131 /// <summary>
@@ -143,15 +134,15 @@ public static Hashtable GetCommentHelpRuleSettings(
143134 /// <param name="scriptContent">The script content to be analyzed.</param>
144135 /// <param name="settings">ScriptAnalyzer settings</param>
145136 /// <returns></returns>
146- public async Task < List < ScriptFileMarker > > GetSemanticMarkersAsync (
137+ public async Task < List < Diagnostic > > GetSemanticMarkersAsync (
147138 string scriptContent ,
148- Hashtable settings )
139+ Settings settings )
149140 {
150141 AnalyzerResult analyzerResult = await _analyzer . AnalyzeAsync (
151142 scriptContent ,
152- _analyzer . CreateSettings ( settings ) ) ;
143+ settings ) ;
153144
154- return analyzerResult . Result . Select ( ScriptFileMarker . FromDiagnosticRecord ) . ToList ( ) ;
145+ return analyzerResult . Result . Select ( DiagnosticCreationHelper . FromDiagnosticRecord ) . ToList ( ) ;
155146 }
156147
157148 /// <summary>
@@ -228,6 +219,11 @@ public async Task RunScriptDiagnosticsAsync(
228219
229220 foreach ( ScriptFile file in filesToAnalyze )
230221 {
222+ if ( ! ct . CanBeCanceled || ct . IsCancellationRequested )
223+ {
224+ break ;
225+ }
226+
231227 AnalyzerResult analyzerResult = await _analyzer . AnalyzeAsync (
232228 file . ScriptAst ,
233229 file . ScriptTokens ,
@@ -239,8 +235,77 @@ public async Task RunScriptDiagnosticsAsync(
239235 break ;
240236 }
241237
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+ }
244309 }
245310 }
246311 catch ( TaskCanceledException )
@@ -256,13 +321,8 @@ public async Task RunScriptDiagnosticsAsync(
256321 }
257322 }
258323
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 > ( ) ) ;
266326
267327 public async Task < IReadOnlyDictionary < string , MarkerCorrection > > GetMostRecentCodeActionsForFileAsync ( string documentUri )
268328 {
@@ -313,63 +373,34 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
313373 return id ;
314374 }
315375
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+
316398 #endregion
317399
318400 private void PublishScriptDiagnostics (
319401 ScriptFile scriptFile ,
320- List < ScriptFileMarker > markers )
402+ List < Diagnostic > diagnostics )
321403 {
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-
373404 // Always send syntax and semantic errors. We want to
374405 // make sure no out-of-date markers are being displayed.
375406 _languageServer . Document . PublishDiagnostics ( new PublishDiagnosticsParams ( )
@@ -378,47 +409,5 @@ private void PublishScriptDiagnostics(
378409 Diagnostics = new Container < Diagnostic > ( diagnostics ) ,
379410 } ) ;
380411 }
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- }
423412 }
424413}
0 commit comments