$ErrorActionPreference = "Stop" $inputDirs = @PSC_LLVM_INPUT_DIRS@ $excludeDirs = @PSC_LLVM_EXCLUDE_DIRS@ $outputDir = "@PSC_LLVM_OUTPUT_DIR@" $compileCommands = "@PSC_LLVM_COMPILE_COMMANDS@" $configuredClangTidy = "@PSC_LLVM_CLANG_TIDY@" $topN = [int]"@PSC_LLVM_TOP_N@" $threshold = [int]"@PSC_LLVM_THRESHOLD@" function Resolve-Tool([string]$Configured, [string]$Name, [string[]]$Fallbacks) { if ($Configured -and (Test-Path -LiteralPath $Configured)) { return (Resolve-Path -LiteralPath $Configured).Path } $command = Get-Command $Name -ErrorAction SilentlyContinue | Select-Object -First 1 if ($command) { return $command.Source } foreach ($fallback in $Fallbacks) { if (Test-Path -LiteralPath $fallback) { return (Resolve-Path -LiteralPath $fallback).Path } } throw "$Name executable was not found" } function Normalize-Path([string]$Path) { return [IO.Path]::GetFullPath($Path).Replace("\", "/").TrimEnd("/") } function Test-InRoots([string]$Path, [string[]]$Roots) { $normalized = Normalize-Path $Path foreach ($root in $Roots) { if ($normalized.StartsWith($root + "/", [StringComparison]::OrdinalIgnoreCase) -or $normalized.Equals($root, [StringComparison]::OrdinalIgnoreCase)) { return $true } } return $false } $clangTidy = Resolve-Tool $configuredClangTidy "clang-tidy" @("C:\Program Files\LLVM\bin\clang-tidy.exe") $clangCl = Join-Path (Split-Path $clangTidy -Parent) "clang-cl.exe" if (!(Test-Path -LiteralPath $clangCl)) { throw "clang-cl executable was not found next to clang-tidy" } if (!(Test-Path -LiteralPath $compileCommands)) { throw "compile_commands.json was not found at $compileCommands" } $roots = @($inputDirs | ForEach-Object { Normalize-Path $_ }) $excludes = @($excludeDirs | ForEach-Object { Normalize-Path $_ }) New-Item -ItemType Directory -Force -Path $outputDir | Out-Null $databaseDir = Join-Path $outputDir "compile_database" New-Item -ItemType Directory -Force -Path $databaseDir | Out-Null $database = Get-Content -LiteralPath $compileCommands -Raw | ConvertFrom-Json $selected = [Collections.Generic.List[object]]::new() foreach ($entry in $database) { $file = Normalize-Path $entry.file if (!(Test-InRoots $file $roots) -or (Test-InRoots $file $excludes)) { continue } if ([IO.Path]::GetExtension($file) -notin @(".c", ".cc", ".cpp", ".cxx")) { continue } $command = [string]$entry.command $escapedClang = '"' + $clangCl + '"' $command = [regex]::Replace($command, '^\s*(?:"[^"]+"|[^\s]+?)(?:cl(?:\.exe|\.bat))\s+', $escapedClang + " ", [Text.RegularExpressions.RegexOptions]::IgnoreCase) $command = [regex]::Replace($command, '(?i)(?:^|\s)[/-](?:Fo|Fd|Fp|FI|Yu|Yc)(?:"[^"]*"|\S+)', " ") $command = [regex]::Replace($command, '(?i)(?:^|\s)(?:/FS|/nologo|/RTC1|/Ob\d|/Od|-Z7|-MDd?)\b', " ") $command = [regex]::Replace($command, '(?i)-external:I', "-I") $command = [regex]::Replace($command, '(?i)\s-external:W\d\b', " ") $command = [regex]::Replace($command, '\s+', " ").Trim() $selected.Add([ordered]@{ directory = $entry.directory command = $command file = $entry.file output = $entry.output }) } if ($selected.Count -eq 0) { throw "No translation units matched the configured input directories" } $sanitizedDatabase = Join-Path $databaseDir "compile_commands.json" [IO.File]::WriteAllText($sanitizedDatabase, ($selected | ConvertTo-Json -Depth 8), [Text.UTF8Encoding]::new($false)) $rawPath = Join-Path $outputDir "clang_tidy_raw.txt" $rawWriter = [IO.StreamWriter]::new($rawPath, $false, [Text.UTF8Encoding]::new($false)) $records = [Collections.Generic.List[object]]::new() $diagnosticErrors = 0 $config = "{CheckOptions: {readability-function-cognitive-complexity.Threshold: $threshold, readability-function-cognitive-complexity.DescribeBasicIncrements: false, readability-function-cognitive-complexity.IgnoreMacros: false}}" foreach ($entry in $selected) { $lines = & $clangTidy $entry.file "-p=$databaseDir" "-checks=-*,readability-function-cognitive-complexity" "-config=$config" "--quiet" 2>&1 foreach ($lineObject in $lines) { $line = [string]$lineObject $rawWriter.WriteLine($line) if ($line -match '^(?.+?):(?\d+):(?\d+): warning: (?:(?:function ''(?[^'']+)'')|(?lambda)) has cognitive complexity of (?\d+)') { $file = Normalize-Path $Matches.file if ((Test-InRoots $file $roots) -and !(Test-InRoots $file $excludes)) { $records.Add([pscustomobject]@{ Score = [int]$Matches.score Function = if ($Matches.name) { $Matches.name } else { "" } File = $file Line = [int]$Matches.line Column = [int]$Matches.column }) } } elseif ($line -match '(^| )error:') { $diagnosticErrors++ } } } $rawWriter.Dispose() $top = @($records | Sort-Object @{Expression = "Score"; Descending = $true}, File, Line | Select-Object -First $topN) $top | Export-Csv -LiteralPath (Join-Path $outputDir "cognitive_complexity.csv") -NoTypeInformation -Encoding UTF8 $summary = [ordered]@{ tool = "clang-tidy" version = (& $clangTidy --version | Select-Object -First 1).Trim() threshold = $threshold translation_units = $selected.Count findings = $records.Count diagnostic_errors = $diagnosticErrors top = $top } [IO.File]::WriteAllText((Join-Path $outputDir "summary.json"), ($summary | ConvertTo-Json -Depth 8), [Text.UTF8Encoding]::new($false)) Write-Output "LLVM cognitive complexity report: $outputDir"