Skip to content

Commit 36d6886

Browse files
committed
Invoke-ScriptAnalyzer: Print summary only once per invocation, include parse errors
Also enumerate diagnostics one by one instead of collecting and fix incorrect Pester test grouping.
1 parent 04a0149 commit 36d6886

File tree

2 files changed

+103
-113
lines changed

2 files changed

+103
-113
lines changed

Engine/Commands/InvokeScriptAnalyzerCommand.cs

Lines changed: 56 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ public class InvokeScriptAnalyzerCommand : PSCmdlet, IOutputWriter
3434

3535
#region Private variables
3636
List<string> processedPaths;
37-
private int totalDiagnosticCount = 0;
37+
// initialize to zero for all severity enum values
38+
private Dictionary<DiagnosticSeverity, int> diagnosticCounts =
39+
Enum.GetValues<DiagnosticSeverity>().ToDictionary(s => s, _ => 0);
3840
#endregion // Private variables
3941

4042
#region Parameters
@@ -414,8 +416,35 @@ protected override void EndProcessing()
414416
ScriptAnalyzer.Instance.CleanUp();
415417
base.EndProcessing();
416418

417-
if (EnableExit) {
418-
this.Host.SetShouldExit(totalDiagnosticCount);
419+
var diagnosticCount = diagnosticCounts.Values.Sum();
420+
421+
if (ReportSummary.IsPresent)
422+
{
423+
if (diagnosticCount == 0)
424+
{
425+
Host.UI.WriteLine("0 rule violations found.");
426+
}
427+
else
428+
{
429+
var infoCount = diagnosticCounts[DiagnosticSeverity.Information];
430+
var warningCount = diagnosticCounts[DiagnosticSeverity.Warning];
431+
var errorCount = diagnosticCounts[DiagnosticSeverity.Error] + diagnosticCounts[DiagnosticSeverity.ParseError];
432+
var severeDiagnosticCount = diagnosticCount - infoCount;
433+
434+
var colorPropertyPrefix = severeDiagnosticCount == 0 ? "Warning" : "Error";
435+
var pluralS = diagnosticCount > 1 ? "s" : string.Empty;
436+
ConsoleHostHelper.DisplayMessageUsingSystemProperties(
437+
Host, colorPropertyPrefix + "ForegroundColor", colorPropertyPrefix + "BackgroundColor",
438+
$"{diagnosticCount} rule violation{pluralS} found. Severity distribution: " +
439+
$"{DiagnosticSeverity.Error} = {errorCount}, " +
440+
$"{DiagnosticSeverity.Warning} = {warningCount}, " +
441+
$"{DiagnosticSeverity.Information} = {infoCount}");
442+
}
443+
}
444+
445+
if (EnableExit)
446+
{
447+
this.Host.SetShouldExit(diagnosticCount);
419448
}
420449
}
421450

@@ -431,86 +460,47 @@ protected override void StopProcessing()
431460

432461
private void ProcessInput()
433462
{
434-
var diagnosticRecords = RunAnalysis();
435-
WriteToOutput(diagnosticRecords);
436-
totalDiagnosticCount += diagnosticRecords.Count;
437-
}
438-
439-
private List<DiagnosticRecord> RunAnalysis()
440-
{
441-
if (!IsFileParameterSet())
463+
foreach (var diagnostic in RunAnalysis())
442464
{
443-
return ScriptAnalyzer.Instance.AnalyzeScriptDefinition(scriptDefinition, out _, out _);
444-
}
465+
diagnosticCounts[diagnostic.Severity]++;
445466

446-
var diagnostics = new List<DiagnosticRecord>();
447-
foreach (string path in this.processedPaths)
448-
{
449-
if (fix)
467+
foreach (var logger in ScriptAnalyzer.Instance.Loggers)
450468
{
451-
ShouldProcess(path, $"Analyzing and fixing path with Recurse={this.recurse}");
452-
diagnostics.AddRange(ScriptAnalyzer.Instance.AnalyzeAndFixPath(path, this.ShouldProcess, this.recurse));
453-
}
454-
else
455-
{
456-
ShouldProcess(path, $"Analyzing path with Recurse={this.recurse}");
457-
diagnostics.AddRange(ScriptAnalyzer.Instance.AnalyzePath(path, this.ShouldProcess, this.recurse));
469+
logger.LogObject(diagnostic, this);
458470
}
459471
}
460-
461-
return diagnostics;
462472
}
463473

