# 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