1+ <#
2+ format.ps1
3+ Formats and lint-fixes the Prefix C codebase with Clang tools.
4+
5+ Usage (from Prefix folder):
6+ powershell -ExecutionPolicy Bypass -File .\format.ps1
7+ #>
8+
9+ $ErrorActionPreference = ' Stop'
10+
11+ $scriptDir = Split-Path - Parent $MyInvocation.MyCommand.Definition
12+ $srcDir = Join-Path $scriptDir " src"
13+
14+ if (-not (Test-Path $srcDir )) {
15+ Write-Error " Could not locate src directory at: $srcDir "
16+ exit 1
17+ }
18+
19+ $clang = Get-Command clang.exe - ErrorAction SilentlyContinue
20+ $clangFormat = Get-Command clang-format.exe - ErrorAction SilentlyContinue
21+ $clangTidy = Get-Command clang-tidy.exe - ErrorAction SilentlyContinue
22+
23+ if (-not $clang ) {
24+ Write-Error " clang.exe not found on PATH."
25+ exit 1
26+ }
27+ if (-not $clangFormat ) {
28+ Write-Error " clang-format.exe not found on PATH."
29+ exit 1
30+ }
31+ if (-not $clangTidy ) {
32+ Write-Error " clang-tidy.exe not found on PATH."
33+ exit 1
34+ }
35+
36+ # Determine clang-tidy checks to use. Priority:
37+ # 1) CLANG_TIDY_CHECKS env var
38+ # 2) repo .clang-tidy file (let clang-tidy find it)
39+ # 3) fix-oriented default checks that stay quiet unless they can rewrite code
40+ $repoTidyFile = Join-Path $scriptDir " .clang-tidy"
41+ $clangTidyChecks = $env: CLANG_TIDY_CHECKS
42+ if (-not $clangTidyChecks ) {
43+ if (-not (Test-Path $repoTidyFile )) {
44+ # Default to checks that usually provide mechanical fixes instead of
45+ # flooding the run with non-actionable audit diagnostics.
46+ $clangTidyChecks = " readability-braces-around-statements,readability-isolate-declaration,readability-redundant-control-flow"
47+ } else {
48+ $clangTidyChecks = " "
49+ }
50+ }
51+ $repoClangFormat = Join-Path $scriptDir " .clang-format"
52+ # If repo provides a .clang-format, prefer it; otherwise use an inline
53+ # style that enforces four-space indents and no tabs.
54+ if (Test-Path $repoClangFormat ) {
55+ $clangFormatStyle = " file"
56+ $clangTidyFormatArg = " -format-style=file"
57+ } else {
58+ $clangFormatStyle = " {BasedOnStyle: LLVM, IndentWidth: 4, UseTab: Never, ColumnLimit: 120}"
59+ $clangTidyFormatArg = " -format-style=$clangFormatStyle "
60+ }
61+ $roots = @ (
62+ (Join-Path $scriptDir " src" ),
63+ (Join-Path $scriptDir " ext" ),
64+ (Join-Path $scriptDir " lib" ),
65+ (Join-Path $scriptDir " tests\helpers" )
66+ )
67+
68+ $allC = @ ()
69+ $allH = @ ()
70+
71+ foreach ($root in $roots ) {
72+ if (-not (Test-Path $root )) {
73+ continue
74+ }
75+ $allC += Get-ChildItem - Path $root - Recurse - File - Filter * .c | ForEach-Object { $_.FullName }
76+ $allH += Get-ChildItem - Path $root - Recurse - File - Filter * .h | ForEach-Object { $_.FullName }
77+ }
78+
79+ $sourceFiles = @ ($allC | Sort-Object - Unique)
80+ $headerFiles = @ ($allH | Sort-Object - Unique)
81+ $formatFiles = @ ($sourceFiles + $headerFiles | Sort-Object - Unique)
82+
83+ if ($sourceFiles.Count -eq 0 ) {
84+ Write-Error " No C source files found under src/, ext/, lib/, or tests/helpers/."
85+ exit 1
86+ }
87+
88+ if ($formatFiles.Count -eq 0 ) {
89+ Write-Error " No C/C header files found to format."
90+ exit 1
91+ }
92+
93+ Write-Host " Formatting $ ( $formatFiles.Count ) C/C header file(s) with clang-format (IndentWidth=$ ( $clangFormatStyle -replace ' ^\{|\}$' , ' ' ) )..."
94+ foreach ($file in $formatFiles ) {
95+ & $clangFormat.Path " -style=$clangFormatStyle " - i $file
96+ if ($LASTEXITCODE -ne 0 ) {
97+ Write-Error " clang-format failed for: $file "
98+ exit 1
99+ }
100+ }
101+
102+ $stamp = Get-Date - Format " yyyyMMdd-HHmmss"
103+ $tidyDbDir = Join-Path $env: TEMP (" prefix-clang-tidy-" + $stamp )
104+ New-Item - ItemType Directory - Path $tidyDbDir - Force | Out-Null
105+
106+ try {
107+ $compileDbPath = Join-Path $tidyDbDir " compile_commands.json"
108+ $compileDb = @ ()
109+
110+ foreach ($file in $sourceFiles ) {
111+ $command = (' "{0}" --driver-mode=cl /std:c17 /I"{1}" /I"{2}" /D_CRT_SECURE_NO_WARNINGS "{3}"' -f $clang.Path , $srcDir , $scriptDir , $file )
112+ $compileDb += [ordered ]@ {
113+ directory = $scriptDir
114+ file = $file
115+ command = $command
116+ }
117+ }
118+
119+ $compileDb | ConvertTo-Json - Depth 4 | Set-Content - Path $compileDbPath - Encoding UTF8
120+
121+ Write-Host " Lint-fixing $ ( $sourceFiles.Count ) C source file(s) with clang-tidy..."
122+ $lintFixFailures = @ ()
123+ foreach ($file in $sourceFiles ) {
124+ $tidyArgs = @ (' -p' , $tidyDbDir )
125+ if ($clangTidyChecks -ne " " ) { $tidyArgs += " --checks=$clangTidyChecks " }
126+ # Narrow header filter to just project files (src/, ext/, lib/, tests/) to avoid analyzing system headers
127+ $headerFilter = " (src|ext|lib|tests)/"
128+ $tidyArgs += @ (' -quiet' , ' -fix' , ' -fix-errors' , ' -fix-notes' , $clangTidyFormatArg , " -header-filter=$headerFilter " , $file )
129+ $tidyOutput = & $clangTidy.Path @tidyArgs 2>&1
130+
131+ if ($LASTEXITCODE -ne 0 ) {
132+ $lintFixFailures += [pscustomobject ]@ {
133+ File = $file
134+ Output = (($tidyOutput | ForEach-Object { $_.ToString () }) -join [Environment ]::NewLine)
135+ }
136+ }
137+ }
138+
139+ if ($lintFixFailures.Count -gt 0 ) {
140+ Write-Host " clang-tidy auto-fix failed for $ ( $lintFixFailures.Count ) file(s):" - ForegroundColor Red
141+ foreach ($failure in $lintFixFailures ) {
142+ Write-Host " --- $ ( $failure.File ) ---" - ForegroundColor Red
143+ if ($failure.Output ) {
144+ Write-Host $failure.Output - ForegroundColor Red
145+ }
146+ }
147+ Write-Error " clang-tidy auto-fix failed."
148+ exit 1
149+ }
150+
151+ Write-Host " Re-formatting after clang-tidy fixes..."
152+ foreach ($file in $formatFiles ) {
153+ & $clangFormat.Path " -style=$clangFormatStyle " - i $file
154+ if ($LASTEXITCODE -ne 0 ) {
155+ Write-Error " clang-format post-lint failed for: $file "
156+ exit 1
157+ }
158+ }
159+
160+ Write-Host " Formatting and lint auto-fix completed successfully." - ForegroundColor Green
161+ exit 0
162+ } finally {
163+ if (Test-Path $tidyDbDir ) {
164+ Remove-Item - Recurse - Force $tidyDbDir - ErrorAction SilentlyContinue
165+ }
166+ }
0 commit comments