Repository navigation
feat(plugins): The java plugin — PMD-backed structural smell detection - #130
Merged
Merged
Conversation
pfichtner
force-pushed
the
feat/java-plugin
branch
from
August 15, 2026 09:15
7aa63da to
1820a5d
Compare
Adds habit-hooks-java, the fifth language plugin: a pmd sensor that runs `pmd check --format json` over the scoped *.java files, treats exits 0/4 as success, and maps PMD 7 rule names to canonical smells (too-many-parameters, high-complexity, oversized-function, unused-variable, unused-import, swallowed-exception). PMD never discovers a ruleset, so the sensor resolves one from a --rulesets in its args, then the conventional Java locations, then the bundled fallback; a project's own ruleset wins. Parse errors surface PMD's processingErrors rather than its generic stderr, and an absent pmd answers the way a shell would (#114). Wires the plugin into the core: the java extra, the all extra, uv sources, dev group, deptry ignore, the java LanguageSignal in recommend, CI's pmd install, release.yml's pypi-java environment, and the docs (README, config.md, smell-vocabulary, npm notices, release skill). Tests: a missing-pmd unit test, a 4-case acceptance spec, and a wheel-smoke case proving the bundled ruleset ships as package data.
pfichtner
force-pushed
the
feat/java-plugin
branch
from
August 15, 2026 09:18
1820a5d to
bf75aef
Compare
Collaborator
|
Hi Peter! Thanks for your PR. I merged it and I cut a new release so this can go live today :) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What the PR does
Adds a fifth language plugin:
habit-hooks-java. It runs PMD against theproject's Java sources and translates PMD rule violations into the core's
canonical smell vocabulary, so Java code gets the same coaching pipeline as
python/typescript/php.
The plugin is a separately-installable dist
habit-hooks-javadiscovered via thehabit_hooks.pluginsentry-point group — same architecture as the other plugins,never a walk of a sibling directory.
New files (
plugins/java/)pyproject.toml— disthabit-hooks-javav1.1.0, entry pointjava = "habit_hooks_java", ships package data.src/habit_hooks_java/config.toml—language = "java",files = ["**/*.java"],sensors = ["pmd"].src/habit_hooks_java/sensors/pmd.toml— the sensor spec:${python} ${dir}/pmd_sensor.py ${args} ${files}.src/habit_hooks_java/sensors/pmd_sensor.py— the wrapper (170 lines):pmd check --no-cache --format json(per-file-d, short-R, because PMD 7's picocli would read a positional path right after the ruleset value as another-R);ExcessiveParameterList→too-many-parameters,CyclomaticComplexity→high-complexity,NcssCount→oversized-function,UnusedLocalVariable→unused-variable,UnnecessaryImport→unused-import,EmptyCatchBlock→swallowed-exception;NcssCountviolations (class-level ones are dropped — no catalogue smell for them; PMD's own message distinguishes them);pmd: command not foundthe way a shell would when PMD is absent (habit-hooks --help, a malformed.habit-hooks/config.toml, and a missingjscpdall answer with a Python traceback #114 seam), so a missing detector is named, not a 20-line traceback;src/habit_hooks_java/sensors/pmd-ruleset.xml— the bundled fallback ruleset (the answer to "project has none"). Project rulesets win: a--rulesets/-Rin the sensor's args first, then conventional locations in the project dir (src/main/resources/pmd/ruleset.xml,pmd/ruleset.xml,ruleset.xml,pmd.xml), because PMD itself never discovers one.docs/java-plugin.spec.md— acceptance spec (also user-facing docs): rule→smell mapping, high-complexity mapping, "a project's own ruleset wins", "a crashing pmd fails the run, never reports clean".tests/test_a_java_pmd_nobody_installed_is_named.py— PMD absent →pmd: command not found, empty stdout, non-zero exit.Changes to existing files
pyproject.toml—javaextra (habit-hooks-java~=1.0), added toall, dev group, deptry DEP002, workspace member.src/habit_hooks/recommend.py— javaLanguageSignal(pom.xml/build.gradle/build.gradle.kts,.java): a java project gets told to install/enable the plugin.tests/test_recommend.py— covers the java recommendation (and that it goes quiet once installed/enabled).tests/installed_env.py—require_pmd()skip-guard;tests/test_installed_wheel_smoke_java.py— end-to-end smoke: builds+installs the core, generic and java wheels into a throwaway venv and asserts the real findings come out with the bundled ruleset (proves the ruleset rides along as package data).docs/smell-vocabulary.md— "Java plugin translation" table (rule → smell), notes onNcssCount, ruleset resolution,UnnecessaryImport= renamedUnusedImports.docs/config.md,README.md,packaging/npm/README.md,packaging/npm/bin/habit-hooks.js— thejavaextra listed everywhere extras are..github/workflows/ci.yml— installs PMD 7.26.0 on PATH for tests + dogfooding..github/workflows/release.yml— sixth packagehabit_hooks_java→ its ownpypi-javaenvironment (trusted publishers are unique per environment).skills/release-habit-hooks/SKILL.md— five → six packages.uv.lock— lockedhabit-hooks-java.What we verified
Toolchain / install (the non-obvious part — java isn't published yet)
uv(was not on PATH).~/.local/opt/pmd-bin-7.26.0, symlinkedbin/pmdinto~/.local/bin(the plugin does not bundle the tool; the sensor shells out topmd). Version matches the one CI installs and the one the sensor was verified against.uv tool install "/workspace[java]"— which built and installedhabit-hooks,habit-hooks-genericandhabit-hooks-javafrom the workspace and produced the four CLIs. Confirm:habit-hooks --version→habit-hooks v1.1.0.Example project end-to-end (
/tmp/habit-hooks-java-example)A git repo with
pom.xml,.habit-hooks/config.toml(plugins = ["java"]), and four compiling Java files that deliberately carry the six smells:Billing.javatoo-many-parameters(pmd:ExcessiveParameterList),unused-import×3 (pmd:UnnecessaryImport),unused-variable(pmd:UnusedLocalVariable)OrderValidator.javahigh-complexity(pmd:CyclomaticComplexity)ReportBuilder.javaoversized-function(pmd:NcssCount)FileReader.javaswallowed-exception(pmd:EmptyCatchBlock)javac(the smells are structural, not compile errors).habit-sensors --all→ all six findings, each stampedlanguage: javaandsource: pmd:<rule>.habit-hooks --all→ coaches each smell with the catalogue guide; exits 1 (enforced findings).habit-hooksrun (branch scope) and--branch mainrun work in the git repo.Behavioural seams the PR cares about
src/main/resources/pmd/ruleset.xmllowering the parameter threshold).incomplete-run, never a false-clean — verified by the spec.pmd: command not found, exit non-zero, no traceback (covered by both the plugin's unit test and, end-to-end, by the empty-pipe semantics of the core).Reviewer notes
allextra and release matrix now cover six packages; release envpypi-javamust exist in GitHub for the publish job.-Rgotcha was verified against — a future PMD bump should re-run the spec cases.NcssCountdropping class-level violations is a deliberate scope choice (no catalogue smell for oversized classes).