From 1fbcfee669b0b546071a85fb48e927dc3fe33662 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Thu, 14 Mar 2024 12:38:10 -0700 Subject: [PATCH] Revert "Use multirun (#160)" (#171) * Revert "Use multirun (#160)" This reverts commit 6df95c60bac5057e0ef0fc2eabcf33e46b5b3e06. * chore: minimize delta from main * chore: docs update --- MODULE.bazel | 1 - docs/format.md | 55 ++++++++--- docs/formatting.md | 4 +- example/WORKSPACE.bazel | 2 +- format/BUILD.bazel | 5 +- format/defs.bzl | 83 ++++------------ format/private/BUILD.bazel | 7 +- format/private/format.sh | 107 +++++++++++++++------ format/private/formatter_binary.bzl | 142 ++++++++++++++++------------ format/repositories.bzl | 7 -- format/test/BUILD.bazel | 93 ++++++++++++++---- format/test/format_test.bats | 59 ++++++++---- 12 files changed, 333 insertions(+), 232 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 96485236..aa5a4fd4 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -14,7 +14,6 @@ bazel_dep(name = "aspect_bazel_lib", version = "1.38.0") bazel_dep(name = "aspect_rules_js", version = "1.33.1") bazel_dep(name = "bazel_skylib", version = "1.4.2") bazel_dep(name = "platforms", version = "0.0.7") -bazel_dep(name = "rules_multirun", version = "0.7.0") bazel_dep(name = "rules_multitool", version = "0.4.0") # Needed in the root because we dereference ProtoInfo in our aspect impl diff --git a/docs/format.md b/docs/format.md index b65ef4ba..a610b2aa 100644 --- a/docs/format.md +++ b/docs/format.md @@ -5,8 +5,8 @@ Produce a multi-formatter that aggregates formatters. Some formatter tools are automatically provided by default in rules_lint. These are listed as defaults in the API docs below. -Other formatter binaries may be declared in your repository. -You can test that they work by running them directly with `bazel run`. +Other formatter binaries may be declared in your repository, and you can test them by running +them with Bazel. For example, to add prettier, your `BUILD.bazel` file should contain: @@ -30,10 +30,46 @@ load("@aspect_rules_lint//format:defs.bzl", "format_multirun") format_multirun( name = "format", javascript = ":prettier", + ... ) ``` + + +## format_multirun_rule + +
+format_multirun_rule(name, cc, go, java, javascript, jsonnet, kotlin, markdown, protocol_buffer,
+                     python, scala, sh, sql, starlark, swift, terraform, yaml)
+
+ +Produces an executable that aggregates the supplied formatter binaries + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| cc | a binary target that runs clang-format (or another tool with compatible CLI arguments) | Label | optional | None | +| go | a binary target that runs gofmt (or another tool with compatible CLI arguments) | Label | optional | None | +| java | a binary target that runs java-format (or another tool with compatible CLI arguments) | Label | optional | None | +| javascript | a binary target that runs prettier (or another tool with compatible CLI arguments) | Label | optional | None | +| jsonnet | a binary target that runs jsonnetfmt (or another tool with compatible CLI arguments) | Label | optional | None | +| kotlin | a binary target that runs ktfmt (or another tool with compatible CLI arguments) | Label | optional | None | +| markdown | a binary target that runs prettier-md (or another tool with compatible CLI arguments) | Label | optional | None | +| protocol_buffer | a binary target that runs buf (or another tool with compatible CLI arguments) | Label | optional | None | +| python | a binary target that runs ruff (or another tool with compatible CLI arguments) | Label | optional | None | +| scala | a binary target that runs scalafmt (or another tool with compatible CLI arguments) | Label | optional | None | +| sh | a binary target that runs shfmt (or another tool with compatible CLI arguments) | Label | optional | None | +| 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-fmt (or another tool with compatible CLI arguments) | Label | optional | None | +| yaml | a binary target that runs yamlfmt (or another tool with compatible CLI arguments) | Label | optional | None | + + ## format_multirun @@ -42,29 +78,20 @@ format_multirun( format_multirun(name, kwargs) -Create a multirun binary for the given formatters. - -Intended to be used with `bazel run` to update source files in-place. +Wrapper macro around format_multirun_rule that sets defaults for some languages. -Also produces a target `[name].check` which does not edit files, rather it exits non-zero -if any sources require formatting. - -Tools are provided by default for some languages. These come from the `@multitool` repo. Under --enable_bzlmod, rules_lint creates this automatically. WORKSPACE users will have to set this up manually. See the release install snippet for an example. Set any attribute to `False` to turn off that language altogether, rather than use a default tool. -Note that `javascript` is a special case which also formats TypeScript, TSX, JSON, CSS, and HTML. - - **PARAMETERS** | Name | Description | Default Value | | :------------- | :------------- | :------------- | -| name | name of the resulting target, typically "format" | none | -| kwargs | attributes named for each language, providing Label of a tool that formats it | none | +| name |

-

| none | +| kwargs |

-

| none | diff --git a/docs/formatting.md b/docs/formatting.md index 76d773df..ade2d4de 100644 --- a/docs/formatting.md +++ b/docs/formatting.md @@ -67,7 +67,7 @@ If you don't use pre-commit, you can just wire directly into the git hook, howev this option will always run the formatter over all files, not just changed files. ```bash -$ echo "bazel run //:format.check" >> .git/hooks/pre-commit +$ echo "bazel run //:format -- --mode check" >> .git/hooks/pre-commit $ chmod u+x .git/hooks/pre-commit ``` @@ -75,4 +75,4 @@ $ chmod u+x .git/hooks/pre-commit This will exit non-zero if formatting is needed. You would typically run the check mode on CI. -`bazel run //tools/format:format.check` +`bazel run //:format -- --mode check` diff --git a/example/WORKSPACE.bazel b/example/WORKSPACE.bazel index 7be482b1..e96b4b9c 100644 --- a/example/WORKSPACE.bazel +++ b/example/WORKSPACE.bazel @@ -241,7 +241,7 @@ fetch_vale() # Optional: multitool provides defaults for some tools such as yamlfmt # If you do not set up multitool, you will be forced to either set an explicit tool # for languages that have a default, or set explicit False value, e.g. -# multi_formatter_binary(jsonnet = False) +# format_multirun(jsonnet = False) load("@rules_multitool//multitool:multitool.bzl", "multitool") multitool( diff --git a/format/BUILD.bazel b/format/BUILD.bazel index 2b072505..cc37287a 100644 --- a/format/BUILD.bazel +++ b/format/BUILD.bazel @@ -6,10 +6,7 @@ bzl_library( name = "defs", srcs = ["defs.bzl"], visibility = ["//visibility:public"], - deps = [ - "//format/private:formatter_binary", - "@rules_multirun//:defs", - ], + deps = ["//format/private:formatter_binary"], ) bzl_library( diff --git a/format/defs.bzl b/format/defs.bzl index 715628a7..97129b51 100644 --- a/format/defs.bzl +++ b/format/defs.bzl @@ -3,8 +3,8 @@ Some formatter tools are automatically provided by default in rules_lint. These are listed as defaults in the API docs below. -Other formatter binaries may be declared in your repository. -You can test that they work by running them directly with `bazel run`. +Other formatter binaries may be declared in your repository, and you can test them by running +them with Bazel. For example, to add prettier, your `BUILD.bazel` file should contain: @@ -28,86 +28,35 @@ load("@aspect_rules_lint//format:defs.bzl", "format_multirun") format_multirun( name = "format", javascript = ":prettier", + ... ) ``` """ -load("@rules_multirun//:defs.bzl", "command", "multirun") -load("//format/private:formatter_binary.bzl", "CHECK_FLAGS", "DEFAULT_TOOL_LABELS", "FIX_FLAGS", "TOOLS", "to_attribute_name") +load("//format/private:formatter_binary.bzl", _fmt = "format_multirun") -def format_multirun(name, **kwargs): - """Create a multirun binary for the given formatters. - - Intended to be used with `bazel run` to update source files in-place. +format_multirun_rule = _fmt - Also produces a target `[name].check` which does not edit files, rather it exits non-zero - if any sources require formatting. +def format_multirun(name, **kwargs): + """Wrapper macro around format_multirun_rule that sets defaults for some languages. - Tools are provided by default for some languages. These come from the `@multitool` repo. Under --enable_bzlmod, rules_lint creates this automatically. WORKSPACE users will have to set this up manually. See the release install snippet for an example. Set any attribute to `False` to turn off that language altogether, rather than use a default tool. - - Note that `javascript` is a special case which also formats TypeScript, TSX, JSON, CSS, and HTML. - - Args: - name: name of the resulting target, typically "format" - **kwargs: attributes named for each language, providing Label of a tool that formats it """ - commands = [] - - for lang, toolname in TOOLS.items(): - lang_attribute = to_attribute_name(lang) + _fmt( + name = name, # Logic: - # - if there's no value for this key, the user omitted it, so use our default if we have one - # - if there is a value, and it's False, then skip this language + # - if there's no value for this key, the user omitted it, so use our default + # - if there is a value, and it's False, then pass None to the underlying rule # (and make sure we don't eagerly reference @multitool in case it isn't defined) # - otherwise use the user-supplied value - tool_label = False - if lang_attribute in kwargs.keys(): - tool_label = kwargs.pop(lang_attribute) - elif lang in DEFAULT_TOOL_LABELS.keys(): - tool_label = Label(DEFAULT_TOOL_LABELS[lang]) - if not tool_label: - continue - - target_name = "_".join([name, lang.replace(" ", "_"), "with", toolname]) - - for mode in ["check", "fix"]: - command( - name = target_name + (".check" if mode == "check" else ""), - command = "@aspect_rules_lint//format/private:format", - description = "Formatting {} with {}...".format(lang, toolname), - environment = { - # NB: can't use str(Label(target_name)) here because bzlmod makes it - # the apparent repository, starts with @@aspect_rules_lint~override - "FIX_TARGET": "//{}:{}".format(native.package_name(), target_name), - "tool": "$(rlocationpaths %s)" % tool_label, - "lang": lang, - "flags": FIX_FLAGS[toolname] if mode == "fix" else CHECK_FLAGS[toolname], - "mode": mode, - }, - data = [tool_label], - ) - commands.append(target_name) - - # Error checking in case some user keys were unmatched and therefore not pop'ed - for attr in kwargs.keys(): - fail("""Unknown language "{}". Valid values: {}""".format(attr, [to_attribute_name(lang) for lang in TOOLS.keys()])) - - multirun( - name = name, - buffer_output = True, - commands = commands, - # Run up to 4 formatters at the same time. This is an arbitrary choice, based on some idea that 4-core machines are typical. - jobs = 4, - keep_going = True, - ) - - multirun( - name = name + ".check", - commands = [c + ".check" for c in commands], + jsonnet = kwargs.pop("jsonnet", Label("@multitool//tools/jsonnetfmt")) or None, + go = kwargs.pop("go", Label("@multitool//tools/gofumpt")) or None, + sh = kwargs.pop("sh", Label("@multitool//tools/shfmt")) or None, + yaml = kwargs.pop("yaml", Label("@multitool//tools/yamlfmt")) or None, + **kwargs ) diff --git a/format/private/BUILD.bazel b/format/private/BUILD.bazel index 2b79c14c..4f981b14 100644 --- a/format/private/BUILD.bazel +++ b/format/private/BUILD.bazel @@ -1,11 +1,6 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") -sh_binary( - name = "format", - srcs = ["format.sh"], - visibility = ["//visibility:public"], - deps = ["@bazel_tools//tools/bash/runfiles"], -) +exports_files(["format.sh"]) bzl_library( name = "formatter_binary", diff --git a/format/private/format.sh b/format/private/format.sh index 6a325153..574f3f31 100755 --- a/format/private/format.sh +++ b/format/private/format.sh @@ -1,17 +1,9 @@ #!/usr/bin/env bash -# Wrapper around a formatter tool - -# --- 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: runfiles.bash initializer cannot find $f. An executable rule may have forgotten to expose it in the runfiles, or the binary may require RUNFILES_DIR to be set."; exit 1; }; f=; set -e -# --- end runfiles.bash initialization v3 --- +# 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: \$BUILD_WORKSPACE_DIRECTORY not set. This program should be executed under 'bazel run'." @@ -29,7 +21,7 @@ function on_exit { ;; *) echo >&2 "FAILED: A formatter tool exited with code $code" - echo >&2 "Try running 'bazel run $FIX_TARGET' to fix this." + echo >&2 "Try running 'bazel run {{fix_target}}' to fix this." ;; esac } @@ -103,6 +95,51 @@ function ls-files { fi } +# Define the flags for the tools based on the mode of operation +mode=fix +if [ "${1:-}" == "--mode" ]; then + readonly mode=$2 + shift 2 +fi + +case "$mode" in + check) + swiftmode="--lint" + prettiermode="--check" + ruffmode="format --check" + shfmtmode="-l" + javamode="--set-exit-if-changed --dry-run" + ktmode="--set-exit-if-changed --dry-run" + gofmtmode="-l" + bufmode="format -d --exit-code" + tfmode="-check -diff" + jsonnetmode="--test" + scalamode="--test" + clangformatmode="--style=file --fallback-style=none --dry-run -Werror" + yamlfmtmode="-lint" + ;; + fix) + swiftmode="" + prettiermode="--write" + # Force exclusions in the configuration file to be honored even when file paths are supplied + # as command-line arguments; see + # https://github.com/astral-sh/ruff/discussions/5857#discussioncomment-6583943 + ruffmode="format --force-exclude" + # NB: apply-ignore added in https://github.com/mvdan/sh/issues/1037 + shfmtmode="-w --apply-ignore" + javamode="--replace" + ktmode="" + gofmtmode="-w" + bufmode="format -w" + tfmode="" + jsonnetmode="--in-place" + scalamode="" + clangformatmode="-style=file --fallback-style=none -i" + yamlfmtmode="" + ;; + *) echo >&2 "unknown mode $mode";; +esac + function time-run { local files="$1" && shift local bin="$1" && shift @@ -117,6 +154,7 @@ function time-run { function run-format { local lang="$1" && shift + local fmtname="$1" && shift local bin="$1" && shift local args="$1" && shift local tuser @@ -124,6 +162,7 @@ function run-format { local files=$(ls-files "$lang" $@) if [ -n "$files" ] && [ -n "$bin" ]; then + echo "Formatting ${lang} with ${fmtname}..." case "$lang" in 'Protocol Buffer') ( for file in $files; do @@ -162,20 +201,26 @@ function run-format { fi } -bin="$(rlocation $tool)" -if [ ! -e "$bin" ]; then - echo >&2 "cannot locate binary $tool" - exit 1 -fi - -run-format "$lang" "$bin" "${flags:-""}" $@ - -# Currently these aren't exposed as separate languages to the attributes of format_multirun -# So we format all these languages as part of "JavaScript". -if [[ "$lang" == "JavaScript" ]]; then - run-format "CSS" "$bin" "${flags:-""}" $@ - run-format "HTML" "$bin" "${flags:-""}" $@ - run-format "JSON" "$bin" "${flags:-""}" $@ - run-format "TSX" "$bin" "${flags:-""}" $@ - run-format "TypeScript" "$bin" "${flags:-""}" $@ -fi +# Run each supplied formatter over the files it owns + +run-format Starlark Buildifier "$(rlocation {{buildifier}})" "-mode=$mode" $@ +run-format Markdown Prettier "$(rlocation {{prettier-md}})" "$prettiermode" $@ +run-format JSON Prettier "$(rlocation {{prettier}})" "$prettiermode" $@ +run-format JavaScript Prettier "$(rlocation {{prettier}})" "$prettiermode" $@ +run-format CSS Prettier "$(rlocation {{prettier}})" "$prettiermode" $@ +run-format HTML Prettier "$(rlocation {{prettier}})" "$prettiermode" $@ +run-format TypeScript Prettier "$(rlocation {{prettier}})" "$prettiermode" $@ +run-format TSX Prettier "$(rlocation {{prettier}})" "$prettiermode" $@ +run-format SQL Prettier "$(rlocation {{prettier-sql}})" "$prettiermode" $@ +run-format Python Ruff "$(rlocation {{ruff}})" "$ruffmode" $@ +run-format Terraform "terraform fmt" "$(rlocation {{terraform-fmt}})" "fmt $tfmode" $@ +run-format Jsonnet jsonnetfmt "$(rlocation {{jsonnetfmt}})" "$jsonnetmode" $@ +run-format Java java-format "$(rlocation {{java-format}})" "$javamode" $@ +run-format Kotlin ktfmt "$(rlocation {{ktfmt}})" "$ktmode" $@ +run-format Scala scalafmt "$(rlocation {{scalafmt}})" "$scalamode" $@ +run-format Go gofmt "$(rlocation {{gofmt}})" "$gofmtmode" $@ +run-format C++ clang-format "$(rlocation {{clang-format}})" "$clangformatmode" $@ +run-format Shell shfmt "$(rlocation {{shfmt}})" "$shfmtmode" $@ +run-format Swift swiftfmt "$(rlocation {{swiftformat}})" "$swiftmode" $@ +run-format 'Protocol Buffer' buf "$(rlocation {{buf}})" "$bufmode" $@ +run-format YAML yamlfmt "$(rlocation {{yamlfmt}})" "$yamlfmtmode" $@ diff --git a/format/private/formatter_binary.bzl b/format/private/formatter_binary.bzl index fb37502d..4e332759 100644 --- a/format/private/formatter_binary.bzl +++ b/format/private/formatter_binary.bzl @@ -1,72 +1,88 @@ "Implementation of formatter_binary" +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 -# Keys in this map must match the `case "$language" in` block in format.sh TOOLS = { - "JavaScript": "prettier", - "Markdown": "prettier", - "Python": "ruff", - "Starlark": "buildifier", - "Jsonnet": "jsonnetfmt", - "Terraform": "terraform-fmt", - "Kotlin": "ktfmt", - "Java": "java-format", - "Scala": "scalafmt", - "Swift": "swiftformat", - "Go": "gofmt", - "SQL": "prettier", - "Shell": "shfmt", - "Protocol Buffer": "buf", - "C++": "clang-format", - "YAML": "yamlfmt", + "javascript": "prettier", + "markdown": "prettier-md", + "python": "ruff", + "starlark": "buildifier", + "jsonnet": "jsonnetfmt", + "terraform": "terraform-fmt", + "kotlin": "ktfmt", + "java": "java-format", + "scala": "scalafmt", + "swift": "swiftformat", + "go": "gofmt", + "sql": "prettier-sql", + "sh": "shfmt", + "protocol_buffer": "buf", + "cc": "clang-format", + "yaml": "yamlfmt", } -DEFAULT_TOOL_LABELS = { - "Jsonnet": "@multitool//tools/jsonnetfmt", - "Go": "@multitool//tools/gofumpt", - "Shell": "@multitool//tools/shfmt", - "YAML": "@multitool//tools/yamlfmt", -} +def _formatter_binary_impl(ctx): + substitutions = {} + tools = {v: getattr(ctx.attr, k) for k, v in TOOLS.items()} + for tool, attr in tools.items(): + if attr: + substitutions["{{%s}}" % tool] = to_rlocation_path(ctx, attr.files_to_run.executable) + if len(substitutions) == 0: + fail("format_multirun should have at least one language attribute set to a formatter tool") -CHECK_FLAGS = { - "buildifier": "-mode=check", - "swiftformat": "--lint", - "prettier": "--check", - "ruff": "format --check", - "shfmt": "--diff", - "java-format": "--set-exit-if-changed --dry-run", - "ktfmt": "--set-exit-if-changed --dry-run", - "gofmt": "-l", - "buf": "format -d --exit-code", - "terraform-fmt": "fmt -check -diff", - "jsonnetfmt": "--test", - "scalafmt": "--test", - "clang-format": "--style=file --fallback-style=none --dry-run -Werror", - "yamlfmt": "-lint", -} + substitutions.update({ + # We need to fill in the rlocation paths in the shell script + "{{BASH_RLOCATION_FUNCTION}}": BASH_RLOCATION_FUNCTION, + # Support helpful error reporting + "{{fix_target}}": str(ctx.label), + }) -FIX_FLAGS = { - "buildifier": "-mode=fix", - "swiftformat": "", - "prettier": "--write", - # Force exclusions in the configuration file to be honored even when file paths are supplied - # as command-line arguments; see - # https://github.com/astral-sh/ruff/discussions/5857#discussioncomment-6583943 - "ruff": "format --force-exclude", - # NB: apply-ignore added in https://github.com/mvdan/sh/issues/1037 - "shfmt": "-w --apply-ignore", - "java-format": "--replace", - "ktfmt": "", - "gofmt": "-w", - "buf": "format -w", - "terraform-fmt": "fmt", - "jsonnetfmt": "--in-place", - "scalafmt": "", - "clang-format": "-style=file --fallback-style=none -i", - "yamlfmt": "", -} + # Uniquely named output file allowing more than one formatter in a package + bin = ctx.actions.declare_file("_{}.fmt.sh".format(ctx.label.name)) + ctx.actions.expand_template( + template = ctx.file._bin, + output = bin, + substitutions = substitutions, + is_executable = True, + ) + + runfiles = ctx.runfiles(files = [ctx.file._runfiles_lib] + [ + f.files_to_run.executable + for f in tools.values() + if f + ] + [ + f.files_to_run.runfiles_manifest + for f in tools.values() + if f and f.files_to_run.runfiles_manifest + ]) + runfiles = runfiles.merge_all([ + f.default_runfiles + for f in tools.values() + if f + ]) + + return [ + DefaultInfo( + executable = bin, + runfiles = runfiles, + ), + ] + +formatter_binary_lib = struct( + implementation = _formatter_binary_impl, + attrs = dict({ + k: attr.label(doc = "a binary target that runs {} (or another tool with compatible CLI arguments)".format(v), executable = True, cfg = "exec", allow_files = True) + for k, v in TOOLS.items() + }, **{ + "_bin": attr.label(default = "//format/private:format.sh", allow_single_file = True), + "_runfiles_lib": attr.label(default = "@bazel_tools//tools/bash/runfiles", allow_single_file = True), + }), +) -def to_attribute_name(lang): - if lang == "C++": - return "cc" - return lang.lower().replace(" ", "_") +format_multirun = rule( + doc = "Produces an executable that aggregates the supplied formatter binaries", + implementation = formatter_binary_lib.implementation, + attrs = formatter_binary_lib.attrs, + executable = True, +) diff --git a/format/repositories.bzl b/format/repositories.bzl index 1ade527a..2382505a 100644 --- a/format/repositories.bzl +++ b/format/repositories.bzl @@ -16,13 +16,6 @@ def http_jar(**kwargs): maybe(_http_jar, **kwargs) def rules_lint_dependencies(): - http_archive( - name = "rules_multirun", - sha256 = "7b699b3922919d727e7f5aa868286e59cc69fa6aa14f6c3e54a4674010bd1582", - strip_prefix = "rules_multirun-0.7.0", - url = "https://github.com/keith/rules_multirun/releases/download/0.7.0/rules_multirun-0.7.0.tar.gz", - ) - http_archive( name = "rules_multitool", sha256 = "793105ea4bb89cf9d82411cbee40b6874085f530314600887539e214e4970a3a", diff --git a/format/test/BUILD.bazel b/format/test/BUILD.bazel index 12f79343..0e6f264b 100644 --- a/format/test/BUILD.bazel +++ b/format/test/BUILD.bazel @@ -1,10 +1,7 @@ -load("@bazel_skylib//lib:sets.bzl", "sets") load("@bazel_skylib//rules:write_file.bzl", "write_file") load("//format:defs.bzl", "format_multirun") load("//format/private:formatter_binary.bzl", "TOOLS") -UNIQUE_TOOLS = sets.to_list(sets.make(TOOLS.values())) - # Avoid depending on a bunch of actual tools in the root module. # That's the job of the example/ submodule. # Instead, just provide "recording mock" for each formatter we support. @@ -17,7 +14,7 @@ UNIQUE_TOOLS = sets.to_list(sets.make(TOOLS.values())) "echo + {} $*".format(t), ], ) - for t in UNIQUE_TOOLS + for t in TOOLS.values() ] [ @@ -25,26 +22,88 @@ UNIQUE_TOOLS = sets.to_list(sets.make(TOOLS.values())) name = "mock_" + t, srcs = ["mock_{}.sh".format(t)], ) - for t in UNIQUE_TOOLS + for t in TOOLS.values() ] +# Make a separate formatter binary to test each language in isolation. +# Users should NOT do it this way! format_multirun( - name = "format", - cc = ":mock_clang-format.sh", - go = ":mock_gofmt.sh", - java = ":mock_java-format.sh", + name = "format_javascript", javascript = ":mock_prettier.sh", - jsonnet = ":mock_jsonnetfmt.sh", - kotlin = ":mock_ktfmt.sh", +) + +format_multirun( + name = "format_starlark", + starlark = ":mock_buildifier.sh", +) + +format_multirun( + name = "format_markdown", markdown = ":mock_prettier.sh", - protocol_buffer = ":mock_buf.sh", - python = ":mock_ruff.sh", - scala = ":mock_scalafmt.sh", - shell = ":mock_shfmt.sh", +) + +format_multirun( + name = "format_sql", sql = ":mock_prettier.sh", - starlark = ":mock_buildifier.sh", - swift = ":mock_swiftformat.sh", +) + +format_multirun( + name = "format_python", + python = ":mock_ruff.sh", +) + +format_multirun( + name = "format_hcl", # TODO: this attribute should be renamed to hcl terraform = ":mock_terraform-fmt.sh", +) + +format_multirun( + name = "format_jsonnet", + jsonnet = ":mock_jsonnetfmt.sh", +) + +format_multirun( + name = "format_java", + java = ":mock_java-format.sh", +) + +format_multirun( + name = "format_kotlin", + kotlin = ":mock_ktfmt.sh", +) + +format_multirun( + name = "format_scala", + scala = ":mock_scalafmt.sh", +) + +format_multirun( + name = "format_go", + go = ":mock_gofmt.sh", +) + +format_multirun( + name = "format_cc", + cc = ":mock_clang-format.sh", +) + +format_multirun( + name = "format_sh", + sh = ":mock_shfmt.sh", +) + +format_multirun( + name = "format_swift", + swift = ":mock_swiftformat.sh", +) + +format_multirun( + name = "format_protobuf", + protocol_buffer = ":mock_buf.sh", +) + +format_multirun( + name = "format_yaml", yaml = ":mock_yamlfmt.sh", ) diff --git a/format/test/format_test.bats b/format/test/format_test.bats index f112a2ca..23d10a09 100644 --- a/format/test/format_test.bats +++ b/format/test/format_test.bats @@ -6,130 +6,151 @@ bats_load_library "bats-assert" # No arguments: will use git ls-files @test "should run prettier on javascript using git ls-files" { - run bazel run //format/test:format_JavaScript_with_prettier + run bazel run //format/test:format_javascript assert_success + assert_output --partial "Formatting JavaScript with Prettier..." assert_output --partial "+ prettier --write example/.eslintrc.cjs" + assert_output --partial "Formatting TypeScript with Prettier..." assert_output --partial "+ prettier --write example/src/file.ts example/test/no_violations.ts" + assert_output --partial "Formatting TSX with Prettier..." assert_output --partial "+ prettier --write example/src/hello.tsx" + assert_output --partial "Formatting JSON with Prettier..." assert_output --partial "+ prettier --write .bcr/metadata.template.json" + assert_output --partial "Formatting CSS with Prettier..." assert_output --partial "+ prettier --write example/src/hello.css" + assert_output --partial "Formatting HTML with Prettier..." assert_output --partial "+ prettier --write example/src/index.html" } # File arguments: will filter with find @test "should run prettier on javascript using find" { - run bazel run //format/test:format_JavaScript_with_prettier README.md example/.eslintrc.cjs + run bazel run //format/test:format_javascript README.md example/.eslintrc.cjs assert_success - assert_output --partial "README.md" - refute_output --partial "file.ts" + assert_output --partial "Formatting JavaScript with Prettier..." + refute_output --partial "Formatting TypeScript with Prettier..." } @test "should run buildozer on starlark" { - run bazel run //format/test:format_Starlark_with_buildifier + run bazel run //format/test:format_starlark assert_success + assert_output --partial "Formatting Starlark with Buildifier..." assert_output --partial "+ buildifier -mode=fix BUILD.bazel" assert_output --partial "format/private/BUILD.bazel" } @test "should run prettier on Markdown" { - run bazel run //format/test:format_Markdown_with_prettier + run bazel run //format/test:format_markdown assert_success + assert_output --partial "Formatting Markdown with Prettier..." assert_output --partial "+ prettier --write .bcr/README.md CONTRIBUTING.md README.md" } @test "should run prettier on SQL" { - run bazel run //format/test:format_SQL_with_prettier + run bazel run //format/test:format_sql assert_success + assert_output --partial "Formatting SQL with Prettier..." assert_output --partial "+ prettier --write example/src/hello.sql" } @test "should run ruff on Python" { - run bazel run //format/test:format_Python_with_ruff + run bazel run //format/test:format_python assert_success + assert_output --partial "Formatting Python with Ruff..." assert_output --partial "+ ruff format --force-exclude example/src/subdir/unused_import.py" } @test "should run terraform fmt on HCL" { - run bazel run //format/test:format_Terraform_with_terraform-fmt + run bazel run //format/test:format_hcl assert_success + assert_output --partial "Formatting Terraform with terraform fmt..." assert_output --partial "+ terraform-fmt fmt example/src/hello.tf" } @test "should run jsonnet-fmt on Jsonnet" { - run bazel run //format/test:format_Jsonnet_with_jsonnetfmt + run bazel run //format/test:format_jsonnet assert_success + assert_output --partial "Formatting Jsonnet with jsonnetfmt..." assert_output --partial "+ jsonnetfmt --in-place example/src/hello.jsonnet example/src/hello.libsonnet" } @test "should run java-format on Java" { - run bazel run //format/test:format_Java_with_java-format + run bazel run //format/test:format_java assert_success + assert_output --partial "Formatting Java with java-format..." assert_output --partial "+ java-format --replace example/src/Foo.java" } @test "should run ktfmt on Kotlin" { - run bazel run //format/test:format_Kotlin_with_ktfmt + run bazel run //format/test:format_kotlin assert_success + assert_output --partial "Formatting Kotlin with ktfmt..." assert_output --partial "+ ktfmt example/src/hello.kt" } @test "should run scalafmt on Scala" { - run bazel run //format/test:format_Scala_with_scalafmt + run bazel run //format/test:format_scala assert_success + assert_output --partial "Formatting Scala with scalafmt..." assert_output --partial "+ scalafmt example/src/hello.scala" } @test "should run gofmt on Go" { - run bazel run //format/test:format_Go_with_gofmt + run bazel run //format/test:format_go assert_success + assert_output --partial "Formatting Go with gofmt..." assert_output --partial "+ gofmt -w example/src/hello.go" } @test "should run clang-format on C++" { - run bazel run //format/test:format_C++_with_clang-format + run bazel run //format/test:format_cc assert_success + assert_output --partial "Formatting C++ with clang-format..." assert_output --partial "+ clang-format -style=file --fallback-style=none -i example/src/hello.cpp" } @test "should run shfmt on Shell" { - run bazel run //format/test:format_Shell_with_shfmt + run bazel run //format/test:format_sh assert_success + assert_output --partial "Formatting Shell with shfmt..." assert_output --partial "+ shfmt -w --apply-ignore .github/workflows/release_prep.sh" } @test "should run swiftformat on Swift" { - run bazel run //format/test:format_Swift_with_swiftformat + run bazel run //format/test:format_swift assert_success + # The real swiftformat prints the "Formatting..." output so we don't assert_output --partial "+ swiftformat example/src/hello.swift" } @test "should run buf on Protobuf" { - run bazel run //format/test:format_Protocol_Buffer_with_buf + run bazel run //format/test:format_protobuf assert_success + assert_output --partial "Formatting Protocol Buffer with buf..." # Buf only formats one file at a time assert_output --partial "+ buf format -w example/src/file.proto" assert_output --partial "+ buf format -w example/src/unused.proto" } @test "should run yamlfmt on YAML" { - run bazel run //format/test:format_YAML_with_yamlfmt + run bazel run //format/test:format_yaml assert_success + assert_output --partial "Formatting YAML with yamlfmt..." assert_output --partial "+ yamlfmt .bcr/config.yml" }