-
Notifications
You must be signed in to change notification settings - Fork 1
/
Invoke-Analyzer.ps1
152 lines (140 loc) · 5.62 KB
/
Invoke-Analyzer.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
[Diagnostics.CodeAnalysis.SuppressMessage(
'PSReviewUnusedParameter',
'ForGitHubActions',
Justification = 'False positive due to https://github.com/PowerShell/PSScriptAnalyzer/issues/1472.')]
[Diagnostics.CodeAnalysis.SuppressMessage(
'PSReviewUnusedParameter',
'ForMsBuild',
Justification = 'False positive due to https://github.com/PowerShell/PSScriptAnalyzer/issues/1472.')]
param(
$SettingsPath = (Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) 'PSScriptAnalyzerSettings.psd1'),
[Switch] $ForGitHubActions,
[Switch] $ForMsBuild,
[Switch] $IncludeTestSolutions,
[switch] $Fix
)
# This is like Get-ChildItem -Recurse -Include $IncludeFile | ? { $PSItem.FullName -NotLike "*\$ExcludeDirectory\*" }
# but much faster. For example, this is relevant for ignoring node_modules.
# - Measure-Command { Find-Recursively -Path . -IncludeFile *.ps1 -ExcludeDirectory node_modules } => 3.83s
# - Measure-Command { Get-ChildItem -Recurse -Force -Include $IncludeFile | ? { $PSItem.FullName -NotLike
# "*\$ExcludeDirectory\*" } } => 111.27s
function Find-Recursively([string] $Path = '.', [string[]] $IncludeFile, [string] $ExcludeDirectory)
{
$ExcludeDirectory = $ExcludeDirectory.ToUpperInvariant()
function Find-Inner([System.IO.DirectoryInfo] $Here)
{
if ($Here.Name -like $ExcludeDirectory)
{
return
}
# The -Force switch is necessary to show hidden results, especially on Linux where entries starting with dot
# are hidden by default.
foreach ($child in (Get-ChildItem $Here.FullName -Force))
{
if ($child -is [System.IO.DirectoryInfo]) { Find-Inner $child }
elseif (($IncludeFile | Where-Object { $child.name -like $PSItem }).Count) { $child }
}
}
Find-Inner (Get-Item .)
}
function Write-FileError([string] $Message, [string] $Path, [int] $Line = 0, [int] $Column = 0)
{
if ($Path) { $Path = Get-Item $Path }
if ($ForGitHubActions)
{
$Message = $Message -replace '\s*(\r?\n\s*)+', ' '
Write-Output "::error::$(if ($Path) { "$(Resolve-Path -Relative -Path $Path)($Line,$Column): " })$Message"
}
elseif ($ForMsBuild)
{
if (-not $Message.Contains(':')) { $Message = ": $Message" }
if ($Path)
{
[Console]::Error.WriteLine("$Path($Line,$Column): error $Message")
}
else
{
[Console]::Error.WriteLine(": error $Message")
}
}
else
{
Write-Error $(if ($Path) { "[$Path|ln $($Line):$Column] $Message" } else { $Message })
}
}
if (Test-Path $SettingsPath)
{
$SettingsPath = Get-Item $SettingsPath
}
else
{
Write-FileError "The settings file `"$SettingsPath`" does not exist."
exit -1
}
$installVersion = '1.22.0'
if ((Get-InstalledModule PSScriptAnalyzer -ErrorAction SilentlyContinue).Version -ne [Version]$installVersion)
{
try
{
# Attempt to install it automatically. This will fail on Windows PowerShell because you have to be admin.
Install-Module -Name PSScriptAnalyzer -Force -RequiredVersion $installVersion
}
catch
{
$infoUrl = 'https://docs.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview?view=ps-modules#installing-psscriptanalyzer'
@(
'Unable to detect Invoke-ScriptAnalyzer and failed to install PSScriptAnalyzer. If you are on Windows'
'Powershell, open an administrator shell and type "Install-Module -Name PSScriptAnalyzer -RequiredVersion'
"$installVersion`". Otherwise see $infoUrl to learn more."
) -join ' ' | Write-FileError
exit -2
}
}
$analyzerParameters = @{
Settings = $SettingsPath.FullName
CustomRulePath = Join-Path -Path (Split-Path $MyInvocation.MyCommand.Path -Parent) -ChildPath Rules
RecurseCustomRulePath = $true
IncludeDefaultRules = $true
Fix = $Fix
}
$results = Find-Recursively -IncludeFile '*.ps1', '*.psm1', '*.psd1' -ExcludeDirectory node_modules |
Where-Object { # Exclude /TestSolutions/Violate-Analyzers.ps1 and /TestSolutions/*/Violate-Analyzers.ps1
$IncludeTestSolutions -or -not (
$PSItem.Name -eq 'Violate-Analyzers.ps1' -and
($PSItem.Directory.Name -eq 'TestSolutions' -or $PSItem.Directory.Parent.Name -eq 'TestSolutions')) } |
ForEach-Object {
$maxRetries = 3
for ($retryCount = 0; $retryCount -lt $maxRetries; $retryCount++)
{
try
{
$extendedAnalyzerParameters = $analyzerParameters.Clone()
$extendedAnalyzerParameters.Verbose = $retryCount -ge 1
Invoke-ScriptAnalyzer -Path $PSItem.FullName @extendedAnalyzerParameters
break
}
catch
{
if ($retryCount -eq ($maxRetries - 1))
{
Write-Error "Failed to analyze $($PSItem.FullName) after $maxRetries attempts. Exception: $_"
}
else
{
Write-Warning "Retry #$($retryCount + 1): An exception occurred: $_. Retrying..."
}
}
}
}
foreach ($result in $results)
{
$message = $result.RuleName + ': ' + $result.Message
Write-FileError -Path $result.ScriptPath -Line $result.Line -Column $result.Column $message
}
# Exit code indicates the existence of analyzer violations instead of the number of violations, because exiting with
# code 5 (when there are 5 violations) changes how MSBuild interprets the results and yields the error MSB3075 instead
# of MSB3073 for some reason.
if ($results.Count -ne 0)
{
exit 1
}