microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/address-powershell-test-comments

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/linting/Link-Lang-Check.ps1

384lines · modecode

1# Copyright (c) Microsoft Corporation.
2# SPDX-License-Identifier: MIT
3#Requires -Version 7.0
4
5<#
6.SYNOPSIS
7 Language Path Link Checker and Fixer
8
9.DESCRIPTION
10 This script finds and optionally fixes URLs in git-tracked text files that contain
11 the language path segment 'en-us'. It helps maintain links that work regardless
12 of user language settings by removing unnecessary language path segments.
13
14 Functionality:
15 - Scans git-tracked text files for URLs containing 'en-us'
16 - Identifies link locations by file and line number
17 - Optionally removes 'en-us/' from URLs to make them language-neutral
18 - Reports changes in human-readable or JSON format
19
20.PARAMETER Fix
21 Fix URLs by removing "en-us/" instead of just reporting them
22
23.PARAMETER ExcludePaths
24 Glob patterns for paths to exclude from checking (e.g., 'scripts/tests/**')
25
26.EXAMPLE
27 # Search for URLs containing 'en-us' and output as JSON
28 .\Link-Lang-Check.ps1
29
30.EXAMPLE
31 # Fix URLs by removing 'en-us/' with verbose output
32 .\Link-Lang-Check.ps1 -Fix -Verbose
33
34.NOTES
35 The script is designed to help maintain documentation links that work regardless
36 of the user's language settings in their browser.
37
38 Dependencies:
39 - git: Required for identifying text files under source control
40 - PowerShell 5.1 or PowerShell 7+
41
42 Returns:
43 - JSON array or console output: When not in fix mode, outputs a JSON array of found links
44 When in fix mode, outputs human-readable summary of changes
45
46 See Also:
47 - Microsoft documentation guidance on language neutrality: https://learn.microsoft.com/style-guide/urls-web-addresses
48#>
49
50[CmdletBinding()]
51param(
52 [switch]$Fix,
53 [string[]]$ExcludePaths = @()
54)
55
56$ErrorActionPreference = 'Stop'
57
58Import-Module (Join-Path $PSScriptRoot "../lib/Modules/CIHelpers.psm1") -Force
59
60$script:SkipMain = $env:HVE_SKIP_MAIN -eq '1'
61
62function Get-GitTextFile {
63 <#
64 .SYNOPSIS
65 Get list of all text files under git source control, excluding binary files.
66
67 .DESCRIPTION
68 Uses git's built-in binary detection to exclude non-text files from processing.
69
70 .OUTPUTS
71 System.String[]
72 A list of file paths to text files tracked by git.
73 #>
74
75 try {
76 # Use git's binary detection with -I flag (--no-binary)
77 $result = & git grep -I --name-only -e '' 2>&1
78
79 if ($LASTEXITCODE -gt 1) {
80 Write-Error "Error executing git grep: $result"
81 return @()
82 }
83
84 if ($result -and $result.Count -gt 0) {
85 return $result | Where-Object { $_ -is [string] -and $_.Trim() -ne '' }
86 }
87
88 return @()
89 }
90 catch {
91 Write-Error "Error getting git text files: $_"
92 return @()
93 }
94}
95
96function Find-LinksInFile {
97 <#
98 .SYNOPSIS
99 Find links with 'en-us' in them and return details.
100
101 .DESCRIPTION
102 Scans the specified file for URLs containing the 'en-us' path segment and
103 collects information about each occurrence.
104
105 .PARAMETER FilePath
106 Path to the file to scan
107
108 .OUTPUTS
109 System.Object[]
110 A list of objects, each containing information about a link:
111 - File: The file path
112 - LineNumber: The line number where the link appears
113 - OriginalUrl: The original URL with 'en-us'
114 - FixedUrl: The URL with 'en-us/' removed
115 #>
116
117 [CmdletBinding()]
118 param(
119 [string]$FilePath
120 )
121
122 $linksFound = @()
123
124 try {
125 $lines = @(Get-Content -Path $FilePath -Encoding UTF8 -ErrorAction Stop)
126 }
127 catch {
128 Write-Verbose "Could not read $FilePath`: $_"
129 return $linksFound
130 }
131
132 # Regular expression to find URLs containing "en-us/"
133 $urlPattern = 'https?://[^\s<>"'']+?en-us/[^\s<>"'']+'
134
135 for ($i = 0; $i -lt $lines.Count; $i++) {
136 $line = $lines[$i]
137 $urlMatches = [regex]::Matches($line, $urlPattern)
138
139 foreach ($match in $urlMatches) {
140 $linksFound += [PSCustomObject]@{
141 File = $FilePath
142 LineNumber = $i + 1
143 OriginalUrl = $match.Value
144 FixedUrl = $match.Value -replace 'en-us/', ''
145 }
146 }
147 }
148
149 return $linksFound
150}
151
152function Repair-LinksInFile {
153 <#
154 .SYNOPSIS
155 Fix links in a single file by removing 'en-us/' from URLs.
156
157 .DESCRIPTION
158 Opens the file, replaces URLs containing 'en-us/' with versions without it,
159 and writes the changes back to the file.
160
161 .PARAMETER FilePath
162 Path to the file to modify
163
164 .PARAMETER Links
165 Array of link objects for the file, each containing:
166 - OriginalUrl: The original URL to replace
167 - FixedUrl: The URL to replace it with
168
169 .OUTPUTS
170 System.Boolean
171 True if the file was modified, False otherwise
172 #>
173
174 [CmdletBinding()]
175 param(
176 [string]$FilePath,
177 [PSCustomObject[]]$Links
178 )
179
180 try {
181 $content = Get-Content -Path $FilePath -Raw -Encoding UTF8 -ErrorAction Stop
182 }
183 catch {
184 Write-Verbose "Could not read $FilePath`: $_"
185 return $false
186 }
187
188 # Replace each link
189 $modifiedContent = $content
190 foreach ($link in $Links) {
191 $modifiedContent = $modifiedContent -replace [regex]::Escape($link.OriginalUrl), $link.FixedUrl
192 }
193
194 # Only write if changes were made
195 if ($modifiedContent -ne $content) {
196 try {
197 Set-Content -Path $FilePath -Value $modifiedContent -Encoding UTF8 -NoNewline -ErrorAction Stop
198 return $true
199 }
200 catch {
201 Write-Verbose "Could not write to $FilePath`: $_"
202 return $false
203 }
204 }
205 return $false
206}
207
208function Repair-AllLink {
209 <#
210 .SYNOPSIS
211 Fix all links in their respective files.
212
213 .DESCRIPTION
214 Groups links by file, then calls Repair-LinksInFile for each file.
215
216 .PARAMETER AllLinks
217 Array of all link objects found across files
218
219 .OUTPUTS
220 System.Int32
221 Number of files that were successfully modified
222 #>
223
224 [CmdletBinding()]
225 param(
226 [PSCustomObject[]]$AllLinks
227 )
228
229 # Group links by file
230 $linksByFile = $AllLinks | Group-Object -Property File
231 $filesModified = 0
232
233 # Fix links in each file
234 foreach ($fileGroup in $linksByFile) {
235 $filePath = $fileGroup.Name
236 $links = $fileGroup.Group
237
238 Write-Verbose "Fixing links in $filePath..."
239
240 if (Repair-LinksInFile -FilePath $filePath -Links $links) {
241 $filesModified++
242 }
243 }
244
245 return $filesModified
246}
247
248function ConvertTo-JsonOutput {
249 <#
250 .SYNOPSIS
251 Prepare links for JSON output by formatting as an array of link objects.
252
253 .DESCRIPTION
254 Creates a clean representation without internal fields used for processing.
255
256 .PARAMETER Links
257 The complete array of link objects
258
259 .OUTPUTS
260 System.Object[]
261 An array of objects ready for JSON serialization, each containing:
262 - File: The file path
263 - LineNumber: The line number where the link appears
264 - OriginalUrl: The original URL with 'en-us'
265 #>
266
267 [CmdletBinding()]
268 param(
269 [PSCustomObject[]]$Links
270 )
271
272 $jsonData = @()
273 foreach ($link in $Links) {
274 # Create a copy without the FixedUrl field
275 $jsonData += [PSCustomObject]@{
276 file = $link.File
277 line_number = $link.LineNumber
278 original_url = $link.OriginalUrl
279 }
280 }
281 return $jsonData
282}
283
284#region Main Execution
285if (-not $script:SkipMain) {
286 try {
287 if ($Verbose) {
288 Write-Information "Getting list of git-tracked text files..." -InformationAction Continue
289 }
290
291 $files = Get-GitTextFile
292
293 # Apply exclusion patterns
294 if ($ExcludePaths.Count -gt 0) {
295 $originalCount = $files.Count
296 $files = $files | Where-Object {
297 $filePath = $_
298 $excluded = $false
299 foreach ($pattern in $ExcludePaths) {
300 if ($filePath -like $pattern) {
301 $excluded = $true
302 break
303 }
304 }
305 -not $excluded
306 }
307 if ($Verbose) {
308 $excludedCount = $originalCount - $files.Count
309 Write-Information "Excluded $excludedCount files matching exclusion patterns" -InformationAction Continue
310 }
311 }
312
313 if ($Verbose) {
314 Write-Information "Found $($files.Count) git-tracked text files" -InformationAction Continue
315 }
316
317 $allLinks = @()
318
319 foreach ($filePath in $files) {
320 if (-not (Test-Path -Path $filePath -PathType Leaf)) {
321 if ($Verbose) {
322 Write-Warning "Skipping $filePath`: not a regular file"
323 }
324 continue
325 }
326
327 if ($Verbose) {
328 Write-Verbose "Processing $filePath..."
329 }
330
331 $links = Find-LinksInFile -FilePath $filePath
332 $allLinks += $links
333 }
334
335 # Report findings
336 if ($allLinks.Count -gt 0) {
337 if ($Fix) {
338 # Human-readable output when fixing links
339 if ($Verbose) {
340 Write-Information "`nFound $($allLinks.Count) URLs containing 'en-us':`n" -InformationAction Continue
341 foreach ($linkInfo in $allLinks) {
342 Write-Information "File: $($linkInfo.File), Line: $($linkInfo.LineNumber)" -InformationAction Continue
343 Write-Information " URL: $($linkInfo.OriginalUrl)" -InformationAction Continue
344 Write-Information "" -InformationAction Continue
345 }
346 }
347
348 $filesModified = Repair-AllLink -AllLinks $allLinks
349 Write-Output "Fixed $($allLinks.Count) URLs in $filesModified files."
350
351 if ($Verbose) {
352 Write-Information "`nDetails of fixes:" -InformationAction Continue
353 foreach ($linkInfo in $allLinks) {
354 Write-Information "File: $($linkInfo.File), Line: $($linkInfo.LineNumber)" -InformationAction Continue
355 Write-Information " Original: $($linkInfo.OriginalUrl)" -InformationAction Continue
356 Write-Information " Fixed: $($linkInfo.FixedUrl)" -InformationAction Continue
357 Write-Information "" -InformationAction Continue
358 }
359 }
360 }
361 else {
362 # JSON output when not fixing links
363 $jsonOutput = ConvertTo-JsonOutput -Links $allLinks
364 Write-Output ($jsonOutput | ConvertTo-Json -Depth 3)
365 }
366 }
367 else {
368 if (-not $Fix) {
369 # Empty JSON array if no links found
370 Write-Output "[]"
371 }
372 else {
373 Write-Output "No URLs containing 'en-us' were found."
374 }
375 }
376 exit 0
377 }
378 catch {
379 Write-Error -ErrorAction Continue "Link Lang Check failed: $($_.Exception.Message)"
380 Write-CIAnnotation -Message "Link Lang Check failed: $($_.Exception.Message)" -Level Error
381 exit 1
382 }
383}
384#endregion
385