Skip to content

Commit 9666ced

Browse files
Fix (and test) regression with PSScriptAnalyzer default rules (#1873)
When we swapped a ternary for a null-coalescing operator we ran into a problem because the two constructor overloads took `string[]` and `object`, and we were now calling the constructor first, which meant the `object` version was always being called. This erroneously sent the rule list to the settings object parameter.
1 parent 1d23697 commit 9666ced

File tree

3 files changed

+51
-45
lines changed

3 files changed

+51
-45
lines changed

Diff for: src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs

+21-21
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,19 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
3939
Position end = diagnostic.Range.End;
4040

4141
StringBuilder sb = new StringBuilder(256)
42-
.Append(diagnostic.Source ?? "?")
43-
.Append('_')
44-
.Append(diagnostic.Code?.IsString ?? true ? diagnostic.Code?.String : diagnostic.Code?.Long.ToString())
45-
.Append('_')
46-
.Append(diagnostic.Severity?.ToString() ?? "?")
47-
.Append('_')
48-
.Append(start.Line)
49-
.Append(':')
50-
.Append(start.Character)
51-
.Append('-')
52-
.Append(end.Line)
53-
.Append(':')
54-
.Append(end.Character);
42+
.Append(diagnostic.Source ?? "?")
43+
.Append('_')
44+
.Append(diagnostic.Code?.IsString ?? true ? diagnostic.Code?.String : diagnostic.Code?.Long.ToString())
45+
.Append('_')
46+
.Append(diagnostic.Severity?.ToString() ?? "?")
47+
.Append('_')
48+
.Append(start.Line)
49+
.Append(':')
50+
.Append(start.Character)
51+
.Append('-')
52+
.Append(end.Line)
53+
.Append(':')
54+
.Append(end.Character);
5555

5656
return sb.ToString();
5757
}
@@ -60,7 +60,7 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
6060
/// Defines the list of Script Analyzer rules to include by default if
6161
/// no settings file is specified.
6262
/// </summary>
63-
private static readonly string[] s_defaultRules = {
63+
internal static readonly string[] s_defaultRules = {
6464
"PSAvoidAssignmentToAutomaticVariable",
6565
"PSUseToExportFieldsInManifest",
6666
"PSMisleadingBacktick",
@@ -141,7 +141,7 @@ public void StartScriptDiagnostics(ScriptFile[] filesToAnalyze)
141141
// If there's an existing task, we want to cancel it here;
142142
CancellationTokenSource cancellationSource = new();
143143
CancellationTokenSource oldTaskCancellation = Interlocked.Exchange(ref _diagnosticsCancellationTokenSource, cancellationSource);
144-
if (oldTaskCancellation != null)
144+
if (oldTaskCancellation is not null)
145145
{
146146
try
147147
{
@@ -194,7 +194,7 @@ public Task<string> FormatAsync(string scriptFileContents, Hashtable formatSetti
194194
/// <returns></returns>
195195
public async Task<string> GetCommentHelpText(string functionText, string helpLocation, bool forBlockComment)
196196
{
197-
if (AnalysisEngine == null)
197+
if (AnalysisEngine is null)
198198
{
199199
return null;
200200
}
@@ -215,7 +215,7 @@ public async Task<string> GetCommentHelpText(string functionText, string helpLoc
215215
/// Get the most recent corrections computed for a given script file.
216216
/// </summary>
217217
/// <param name="uri">The URI string of the file to get code actions for.</param>
218-
/// <returns>A threadsafe readonly dictionary of the code actions of the particular file.</returns>
218+
/// <returns>A thread-safe readonly dictionary of the code actions of the particular file.</returns>
219219
public async Task<IReadOnlyDictionary<string, IEnumerable<MarkerCorrection>>> GetMostRecentCodeActionsForFileAsync(DocumentUri uri)
220220
{
221221
if (!_workspaceService.TryGetFile(uri, out ScriptFile file)
@@ -252,8 +252,8 @@ public void OnConfigurationUpdated(object _, LanguageServerSettings settings)
252252

253253
private void EnsureEngineSettingsCurrent()
254254
{
255-
if (_analysisEngineLazy == null
256-
|| (_pssaSettingsFilePath != null
255+
if (_analysisEngineLazy is null
256+
|| (_pssaSettingsFilePath is not null
257257
&& !File.Exists(_pssaSettingsFilePath)))
258258
{
259259
InitializeAnalysisEngineToCurrentSettings();
@@ -264,7 +264,7 @@ private void InitializeAnalysisEngineToCurrentSettings()
264264
{
265265
// We may be triggered after the lazy factory is set,
266266
// but before it's been able to instantiate
267-
if (_analysisEngineLazy == null)
267+
if (_analysisEngineLazy is null)
268268
{
269269
_analysisEngineLazy = new Lazy<PssaCmdletAnalysisEngine>(InstantiateAnalysisEngine);
270270
return;
@@ -327,7 +327,7 @@ private bool TryFindSettingsFile(out string settingsFilePath)
327327

328328
settingsFilePath = _workspaceService?.ResolveWorkspacePath(configuredPath);
329329

330-
if (settingsFilePath == null
330+
if (settingsFilePath is null
331331
|| !File.Exists(settingsFilePath))
332332
{
333333
_logger.LogInformation($"Unable to find PSSA settings file at '{configuredPath}'. Loading default rules.");

Diff for: src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs

+18-18
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public PssaCmdletAnalysisEngine Build(string pssaModulePath)
7575
{
7676
logger.LogDebug("Creating PSScriptAnalyzer runspace with module at: '{Path}'", pssaModulePath);
7777
RunspacePool pssaRunspacePool = CreatePssaRunspacePool(pssaModulePath);
78-
PssaCmdletAnalysisEngine cmdletAnalysisEngine = new(logger, pssaRunspacePool, _settingsParameter ?? _rules);
78+
PssaCmdletAnalysisEngine cmdletAnalysisEngine = new(logger, pssaRunspacePool, _rules, _settingsParameter);
7979
cmdletAnalysisEngine.LogAvailablePssaFeatures();
8080
return cmdletAnalysisEngine;
8181
}
@@ -105,28 +105,20 @@ public PssaCmdletAnalysisEngine Build(string pssaModulePath)
105105

106106
private readonly RunspacePool _analysisRunspacePool;
107107

108-
private readonly object _settingsParameter;
108+
internal readonly object _settingsParameter;
109109

110-
private readonly string[] _rulesToInclude;
110+
internal readonly string[] _rulesToInclude;
111111

112112
private PssaCmdletAnalysisEngine(
113113
ILogger logger,
114114
RunspacePool analysisRunspacePool,
115-
string[] rulesToInclude)
116-
: this(logger, analysisRunspacePool) => _rulesToInclude = rulesToInclude;
117-
118-
private PssaCmdletAnalysisEngine(
119-
ILogger logger,
120-
RunspacePool analysisRunspacePool,
121-
object analysisSettingsParameter)
122-
: this(logger, analysisRunspacePool) => _settingsParameter = analysisSettingsParameter;
123-
124-
private PssaCmdletAnalysisEngine(
125-
ILogger logger,
126-
RunspacePool analysisRunspacePool)
115+
string[] rulesToInclude = default,
116+
object analysisSettingsParameter = default)
127117
{
128118
_logger = logger;
129119
_analysisRunspacePool = analysisRunspacePool;
120+
_rulesToInclude = rulesToInclude;
121+
_settingsParameter = analysisSettingsParameter;
130122
}
131123

132124
/// <summary>
@@ -228,9 +220,17 @@ public Task<ScriptFileMarker[]> AnalyzeScriptAsync(string scriptContent, Hashtab
228220
return GetSemanticMarkersFromCommandAsync(command);
229221
}
230222

231-
public PssaCmdletAnalysisEngine RecreateWithNewSettings(string settingsPath) => new(_logger, _analysisRunspacePool, settingsPath);
232-
233-
public PssaCmdletAnalysisEngine RecreateWithRules(string[] rules) => new(_logger, _analysisRunspacePool, rules);
223+
public PssaCmdletAnalysisEngine RecreateWithNewSettings(string settingsPath) => new(
224+
_logger,
225+
_analysisRunspacePool,
226+
rulesToInclude: null,
227+
analysisSettingsParameter: settingsPath);
228+
229+
public PssaCmdletAnalysisEngine RecreateWithRules(string[] rules) => new(
230+
_logger,
231+
_analysisRunspacePool,
232+
rulesToInclude: rules,
233+
analysisSettingsParameter: null);
234234

235235
#region IDisposable Support
236236
private bool disposedValue; // To detect redundant calls

Diff for: test/PowerShellEditorServices.Test/Services/Symbols/PSScriptAnalyzerTests.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using System.Linq;
54
using System.Threading;
65
using System.Threading.Tasks;
76
using Microsoft.Extensions.Logging.Abstractions;
@@ -18,7 +17,7 @@ public class PSScriptAnalyzerTests
1817
{
1918
private readonly WorkspaceService workspaceService = new(NullLoggerFactory.Instance);
2019
private readonly AnalysisService analysisService;
21-
private const string script = "function Get-Widgets {}";
20+
private const string script = "function Do-Work {}";
2221

2322
public PSScriptAnalyzerTests() => analysisService = new(
2423
NullLoggerFactory.Instance,
@@ -40,6 +39,13 @@ public class PSScriptAnalyzerTests
4039
usesLegacyReadLine: false,
4140
bundledModulePath: PsesHostFactory.BundledModulePath));
4241

42+
[Fact]
43+
public void IncludesDefaultRules()
44+
{
45+
Assert.Null(analysisService.AnalysisEngine._settingsParameter);
46+
Assert.Equal(AnalysisService.s_defaultRules, analysisService.AnalysisEngine._rulesToInclude);
47+
}
48+
4349
[Fact]
4450
public async Task CanLoadPSScriptAnalyzerAsync()
4551
{
@@ -51,10 +57,10 @@ public async Task CanLoadPSScriptAnalyzerAsync()
5157
Assert.Collection(violations,
5258
(actual) =>
5359
{
54-
Assert.Single(actual.Corrections);
55-
Assert.Equal("Singularized correction of 'Get-Widgets'", actual.Corrections.First().Name);
60+
Assert.Empty(actual.Corrections);
5661
Assert.Equal(ScriptFileMarkerLevel.Warning, actual.Level);
57-
Assert.Equal("PSUseSingularNouns", actual.RuleName);
62+
Assert.Equal("The cmdlet 'Do-Work' uses an unapproved verb.", actual.Message);
63+
Assert.Equal("PSUseApprovedVerbs", actual.RuleName);
5864
Assert.Equal("PSScriptAnalyzer", actual.Source);
5965
});
6066
}
@@ -96,7 +102,7 @@ await analysisService
96102
},
97103
(actual) =>
98104
{
99-
Assert.Equal("PSUseSingularNouns", actual.RuleName);
105+
Assert.Equal("PSUseApprovedVerbs", actual.RuleName);
100106
Assert.Equal("PSScriptAnalyzer", actual.Source);
101107
});
102108
}

0 commit comments

Comments
 (0)