# Code formatter - runs targeted formatters based on what changed from trunk. # Usage: format.ps1 [-All] [-PreCommit] [-PrePush] [-Lint] # (default) Check all changes relative to trunk including uncommitted work # -All Format everything, skip change detection # -PreCommit Only check staged changes # -PrePush Only check committed changes relative to trunk # -Lint Also run linters before formatting param( [switch]$All, [switch]$PreCommit, [switch]$PrePush, [switch]$Lint ) Set-StrictMode -Version 'Latest' $ErrorActionPreference = 'Stop' # Validate mutually exclusive flags if ($PreCommit -and $PrePush) { Write-Error "Cannot use both -PreCommit and -PrePush" exit 1 } function section($message) { Write-Host "- $message" -ForegroundColor Green } # Find what's changed compared to trunk (skip if -All) $formatAll = $All $trunkRef = git rev-parse --verify trunk 2>$null if (-not $formatAll -and $trunkRef) { $base = git merge-base HEAD $trunkRef 2>$null if ($base) { if ($PreCommit) { $changed = git diff --name-only --cached } elseif ($PrePush) { $changed = git diff --name-only $base HEAD } else { $committed = git diff --name-only $base HEAD $staged = git diff --name-only --cached $unstaged = git diff --name-only $untracked = git ls-files --others --exclude-standard $changed = ($committed + $staged + $unstaged + $untracked) | Sort-Object -Unique } } else { $formatAll = $true } } elseif (-not $formatAll) { # No trunk ref found, format everything $formatAll = $true } # Helper to check if a pattern matches changed files function changedMatches($pattern) { if ($formatAll) { return $true } return ($changed | Where-Object { $_ -match $pattern }).Count -gt 0 } $WORKSPACE_ROOT = (bazel info workspace) # Capture baseline to detect formatter-introduced changes $baseline = git status --porcelain # Always run buildifier and copyright section "Buildifier" Write-Host " buildifier" bazel run //:buildifier section "Copyright" Write-Host " update_copyright" bazel run //scripts:update_copyright # Run language formatters only if those files changed if (changedMatches '^java/') { section "Java" Write-Host " google-java-format" $GOOGLE_JAVA_FORMAT = (bazel run --run_under=echo //scripts:google-java-format) Get-ChildItem -Path "$WORKSPACE_ROOT/java" -Include "*.java" -Recurse | ForEach-Object { & "$GOOGLE_JAVA_FORMAT" --replace $_.FullName } } if (changedMatches '^javascript/selenium-webdriver/') { section "JavaScript" Write-Host " prettier" $NODE_WEBDRIVER = "$WORKSPACE_ROOT/javascript/selenium-webdriver" bazel run //javascript:prettier -- "$NODE_WEBDRIVER" --write "$NODE_WEBDRIVER/.prettierrc" --log-level=warn } if (changedMatches '^rb/|^rake_tasks/|^Rakefile') { section "Ruby" Write-Host " rubocop -a" if ($Lint) { bazel run //rb:rubocop -- -a } else { bazel run //rb:rubocop -- -a --fail-level F } } if (changedMatches '^rust/') { section "Rust" Write-Host " rustfmt" bazel run @rules_rust//:rustfmt } if (changedMatches '^py/') { section "Python" if ($Lint) { Write-Host " ruff check" bazel run //py:ruff-check } Write-Host " ruff format" bazel run //py:ruff-format } if (changedMatches '^dotnet/') { section ".NET" Write-Host " dotnet format" bazel run //dotnet:format -- style --severity warn bazel run //dotnet:format -- whitespace } # Run shellcheck and actionlint when -Lint is passed if ($Lint) { section "Shell/Actions" Write-Host " actionlint (with shellcheck)" $SHELLCHECK = (bazel run --run_under=echo @multitool//tools/shellcheck) bazel run @multitool//tools/actionlint:cwd -- -shellcheck "$SHELLCHECK" } # Check if formatting introduced new changes (comparing to baseline) $after = git status --porcelain if ($after -ne $baseline) { Write-Host "" Write-Host "Formatters modified files:" -ForegroundColor Red git diff --name-only exit 1 } Write-Host "Format check passed." -ForegroundColor Green