464-
private void WriteToOutput(List<DiagnosticRecord> diagnosticRecords)
474+
private IEnumerable<DiagnosticRecord> RunAnalysis()
465475
{
466-
foreach (ILogger logger in ScriptAnalyzer.Instance.Loggers)
476+
if (!IsFileParameterSet())
467477
{
468-
var errorCount = 0;
469-
var warningCount = 0;
470-
var infoCount = 0;
471-
var parseErrorCount = 0;
478+
foreach (var record in ScriptAnalyzer.Instance.AnalyzeScriptDefinition(scriptDefinition, out _, out _))
479+
{
480+
yield return record;
481+
}
482+
yield break;
483+
}
472484

473-
foreach (DiagnosticRecord diagnostic in diagnosticRecords)
485+
foreach (var path in this.processedPaths)
486+
{
487+
if (!ShouldProcess(path, $"Analyzing path with Fix={this.fix} and Recurse={this.recurse}"))
474488
{
475-
logger.LogObject(diagnostic, this);
476-
switch (diagnostic.Severity)
477-
{
478-
case DiagnosticSeverity.Information:
479-
infoCount++;
480-
break;
481-
case DiagnosticSeverity.Warning:
482-
warningCount++;
483-
break;
484-
case DiagnosticSeverity.Error:
485-
errorCount++;
486-
break;
487-
case DiagnosticSeverity.ParseError:
488-
parseErrorCount++;
489-
break;
490-
default:
491-
throw new ArgumentOutOfRangeException(nameof(diagnostic.Severity), $"Severity '{diagnostic.Severity}' is unknown");
492-
}
489+
continue;
493490
}
494491

495-
if (ReportSummary.IsPresent)
492+
if (fix)
496493
{
497-
var numberOfRuleViolations = infoCount + warningCount + errorCount;
498-
if (numberOfRuleViolations == 0)
494+
foreach (var record in ScriptAnalyzer.Instance.AnalyzeAndFixPath(path, this.ShouldProcess, this.recurse))
499495
{
500-
Host.UI.WriteLine("0 rule violations found.");
496+
yield return record;
501497
}
502-
else
498+
}
499+
else
500+
{
501+
foreach (var record in ScriptAnalyzer.Instance.AnalyzePath(path, this.ShouldProcess, this.recurse))
503502
{
504-
var pluralS = numberOfRuleViolations > 1 ? "s" : string.Empty;
505-
var message = $"{numberOfRuleViolations} rule violation{pluralS} found. Severity distribution: {DiagnosticSeverity.Error} = {errorCount}, {DiagnosticSeverity.Warning} = {warningCount}, {DiagnosticSeverity.Information} = {infoCount}";
506-
if (warningCount + errorCount == 0)
507-
{
508-
ConsoleHostHelper.DisplayMessageUsingSystemProperties(Host, "WarningForegroundColor", "WarningBackgroundColor", message);
509-
}
510-
else
511-
{
512-
ConsoleHostHelper.DisplayMessageUsingSystemProperties(Host, "ErrorForegroundColor", "ErrorBackgroundColor", message);
513-
}
503+
yield return record;
514504
}
515505
}
516506
}

Tests/Engine/InvokeScriptAnalyzer.tests.ps1

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -585,65 +585,65 @@ Describe "Test -EnableExit Switch" {
585585

586586
$LASTEXITCODE | Should -Be 2
587587
}
588+
}
588589

