Skip to content

Commit

Permalink
feat: color the human-readable outputs (#333)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
alexeagle authored Jul 17, 2024
1 parent 85f628b commit 32c091a
Show file tree
Hide file tree
Showing 16 changed files with 95 additions and 45 deletions.
6 changes: 4 additions & 2 deletions docs/eslint.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion docs/flake8.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion docs/ktlint.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion docs/pmd.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions docs/ruff.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion docs/vale.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions example/test/lint_test.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand All @@ -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

Expand Down
15 changes: 15 additions & 0 deletions lint/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
28 changes: 17 additions & 11 deletions lint/eslint.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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",
)
Expand All @@ -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:
Expand All @@ -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))

Expand All @@ -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,
}),
Expand All @@ -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",
Expand All @@ -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)
Expand Down
8 changes: 5 additions & 3 deletions lint/flake8.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -43,13 +43,15 @@ 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]

# 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")

Expand Down Expand Up @@ -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]

Expand Down
Loading

0 comments on commit 32c091a

Please sign in to comment.