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",
+ )