Skip to content

Commit a038c32

Browse files
remove ScriptFileMarker type and refactor comment-based help
1 parent c91e411 commit a038c32

File tree

8 files changed

+239
-327
lines changed

8 files changed

+239
-327
lines changed

NuGet.Config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
<packageSources>
77
<add key="omnisharp" value="https://www.myget.org/F/omnisharp/api/v3/index.json" protocolVersion="3" />
88
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
9+
<add key="local" value="/Users/tyleonha/Downloads/nuget" />
910
</packageSources>
1011
</configuration>

src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs

Lines changed: 119 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Hosting;
1919
using Microsoft.Windows.PowerShell.ScriptAnalyzer;
2020
using Microsoft.PowerShell.EditorServices.Utility;
21+
using Microsoft.PowerShell.EditorServices.Services.Analysis;
22+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
2123

2224
namespace 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

Comments
 (0)