Files
UnrealEngine/Engine/Extras/clang-format/perforce-clang-format-diff.ps1
2025-05-18 13:04:45 +08:00

283 lines
9.9 KiB
PowerShell

[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(ValueFromPipeline = $true, Position = 0)]
[String]
$Path,
[Parameter()]
[string]
$Changelist,
[Parameter()]
[switch]
$PerformSlateFiltering
)
begin {
Set-StrictMode -Version 3
$FilesToFormat = @()
}
process {
if (![string]::IsNullOrEmpty($Path)) {
$FilesToFormat += $Path
}
}
end {
function ProcessZTag {
param (
[Parameter(ValueFromPipeline)]
[String]
$Line
)
process {
switch -regex ($Line) {
"^\.\.\. depotFile (.*)" { $DepotFile = $Matches[1] }
"^\.\.\. type (.*)" { $Type = $Matches[1] }
"^\.\.\. action (.*)" { $Action = $Matches[1] }
"^\s*$" {
if ($Type -like "*text*") {
Write-Verbose "Found text file $DepotFile action $Action"
[PSCustomObject]@{
DepotPath = $DepotFile
LocalPath = $null
IsAdd = $Action -like "add"
}
}
else {
Write-Verbose "Skipping non-text file $DepotFile"
}
$DepotFile = $null
$Type = $null
$Action = $null
}
}
}
}
function filterSlateCodeFromDiffOutput($diffOutput) {
Write-Verbose "Performing Slater filtering by skipping formatting on parts of the code that looks like declarative Slate code."
$fileContent = Get-Content $LocalPath
$fileContentLines = $fileContent -split "\r?\n|\r"
# Build zones.
$currentStartLineIndex = -1
$currentBracketNesting = 0
$slateBracketZones = for ($i=0; $i -lt $fileContentLines.Length; $i++)
{
$line = $fileContentLines[$i]
if ($line.Trim() -eq "[")
{
$currentBracketNesting += 1
# We don't record the start of a nested Slate bracket.
if ($currentBracketNesting -gt 0)
{
$currentStartLineIndex = $i
}
}
elseif ($line.Trim() -eq "]" -or $line.Trim() -eq "];")
{
$currentBracketNesting -= 1
if ($currentBracketNesting -eq 0)
{
$start = $currentStartLineIndex
$currentStartLineIndex = -1
@{ "startLineIndex" = [int]$start; "endLineIndex" = [int]$i };
}
}
}
# Expand zones up and down to the nearest whitespace-only lines.
foreach ($zone in $slateBracketZones)
{
$startIndex = $zone["startLineIndex"]
$endIndex = $zone["endLineIndex"]
$originalstartIndex = $startIndex
$originalEndIndex = $endIndex
# Keep moving the start of the zone up until we find a whitespace-only line or the start of the file.
while (($startIndex -gt 0) -and ($fileContentLines[$startIndex - 1].Trim() -ne ""))
{
$startIndex--
}
# Keep moving the end of the zone down until we find a whitespace-only line or the end of the file.
while (($endIndex -lt $fileContentLines.Length-1) -and ($fileContentLines[$endIndex + 1].Trim() -ne ""))
{
$endIndex++
}
if (($originalStartIndex -ne $startIndex) -or ($originalEndIndex -ne $endIndex))
{
Write-Verbose "Expanded Slate bracket zone from ($originalStartIndex, $originalEndIndex) to ($startIndex, $endIndex)"
}
$zone["startLineIndex"] = $startIndex
$zone["endLineIndex"] = $endIndex
}
$skipCurrentHunk = $false
$diffLines = $diffOutput -split "\r?\n|\r"
for ($i=0; $i -lt $diffLines.Length; $i++)
{
$diffLine = $diffLines[$i]
if ($diffLine -match "^@@.*\+(\d+)(?:,(\d+))?")
{
$skipCurrentHunk = $false
$match = $Matches
$hunkStartLine = [int]$match.1
$hunkLineCount = [int]$match.2
if (-Not $hunkLineCount)
{
$hunkLineCount = 1
}
$hunkEndLine = $hunkStartLine + $hunkLineCount - 1
$hunkStartLineIndex = $hunkStartLine - 1
$hunkEndLineIndex = $hunkEndLine - 1
foreach ($slateBracketZone in $slateBracketZones)
{
$zoneStart = $slateBracketZone["startLineIndex"]
$zoneEnd = $slateBracketZone["endLineIndex"]
$hunkStartInside = ($zoneStart -le $hunkStartLineIndex) -and ($hunkStartLineIndex -le $zoneEnd)
$hunkEndInside = ($zoneStart -le $hunkEndLineIndex) -and ($hunkEndLineIndex -le $zoneEnd)
if ($hunkStartInside -or $hunkEndInside)
{
Write-Verbose "Diff hunk overlaps Slate bracket zone, skipping the following lines:"
$skipCurrentHunk = $true
}
}
}
if ($skipCurrentHunk)
{
Write-Verbose "SKIPPING: $diffLine"
}
else
{
Write-Output $diffLine
}
}
}
# Filter files on the command line to those actually present in perforce and convert to depot paths
if (0 -ne $FilesToFormat.Length) {
Write-Verbose "Checking perforce status of requested files $FilesToFormat"
[array]$DepotFiles = p4 "-ztag" "fstat" "-Ro" @FilesToFormat | ProcessZTag
}
else {
if (![string]::IsNullOrEmpty($Changelist)) {
Write-Verbose "Requested files for changelist $Changelist"
# Get opened files in a specific changelist
$P4Args = @("-ztag", "opened", "-c", $Changelist);
}
else {
Write-Verbose "Requested files for all pending changelists"
# Get current client name
switch -regex (p4 "-ztag" "info") {
"^\.\.\. clientName (.*)" { $ClientName = $Matches[1] }
}
if ([string]::IsNullOrEmpty($ClientName)) {
throw "Unable to get perforce client name"
}
# Get all opened files
$P4Args = @("-ztag", "opened", "-C", $ClientName);
}
Write-Verbose "Fetching open files from Perforce $P4Args"
[array]$DepotFiles = p4 @P4Args | ProcessZTag
}
if ($DepotFiles.Length -eq 0) {
throw "No files to format"
}
$Index = 0
switch -regex (p4 -ztag "where" @($DepotFiles | Select-Object -ExpandProperty "DepotPath")) {
"^\.\.\. path (.*)" {
$DepotFiles[$Index].LocalPath = $Matches[1]
++$Index
}
}
# We need this to use the function from within Foreach-Object.
$filterSlateCodeFromDiffOutputSource = ${function:filterSlateCodeFromDiffOutput}.ToString()
$Root = $PSScriptRoot
$DepotFiles | Foreach-Object -ThrottleLimit 5 -Parallel {
$VerbosePreference = $using:VerbosePreference
$WhatIfPreference = $using:WhatIfPreference
$PerformSlateFiltering = $using:PerformSlateFiltering
${function:filterSlateCodeFromDiffOutput} = $using:filterSlateCodeFromDiffOutputSource
$Root = $using:Root
$LocalPath = $_.LocalPath
$IsAdd = $_.IsAdd
if ([string]::IsNullOrEmpty($LocalPath)) {
throw "Unexpected empty path"
}
if ($false -eq (Test-Path $LocalPath)) {
Write-Verbose "Skipping non-existed (deleted/moved?) file $LocalPath"
return;
}
$clangFormatPath = Join-Path $Root "clang-format.exe"
$formatLocalPath = Join-Path $Root "experimental.clang-format"
# For newly added files, just format the entire file
if ($IsAdd) {
if ($WhatIfPreference) {
Write-Host "Skipping full format of newly added file $LocalPath"
}
else {
& $clangFormatPath "-style=file:$formatLocalPath" "-i" $LocalPath | Out-Null
}
}
else {
$clangFormatDiffPath = Join-Path $Root "clang-format-diff.py"
$diffOutput = p4 "diff" "-du0" $LocalPath
if ($PerformSlateFiltering) {
$diffOutput = filterSlateCodeFromDiffOutput $diffOutput
Write-Verbose "Final diff given to clang-format-diff:"
foreach ($line in $diffOutput) {
Write-Verbose $line
}
}
# Check output is what we expect and bail if not
if ($diffOutput.Length -lt 2) {
throw "Diff output for $LocalPath unexpectedly short"
$diffOutput | Write-Host
}
if ($diffOutput[0] -notlike "---*") {
throw "Missing old file path in diff output"
}
if ($diffOutput[1] -notlike "+++*") {
throw "Missing new file path in diff output"
}
$pythonArgs = @($clangFormatDiffPath, "-binary=$clangFormatPath", "-style=file:$formatLocalPath", "-v", "-i")
if ($WhatIfPreference) {
Write-Host "Skipping running diff script for file $LocalPath"
$diffOutput | Select-Object -first 2 | Write-Host
}
else {
$diffFormatOutput = Write-Output $diffOutput | python @pythonArgs | Out-String
$diffFormatExitCode = $LASTEXITCODE
if ($diffFormatExitCode -ne 0) {
throw "Failed to run clang-format-diff.py on $LocalPath.`nclang-format-diff.py output:`n$diffFormatOutput"
}
}
}
}
}