habit-hooks-java 1.4.0rc2__tar.gz → 1.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/PKG-INFO +1 -1
  2. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/docs/java-plugin.spec.md +122 -1
  3. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/pyproject.toml +1 -1
  4. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/src/habit_hooks_java/sensors/pmd-ruleset.xml +3 -0
  5. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/src/habit_hooks_java/sensors/pmd_sensor.py +1 -3
  6. habit_hooks_java-1.5.0/tests/test_bundled_ruleset_declares_its_contract.py +33 -0
  7. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/.gitignore +0 -0
  8. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/README.md +0 -0
  9. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/src/habit_hooks_java/__init__.py +0 -0
  10. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/src/habit_hooks_java/config.toml +0 -0
  11. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/src/habit_hooks_java/sensors/pmd.toml +0 -0
  12. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/src/habit_hooks_java/sensors/pmd_ruleset.py +0 -0
  13. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/tests/conftest.py +0 -0
  14. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/tests/test_a_vendored_sensor_finds_its_neighbour.py +0 -0
  15. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/tests/test_class_level_metric_violations_are_dropped.py +0 -0
  16. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/tests/test_every_ruleset_spelling_wins.py +0 -0
  17. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/tests/test_sensor_args_reach_pmd_not_the_file_list.py +0 -0
  18. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/tests/test_the_java_files_leave_build_output_alone.py +0 -0
  19. {habit_hooks_java-1.4.0rc2 → habit_hooks_java-1.5.0}/tests/test_the_sensor_runs_the_pmd_it_is_handed.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: habit-hooks-java
3
- Version: 1.4.0rc2
3
+ Version: 1.5.0
4
4
  Summary: The Java Habit Hooks plugin
5
5
  Project-URL: Homepage, https://habit-hooks.com
6
6
  Project-URL: Repository, https://github.com/habit-hooks/habit-hooks
@@ -66,6 +66,71 @@ habit-sensors --all | jq 'sort_by(.smell)[] | {smell, language, key: (.issues[0]
66
66
  }
67
67
  ```
68
68
 
69
+ ## pmd sensor reports the third nested if from the bundled ruleset
70
+
71
+ With no PMD ruleset in the project, the plugin's bundled fallback explicitly
72
+ sets `AvoidDeeplyNestedIfStmts` to report depth 3. The finding points at the
73
+ third `if` and translates the PMD rule to the shared `deep-nesting` smell.
74
+
75
+ 📄Nested.java
76
+ ```java
77
+ class Nested {
78
+ boolean accepts(int value) {
79
+ if (value > 0) {
80
+ if (value < 10) {
81
+ if (value != 5) {
82
+ return true;
83
+ }
84
+ }
85
+ }
86
+ return false;
87
+ }
88
+ }
89
+ ```
90
+
91
+ ```bash
92
+ habit-sensors --all | jq '.[] | {smell, language, key: (.issues[0].key | sub(".*/"; "")), line: .issues[0].details.line, source: .issues[0].details.source}'
93
+ ```
94
+
95
+ 🖥️ ✅
96
+ ```json
97
+ {
98
+ "smell": "deep-nesting",
99
+ "language": "java",
100
+ "key": "Nested.java",
101
+ "line": 5,
102
+ "source": "pmd:AvoidDeeplyNestedIfStmts"
103
+ }
104
+ ```
105
+
106
+ ## two nested ifs remain below the bundled threshold
107
+
108
+ The third nested `if` is the first one PMD reports at `problemDepth=3`, so an
109
+ otherwise equivalent two-level chain remains clean.
110
+
111
+ 📄Nested.java
112
+ ```java
113
+ class Nested {
114
+ boolean accepts(int value) {
115
+ if (value > 0) {
116
+ if (value < 10) {
117
+ return true;
118
+ }
119
+ }
120
+ return false;
121
+ }
122
+ }
123
+ ```
124
+
125
+ ```bash
126
+ habit-sensors --all | jq '[.[].smell]'
127
+ ```
128
+
129
+ 🖥️ ✅
130
+ ```json
131
+ []
132
+ ```
133
+
69
134
  ## pmd sensor maps a deeply-branched method to high-complexity
70
135
 
71
136
  A method whose conditions are littered with `||` exceeds PMD's cyclomatic
@@ -103,7 +168,9 @@ habit-sensors --all | jq '.[] | {smell, language, source: .issues[0].details.sou
103
168
  PMD never discovers a project ruleset, so the sensor reaches for one only where
104
169
  the Java ecosystem conventionally keeps it. A `src/main/resources/pmd/ruleset.xml`
105
170
  that lowers the parameter threshold is in force for the run — the bundled
106
- fallback is only the answer to "this project has none".
171
+ fallback is only the answer to "this project has none". The source also has
172
+ three nested `if`s, but this project ruleset omits `AvoidDeeplyNestedIfStmts`,
173
+ so only the rule the project selected produces a finding.
107
174
 
108
175
  📄src/main/resources/pmd/ruleset.xml
109
176
  ```xml
