Skip to content

feat: add a JVM ecosystem profile (Java and Kotlin, with Maven and Gradle) - #125

Closed
leftibot wants to merge 2 commits into
cpp-best-practices:mainfrom
leftibot:jvm-profile
Closed

leftibot wants to merge 2 commits into
cpp-best-practices:mainfrom
leftibot:jvm-profile

Conversation

@leftibot

@leftibot leftibot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Closes #66. Phase 4 of #107. Stacked on #124: the branch lives on a fork, so this PR targets main and also contains #124's commit. Review only the last commit (3d67cae). Once #124 merges, this PR shows just that commit.

project_validator/ecosystems/jvm.py judges Java (.java) and Kotlin (.kt) projects built with Maven (pom.xml) or Gradle (build.gradle(.kts), settings.gradle(.kts)). .kts and .gradle scripts add no language, because they are mostly builds.

The build counts as CI configuration. JVM projects wire their tools into the build:

  • A Maven plugin binds a goal to a phase (<goal>check</goal> runs in mvn verify).
  • A Gradle plugin adds its task to check.

So the build files count as evidence when CI runs the build far enough:

  • Compiler flags need any compiling phase or task (mvn test, gradle build).
  • Verification plugins need mvn verify / install / deploy or gradle check / build.
  • A Maven plugin counts only when an execution binds the goal. For example, jacoco-maven-plugin with only prepare-agent reports nothing.
Practice Passes when
tests-run @Test methods (JUnit, TestNG) or Kotest specs under src/test/java or src/test/kotlin, run by mvn test / verify or gradle test / check / build
coverage JaCoCo (report goal bound, jacoco:report, jacocoTestReport) or Kover. Both report branches; neither reports path coverage, so there is no bonus
code-formatting Spotless (spotless:check bound, spotlessCheck, or the com.diffplug.spotless Gradle plugin), fmt-maven-plugin, google-java-format --set-exit-if-changed, ktfmt --set-exit-if-changed, or ktlint (not -F, and also as the jlleitschuh Gradle plugin)
compiler-warnings Java: -Xlint:all (or -Xlint). A Kotlin-only project passes on Kotlin's default warnings, as #66 says; a mixed project still needs -Xlint:all
warnings-as-errors -Werror (javac or kotlinc) or allWarningsAsErrors = true
dependency-audit OWASP dependency-check: dependency-check-maven bound to check / aggregate, dependencyCheckAnalyze, the CLI or the action. Applying the Gradle plugin alone does not count, because it adds no task to check
security-scanning SpotBugs with Find Security Bugs, or PMD's category/java/security.xml
function-limits lizard runs; or Checkstyle runs with MethodLength (it warns when the check is configured but Checkstyle never runs); or detekt runs, since its LongMethod rule is on unless the config turns it off
static-analysis Error Prone, SpotBugs, PMD (not pmd cpd), Checkstyle or detekt runs
dependency-management <dependency> entries in pom.xml or Gradle implementation / api / ... are verified: a committed gradle/verification-metadata.xml or gradle.lockfile, or Maven with --strict-checksums (in CI or .mvn/maven.config)
vcs-ignore / committed-artifacts Maven ignores target/, Gradle ignores build/. *.class files, plus target/ or build/ and .gradle/, must not be committed
platform-matrix CI runs at least two JDK versions (java-version, or eclipse-temurin: / openjdk: / amazoncorretto: / maven:…-21 / gradle:…jdk21 images)

hardening and ci-build-types do not apply, because the JVM builds bytecode, not a native binary. type-checking does not apply either, because both languages are statically typed. The build-tool-version row is left for #21.

target/ beside a pom.xml and build/ beside a Gradle build are treated as build output, so they are never inspected.

Changes outside the profile (please review)

These follow the same idea as Formatter.texts in #124: work with how the ecosystem is normally set up.

  • DependencyAudit gets the same optional texts field (default: CI configuration). Without it, dependency-check bound in pom.xml and run by mvn verify would never count. dependency-audit reads it, a one-line change.
  • vcs-ignore now judges the BuildOutputs rule of the build system a project uses, as committed-artifacts already did. Add a JVM ecosystem profile (Java and Kotlin, with Maven and Gradle) #66 asks for Maven target/ vs Gradle build/, and before this vcs-ignore read only an ecosystem's own rule. The shared helper is judge_each_ecosystem, which practice now uses too. Results for every existing ecosystem are unchanged, because none of them has a build-system BuildOutputs rule.

