From 32c091a003aad87c57ef777682a030403d0258ca Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 17 Jul 2024 14:24:27 -0700 Subject: [PATCH] feat: color the human-readable outputs (#333) * feat: color the human-readable report add a flag to disable color in cases where the output is presented in a terminal that can't render it Fixes #332 * chore: fix test * chore: fix PMD test * chore: use shorter flag syntax * chore: code review feedback --- docs/eslint.md | 6 ++++-- docs/flake8.md | 3 ++- docs/ktlint.md | 3 ++- docs/pmd.md | 3 ++- docs/ruff.md | 6 ++++-- docs/vale.md | 3 ++- example/test/lint_test.bats | 10 ++++++++-- lint/BUILD.bazel | 15 +++++++++++++++ lint/eslint.bzl | 28 +++++++++++++++++----------- lint/flake8.bzl | 8 +++++--- lint/ktlint.bzl | 8 +++++--- lint/pmd.bzl | 9 ++++++--- lint/private/lint_aspect.bzl | 3 +++ lint/ruff.bzl | 18 +++++++++++------- lint/shellcheck.bzl | 5 +++-- lint/vale.bzl | 12 ++++++------ 16 files changed, 95 insertions(+), 45 deletions(-) diff --git a/docs/eslint.md b/docs/eslint.md index 26afd240..2a9746c8 100644 --- a/docs/eslint.md +++ b/docs/eslint.md @@ -60,7 +60,7 @@ See the [react example](https://github.com/bazelbuild/examples/blob/b498bb106b20 ## eslint_action
-eslint_action(ctx, executable, srcs, stdout, exit_code, format)
+eslint_action(ctx, executable, srcs, stdout, exit_code, format, env)
 
Create a Bazel Action that spawns an eslint process. @@ -80,6 +80,7 @@ https://eslint.org/docs/latest/use/command-line-interface | stdout | output file containing the stdout or --output-file of eslint | none | | exit_code | output file containing the exit code of eslint. If None, then fail the build when eslint exits non-zero. | None | | format | value for eslint --format CLI flag | "stylish" | +| env | environment variables for eslint | {} | @@ -87,7 +88,7 @@ https://eslint.org/docs/latest/use/command-line-interface ## eslint_fix
-eslint_fix(ctx, executable, srcs, patch, stdout, exit_code, format)
+eslint_fix(ctx, executable, srcs, patch, stdout, exit_code, format, env)
 
Create a Bazel Action that spawns eslint with --fix. @@ -104,6 +105,7 @@ Create a Bazel Action that spawns eslint with --fix. | stdout | output file containing the stdout or --output-file of eslint | none | | exit_code | output file containing the exit code of eslint | none | | format | value for eslint --format CLI flag | "stylish" | +| env | environment variaables for eslint | {} | diff --git a/docs/flake8.md b/docs/flake8.md index f767e757..5bdda89f 100644 --- a/docs/flake8.md +++ b/docs/flake8.md @@ -33,7 +33,7 @@ flake8 = lint_flake8_aspect( ## flake8_action
-flake8_action(ctx, executable, srcs, config, stdout, exit_code)
+flake8_action(ctx, executable, srcs, config, stdout, exit_code, options)
 
Run flake8 as an action under Bazel. @@ -52,6 +52,7 @@ Based on https://flake8.pycqa.org/en/latest/user/invocation.html | config | label of the flake8 config file (setup.cfg, tox.ini, or .flake8) | none | | stdout | output file containing stdout of flake8 | none | | exit_code | output file containing exit code of flake8 If None, then fail the build when flake8 exits non-zero. | None | +| options | additional command-line options, see https://flake8.pycqa.org/en/latest/user/options.html | [] | diff --git a/docs/ktlint.md b/docs/ktlint.md index 7205f26a..8e4b2ad9 100644 --- a/docs/ktlint.md +++ b/docs/ktlint.md @@ -70,7 +70,7 @@ fetch_ktlint()
 ktlint_action(ctx, executable, srcs, editorconfig, stdout, baseline_file, java_runtime, ruleset_jar,
-              exit_code)
+              exit_code, options)
 
Runs ktlint as build action in Bazel. @@ -93,6 +93,7 @@ https://pinterest.github.io/ktlint/latest/install/cli/ | java_runtime | The Java Runtime configured for this build, pulled from the registered toolchain. | none | | ruleset_jar | An optional, custom ktlint ruleset jar. | None | | exit_code | output file to write the exit code. If None, then fail the build when ktlint exits non-zero. | None | +| options | additional command-line arguments to ktlint, see https://pinterest.github.io/ktlint/latest/install/cli/#miscellaneous-flags-and-commands | [] | diff --git a/docs/pmd.md b/docs/pmd.md index df8d1d46..2ed5b192 100644 --- a/docs/pmd.md +++ b/docs/pmd.md @@ -80,7 +80,7 @@ Attrs: ## pmd_action
-pmd_action(ctx, executable, srcs, rulesets, stdout, exit_code)
+pmd_action(ctx, executable, srcs, rulesets, stdout, exit_code, options)
 
Run PMD as an action under Bazel. @@ -99,5 +99,6 @@ Based on https://docs.pmd-code.org/latest/pmd_userdocs_installation.html#running | rulesets | list of labels of the PMD ruleset files | none | | stdout | output file to generate | none | | exit_code | output file to write the exit code. If None, then fail the build when PMD exits non-zero. | None | +| options | additional command-line options, see https://pmd.github.io/pmd/pmd_userdocs_cli_reference.html | [] | diff --git a/docs/ruff.md b/docs/ruff.md index ddb3a7de..6cb7b32b 100644 --- a/docs/ruff.md +++ b/docs/ruff.md @@ -123,7 +123,7 @@ Attrs: ## ruff_action
-ruff_action(ctx, executable, srcs, config, stdout, exit_code)
+ruff_action(ctx, executable, srcs, config, stdout, exit_code, env)
 
Run ruff as an action under Bazel. @@ -153,6 +153,7 @@ However this is needed because: | config | labels of ruff config files (pyproject.toml, ruff.toml, or .ruff.toml) | none | | stdout | output file of linter results to generate | none | | exit_code | output file to write the exit code. If None, then fail the build when ruff exits non-zero. See https://github.com/astral-sh/ruff/blob/dfe4291c0b7249ae892f5f1d513e6f1404436c13/docs/linter.md#exit-codes | None | +| env | environment variaables for ruff | {} | @@ -160,7 +161,7 @@ However this is needed because: ## ruff_fix
-ruff_fix(ctx, executable, srcs, config, patch, stdout, exit_code)
+ruff_fix(ctx, executable, srcs, config, patch, stdout, exit_code, env)
 
Create a Bazel Action that spawns ruff with --fix. @@ -177,5 +178,6 @@ Create a Bazel Action that spawns ruff with --fix. | patch | output file containing the applied fixes that can be applied with the patch(1) command. | none | | stdout | output file of linter results to generate | none | | exit_code | output file to write the exit code | none | +| env | environment variaables for ruff | {} | diff --git a/docs/vale.md b/docs/vale.md index 63977069..49a53759 100644 --- a/docs/vale.md +++ b/docs/vale.md @@ -109,7 +109,7 @@ A factory function to create a linter aspect. ## vale_action
-vale_action(ctx, executable, srcs, styles, config, stdout, exit_code, output)
+vale_action(ctx, executable, srcs, styles, config, stdout, exit_code, output, env)
 
Run Vale as an action under Bazel. @@ -127,5 +127,6 @@ Run Vale as an action under Bazel. | stdout | output file containing stdout of Vale | none | | exit_code | output file containing Vale exit code. If None, then fail the build when Vale exits non-zero. | None | | output | the value for the --output flag | "CLI" | +| env | environment variables for vale | {} | diff --git a/example/test/lint_test.bats b/example/test/lint_test.bats index 80ba7ecb..9c541f5c 100644 --- a/example/test/lint_test.bats +++ b/example/test/lint_test.bats @@ -20,7 +20,13 @@ EOF 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' + echo <<"EOF" | assert_output --partial +* file: src/Foo.java + src: Foo.java:9:9 + rule: FinalizeOverloaded + msg: Finalize methods should not be overloaded + code: protected void finalize(int a) {} +EOF # ktlint assert_output --partial "src/hello.kt:1:1: File name 'hello.kt' should conform PascalCase (standard:filename)" @@ -42,7 +48,7 @@ EOF } @test "should produce reports" { - run $BATS_TEST_DIRNAME/../lint.sh //src:all + run $BATS_TEST_DIRNAME/../lint.sh //src:all --no@aspect_rules_lint//lint:color assert_success assert_lints diff --git a/lint/BUILD.bazel b/lint/BUILD.bazel index 01f2d5fb..6b616f44 100644 --- a/lint/BUILD.bazel +++ b/lint/BUILD.bazel @@ -69,8 +69,23 @@ config_setting( flag_values = {":fix": "true"}, ) +bool_flag( + name = "color", + build_setting_default = True, + visibility = ["//visibility:public"], +) + +config_setting( + name = "color.enabled", + flag_values = {":color": "true"}, +) + lint_options( name = "options", + color = select({ + ":color.enabled": True, + "//conditions:default": False, + }), debug = select({ ":debug.enabled": True, "//conditions:default": False, diff --git a/lint/eslint.bzl b/lint/eslint.bzl index cfa0ea08..d1b78caf 100644 --- a/lint/eslint.bzl +++ b/lint/eslint.bzl @@ -84,7 +84,7 @@ def _gather_inputs(ctx, srcs, files): inputs.extend(js_inputs.to_list()) return inputs -def eslint_action(ctx, executable, srcs, stdout, exit_code = None, format = "stylish"): +def eslint_action(ctx, executable, srcs, stdout, exit_code = None, format = "stylish", env = {}): """Create a Bazel Action that spawns an eslint process. Adapter for wrapping Bazel around @@ -98,6 +98,7 @@ def eslint_action(ctx, executable, srcs, stdout, exit_code = None, format = "sty exit_code: output file containing the exit code of eslint. If None, then fail the build when eslint exits non-zero. format: value for eslint `--format` CLI flag + env: environment variables for eslint """ args = ctx.actions.args() @@ -120,9 +121,9 @@ def eslint_action(ctx, executable, srcs, stdout, exit_code = None, format = "sty tools = [executable._eslint], arguments = [args], command = executable._eslint.path + " $@ && touch " + stdout.path, - env = { + env = dict(env, **{ "BAZEL_BINDIR": ctx.bin_dir.path, - }, + }), mnemonic = _MNEMONIC, progress_message = "Linting %{label} with ESLint", ) @@ -138,15 +139,15 @@ def eslint_action(ctx, executable, srcs, stdout, exit_code = None, format = "sty outputs = [stdout, exit_code], executable = executable._eslint, arguments = [args], - env = { + env = dict(env, **{ "BAZEL_BINDIR": ctx.bin_dir.path, "JS_BINARY__EXIT_CODE_OUTPUT_FILE": exit_code.path, - }, + }), mnemonic = _MNEMONIC, progress_message = "Linting %{label} with ESLint", ) -def eslint_fix(ctx, executable, srcs, patch, stdout, exit_code, format = "stylish"): +def eslint_fix(ctx, executable, srcs, patch, stdout, exit_code, format = "stylish", env = {}): """Create a Bazel Action that spawns eslint with --fix. Args: @@ -157,6 +158,7 @@ def eslint_fix(ctx, executable, srcs, patch, stdout, exit_code, format = "stylis stdout: output file containing the stdout or --output-file of eslint exit_code: output file containing the exit code of eslint format: value for eslint `--format` CLI flag + env: environment variaables for eslint """ patch_cfg = ctx.actions.declare_file("_{}.patch_cfg".format(ctx.label.name)) @@ -165,7 +167,7 @@ def eslint_fix(ctx, executable, srcs, patch, stdout, exit_code, format = "stylis content = json.encode({ "linter": executable._eslint.path, "args": ["--fix", "--format", format] + [s.short_path for s in srcs], - "env": {"BAZEL_BINDIR": ctx.bin_dir.path}, + "env": dict(env, **{"BAZEL_BINDIR": ctx.bin_dir.path}), "files_to_diff": [s.path for s in srcs], "output": patch.path, }), @@ -176,12 +178,12 @@ def eslint_fix(ctx, executable, srcs, patch, stdout, exit_code, format = "stylis outputs = [patch, stdout, exit_code], executable = executable._patcher, arguments = [patch_cfg.path], - env = { + env = dict(env, **{ "BAZEL_BINDIR": ".", "JS_BINARY__EXIT_CODE_OUTPUT_FILE": exit_code.path, "JS_BINARY__STDOUT_OUTPUT_FILE": stdout.path, "JS_BINARY__SILENT_ON_SUCCESS": "1", - }, + }), tools = [executable._eslint], mnemonic = _MNEMONIC, progress_message = "Linting %{label} with ESLint", @@ -198,11 +200,15 @@ def _eslint_aspect_impl(target, ctx): else: outputs, info = output_files(_MNEMONIC, target, ctx) + # https://www.npmjs.com/package/chalk#chalklevel + # 2: Force 256 color support even when a tty isn't detected + color_env = {"FORCE_COLOR": "2"} if ctx.attr._options[LintOptionsInfo].color else {} + # eslint can produce a patch file at the same time it reports the unpatched violations if hasattr(outputs, "patch"): - eslint_fix(ctx, ctx.executable, files_to_lint, outputs.patch, outputs.human.out, outputs.human.exit_code) + eslint_fix(ctx, ctx.executable, files_to_lint, outputs.patch, outputs.human.out, outputs.human.exit_code, env = color_env) else: - eslint_action(ctx, ctx.executable, files_to_lint, outputs.human.out, outputs.human.exit_code) + eslint_action(ctx, ctx.executable, files_to_lint, outputs.human.out, outputs.human.exit_code, env = color_env) # TODO(alex): if we run with --fix, this will report the issues that were fixed. Does a machine reader want to know about them? eslint_action(ctx, ctx.executable, files_to_lint, outputs.machine.out, outputs.machine.exit_code, format = ctx.attr._formatter) diff --git a/lint/flake8.bzl b/lint/flake8.bzl index f1c08952..57127690 100644 --- a/lint/flake8.bzl +++ b/lint/flake8.bzl @@ -30,7 +30,7 @@ load("//lint/private:lint_aspect.bzl", "LintOptionsInfo", "filter_srcs", "noop_l _MNEMONIC = "AspectRulesLintFlake8" -def flake8_action(ctx, executable, srcs, config, stdout, exit_code = None): +def flake8_action(ctx, executable, srcs, config, stdout, exit_code = None, options = []): """Run flake8 as an action under Bazel. Based on https://flake8.pycqa.org/en/latest/user/invocation.html @@ -43,6 +43,7 @@ def flake8_action(ctx, executable, srcs, config, stdout, exit_code = None): stdout: output file containing stdout of flake8 exit_code: output file containing exit code of flake8 If None, then fail the build when flake8 exits non-zero. + options: additional command-line options, see https://flake8.pycqa.org/en/latest/user/options.html """ inputs = srcs + [config] outputs = [stdout] @@ -50,6 +51,7 @@ def flake8_action(ctx, executable, srcs, config, stdout, exit_code = None): # Wire command-line options, see # https://flake8.pycqa.org/en/latest/user/options.html args = ctx.actions.args() + args.add_all(options) args.add_all(srcs) args.add(config, format = "--config=%s") @@ -84,8 +86,8 @@ def _flake8_aspect_impl(target, ctx): noop_lint_action(ctx, outputs) return [info] - # TODO(#332): colorize the human output - flake8_action(ctx, ctx.executable._flake8, files_to_lint, ctx.file._config_file, outputs.human.out, outputs.human.exit_code) + color_options = ["--color=always"] if ctx.attr._options[LintOptionsInfo].color else [] + flake8_action(ctx, ctx.executable._flake8, files_to_lint, ctx.file._config_file, outputs.human.out, outputs.human.exit_code, color_options) flake8_action(ctx, ctx.executable._flake8, files_to_lint, ctx.file._config_file, outputs.machine.out, outputs.machine.exit_code) return [info] diff --git a/lint/ktlint.bzl b/lint/ktlint.bzl index 9fb903c4..b25ec40d 100644 --- a/lint/ktlint.bzl +++ b/lint/ktlint.bzl @@ -56,7 +56,7 @@ load("//lint/private:lint_aspect.bzl", "LintOptionsInfo", "filter_srcs", "noop_l _MNEMONIC = "AspectRulesLintKTLint" -def ktlint_action(ctx, executable, srcs, editorconfig, stdout, baseline_file, java_runtime, ruleset_jar = None, exit_code = None): +def ktlint_action(ctx, executable, srcs, editorconfig, stdout, baseline_file, java_runtime, ruleset_jar = None, exit_code = None, options = []): """ Runs ktlint as build action in Bazel. Adapter for wrapping Bazel around @@ -73,9 +73,11 @@ def ktlint_action(ctx, executable, srcs, editorconfig, stdout, baseline_file, ja ruleset_jar: An optional, custom ktlint ruleset jar. exit_code: output file to write the exit code. If None, then fail the build when ktlint exits non-zero. + options: additional command-line arguments to ktlint, see https://pinterest.github.io/ktlint/latest/install/cli/#miscellaneous-flags-and-commands """ args = ctx.actions.args() + args.add_all(options) inputs = srcs outputs = [stdout] @@ -141,8 +143,8 @@ def _ktlint_aspect_impl(target, ctx): noop_lint_action(ctx, outputs) return [info] - # TODO(#332): colorize the human output - ktlint_action(ctx, ctx.executable._ktlint, files_to_lint, ctx.file._editorconfig, outputs.human.out, ctx.file._baseline_file, ctx.attr._java_runtime, ruleset_jar, outputs.human.exit_code) + color_options = ["--color"] if ctx.attr._options[LintOptionsInfo].color else [] + ktlint_action(ctx, ctx.executable._ktlint, files_to_lint, ctx.file._editorconfig, outputs.human.out, ctx.file._baseline_file, ctx.attr._java_runtime, ruleset_jar, outputs.human.exit_code, color_options) ktlint_action(ctx, ctx.executable._ktlint, files_to_lint, ctx.file._editorconfig, outputs.machine.out, ctx.file._baseline_file, ctx.attr._java_runtime, ruleset_jar, outputs.machine.exit_code) return [info] diff --git a/lint/pmd.bzl b/lint/pmd.bzl index 21d2d657..2405f152 100644 --- a/lint/pmd.bzl +++ b/lint/pmd.bzl @@ -32,7 +32,7 @@ load("//lint/private:lint_aspect.bzl", "LintOptionsInfo", "filter_srcs", "noop_l _MNEMONIC = "AspectRulesLintPMD" -def pmd_action(ctx, executable, srcs, rulesets, stdout, exit_code = None): +def pmd_action(ctx, executable, srcs, rulesets, stdout, exit_code = None, options = []): """Run PMD as an action under Bazel. Based on https://docs.pmd-code.org/latest/pmd_userdocs_installation.html#running-pmd-via-command-line @@ -45,6 +45,7 @@ def pmd_action(ctx, executable, srcs, rulesets, stdout, exit_code = None): stdout: output file to generate exit_code: output file to write the exit code. If None, then fail the build when PMD exits non-zero. + options: additional command-line options, see https://pmd.github.io/pmd/pmd_userdocs_cli_reference.html """ inputs = srcs + rulesets outputs = [stdout] @@ -52,6 +53,7 @@ def pmd_action(ctx, executable, srcs, rulesets, stdout, exit_code = None): # Wire command-line options, see # https://docs.pmd-code.org/latest/pmd_userdocs_cli_reference.html args = ctx.actions.args() + args.add_all(options) args.add("--rulesets") args.add_joined(rulesets, join_with = ",") @@ -87,8 +89,9 @@ def _pmd_aspect_impl(target, ctx): noop_lint_action(ctx, outputs) return [info] - # TODO(#332): colorize the human output - pmd_action(ctx, ctx.executable._pmd, files_to_lint, ctx.files._rulesets, outputs.human.out, outputs.human.exit_code) + # https://github.com/pmd/pmd/blob/master/docs/pages/pmd/userdocs/pmd_report_formats.md + format_options = ["--format", "textcolor" if ctx.attr._options[LintOptionsInfo].color else "text"] + pmd_action(ctx, ctx.executable._pmd, files_to_lint, ctx.files._rulesets, outputs.human.out, outputs.human.exit_code, format_options) pmd_action(ctx, ctx.executable._pmd, files_to_lint, ctx.files._rulesets, outputs.machine.out, outputs.machine.exit_code) return [info] diff --git a/lint/private/lint_aspect.bzl b/lint/private/lint_aspect.bzl index 303f58a2..5856dc3c 100644 --- a/lint/private/lint_aspect.bzl +++ b/lint/private/lint_aspect.bzl @@ -3,6 +3,7 @@ LintOptionsInfo = provider( doc = "Global options for running linters", fields = { + "color": "whether ANSI color escape codes are desired in the human-readable stdout", "debug": "print additional information for rules_lint developers", "fail_on_violation": "whether to honor the exit code of linter tools run as actions", "fix": "whether to run linters in their --fix mode. Fixes are collected into patch files.", @@ -11,6 +12,7 @@ LintOptionsInfo = provider( def _lint_options_impl(ctx): return LintOptionsInfo( + color = ctx.attr.color, debug = ctx.attr.debug, fail_on_violation = ctx.attr.fail_on_violation, fix = ctx.attr.fix, @@ -19,6 +21,7 @@ def _lint_options_impl(ctx): lint_options = rule( implementation = _lint_options_impl, attrs = { + "color": attr.bool(), "debug": attr.bool(), "fix": attr.bool(), "fail_on_violation": attr.bool(), diff --git a/lint/ruff.bzl b/lint/ruff.bzl index 904da918..0c585763 100644 --- a/lint/ruff.bzl +++ b/lint/ruff.bzl @@ -55,7 +55,7 @@ load(":ruff_versions.bzl", "RUFF_VERSIONS") _MNEMONIC = "AspectRulesLintRuff" -def ruff_action(ctx, executable, srcs, config, stdout, exit_code = None): +def ruff_action(ctx, executable, srcs, config, stdout, exit_code = None, env = {}): """Run ruff as an action under Bazel. Ruff will select the configuration file to use for each source file, as documented here: @@ -80,6 +80,7 @@ def ruff_action(ctx, executable, srcs, config, stdout, exit_code = None): exit_code: output file to write the exit code. If None, then fail the build when ruff exits non-zero. See https://github.com/astral-sh/ruff/blob/dfe4291c0b7249ae892f5f1d513e6f1404436c13/docs/linter.md#exit-codes + env: environment variaables for ruff """ inputs = srcs + config outputs = [stdout] @@ -103,11 +104,12 @@ def ruff_action(ctx, executable, srcs, config, stdout, exit_code = None): command = command.format(ruff = executable.path, stdout = stdout.path), arguments = [args], mnemonic = _MNEMONIC, + env = env, progress_message = "Linting %{label} with Ruff", tools = [executable], ) -def ruff_fix(ctx, executable, srcs, config, patch, stdout, exit_code): +def ruff_fix(ctx, executable, srcs, config, patch, stdout, exit_code, env = {}): """Create a Bazel Action that spawns ruff with --fix. Args: @@ -118,6 +120,7 @@ def ruff_fix(ctx, executable, srcs, config, patch, stdout, exit_code): patch: output file containing the applied fixes that can be applied with the patch(1) command. stdout: output file of linter results to generate exit_code: output file to write the exit code + env: environment variaables for ruff """ patch_cfg = ctx.actions.declare_file("_{}.patch_cfg".format(ctx.label.name)) @@ -136,12 +139,12 @@ def ruff_fix(ctx, executable, srcs, config, patch, stdout, exit_code): outputs = [patch, exit_code, stdout], executable = executable._patcher, arguments = [patch_cfg.path], - env = { + env = dict(env, **{ "BAZEL_BINDIR": ".", "JS_BINARY__EXIT_CODE_OUTPUT_FILE": exit_code.path, "JS_BINARY__STDOUT_OUTPUT_FILE": stdout.path, "JS_BINARY__SILENT_ON_SUCCESS": "1", - }, + }), tools = [executable._ruff], mnemonic = _MNEMONIC, progress_message = "Linting %{label} with Ruff", @@ -153,7 +156,6 @@ def _ruff_aspect_impl(target, ctx): return [] files_to_lint = filter_srcs(ctx.rule) - if ctx.attr._options[LintOptionsInfo].fix: outputs, info = patch_and_output_files(_MNEMONIC, target, ctx) else: @@ -163,11 +165,13 @@ def _ruff_aspect_impl(target, ctx): noop_lint_action(ctx, outputs) return [info] + color_env = {"FORCE_COLOR": "1"} if ctx.attr._options[LintOptionsInfo].color else {} + # Ruff can produce a patch at the same time as reporting the unpatched violations if hasattr(outputs, "patch"): - ruff_fix(ctx, ctx.executable, files_to_lint, ctx.files._config_files, outputs.patch, outputs.human.out, outputs.human.exit_code) + ruff_fix(ctx, ctx.executable, files_to_lint, ctx.files._config_files, outputs.patch, outputs.human.out, outputs.human.exit_code, env = color_env) else: - ruff_action(ctx, ctx.executable._ruff, files_to_lint, ctx.files._config_files, outputs.human.out, outputs.human.exit_code) + ruff_action(ctx, ctx.executable._ruff, files_to_lint, ctx.files._config_files, outputs.human.out, outputs.human.exit_code, env = color_env) # TODO(alex): if we run with --fix, this will report the issues that were fixed. Does a machine reader want to know about them? ruff_action(ctx, ctx.executable._ruff, files_to_lint, ctx.files._config_files, outputs.machine.out, outputs.machine.exit_code) diff --git a/lint/shellcheck.bzl b/lint/shellcheck.bzl index 9902afec..c0cc7ed9 100644 --- a/lint/shellcheck.bzl +++ b/lint/shellcheck.bzl @@ -79,14 +79,15 @@ def _shellcheck_aspect_impl(target, ctx): noop_lint_action(ctx, outputs) return [info] + color_options = ["--color"] if ctx.attr._options[LintOptionsInfo].color else [] + # shellcheck does not have a --fix mode that applies fixes for some violations while reporting others. # So we must run an action to generate the report separately from an action that writes the human-readable report. if hasattr(outputs, "patch"): discard_exit_code = ctx.actions.declare_file(_OUTFILE_FORMAT.format(label = target.label.name, mnemonic = _MNEMONIC, suffix = "patch_exit_code")) shellcheck_action(ctx, ctx.executable._shellcheck, files_to_lint, ctx.file._config_file, outputs.patch, discard_exit_code, ["--format", "diff"]) - # TODO(#332): the human action should run with color enabled - shellcheck_action(ctx, ctx.executable._shellcheck, files_to_lint, ctx.file._config_file, outputs.human.out, outputs.human.exit_code) + shellcheck_action(ctx, ctx.executable._shellcheck, files_to_lint, ctx.file._config_file, outputs.human.out, outputs.human.exit_code, color_options) shellcheck_action(ctx, ctx.executable._shellcheck, files_to_lint, ctx.file._config_file, outputs.machine.out, outputs.machine.exit_code) return [info] diff --git a/lint/vale.bzl b/lint/vale.bzl index ee6bf078..b668a93c 100644 --- a/lint/vale.bzl +++ b/lint/vale.bzl @@ -70,7 +70,7 @@ load(":vale_versions.bzl", "VALE_VERSIONS") _MNEMONIC = "AspectRulesLintVale" -def vale_action(ctx, executable, srcs, styles, config, stdout, exit_code = None, output = "CLI"): +def vale_action(ctx, executable, srcs, styles, config, stdout, exit_code = None, output = "CLI", env = {}): """Run Vale as an action under Bazel. Args: @@ -83,17 +83,16 @@ def vale_action(ctx, executable, srcs, styles, config, stdout, exit_code = None, exit_code: output file containing Vale exit code. If None, then fail the build when Vale exits non-zero. output: the value for the --output flag + env: environment variables for vale """ inputs = srcs + [config] - # TODO(#332): enable color when requested - env = {"NO_COLOR": "1"} if styles: inputs.append(styles) # Introduced in https://github.com/errata-ai/vale/commit/2139c4176a4d2e62d7dfb95dca24b96b9e8b7251 # and released in v3.1.0 - env["VALE_STYLES_PATH"] = styles.path + env = dict(env, **{"VALE_STYLES_PATH": styles.path}) # Wire command-line options, see output of vale --help args = ctx.actions.args() @@ -128,6 +127,8 @@ def _vale_aspect_impl(target, ctx): if not should_visit(ctx.rule, ctx.attr._rule_kinds, ctx.attr._filegroup_tags): return [] + # The "CLI" output style is automatically colored unless disabled + color_env = {} if ctx.attr._options[LintOptionsInfo].color else {"NO_COLOR": "1"} outputs, info = output_files(_MNEMONIC, target, ctx) styles = None if ctx.files._styles: @@ -136,8 +137,7 @@ def _vale_aspect_impl(target, ctx): styles = ctx.files._styles[0] if not styles.is_directory: fail("Styles should be a directory containing installed styles") - vale_action(ctx, ctx.executable._vale, ctx.rule.files.srcs, styles, ctx.file._config, outputs.human.out, outputs.human.exit_code) - + vale_action(ctx, ctx.executable._vale, ctx.rule.files.srcs, styles, ctx.file._config, outputs.human.out, outputs.human.exit_code, env = color_env) vale_action(ctx, ctx.executable._vale, ctx.rule.files.srcs, styles, ctx.file._config, outputs.machine.out, outputs.machine.exit_code, output = "line") return [info]