589-
Describe "-ReportSummary switch" {
590-
BeforeAll {
591-
$pssaPath = (Get-Module PSScriptAnalyzer).Path
592-
593-
if ($IsCoreCLR)
594-
{
595-
$pwshExe = (Get-Process -Id $PID).Path
596-
}
597-
else
598-
{
599-
$pwshExe = 'powershell'
600-
}
590+
Describe "-ReportSummary switch" {
591+
BeforeAll {
592+
$pssaPath = (Get-Module PSScriptAnalyzer).Path
601593

602-
$reportSummaryFor1Warning = '*1 rule violation found. Severity distribution: Error = 0, Warning = 1, Information = 0*'
594+
if ($IsCoreCLR)
595+
{
596+
$pwshExe = (Get-Process -Id $PID).Path
597+
}
598+
else
599+
{
600+
$pwshExe = 'powershell'
603601
}
604602

605-
It "prints the correct report summary using the -NoReportSummary switch" {
606-
$result = & $pwshExe -Command "Import-Module '$pssaPath'; Invoke-ScriptAnalyzer -ScriptDefinition gci -ReportSummary"
603+
$reportSummaryFor1Warning = '*1 rule violation found. Severity distribution: Error = 0, Warning = 1, Information = 0*'
604+
}
607605

608-
"$result" | Should -BeLike $reportSummaryFor1Warning
609-
}
610-
It "does not print the report summary when not using -NoReportSummary switch" {
611-
$result = & $pwshExe -Command "Import-Module '$pssaPath'; Invoke-ScriptAnalyzer -ScriptDefinition gci"
606+
It "prints the correct report summary using the -NoReportSummary switch" {
607+
$result = & $pwshExe -Command "Import-Module '$pssaPath'; Invoke-ScriptAnalyzer -ScriptDefinition gci -ReportSummary"
612608

613-
"$result" | Should -Not -BeLike $reportSummaryFor1Warning
614-
}
609+
"$result" | Should -BeLike $reportSummaryFor1Warning
615610
}
611+
It "does not print the report summary when not using -NoReportSummary switch" {
612+
$result = & $pwshExe -Command "Import-Module '$pssaPath'; Invoke-ScriptAnalyzer -ScriptDefinition gci"
616613

617-
# using statements are only supported in v5+
618-
Describe "Handles parse errors due to unknown types" -Skip:($testingLibraryUsage -or ($PSVersionTable.PSVersion -lt '5.0')) {
619-
BeforeAll {
620-
$script = @'
621-
using namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels
622-
using namespace Microsoft.Azure.Commands.Common.Authentication.Abstractions
623-
Import-Module "AzureRm"
624-
class MyClass { [IStorageContext]$StorageContext } # This will result in a parser error due to [IStorageContext] type that comes from the using statement but is not known at parse time
614+
"$result" | Should -Not -BeLike $reportSummaryFor1Warning
615+
}
616+
}
617+
618+
# using statements are only supported in v5+
619+
Describe "Handles parse errors due to unknown types" -Skip:($testingLibraryUsage -or ($PSVersionTable.PSVersion -lt '5.0')) {
620+
BeforeAll {
621+
$script = @'
622+
using namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels
623+
using namespace Microsoft.Azure.Commands.Common.Authentication.Abstractions
624+
Import-Module "AzureRm"
625+
class MyClass { [IStorageContext]$StorageContext } # This will result in a parser error due to [IStorageContext] type that comes from the using statement but is not known at parse time
625626
'@
626-
}
627-
It "does not throw and detect one expected warning after the parse error has occured when using -ScriptDefintion parameter set" {
628-
$warnings = Invoke-ScriptAnalyzer -ScriptDefinition $script
629-
$warnings.Count | Should -Be 1
630-
$warnings.RuleName | Should -Be 'TypeNotFound'
631-
}
627+
}
628+
It "does not throw and detect one expected warning after the parse error has occured when using -ScriptDefintion parameter set" {
629+
$warnings = Invoke-ScriptAnalyzer -ScriptDefinition $script
630+
$warnings.Count | Should -Be 1
631+
$warnings.RuleName | Should -Be 'TypeNotFound'
632+
}
632633

633-
It "does not throw and detect one expected warning after the parse error has occured when using -Path parameter set" {
634-
$testFilePath = "TestDrive:\testfile.ps1"
635-
Set-Content $testFilePath -Value $script
636-
$warnings = Invoke-ScriptAnalyzer -Path $testFilePath
637-
$warnings.Count | Should -Be 1
638-
$warnings.RuleName | Should -Be 'TypeNotFound'
639-
}
634+
It "does not throw and detect one expected warning after the parse error has occured when using -Path parameter set" {
635+
$testFilePath = "TestDrive:\testfile.ps1"
636+
Set-Content $testFilePath -Value $script
637+
$warnings = Invoke-ScriptAnalyzer -Path $testFilePath
638+
$warnings.Count | Should -Be 1
639+
$warnings.RuleName | Should -Be 'TypeNotFound'
640640
}
641+
}
641642

642-
Describe 'Handles static Singleton (issue 1182)' -Skip:($testingLibraryUsage -or ($PSVersionTable.PSVersion -lt '5.0')) {
643-
It 'Does not throw or return diagnostic record' {
644-
$scriptDefinition = 'class T { static [T]$i }; function foo { [CmdletBinding()] param () $script:T.WriteLog() }'
645-
Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -ErrorAction Stop | Should -BeNullOrEmpty
646-
}
643+
Describe 'Handles static Singleton (issue 1182)' -Skip:($testingLibraryUsage -or ($PSVersionTable.PSVersion -lt '5.0')) {
644+
It 'Does not throw or return diagnostic record' {
645+
$scriptDefinition = 'class T { static [T]$i }; function foo { [CmdletBinding()] param () $script:T.WriteLog() }'
646+
Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -ErrorAction Stop | Should -BeNullOrEmpty
647647
}
648648
}
649649

0 commit comments

Comments
 (0)