Notes

Tests

  • tests/fixtures/well_configured_jvm is a Maven project whose plugins are bound in pom.xml and run by mvn --strict-checksums verify. It passes every check that applies.
  • tests/fixtures/bare_jvm fails or warns on every JVM practice. It commits a target/ directory.
  • tests/test_jvm.py covers lifecycle gating, the Gradle paths, Kotlin-only warnings, and per-build-system ignores. There are two new layouts: Maven Java and Gradle Kotlin.
  • In the existing snapshots, only the "applies to" lists inside SKIP messages change.

Local gates all pass:

  • python -W error -m unittest
  • ruff check and format
  • strict mypy
  • branch coverage 99% (jvm.py 100%)
  • lizard --length 30
  • jscpd: 0 clones
  • strict self-validation

🤖 Generated with Claude Code

https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt

leftibot and others added 2 commits November 5, 2026 23:24
npm-compatible (package.json) and Deno (deno.json) projects are judged
by prettier, biome, deno fmt or dprint; branch coverage from c8, nyc,
jest, vitest, deno test or node's test runner; *.test.*, *.spec.* and
__tests__/ files run by any common runner; eslint, biome or deno lint
as the linter, failing on warnings with --max-warnings 0; npm, pnpm,
yarn or bun audits; eslint-plugin-security or semgrep; eslint's
max-lines-per-function; tsc (strict in tsconfig.json) or deno check;
committed lockfiles; package.json's license; an ignored node_modules/;
and the Node.js, Deno and Bun versions CI runs.

A package.json alone is the tooling of many projects in other
languages, so it makes a JavaScript project only when it names entry
points (main, module, exports, bin); JavaScript and TypeScript sources
do too.

JavaScript tooling mostly runs through package scripts, so the scripts
and Deno tasks that CI or a pre-commit hook runs (npm run lint, pnpm
lint, deno task fmt, with their pre/post scripts and the scripts they
run) count as CI configuration. For that, the Formatter rule gets an
optional texts, CI configuration by default, which code-formatting
reads; no other check changes. The JSON-with-comments reader moves from
data-wellformed to text.py, so profiles can read tsconfig.json and
deno.jsonc with evidence.json_value.

Closes cpp-best-practices#65

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
…adle)

Maven and Gradle projects are judged by Spotless (or fmt-maven-plugin,
google-java-format, ktfmt, ktlint); JaCoCo or Kover branch coverage;
@test methods and Kotest specs under src/test run by mvn or gradle;
javac's -Xlint:all (Kotlin warns by default); -Werror or Kotlin's
allWarningsAsErrors; OWASP dependency-check; Find Security Bugs or
PMD's security rules; Checkstyle MethodLength or detekt LongMethod;
Error Prone, SpotBugs, PMD, Checkstyle or detekt; Gradle dependency
verification, a gradle.lockfile or Maven's --strict-checksums; ignored
target/ (Maven) or build/ (Gradle); and the JDK versions CI runs.

JVM builds wire their tools in: a Maven plugin binds a goal to a phase,
a Gradle plugin adds its task to check. So the build files count as CI
configuration when CI runs the build far enough: any compiling phase
or task for compiler flags, mvn verify or gradle check/build for the
verification plugins. A Maven plugin counts only when an execution
binds the goal.

Two language-agnostic changes support it: DependencyAudit gets the
optional texts that Formatter got (dependency-check bound in pom.xml),
and vcs-ignore judges the BuildOutputs of the build system a project
uses, as committed-artifacts already did (judge_each_ecosystem).
Tests look up checks by name (checks_named) and layouts by name
(run_in_layout).

Closes cpp-best-practices#66

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
@codecov

codecov Bot commented Nov 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.67%. Comparing base (7f93afa) to head (3d67cae).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #125      +/-   ##
==========================================
+ Coverage   98.51%   98.67%   +0.15%     
==========================================
  Files          58       60       +2     
  Lines        2297     2564     +267     
  Branches      231      254      +23     
==========================================
+ Hits         2263     2530     +267     
  Misses         28       28              
  Partials        6        6              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@leftibot

leftibot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Moved to leftibot#1, stacked on javascript-profile (#124) on the fork until the stack can live upstream.

@leftibot leftibot closed this Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a JVM ecosystem profile (Java and Kotlin, with Maven and Gradle)

1 participant