@@ -122,6 +189,13 @@ fallback is only the answer to "this project has none".
122
189
  ```java
123
190
  class Project {
124
191
  void save(String a, String b) {
192
+ if (a != null) {
193
+ if (b != null) {
194
+ if (!a.equals(b)) {
195
+ System.out.println(a);
196
+ }
197
+ }
198
+ }
125
199
  }
126
200
  }
127
201
  ```
@@ -139,6 +213,53 @@ habit-sensors --all | jq '.[] | {smell, language, key: (.issues[0].key | sub(".*
139
213
  }
140
214
  ```
141
215
 
216
+ ## a project ruleset can enable deep nesting at its own threshold
217
+
218
+ A project can opt into the same PMD rule with a different `problemDepth`. Its
219
+ ruleset still replaces the bundled fallback as a whole; the sensor translates
220
+ the enabled rule normally.
221
+
222
+ 📄pmd/ruleset.xml
223
+ ```xml
224
+ <?xml version="1.0"?>
225
+ <ruleset name="custom" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
226
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
227
+ xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
228
+ <description>report the second nested if</description>
229
+ <rule ref="category/java/design.xml/AvoidDeeplyNestedIfStmts">
230
+ <properties><property name="problemDepth" value="2"/></properties>
231
+ </rule>
232
+ </ruleset>
233
+ ```
234
+
235
+ 📄Nested.java
236
+ ```java
237
+ class Nested {
238
+ boolean accepts(int value) {
239
+ if (value > 0) {
240
+ if (value < 10) {
241
+ return true;
242
+ }
243
+ }
244
+ return false;
245
+ }
246
+ }
247
+ ```
248
+
249
+ ```bash
250
+ habit-sensors --all | jq '.[] | {smell, language, line: .issues[0].details.line, source: .issues[0].details.source}'
251
+ ```
252
+
253
+ 🖥️ ✅
254
+ ```json
255
+ {
256
+ "smell": "deep-nesting",
257
+ "language": "java",
258
+ "line": 4,
259
+ "source": "pmd:AvoidDeeplyNestedIfStmts"
260
+ }
261
+ ```
262
+
142
263
  ## `[sensors.pmd] args` reaches PMD directly
143
264
 
144
265
  `args` is spliced into the sensor's command as `${args} -- ${files}`, so a real
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "habit-hooks-java"
3
- version = "1.4.0rc2"
3
+ version = "1.5.0"
4
4
  description = "The Java Habit Hooks plugin"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -8,6 +8,9 @@
8
8
  <rule ref="category/java/design.xml/ExcessiveParameterList">
9
9
  <properties><property name="minimum" value="4"/></properties>
10
10
  </rule>
11
+ <rule ref="category/java/design.xml/AvoidDeeplyNestedIfStmts">
12
+ <properties><property name="problemDepth" value="3"/></properties>
13
+ </rule>
11
14
  <rule ref="category/java/design.xml/CyclomaticComplexity">
12
15
  <properties><property name="methodReportLevel" value="10"/></properties>
13
16
  </rule>
@@ -35,6 +35,7 @@ from pathlib import Path
35
35
  from pmd_ruleset import ruleset_of
36
36
 
37
37
  RULE_SMELLS = {
38
+ "AvoidDeeplyNestedIfStmts": "deep-nesting",
38
39
  "ExcessiveParameterList": "too-many-parameters",
39
40
  "CyclomaticComplexity": "high-complexity",
40
41
  "NcssCount": "oversized-function",
@@ -131,9 +132,6 @@ def findings(entries: list[dict]) -> list[dict]:
131
132
 
132
133
  def main() -> int:
133
134
  pmd, argv = sys.argv[1], sys.argv[2:]
134
- if not argv:
135
- print("[]")
136
- return 0
137
135
  pmd_args, files = split_argv(argv)
138
136
  ruleset, remaining_args = ruleset_of(pmd_args, Path.cwd())
139
137
  file_args = [token for file in files for token in ("-d", file)]
@@ -0,0 +1,33 @@
1
+ """The bundled PMD fallback pins behavior that PMD's defaults must not move."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import xml.etree.ElementTree as ElementTree
6
+ from pathlib import Path
7
+
8
+ RULESET = (
9
+ Path(__file__).resolve().parents[1]
10
+ / "src"
11
+ / "habit_hooks_java"
12
+ / "sensors"
13
+ / "pmd-ruleset.xml"
14
+ )
15
+ PMD_NAMESPACE = {"pmd": "http://pmd.sourceforge.net/ruleset/2.0.0"}
16
+ DEEP_NESTING_RULE = "category/java/design.xml/AvoidDeeplyNestedIfStmts"
17
+
18
+
19
+ def test_the_bundled_ruleset_explicitly_reports_the_third_nested_if() -> None:
20
+ root = ElementTree.parse(RULESET).getroot()
21
+
22
+ rules = [
23
+ rule
24
+ for rule in root.findall("pmd:rule", PMD_NAMESPACE)
25
+ if rule.get("ref") == DEEP_NESTING_RULE
26
+ ]
27
+
28
+ assert len(rules) == 1
29
+ problem_depth = rules[0].findall(
30
+ "pmd:properties/pmd:property[@name='problemDepth']", PMD_NAMESPACE
31
+ )
32
+ assert len(problem_depth) == 1
33
+ assert problem_depth[0].get("value") == "3"