microsoft/hve-core

Public

mirrored from https://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
docs/fix-broken-links-115-116

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

scripts/linting/Modules/LintingHelpers.psm1

321lines · modecode

1# LintingHelpers.psm1
2#
3# Purpose: Shared helper functions for linting scripts and workflows
4# Author: HVE Core Team
5# Created: 2025-11-05
6
7function Get-ChangedFilesFromGit {
8 <#
9 .SYNOPSIS
10 Gets changed files from git with intelligent fallback strategies.
11
12 .DESCRIPTION
13 Attempts to detect changed files using merge-base, with fallbacks for different scenarios.
14
15 .PARAMETER BaseBranch
16 The base branch to compare against (default: origin/main).
17
18 .PARAMETER FileExtensions
19 Array of file extensions to filter (e.g., @('*.ps1', '*.md')).
20
21 .OUTPUTS
22 Array of changed file paths.
23 #>
24 [CmdletBinding()]
25 param(
26 [Parameter(Mandatory = $false)]
27 [string]$BaseBranch = "origin/main",
28
29 [Parameter(Mandatory = $false)]
30 [string[]]$FileExtensions = @('*')
31 )
32
33 $changedFiles = @()
34
35 try {
36 # Try merge-base first (best for PRs)
37 $mergeBase = git merge-base HEAD $BaseBranch 2>$null
38
39 if ($LASTEXITCODE -eq 0 -and $mergeBase) {
40 Write-Verbose "Using merge-base: $mergeBase"
41 $changedFiles = git diff --name-only --diff-filter=ACMR $mergeBase HEAD 2>$null
42 }
43 elseif ((git rev-parse HEAD~1 2>$null)) {
44 Write-Verbose "Merge base failed, using HEAD~1"
45 $changedFiles = git diff --name-only --diff-filter=ACMR HEAD~1 HEAD 2>$null
46 }
47 else {
48 Write-Verbose "HEAD~1 failed, using staged/unstaged files"
49 $changedFiles = git diff --name-only HEAD 2>$null
50 }
51
52 if ($LASTEXITCODE -ne 0) {
53 Write-Warning "Unable to determine changed files from git"
54 return @()
55 }
56
57 # Filter by extensions and verify files exist
58 $filteredFiles = $changedFiles | Where-Object {
59 if ([string]::IsNullOrEmpty($_)) { return $false }
60
61 # Check if file matches any of the allowed extensions
62 $currentFile = $_
63 $matchesExtension = $false
64 foreach ($pattern in $FileExtensions) {
65 if ($currentFile -like $pattern) {
66 $matchesExtension = $true
67 break
68 }
69 }
70
71 $matchesExtension -and (Test-Path $currentFile -PathType Leaf)
72 }
73
74 Write-Verbose "Found $($filteredFiles.Count) changed files matching extensions: $($FileExtensions -join ', ')"
75 return $filteredFiles
76 }
77 catch {
78 Write-Warning "Error getting changed files: $($_.Exception.Message)"
79 return @()
80 }
81}
82
83function Get-FilesRecursive {
84 <#
85 .SYNOPSIS
86 Gets files recursively with gitignore filtering.
87
88 .DESCRIPTION
89 Recursively finds files by extension, respecting .gitignore patterns.
90
91 .PARAMETER Path
92 Root path to search from.
93
94 .PARAMETER Include
95 File patterns to include (e.g., @('*.ps1', '*.psm1')).
96
97 .PARAMETER GitIgnorePath
98 Path to .gitignore file for exclusion patterns.
99
100 .OUTPUTS
101 Array of FileInfo objects.
102 #>
103 [CmdletBinding()]
104 param(
105 [Parameter(Mandatory = $true)]
106 [string]$Path,
107
108 [Parameter(Mandatory = $true)]
109 [string[]]$Include,
110
111 [Parameter(Mandatory = $false)]
112 [string]$GitIgnorePath
113 )
114
115 $files = Get-ChildItem -Path $Path -Recurse -Include $Include -File -ErrorAction SilentlyContinue
116
117 # Apply gitignore filtering if provided
118 if ($GitIgnorePath -and (Test-Path $GitIgnorePath)) {
119 $gitignorePatterns = Get-GitIgnorePatterns -GitIgnorePath $GitIgnorePath
120
121 $files = $files | Where-Object {
122 $file = $_
123 $excluded = $false
124
125 foreach ($pattern in $gitignorePatterns) {
126 if ($file.FullName -like $pattern) {
127 $excluded = $true
128 break
129 }
130 }
131
132 -not $excluded
133 }
134 }
135
136 return $files
137}
138
139function Get-GitIgnorePatterns {
140 <#
141 .SYNOPSIS
142 Parses .gitignore into PowerShell wildcard patterns.
143
144 .PARAMETER GitIgnorePath
145 Path to .gitignore file.
146
147 .OUTPUTS
148 Array of wildcard patterns.
149 #>
150 [CmdletBinding()]
151 param(
152 [Parameter(Mandatory = $true)]
153 [string]$GitIgnorePath
154 )
155
156 if (-not (Test-Path $GitIgnorePath)) {
157 return @()
158 }
159
160 $patterns = Get-Content $GitIgnorePath | Where-Object {
161 $_ -and -not $_.StartsWith('#') -and $_.Trim() -ne ''
162 } | ForEach-Object {
163 $pattern = $_.Trim()
164
165 if ($pattern.EndsWith('/')) {
166 "*\$($pattern.TrimEnd('/'))\*"
167 }
168 elseif ($pattern.Contains('/')) {
169 "*\$($pattern.Replace('/', '\'))*"
170 }
171 else {
172 "*\$pattern\*"
173 }
174 }
175
176 return $patterns
177}
178
179function Write-GitHubAnnotation {
180 <#
181 .SYNOPSIS
182 Writes GitHub Actions annotations for errors, warnings, or notices.
183
184 .PARAMETER Type
185 Annotation type: 'error', 'warning', or 'notice'.
186
187 .PARAMETER Message
188 The annotation message.
189
190 .PARAMETER File
191 Optional file path.
192
193 .PARAMETER Line
194 Optional line number.
195
196 .PARAMETER Column
197 Optional column number.
198 #>
199 [CmdletBinding()]
200 param(
201 [Parameter(Mandatory = $true)]
202 [ValidateSet('error', 'warning', 'notice')]
203 [string]$Type,
204
205 [Parameter(Mandatory = $true)]
206 [string]$Message,
207
208 [Parameter(Mandatory = $false)]
209 [string]$File,
210
211 [Parameter(Mandatory = $false)]
212 [int]$Line,
213
214 [Parameter(Mandatory = $false)]
215 [int]$Column
216 )
217
218 $annotation = "::${Type}"
219
220 $properties = @()
221 if ($File) { $properties += "file=$File" }
222 if ($Line -gt 0) { $properties += "line=$Line" }
223 if ($Column -gt 0) { $properties += "col=$Column" }
224
225 if ($properties.Count -gt 0) {
226 $annotation += " $($properties -join ',')"
227 }
228
229 $annotation += "::$Message"
230
231 Write-Host $annotation
232}
233
234function Set-GitHubOutput {
235 <#
236 .SYNOPSIS
237 Sets GitHub Actions output variable.
238
239 .PARAMETER Name
240 Output variable name.
241
242 .PARAMETER Value
243 Output value.
244 #>
245 [CmdletBinding()]
246 param(
247 [Parameter(Mandatory = $true)]
248 [string]$Name,
249
250 [Parameter(Mandatory = $true)]
251 [string]$Value
252 )
253
254 if ($env:GITHUB_OUTPUT) {
255 "$Name=$Value" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
256 }
257 else {
258 Write-Verbose "Not in GitHub Actions environment - output: $Name=$Value"
259 }
260}
261
262function Set-GitHubEnv {
263 <#
264 .SYNOPSIS
265 Sets GitHub Actions environment variable.
266
267 .PARAMETER Name
268 Environment variable name.
269
270 .PARAMETER Value
271 Environment value.
272 #>
273 [CmdletBinding()]
274 param(
275 [Parameter(Mandatory = $true)]
276 [string]$Name,
277
278 [Parameter(Mandatory = $true)]
279 [string]$Value
280 )
281
282 if ($env:GITHUB_ENV) {
283 "$Name=$Value" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
284 }
285 else {
286 Write-Verbose "Not in GitHub Actions environment - env: $Name=$Value"
287 }
288}
289
290function Write-GitHubStepSummary {
291 <#
292 .SYNOPSIS
293 Appends content to GitHub Actions step summary.
294
295 .PARAMETER Content
296 Markdown content to append.
297 #>
298 [CmdletBinding()]
299 param(
300 [Parameter(Mandatory = $true)]
301 [string]$Content
302 )
303
304 if ($env:GITHUB_STEP_SUMMARY) {
305 $Content | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
306 }
307 else {
308 Write-Verbose "Not in GitHub Actions environment - summary content: $Content"
309 }
310}
311
312# Export functions
313Export-ModuleMember -Function @(
314 'Get-ChangedFilesFromGit',
315 'Get-FilesRecursive',
316 'Get-GitIgnorePatterns',
317 'Write-GitHubAnnotation',
318 'Set-GitHubOutput',
319 'Set-GitHubEnv',
320 'Write-GitHubStepSummary'
321)
322