From 175da17bd060a23aa96f4e0a1da0a13af6e1b84f Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 19 Jan 2024 09:58:48 -0800 Subject: [PATCH] refactor: overhaul of format.sh (#105) * refactor: overhaul of format.sh Use GitHub Linguist to define the file extensions for each language, getting much more accurate. Extract a helper function for listing files we want to format to reduce duplication. * Update mirror_linguist_languages.sh --- docs/format.md | 2 +- example/src/tsconfig.json | 1 + example/test/lint_test.bats | 66 +++--- format/private/filter.jq | 37 ++++ format/private/format.sh | 223 ++++++++++---------- format/private/formatter_binary.bzl | 5 +- format/private/mirror_linguist_languages.sh | 7 + 7 files changed, 199 insertions(+), 142 deletions(-) create mode 100644 format/private/filter.jq create mode 100755 format/private/mirror_linguist_languages.sh diff --git a/docs/format.md b/docs/format.md index a3906a76..37932e90 100644 --- a/docs/format.md +++ b/docs/format.md @@ -63,6 +63,6 @@ Produces an executable that aggregates the supplied formatter binaries | sql | a binary target that runs prettier-sql (or another tool with compatible CLI arguments) | Label | optional | None | | starlark | a binary target that runs buildifier (or another tool with compatible CLI arguments) | Label | optional | None | | swift | a binary target that runs swiftformat (or another tool with compatible CLI arguments) | Label | optional | None | -| terraform | a binary target that runs terraform (or another tool with compatible CLI arguments) | Label | optional | None | +| terraform | a binary target that runs terraform-fmt (or another tool with compatible CLI arguments) | Label | optional | None | diff --git a/example/src/tsconfig.json b/example/src/tsconfig.json index e69de29b..0967ef42 100644 --- a/example/src/tsconfig.json +++ b/example/src/tsconfig.json @@ -0,0 +1 @@ +{} diff --git a/example/test/lint_test.bats b/example/test/lint_test.bats index c640dbba..380b1fac 100644 --- a/example/test/lint_test.bats +++ b/example/test/lint_test.bats @@ -2,46 +2,46 @@ bats_load_library "bats-support" bats_load_library "bats-assert" function assert_lints() { - # Shellcheck - echo <<"EOF" | assert_output --partial + # Shellcheck + echo <<"EOF" | assert_output --partial In src/hello.sh line 3: [ -z $THING ] && echo "hello world" ^----^ SC2086 (info): Double quote to prevent globbing and word splitting. EOF - # Ruff - echo <<"EOF" | assert_output --partial + # Ruff + echo <<"EOF" | assert_output --partial src/unused_import.py:13:8: F401 [*] `os` imported but unused Found 1 error. [*] 1 fixable with the `--fix` option. EOF - # Flake8 - assert_output --partial "src/unused_import.py:13:1: F401 'os' imported but unused" + # Flake8 + assert_output --partial "src/unused_import.py:13:1: F401 'os' imported but unused" - # PMD - assert_output --partial 'src/Foo.java:9: FinalizeOverloaded: Finalize methods should not be overloaded' + # PMD + assert_output --partial 'src/Foo.java:9: FinalizeOverloaded: Finalize methods should not be overloaded' - # ESLint - assert_output --partial 'src/file.ts:2:7: Type string trivially inferred from a string literal, remove type annotation [error from @typescript-eslint/no-inferrable-types]' + # ESLint + assert_output --partial 'src/file.ts:2:7: Type string trivially inferred from a string literal, remove type annotation [error from @typescript-eslint/no-inferrable-types]' - # Buf - assert_output --partial 'src/file.proto:1:1:Import "src/unused.proto" is unused.' + # Buf + assert_output --partial 'src/file.proto:1:1:Import "src/unused.proto" is unused.' - # Golangci-lint - assert_output --partial 'src/hello.go:13:2: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)' + # Golangci-lint + assert_output --partial 'src/hello.go:13:2: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)' } @test "should produce reports" { - run $BATS_TEST_DIRNAME/../lint.sh //src:all - assert_success - assert_lints + run $BATS_TEST_DIRNAME/../lint.sh //src:all + assert_success + assert_lints - run $BATS_TEST_DIRNAME/../lint.sh --fix --dry-run //src:all - # Check that we created a 'patch -p1' format file that fixes the ESLint violation - run cat bazel-bin/src/ESLint.ts.aspect_rules_lint.patch - assert_success - echo <<"EOF" | assert_output --partial + run $BATS_TEST_DIRNAME/../lint.sh --fix --dry-run //src:all + # Check that we created a 'patch -p1' format file that fixes the ESLint violation + run cat bazel-bin/src/ESLint.ts.aspect_rules_lint.patch + assert_success + echo <<"EOF" | assert_output --partial --- a/src/file.ts +++ b/src/file.ts @@ -1,3 +1,3 @@ @@ -51,10 +51,10 @@ EOF console.log(a); EOF - # Check that we created a 'patch -p1' format file that fixes the ruff violation - run cat bazel-bin/src/ruff.unused_import.aspect_rules_lint.patch - assert_success - echo <<"EOF" | assert_output --partial + # Check that we created a 'patch -p1' format file that fixes the ruff violation + run cat bazel-bin/src/ruff.unused_import.aspect_rules_lint.patch + assert_success + echo <<"EOF" | assert_output --partial --- a/src/unused_import.py +++ b/src/unused_import.py @@ -10,4 +10,3 @@ @@ -66,14 +66,14 @@ EOF } @test "should fail when --fail-on-violation is passed" { - run $BATS_TEST_DIRNAME/../lint.sh --fail-on-violation //src:all - assert_failure - assert_lints + run $BATS_TEST_DIRNAME/../lint.sh --fail-on-violation //src:all + assert_failure + assert_lints } @test "should use nearest ancestor .eslintrc file" { - run $BATS_TEST_DIRNAME/../lint.sh //src/subdir:eslint-override - assert_success - # This lint check is disabled in the .eslintrc.cjs file - refute_output --partial "Unexpected 'debugger' statement" + run $BATS_TEST_DIRNAME/../lint.sh //src/subdir:eslint-override + assert_success + # This lint check is disabled in the .eslintrc.cjs file + refute_output --partial "Unexpected 'debugger' statement" } diff --git a/format/private/filter.jq b/format/private/filter.jq new file mode 100644 index 00000000..c225c1f3 --- /dev/null +++ b/format/private/filter.jq @@ -0,0 +1,37 @@ +# Filter languages we understand +with_entries(select(.key | IN( + "C++", + "CSS", + "Markdown", + "Go", + "HCL", + "HTML", + "Java", + "JavaScript", + "Jsonnet", + "JSON", + "Kotlin", + "Protocol Buffer", + "Python", + "Scala", + "Shell", + "SQL", + "Starlark", + "Swift", + "TSX", + "TypeScript" +))) + +# Convert values to filenames and extensions with star prefix +| with_entries(.value = ( + .value.filenames + (.value.extensions | map("*" + .)) +)) + +# Render each language as a line in a Bash case statement, e.g. +# 'Jsonnet') patterns=('*.jsonnet' '*.libsonnet') ;; +| to_entries | map( + "'" + .key + "') patterns=(" + (.value | map("'" + . + "'") | join(" ")) + ") ;;" +) + +# Suitable for --raw-output +|.[] \ No newline at end of file diff --git a/format/private/format.sh b/format/private/format.sh index 971c5107..4d40c3fc 100755 --- a/format/private/format.sh +++ b/format/private/format.sh @@ -1,14 +1,17 @@ #!/usr/bin/env bash -# TODO: -# - should this program be written in a different language? -# - if bash, we could reuse https://github.com/github/super-linter/blob/main/lib/functions/worker.sh -# - can we detect what version control system is used? (start with git) +# Template file for a formatter binary. This is expanded by a Bazel action in formatter_binary.bzl +# to produce an actual Bash script. +# Expansions are written in "mustache" syntax like {{interpolate_this}}. + +{{BASH_RLOCATION_FUNCTION}} if [[ -z "$BUILD_WORKSPACE_DIRECTORY" ]]; then - echo >&2 "$0: FATAL: This program must be executed under 'bazel run'" + echo >&2 "$0: FATAL: \$BUILD_WORKSPACE_DIRECTORY not set. This program should be executed under 'bazel run'." exit 1 fi +cd $BUILD_WORKSPACE_DIRECTORY + function on_exit { code=$? if [[ $code != 0 ]]; then @@ -19,32 +22,72 @@ function on_exit { trap on_exit EXIT +# ls-files [...] +function ls-files { + language="$1" && shift; + # Copied file patterns from + # https://github.com/github-linguist/linguist/blob/559a6426942abcae16b6d6b328147476432bf6cb/lib/linguist/languages.yml + # using the ./mirror_linguist_languages.sh tool to transform to Bash code + case "$language" in + 'C++') patterns=('*.cpp' '*.c++' '*.cc' '*.cp' '*.cppm' '*.cxx' '*.h' '*.h++' '*.hh' '*.hpp' '*.hxx' '*.inc' '*.inl' '*.ino' '*.ipp' '*.ixx' '*.re' '*.tcc' '*.tpp' '*.txx') ;; + 'CSS') patterns=('*.css') ;; + 'Go') patterns=('*.go') ;; + 'HCL') patterns=('*.hcl' '*.nomad' '*.tf' '*.tfvars' '*.workflow') ;; + 'HTML') patterns=('*.html' '*.hta' '*.htm' '*.html.hl' '*.inc' '*.xht' '*.xhtml') ;; + 'JSON') patterns=('.all-contributorsrc' '.arcconfig' '.auto-changelog' '.c8rc' '.htmlhintrc' '.imgbotconfig' '.nycrc' '.tern-config' '.tern-project' '.watchmanconfig' 'Pipfile.lock' 'composer.lock' 'deno.lock' 'flake.lock' 'mcmod.info' '*.json' '*.4DForm' '*.4DProject' '*.avsc' '*.geojson' '*.gltf' '*.har' '*.ice' '*.JSON-tmLanguage' '*.jsonl' '*.mcmeta' '*.tfstate' '*.tfstate.backup' '*.topojson' '*.webapp' '*.webmanifest' '*.yy' '*.yyp') ;; + 'Java') patterns=('*.java' '*.jav' '*.jsh') ;; + 'JavaScript') patterns=('Jakefile' '*.js' '*._js' '*.bones' '*.cjs' '*.es' '*.es6' '*.frag' '*.gs' '*.jake' '*.javascript' '*.jsb' '*.jscad' '*.jsfl' '*.jslib' '*.jsm' '*.jspre' '*.jss' '*.jsx' '*.mjs' '*.njs' '*.pac' '*.sjs' '*.ssjs' '*.xsjs' '*.xsjslib') ;; + 'Jsonnet') patterns=('*.jsonnet' '*.libsonnet') ;; + 'Kotlin') patterns=('*.kt' '*.ktm' '*.kts') ;; + 'Markdown') patterns=('contents.lr' '*.md' '*.livemd' '*.markdown' '*.mdown' '*.mdwn' '*.mkd' '*.mkdn' '*.mkdown' '*.ronn' '*.scd' '*.workbook') ;; + 'Protocol Buffer') patterns=('*.proto') ;; + 'Python') patterns=('.gclient' 'DEPS' 'SConscript' 'SConstruct' 'wscript' '*.py' '*.cgi' '*.fcgi' '*.gyp' '*.gypi' '*.lmi' '*.py3' '*.pyde' '*.pyi' '*.pyp' '*.pyt' '*.pyw' '*.rpy' '*.spec' '*.tac' '*.wsgi' '*.xpy') ;; + 'SQL') patterns=('*.sql' '*.cql' '*.ddl' '*.inc' '*.mysql' '*.prc' '*.tab' '*.udf' '*.viw') ;; + 'Scala') patterns=('*.scala' '*.kojo' '*.sbt' '*.sc') ;; + 'Shell') patterns=('.bash_aliases' '.bash_functions' '.bash_history' '.bash_logout' '.bash_profile' '.bashrc' '.cshrc' '.flaskenv' '.kshrc' '.login' '.profile' '.zlogin' '.zlogout' '.zprofile' '.zshenv' '.zshrc' '9fs' 'PKGBUILD' 'bash_aliases' 'bash_logout' 'bash_profile' 'bashrc' 'cshrc' 'gradlew' 'kshrc' 'login' 'man' 'profile' 'zlogin' 'zlogout' 'zprofile' 'zshenv' 'zshrc' '*.sh' '*.bash' '*.bats' '*.cgi' '*.command' '*.fcgi' '*.ksh' '*.sh.in' '*.tmux' '*.tool' '*.trigger' '*.zsh' '*.zsh-theme') ;; + 'Starlark') patterns=('BUCK' 'BUILD' 'BUILD.bazel' 'MODULE.bazel' 'Tiltfile' 'WORKSPACE' 'WORKSPACE.bazel' '*.bzl' '*.star') ;; + 'Swift') patterns=('*.swift') ;; + 'TSX') patterns=('*.tsx') ;; + 'TypeScript') patterns=('*.ts' '*.cts' '*.mts') ;; + *) + echo >&2 "Internal error: unknown language $language" + exit 1 + ;; + esac + + if [ "$#" -eq 0 ]; then + # When the formatter is run with no arguments, we run over "all files in the repo". + # However, we want to ignore anything that is in .gitignore, is marked for delete, etc. + # So we use `git ls-files` with some additional care. + + # TODO: determine which staged changes we should format; avoid formatting unstaged changes + # TODO: try to format only modified regions of the file (where supported) + git ls-files --cached --modified --other --exclude-standard ${patterns[@]} | { + grep -vE "^$(git ls-files --deleted)$" || true; + } + else + # When given arguments, they are glob patterns of the superset of files to format. + # We just need to filter those so we only select files for this language + # Construct a command-line like + # find src/* -name *.ext1 -or -name *.ext2 + find_args=() + for (( i=0; i<${#patterns[@]}; i++ )); do + if [[ i -gt 0 ]]; then + find_args+=('-or') + fi + find_args+=("-name" "${patterns[$i]}") + done + find "$@" "${find_args[@]}" + fi +} + +# Define the flags for the tools based on the mode of operation mode=fix -if [ "$1" == "--mode" ]; then +if [ "${1:-}" == "--mode" ]; then readonly mode=$2 shift 2 fi -# --- begin runfiles.bash initialization v3 --- -# Copy-pasted from the Bazel Bash runfiles library v3. -# https://github.com/bazelbuild/bazel/blob/master/tools/bash/runfiles/runfiles.bash -set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash -source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ - source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ - source "$0.runfiles/$f" 2>/dev/null || \ - source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ - source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ - { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e -# --- end runfiles.bash initialization v3 --- - -cd $BUILD_WORKSPACE_DIRECTORY - -# NOTE: we need to honor .gitignore, so we use git ls-files below -# TODO: talk to version control to determine which staged changes we should format -# TODO: avoid formatting unstaged changes -# TODO: try to format only regions where supported -# TODO: run them concurrently, not serial - case "$mode" in check) swiftmode="--lint" @@ -80,88 +123,87 @@ case "$mode" in *) echo >&2 "unknown mode $mode";; esac -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard 'BUILD' '*/BUILD.bazel' '*.bzl' '*.BUILD' 'WORKSPACE' '*.bazel' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name 'BUILD' -or -name '*.bzl' -or -name '*.BUILD' -or -name 'WORKSPACE' -or -name '*.bazel') -fi +# Run each supplied formatter over the files it owns +# TODO: run them concurrently, not serial + +files=$(ls-files Starlark $@) bin=$(rlocation {{buildifier}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting Starlark with Buildifier..." echo "$files" | tr \\n \\0 | xargs -0 $bin -mode="$mode" fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.md' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.md') -fi +files=$(ls-files Markdown $@) bin=$(rlocation {{prettier-md}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting Markdown with Prettier..." echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.js' '*.cjs' '*.mjs' '*.ts' '*.tsx' '*.mts' '*.cts' '*.json' '*.css' '*.html' '*.md' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.js' -or -name '*.cjs' -or -name '*.mjs' -or -name '*.ts' -or -name '*.tsx' -or -name '*.mts' -or -name '*.cts' -or -name '*.json' -or -name '*.css' -or -name '*.html' -or -name '*.md') -fi +files=$(ls-files JavaScript $@) bin=$(rlocation {{prettier}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting JavaScript with Prettier..." echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.sql' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.sql') +files=$(ls-files CSS $@) +bin=$(rlocation {{prettier}}) +if [ -n "$files" ] && [ -n "$bin" ]; then + echo "Formatting CSS with Prettier..." + echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode +fi + +files=$(ls-files HTML $@) +bin=$(rlocation {{prettier}}) +if [ -n "$files" ] && [ -n "$bin" ]; then + echo "Formatting HTML with Prettier..." + echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode +fi + +files=$(ls-files TypeScript $@) +bin=$(rlocation {{prettier}}) +if [ -n "$files" ] && [ -n "$bin" ]; then + echo "Formatting TypeScript with Prettier..." + echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode fi + +files=$(ls-files TSX $@) +bin=$(rlocation {{prettier}}) +if [ -n "$files" ] && [ -n "$bin" ]; then + echo "Formatting TSX with Prettier..." + echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode +fi + +files=$(ls-files SQL $@) bin=$(rlocation {{prettier-sql}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Running SQL with Prettier..." echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.py' '*.pyi' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.py' -or -name '*.pyi') -fi +files=$(ls-files Python $@) bin=$(rlocation {{ruff}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting Python with ruff..." echo "$files" | tr \\n \\0 | xargs -0 $bin $ruffmode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.tf' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.tf') -fi -bin=$(rlocation {{terraform}}) +files=$(ls-files HCL $@) +bin=$(rlocation {{terraform-fmt}}) if [ -n "$files" ] && [ -n "$bin" ]; then - echo "Formatting terraform..." + echo "Formatting Hashicorp Config Language with terraform fmt..." echo "$files" | tr \\n \\0 | xargs -0 $bin fmt $tfmode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.jsonnet' '*.libsonnet' | { grep -vE "^$(git ls-files --deleted)$" || true; } ) -else - files=$(find "$@" -name '*.jsonnet' -or -name '*.libsonnet') -fi +files=$(ls-files Jsonnet $@) bin=$(rlocation {{jsonnetfmt}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting Jsonnet with jsonnetfmt..." echo "$files" | tr \\n \\0 | xargs -0 $bin $jsonnetmode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.java' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.java') -fi +files=$(ls-files Java $@) bin=$(rlocation {{java-format}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting Java with java-format..." @@ -169,22 +211,14 @@ if [ -n "$files" ] && [ -n "$bin" ]; then echo "$files" | tr \\n \\0 | JAVA_RUNFILES="${RUNFILES_MANIFEST_FILE%_manifest}" xargs -0 $bin $javamode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.kt' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.kt') -fi +files=$(ls-files Kotlin $@) bin=$(rlocation {{ktfmt}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting Kotlin with ktfmt..." echo "$files" | tr \\n \\0 | xargs -0 $bin $ktmode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.scala' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.scala') -fi +files=$(ls-files Scala $@) bin=$(rlocation {{scalafmt}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting Scala with scalafmt..." @@ -192,11 +226,7 @@ if [ -n "$files" ] && [ -n "$bin" ]; then echo "$files" | tr \\n \\0 | JAVA_RUNFILES="${RUNFILES_MANIFEST_FILE%_manifest}" xargs -0 $bin $scalamode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.go' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.go') -fi +files=$(ls-files Go $@) bin=$(rlocation {{gofmt}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting Go with gofmt..." @@ -214,51 +244,32 @@ if [ -n "$files" ] && [ -n "$bin" ]; then fi fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.cc' '*.cpp' '*.cxx' '*.c++' '*.C' '*.c' '*.h' '*.hh' '*.hpp' '*.ipp' '*.hxx' '*.h++' '*.inc' '*.inl' '*.tlh' '*.tli' '*.H' | { grep -vE "^$(git ls-files --deleted)$" || true; } ) -else - files=$(find "$@" -name '*.cc' -or -name '*.cpp' -or -name '*.cxx' -or -name '*.c++' -or -name '*.C' -or -name '*.c' -or -name '*.h' -or -name '*.hh' -or -name '*.hpp' -or -name '*.ipp' -or -name '*.hxx' -or -name '*.h++' -or -name '*.inc' -or -name '*.inl' -or -name '*.tlh' -or -name '*.tli' -or -name '*.H') -fi +files=$(ls-files C++ $@) bin=$(rlocation {{clang-format}}) if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting C/C++ with clang-format..." echo "$files" | tr \\n \\0 | xargs -0 $bin $clangformatmode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.sh' '*.bash' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.sh' -or -name '*.bash') -fi +files=$(ls-files Shell $@) bin=$(rlocation {{shfmt}}) if [ -n "$files" ] && [ -n "$bin" ]; then - echo "Formatting Bash/Shell with shfmt..." + echo "Formatting Shell with shfmt..." echo "$files" | tr \\n \\0 | xargs -0 $bin $shfmtmode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.swift' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.swift') -fi +files=$(ls-files Swift $@) bin=$(rlocation {{swiftformat}}) - if [ -n "$files" ] && [ -n "$bin" ]; then # swiftformat itself prints Running SwiftFormat... echo "$files" | tr \\n \\0 | xargs -0 $bin $swiftmode fi -if [ "$#" -eq 0 ]; then - files=$(git ls-files --cached --modified --other --exclude-standard '*.proto' | { grep -vE "^$(git ls-files --deleted)$" || true; }) -else - files=$(find "$@" -name '*.proto') -fi +files=$(ls-files 'Protocol Buffer' $@) bin=$(rlocation {{buf}}) - if [ -n "$files" ] && [ -n "$bin" ]; then echo "Formatting Protobuf with buf..." for file in $files; do $bin $bufmode $file done fi - diff --git a/format/private/formatter_binary.bzl b/format/private/formatter_binary.bzl index abb9c9e3..e691c2fe 100644 --- a/format/private/formatter_binary.bzl +++ b/format/private/formatter_binary.bzl @@ -1,6 +1,6 @@ "Implementation of formatter_binary" -load("@aspect_bazel_lib//lib:paths.bzl", "to_rlocation_path") +load("@aspect_bazel_lib//lib:paths.bzl", "BASH_RLOCATION_FUNCTION", "to_rlocation_path") # Per the formatter design, each language can only have a single formatter binary _TOOLS = { @@ -9,7 +9,7 @@ _TOOLS = { "python": "ruff", "starlark": "buildifier", "jsonnet": "jsonnetfmt", - "terraform": "terraform", + "terraform": "terraform-fmt", "kotlin": "ktfmt", "java": "java-format", "scala": "scalafmt", @@ -24,6 +24,7 @@ _TOOLS = { def _formatter_binary_impl(ctx): # We need to fill in the rlocation paths in the shell script substitutions = { + "{{BASH_RLOCATION_FUNCTION}}": BASH_RLOCATION_FUNCTION, "{{fix_target}}": str(ctx.label), } tools = {v: getattr(ctx.attr, k) for k, v in _TOOLS.items()} diff --git a/format/private/mirror_linguist_languages.sh b/format/private/mirror_linguist_languages.sh new file mode 100755 index 00000000..d6114fe4 --- /dev/null +++ b/format/private/mirror_linguist_languages.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Generate a snippet of bash code to help us detect languages based on filenames +raw=$(mktemp --suffix=.yml) +json=$(mktemp --suffix=.json) +wget -O "$raw" https://raw.githubusercontent.com/github-linguist/linguist/master/lib/linguist/languages.yml +# We could do this entirely in yq, but the author happens to already know JQ :shrug: +yq -o=json '.' "$raw" | jq --from-file=filter.jq --raw-output