diff --git a/scripts/format.ps1 b/scripts/format.ps1 index 2d61f5170450e..e9860b3f7351c 100644 --- a/scripts/format.ps1 +++ b/scripts/format.ps1 @@ -1,41 +1,142 @@ -# Code formatter. +# 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) -$GOOGLE_JAVA_FORMAT = (bazel run --run_under=echo //scripts:google-java-format) +# Capture baseline to detect formatter-introduced changes +$baseline = git status --porcelain + +# Always run buildifier and copyright section "Buildifier" -Write-Host " buildifier" -ForegroundColor Green +Write-Host " buildifier" bazel run //:buildifier -section "Java" -Write-Host " google-java-format" -ForegroundColor Green -Get-ChildItem -Path "$PWD/java" -Include "*.java" -Recurse | ForEach-Object { - &"$GOOGLE_JAVA_FORMAT" --replace $_.FullName +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 + } } -section "Javascript" -Write-Host " javascript/selenium-webdriver - prettier" -ForegroundColor Green -$NODE_WEBDRIVER = "$WORKSPACE_ROOT/javascript/selenium-webdriver" -bazel run //javascript:prettier -- "$NODE_WEBDRIVER" --write "$NODE_WEBDRIVER/.prettierrc" --log-level=warn +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 +} -section "Ruby" -Write-Host " rubocop" -ForegroundColor Green -bazel run //rb:lint +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 + } +} -section "Rust" -Write-Host " rustfmt" -ForegroundColor Green -bazel run @rules_rust//:rustfmt +if (changedMatches '^rust/') { + section "Rust" + Write-Host " rustfmt" + bazel run @rules_rust//:rustfmt +} -section "Python" -Write-Host " python - ruff" -ForegroundColor Green -bazel run //py:ruff-format +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 +} -section "Copyright" -bazel run //scripts:update_copyright +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 diff --git a/scripts/format.sh b/scripts/format.sh index a9a271126d71c..66a2183eaed94 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -1,39 +1,147 @@ #!/usr/bin/env bash -# Code formatter. +# Code formatter - runs targeted formatters based on what changed from trunk. +# Usage: format.sh [--all] [--pre-commit] [--pre-push] [--lint] +# (default) Check all changes relative to trunk including uncommitted work +# --all Format everything, skip change detection (previous behavior) +# --pre-commit Only check staged changes +# --pre-push Only check committed changes relative to trunk +# --lint Also run linters before formatting set -eufo pipefail -echo "Note: for more flexibility, use './go format' or './go dotnet:format' or './go format -dotnet', etc" >&2 -echo "" >&2 +run_lint=false +format_all=false +mode="default" +for arg in "$@"; do + case "$arg" in + --lint) run_lint=true ;; + --all) format_all=true ;; + + --pre-commit|--pre-push) + [[ "$mode" == "default" ]] || { echo "Cannot use both --pre-commit and --pre-push" >&2; exit 1; } + mode="${arg#--}" + ;; + *) + echo "Unknown option: $arg" >&2 + echo "Usage: $0 [--all] [--pre-commit] [--pre-push] [--lint]" >&2 + exit 1 + ;; + esac +done section() { echo "- $*" >&2 } +# Find what's changed compared to trunk (skip if --all) +trunk_ref="$(git rev-parse --verify trunk 2>/dev/null || echo "")" + +if [[ "$format_all" == "false" && -n "$trunk_ref" ]]; then + base="$(git merge-base HEAD "$trunk_ref" 2>/dev/null || echo "")" + if [[ -n "$base" ]]; then + case "$mode" in + pre-commit) + changed="$(git diff --name-only --cached)" + ;; + pre-push) + changed="$(git diff --name-only "$base" HEAD)" + ;; + default) + 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="$(printf '%s\n%s\n%s\n%s' "$committed" "$staged" "$unstaged" "$untracked" | sort -u)" + ;; + esac + else + format_all=true + fi +elif [[ "$format_all" == "false" ]]; then + # No trunk ref found, format everything + format_all=true +fi + +# Helper to check if a pattern matches changed files +changed_matches() { + [[ "$format_all" == "true" ]] || echo "$changed" | grep -qE "$1" +} + WORKSPACE_ROOT="$(bazel info workspace)" -GOOGLE_JAVA_FORMAT="$(bazel run --run_under=echo //scripts:google-java-format)" +# Capture baseline to detect formatter-introduced changes (allows pre-existing uncommitted work) +baseline="$(git status --porcelain)" +# Always run buildifier and copyright section "Buildifier" echo " buildifier" >&2 bazel run //:buildifier -section "Java" -echo " google-java-format" >&2 -find "$PWD/java" -type f -name '*.java' | xargs "$GOOGLE_JAVA_FORMAT" --replace +section "Copyright" +echo " update_copyright" >&2 +bazel run //scripts:update_copyright + +# Run language formatters only if those files changed +if changed_matches '^java/'; then + section "Java" + echo " google-java-format" >&2 + GOOGLE_JAVA_FORMAT="$(bazel run --run_under=echo //scripts:google-java-format)" + find "${WORKSPACE_ROOT}/java" -type f -name '*.java' -exec "$GOOGLE_JAVA_FORMAT" --replace {} + +fi + +if changed_matches '^javascript/selenium-webdriver/'; then + section "JavaScript" + echo " prettier" >&2 + NODE_WEBDRIVER="${WORKSPACE_ROOT}/javascript/selenium-webdriver" + bazel run //javascript:prettier -- "${NODE_WEBDRIVER}" --write "${NODE_WEBDRIVER}/.prettierrc" --log-level=warn +fi + +if changed_matches '^rb/|^rake_tasks/|^Rakefile'; then + section "Ruby" + echo " rubocop -a" >&2 + if [[ "$run_lint" == "true" ]]; then + bazel run //rb:rubocop -- -a + else + bazel run //rb:rubocop -- -a --fail-level F + fi +fi + +if changed_matches '^rust/'; then + section "Rust" + echo " rustfmt" >&2 + bazel run @rules_rust//:rustfmt +fi + +if changed_matches '^py/'; then + section "Python" + if [[ "$run_lint" == "true" ]]; then + echo " ruff check" >&2 + bazel run //py:ruff-check + fi + echo " ruff format" >&2 + bazel run //py:ruff-format +fi -section "Javascript" -echo " javascript/selenium-webdriver - prettier" >&2 -NODE_WEBDRIVER="${WORKSPACE_ROOT}/javascript/selenium-webdriver" -bazel run //javascript:prettier -- "${NODE_WEBDRIVER}" --write "${NODE_WEBDRIVER}/.prettierrc" --log-level=warn +if changed_matches '^dotnet/'; then + section ".NET" + echo " dotnet format" >&2 + bazel run //dotnet:format -- style --severity warn + bazel run //dotnet:format -- whitespace +fi -section "Ruby" -echo " rubocop" >&2 -bazel run //rb:rubocop -- -a --fail-level F +# Run shellcheck and actionlint when --lint is passed +if [[ "$run_lint" == "true" ]]; then + section "Shell/Actions" + echo " actionlint (with shellcheck)" >&2 + SHELLCHECK="$(bazel run --run_under=echo @multitool//tools/shellcheck)" + bazel run @multitool//tools/actionlint:cwd -- -shellcheck "$SHELLCHECK" +fi -section "Rust" -echo " rustfmt" >&2 -bazel run @rules_rust//:rustfmt +# Check if formatting introduced new changes (comparing to baseline) +if [[ "$(git status --porcelain)" != "$baseline" ]]; then + echo "" >&2 + echo "Formatters modified files:" >&2 + git diff --name-only >&2 + exit 1 +fi -section "Python" -echo " python - ruff" >&2 -bazel run //py:ruff-format +echo "Format check passed." >&2