diff --git a/README.md b/README.md index 9e1ad5f1..1e7325c7 100644 --- a/README.md +++ b/README.md @@ -21,37 +21,38 @@ Features: New tools are being added frequently, so check this page again! -| Language | Formatter | Linter(s) | -| ---------------------- | --------------------- | ---------------- | -| C / C++ | [clang-format] | [clang-tidy] | -| Cuda | [clang-format] | | -| CSS, Less, Sass | [Prettier] | [Stylelint] | -| Go | [gofmt] or [gofumpt] | | -| GraphQL | [Prettier] | | -| HCL (Hashicorp Config) | [terraform] fmt | | -| HTML | [Prettier] | | -| JSON | [Prettier] | | -| Java | [google-java-format] | [pmd] | -| JavaScript | [Prettier] | [ESLint] | -| Jsonnet | [jsonnetfmt] | | -| Kotlin | [ktfmt] | [ktlint] | -| Markdown | [Prettier] | [Vale] | -| Protocol Buffer | [buf] | [buf lint] | -| Python | [ruff] | [flake8], [ruff] | -| Rust | [rustfmt] | | -| SQL | [prettier-plugin-sql] | | -| Scala | [scalafmt] | | -| Shell | [shfmt] | [shellcheck] | -| Starlark | [Buildifier] | | -| Swift | [SwiftFormat] (1) | | -| TSX | [Prettier] | [ESLint] | -| TypeScript | [Prettier] | [ESLint] | -| YAML | [yamlfmt] | | +| Language | Formatter | Linter(s) | +| ---------------------- | --------------------- |----------------------| +| C / C++ | [clang-format] | [clang-tidy] | +| Cuda | [clang-format] | | +| CSS, Less, Sass | [Prettier] | [Stylelint] | +| Go | [gofmt] or [gofumpt] | | +| GraphQL | [Prettier] | | +| HCL (Hashicorp Config) | [terraform] fmt | | +| HTML | [Prettier] | | +| JSON | [Prettier] | | +| Java | [google-java-format] | [pmd] , [Checkstyle] | +| JavaScript | [Prettier] | [ESLint] | +| Jsonnet | [jsonnetfmt] | | +| Kotlin | [ktfmt] | [ktlint] | +| Markdown | [Prettier] | [Vale] | +| Protocol Buffer | [buf] | [buf lint] | +| Python | [ruff] | [flake8], [ruff] | +| Rust | [rustfmt] | | +| SQL | [prettier-plugin-sql] | | +| Scala | [scalafmt] | | +| Shell | [shfmt] | [shellcheck] | +| Starlark | [Buildifier] | | +| Swift | [SwiftFormat] (1) | | +| TSX | [Prettier] | [ESLint] | +| TypeScript | [Prettier] | [ESLint] | +| YAML | [yamlfmt] | | [prettier]: https://prettier.io [google-java-format]: https://github.com/google/google-java-format [flake8]: https://flake8.pycqa.org/en/latest/index.html [pmd]: https://docs.pmd-code.org/latest/index.html +[checkstyle]: https://checkstyle.sourceforge.io/cmdline.html [buf lint]: https://buf.build/docs/lint/overview [eslint]: https://eslint.org/ [swiftformat]: https://github.com/nicklockwood/SwiftFormat diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index 1f6d47e9..0f7cc148 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -27,6 +27,11 @@ stardoc_with_diff_test( bzl_library_target = "//lint:pmd", ) +stardoc_with_diff_test( + name = "checkstyle", + bzl_library_target = "//lint:checkstyle", +) + stardoc_with_diff_test( name = "format", bzl_library_target = "//format:defs", diff --git a/docs/checkstyle.md b/docs/checkstyle.md new file mode 100644 index 00000000..b31f630d --- /dev/null +++ b/docs/checkstyle.md @@ -0,0 +1,111 @@ + + +API for declaring a checkstyle lint aspect that visits java_library rules. + +Typical usage: + +First, call the `fetch_checkstyle` helper in `WORKSPACE` to download the jar file. +Alternatively you could use whatever you prefer for managing Java dependencies, such as a Maven integration rule. + +Next, declare a binary target for it, typically in `tools/lint/BUILD.bazel`: + +```starlark +java_binary( + name = "checkstyle", + main_class = "com.puppycrawl.tools.checkstyle.Main", + runtime_deps = ["@com_puppycrawl_tools_checkstyle"], +) +``` + +Finally, declare an aspect for it, typically in `tools/lint/linters.bzl`: + +```starlark +load("@aspect_rules_lint//lint:checkstyle.bzl", "checkstyle_aspect") + +checkstyle = checkstyle_aspect( + binary = "@@//tools/lint:checkstyle", + config = ["@@//:checkstyle.xml"], +) +``` + + + +## checkstyle_action + +
+load("@aspect_rules_lint//lint:checkstyle.bzl", "checkstyle_action")
+
+checkstyle_action(ctx, executable, srcs, config, data, stdout, exit_code, options)
+
+ +Run Checkstyle as an action under Bazel. + +Based on https://checkstyle.sourceforge.io/cmdline.html + + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| ctx | Bazel Rule or Aspect evaluation context | none | +| executable | label of the the Checkstyle program | none | +| srcs | java files to be linted | none | +| config | label of the checkstyle.xml file | none | +| data | labels of additional xml files such as suppressions.xml | none | +| stdout | output file to generate | none | +| exit_code | output file to write the exit code. If None, then fail the build when Checkstyle exits non-zero. | `None` | +| options | additional command-line options, see https://checkstyle.sourceforge.io/cmdline.html | `[]` | + + + + +## fetch_checkstyle + +
+load("@aspect_rules_lint//lint:checkstyle.bzl", "fetch_checkstyle")
+
+fetch_checkstyle()
+
+ + + + + + + +## lint_checkstyle_aspect + +
+load("@aspect_rules_lint//lint:checkstyle.bzl", "lint_checkstyle_aspect")
+
+lint_checkstyle_aspect(binary, config, data, rule_kinds)
+
+ +A factory function to create a linter aspect. + +Attrs: + binary: a Checkstyle executable. Can be obtained from rules_java like so: + + ``` + java_binary( + name = "checkstyle", + main_class = "com.puppycrawl.tools.checkstyle.Main", + # Point to wherever you have the java_import rule defined, see our example + runtime_deps = ["@com_puppycrawl_tools_checkstyle"], + ) + ``` + + config: the Checkstyle XML file + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| binary |

-

| none | +| config |

-

| none | +| data |

-

| `[]` | +| rule_kinds |

-

| `["java_binary", "java_library"]` | + + diff --git a/example/.aspect/cli/config.yaml b/example/.aspect/cli/config.yaml index d67784b7..36f19e92 100644 --- a/example/.aspect/cli/config.yaml +++ b/example/.aspect/cli/config.yaml @@ -8,3 +8,4 @@ lint: - //tools/lint:linters.bzl%stylelint - //tools/lint:linters.bzl%ruff - //tools/lint:linters.bzl%vale + - //tools/lint:linters.bzl%checkstyle diff --git a/example/BUILD.bazel b/example/BUILD.bazel index 23705279..15210704 100644 --- a/example/BUILD.bazel +++ b/example/BUILD.bazel @@ -16,6 +16,8 @@ exports_files( "buf.yaml", ".flake8", "pmd.xml", + "checkstyle.xml", + "checkstyle-suppressions.xml", ".ruff.toml", ".shellcheckrc", ".scalafmt.conf", diff --git a/example/WORKSPACE.bazel b/example/WORKSPACE.bazel index 9ab357f7..11e627fb 100644 --- a/example/WORKSPACE.bazel +++ b/example/WORKSPACE.bazel @@ -303,6 +303,10 @@ fetch_ktfmt() fetch_swiftformat() +load("@aspect_rules_lint//lint:checkstyle.bzl", "fetch_checkstyle") + +fetch_checkstyle() + load("@aspect_rules_lint//lint:pmd.bzl", "fetch_pmd") fetch_pmd() diff --git a/example/WORKSPACE.bzlmod b/example/WORKSPACE.bzlmod index c11ba817..840a1e98 100644 --- a/example/WORKSPACE.bzlmod +++ b/example/WORKSPACE.bzlmod @@ -20,6 +20,10 @@ load("@aspect_rules_lint//lint:pmd.bzl", "fetch_pmd") fetch_pmd() +load("@aspect_rules_lint//lint:checkstyle.bzl", "fetch_checkstyle") + +fetch_checkstyle() + load("@aspect_rules_lint//lint:vale.bzl", "fetch_vale") fetch_vale() diff --git a/example/checkstyle-suppressions.xml b/example/checkstyle-suppressions.xml new file mode 100644 index 00000000..9a898278 --- /dev/null +++ b/example/checkstyle-suppressions.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/example/checkstyle.xml b/example/checkstyle.xml new file mode 100644 index 00000000..2d72ff40 --- /dev/null +++ b/example/checkstyle.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/example/src/BUILD.bazel b/example/src/BUILD.bazel index 4cbcdae9..0e627a85 100644 --- a/example/src/BUILD.bazel +++ b/example/src/BUILD.bazel @@ -71,6 +71,11 @@ java_library( srcs = ["Foo.java"], ) +java_library( + name = "bar", + srcs = ["Bar.java"], +) + sh_library( name = "hello_shell", srcs = ["hello.sh"], diff --git a/example/src/Bar.java b/example/src/Bar.java new file mode 100644 index 00000000..64077315 --- /dev/null +++ b/example/src/Bar.java @@ -0,0 +1,10 @@ +package src; + +// Unused imports are suppressed in suppressions.xml, so this should not raise issue. +import java.util.Objects; +import java.io.BufferedInputStream; + +public class Bar { + // Max line length set to 20, so this should raise issue. + protected void finalize(int a) {} +} diff --git a/example/test/BUILD.bazel b/example/test/BUILD.bazel index 60d2c3e6..0a06d432 100644 --- a/example/test/BUILD.bazel +++ b/example/test/BUILD.bazel @@ -3,7 +3,7 @@ load("@aspect_rules_lint//format:defs.bzl", "format_test") load("@aspect_rules_ts//ts:defs.bzl", "ts_project") load("@bazel_skylib//rules:write_file.bzl", "write_file") -load("//tools/lint:linters.bzl", "eslint_test", "flake8_test", "pmd_test", "ruff_test", "shellcheck_test") +load("//tools/lint:linters.bzl", "checkstyle_test", "eslint_test", "flake8_test", "pmd_test", "ruff_test", "shellcheck_test") write_file( name = "ts_code_generator", @@ -94,6 +94,12 @@ pmd_test( tags = ["manual"], ) +checkstyle_test( + name = "checkstyle", + srcs = ["//src:bar"], + tags = ["manual"], +) + eslint_test( name = "eslint", # NB: we must lint the `ts_typings` target that has the .ts files in srcs, diff --git a/example/tools/lint/BUILD.bazel b/example/tools/lint/BUILD.bazel index 5224e81d..e8c6e038 100644 --- a/example/tools/lint/BUILD.bazel +++ b/example/tools/lint/BUILD.bazel @@ -36,6 +36,12 @@ java_binary( runtime_deps = ["@net_sourceforge_pmd"], ) +java_binary( + name = "checkstyle", + main_class = "com.puppycrawl.tools.checkstyle.Main", + runtime_deps = ["@com_puppycrawl_tools_checkstyle//jar"], +) + native_binary( name = "vale", src = select( diff --git a/example/tools/lint/linters.bzl b/example/tools/lint/linters.bzl index c29c25fc..49ee67d2 100644 --- a/example/tools/lint/linters.bzl +++ b/example/tools/lint/linters.bzl @@ -1,6 +1,7 @@ "Define linter aspects" load("@aspect_rules_lint//lint:buf.bzl", "lint_buf_aspect") +load("@aspect_rules_lint//lint:checkstyle.bzl", "lint_checkstyle_aspect") load("@aspect_rules_lint//lint:clang_tidy.bzl", "lint_clang_tidy_aspect") load("@aspect_rules_lint//lint:eslint.bzl", "lint_eslint_aspect") load("@aspect_rules_lint//lint:flake8.bzl", "lint_flake8_aspect") @@ -48,6 +49,14 @@ pmd = lint_pmd_aspect( pmd_test = lint_test(aspect = pmd) +checkstyle = lint_checkstyle_aspect( + binary = "@@//tools/lint:checkstyle", + config = "@@//:checkstyle.xml", + data = ["@@//:checkstyle-suppressions.xml"], +) + +checkstyle_test = lint_test(aspect = checkstyle) + ruff = lint_ruff_aspect( binary = "@multitool//tools/ruff", configs = [ diff --git a/format/test/format_test.bats b/format/test/format_test.bats index 65945de5..81871ceb 100644 --- a/format/test/format_test.bats +++ b/format/test/format_test.bats @@ -95,7 +95,7 @@ bats_load_library "bats-assert" run bazel run //format/test:format_Java_with_java-format assert_success - assert_output --partial "+ java-format --replace example/src/Foo.java" + assert_output --partial "+ java-format --replace example/src/Bar.java example/src/Foo.java" } @test "should run ktfmt on Kotlin" { diff --git a/lint/BUILD.bazel b/lint/BUILD.bazel index b788f804..fb99a6f2 100644 --- a/lint/BUILD.bazel +++ b/lint/BUILD.bazel @@ -163,6 +163,15 @@ bzl_library( ], ) +bzl_library( + name = "checkstyle", + srcs = ["checkstyle.bzl"], + visibility = ["//visibility:public"], + deps = _BAZEL_TOOLS + [ + "//lint/private:lint_aspect", + ], +) + bzl_library( name = "ruff", srcs = ["ruff.bzl"], diff --git a/lint/checkstyle.bzl b/lint/checkstyle.bzl new file mode 100644 index 00000000..5c2bef9b --- /dev/null +++ b/lint/checkstyle.bzl @@ -0,0 +1,166 @@ +"""API for declaring a checkstyle lint aspect that visits java_library rules. + +Typical usage: + +First, call the `fetch_checkstyle` helper in `WORKSPACE` to download the jar file. +Alternatively you could use whatever you prefer for managing Java dependencies, such as a Maven integration rule. + +Next, declare a binary target for it, typically in `tools/lint/BUILD.bazel`: + +```starlark +java_binary( + name = "checkstyle", + main_class = "com.puppycrawl.tools.checkstyle.Main", + runtime_deps = ["@com_puppycrawl_tools_checkstyle"], +) +``` + +Finally, declare an aspect for it, typically in `tools/lint/linters.bzl`: + +```starlark +load("@aspect_rules_lint//lint:checkstyle.bzl", "checkstyle_aspect") + +checkstyle = checkstyle_aspect( + binary = "@@//tools/lint:checkstyle", + config = ["@@//:checkstyle.xml"], +) +``` +""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_jar") +load("//lint/private:lint_aspect.bzl", "LintOptionsInfo", "filter_srcs", "noop_lint_action", "output_files", "should_visit") + +_MNEMONIC = "AspectRulesLintCheckstyle" + +def checkstyle_action(ctx, executable, srcs, config, data, stdout, exit_code = None, options = []): + """Run Checkstyle as an action under Bazel. + + Based on https://checkstyle.sourceforge.io/cmdline.html + + Args: + ctx: Bazel Rule or Aspect evaluation context + executable: label of the the Checkstyle program + srcs: java files to be linted + config: label of the checkstyle.xml file + data: labels of additional xml files such as suppressions.xml + stdout: output file to generate + exit_code: output file to write the exit code. + If None, then fail the build when Checkstyle exits non-zero. + options: additional command-line options, see https://checkstyle.sourceforge.io/cmdline.html + """ + inputs = srcs + [config] + data + outputs = [stdout] + + # Wire command-line options, see + # https://checkstyle.sourceforge.io/cmdline.html + args = ctx.actions.args() + args.add_all(options) + + args.add_all(["-c", config.path]) + args.add_all(srcs) + + if exit_code: + command = "{CHECKSTYLE} $@ >{stdout}; echo $? > " + exit_code.path + outputs.append(exit_code) + else: + # Create empty stdout file on success, as Bazel expects one + command = "{CHECKSTYLE} $@ && touch {stdout}" + + ctx.actions.run_shell( + inputs = inputs, + outputs = outputs, + command = command.format(CHECKSTYLE = executable.path, stdout = stdout.path), + arguments = [args], + mnemonic = _MNEMONIC, + tools = [executable], + progress_message = "Linting %{label} with Checkstyle", + ) + +# buildifier: disable=function-docstring +def _checkstyle_aspect_impl(target, ctx): + if not should_visit(ctx.rule, ctx.attr._rule_kinds): + return [] + + files_to_lint = filter_srcs(ctx.rule) + outputs, info = output_files(_MNEMONIC, target, ctx) + if len(files_to_lint) == 0: + noop_lint_action(ctx, outputs) + return [info] + + checkstyle_action( + ctx, + ctx.executable._checkstyle, + files_to_lint, + ctx.file._config, + ctx.files._data, + outputs.human.out, + outputs.human.exit_code, + ["-f", "plain"], + ) + checkstyle_action( + ctx, + ctx.executable._checkstyle, + files_to_lint, + ctx.file._config, + ctx.files._data, + outputs.machine.out, + outputs.machine.exit_code, + ["-f", "sarif"], + ) + return [info] + +def lint_checkstyle_aspect(binary, config, data = [], rule_kinds = ["java_binary", "java_library"]): + """A factory function to create a linter aspect. + + Attrs: + binary: a Checkstyle executable. Can be obtained from rules_java like so: + + ``` + java_binary( + name = "checkstyle", + main_class = "com.puppycrawl.tools.checkstyle.Main", + # Point to wherever you have the java_import rule defined, see our example + runtime_deps = ["@com_puppycrawl_tools_checkstyle"], + ) + ``` + + config: the Checkstyle XML file + """ + return aspect( + implementation = _checkstyle_aspect_impl, + # Edges we need to walk up the graph from the selected targets. + # Needed for linters that need semantic information like transitive type declarations. + # attr_aspects = ["deps"], + attrs = { + "_options": attr.label( + default = "//lint:options", + providers = [LintOptionsInfo], + ), + "_checkstyle": attr.label( + default = binary, + executable = True, + cfg = "exec", + ), + "_config": attr.label( + allow_single_file = True, + mandatory = True, + doc = "Config file", + default = config, + ), + "_data": attr.label_list( + doc = "Additional files to make available to Checkstyle such as any included XML files", + allow_files = True, + default = data, + ), + "_rule_kinds": attr.string_list( + default = rule_kinds, + ), + }, + ) + +def fetch_checkstyle(): + http_jar( + name = "com_puppycrawl_tools_checkstyle", + url = "https://github.com/checkstyle/checkstyle/releases/download/checkstyle-10.17.0/checkstyle-10.17.0-all.jar", + sha256 = "51c34d738520c1389d71998a9ab0e6dabe0d7cf262149f3e01a7294496062e42", + )