Skip to content

Commit 9ed98ca

Browse files
shai-almogclaude
andauthored
ci: make PMD a zero-findings gate (#6119)
* ci: make PMD a zero-findings gate The PMD gate failed only on a fixed list of rules, so an unlisted one (AvoidUsingHardCodedIP in OtlpTestCollector, from #6114) reached master while the report showed it in red. Any PMD finding now fails the build, matching SpotBugs; the one finding gets a NOPMD with its reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * unity-compat: mark the C#-shaped PMD findings the gate now enforces ClassNamingConventions (Action_1, Int32_, TMP_Text: names the translator binds to), AbstractClassWithoutAnyMethod (abstract in C#) and ExtendsObject (UnityEngine.Object, which PMD mistakes for java.lang.Object). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop the remaining references to the old PMD forbidden list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent cd41732 commit 9ed98ca

53 files changed

Lines changed: 74 additions & 112 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/scripts/generate-quality-report.py‎

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ def parse_pmd() -> Optional[AnalysisReport]:
485485
root = ET.parse(report_path).getroot()
486486
except ET.ParseError:
487487
# Unreadable is not clean. Reported as a finding of its own so the
488-
# forbidden-rule gate below cannot mistake it for an empty report.
488+
# PMD gate below cannot mistake it for an empty report.
489489
findings.append(Finding(severity="P1", location=str(report_path),
490490
message="PMD report could not be parsed",
491491
rule="UnparseableReport"))
@@ -950,62 +950,15 @@ def main() -> None:
950950

951951
pmd = parse_pmd()
952952
if pmd:
953-
forbidden_pmd_rules = {
954-
"UnparseableReport",
955-
"ClassWithOnlyPrivateConstructorsShouldBeFinal",
956-
"CompareObjectsWithEquals",
957-
"FormalParameterNamingConventions",
958-
"LiteralsFirstInComparisons",
959-
"LocalVariableNamingConventions",
960-
"MethodNamingConventions",
961-
"OneDeclarationPerLine",
962-
"MissingOverride",
963-
"ControlStatementBraces",
964-
"UnnecessaryFullyQualifiedName",
965-
"UseCollectionIsEmpty",
966-
"UnusedPrivateField",
967-
"UnnecessaryLocalBeforeReturn",
968-
"PreserveStackTrace",
969-
"CloseResource",
970-
"AssignmentInOperand",
971-
"EmptyControlStatement",
972-
"ForLoopCanBeForeach",
973-
"UnusedFormalParameter",
974-
"SwitchStmtsShouldHaveDefault",
975-
"UnconditionalIfStatement",
976-
"SimplifyConditional",
977-
"UselessOverridingMethod",
978-
"UselessQualifiedThis",
979-
"AvoidBranchingStatementAsLastInLoop",
980-
"AvoidUsingVolatile",
981-
"DoNotCallGarbageCollectionExplicitly",
982-
"SuspiciousEqualsMethodName",
983-
"UseUtilityClass",
984-
"EmptyCatchBlock",
985-
"SimplifyBooleanReturns",
986-
"UnusedLocalVariable",
987-
"UnnecessaryConstructor",
988-
"UnnecessaryImport",
989-
"AvoidStringBufferField",
990-
"LogicInversion",
991-
"DefaultLabelNotLastInSwitchStmt",
992-
"SingularField",
993-
"InstantiationToGetClass",
994-
"UnnecessaryModifier",
995-
"FinalFieldCouldBeStatic",
996-
"AvoidInstanceofChecksInCatchClause",
997-
"AvoidUsingOctalValues",
998-
"NonThreadSafeSingleton",
999-
"ComparisonWithNaN",
1000-
"DontUseFloatTypeForLoopIndices",
1001-
"MissingStaticMethodInNonInstantiatableClass",
1002-
"OverrideBothEqualsAndHashcode",
1003-
"UnnecessarySemicolon"
1004-
}
1005-
violations = [f for f in pmd.findings if f.rule in forbidden_pmd_rules]
1006-
if violations:
1007-
print("\n❌ Build failed due to forbidden PMD violations:")
1008-
for v in violations:
953+
# Zero findings, like SpotBugs: ANY rule fails the build. This used to
954+
# be a list of forbidden rules, and a rule nobody had listed
955+
# (AvoidUsingHardCodedIP) reached master while the report showed it
956+
# in red. An intentional exception is a `//NOPMD <Rule> - <reason>`
957+
# on the line, never a gap in this gate. UnparseableReport is a
958+
# finding too, so a corrupt report also fails here.
959+
if pmd.findings:
960+
print("\n\u274c Build failed due to PMD findings (the gate is zero findings):")
961+
for v in pmd.findings:
1009962
print(f" - {v.rule}: {v.location} - {v.message}")
1010963
sys.exit(1)
1011964

‎.github/workflows/pr.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -782,7 +782,7 @@ jobs:
782782
# SpotBugs run that silently stops running is not mistaken for clean.
783783
QUALITY_REPORT_REQUIRED_SPOTBUGS: core-unittests:android:ios:ByteCodeTranslator:codenameone-maven-plugin:build-engine:project-model:codenameone-gradle-plugin:build-hint-catalog:build-hint-tools:backend:backend-test:javac:android-res-compiler:android-compat:swing-compat:javafx-compat:fxml-compiler:desktop-compat-runtime
784784
# PMD and Checkstyle run on the core and on the backend runtime, and each
785-
# must produce its report: without one, the forbidden-rule gate below it
785+
# must produce its report: without one, the PMD gate below it
786786
# has nothing to read and passes.
787787
QUALITY_REPORT_REQUIRED_PMD: core-unittests:backend:backend-test:backend-parparvm
788788
QUALITY_REPORT_REQUIRED_CHECKSTYLE: core-unittests:backend:backend-test

‎.github/workflows/unity-compat.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ name: Unity compatibility
2020
# built by the application's own Maven build and
2121
# staged for every target, then converted to
2222
# Gradle and staged again: the same entries
23-
# static analysis SpotBugs (zero findings), PMD (the forbidden
24-
# list) and Checkstyle (zero errors) over both
23+
# static analysis SpotBugs (zero findings), PMD (zero
24+
# findings) and Checkstyle (zero errors) over both
2525
# modules, enforced by the script PR CI uses for
2626
# the rest of the tree, and the ClassCastException
2727
# gate over the runtime, which ParparVM translates
@@ -180,7 +180,7 @@ jobs:
180180
rm -f cil-translator/target/spotbugsXml.xml cil-translator/target/pmd.xml cil-translator/target/checkstyle-result.xml
181181
rm -f unity-compat/target/spotbugsXml.xml unity-compat/target/pmd.xml unity-compat/target/checkstyle-result.xml
182182
xvfb-run -a mvn -B -Plocal-dev-javase -Dunity-compat -DskipTests -pl cil-translator,unity-compat verify
183-
# The same script, with the same forbidden PMD list, that judges the rest
183+
# The same script, with the same zero-findings PMD gate, that judges the rest
184184
# of the tree in pr.yml. PMD only writes a report, so this step is what
185185
# enforces it; every module named must have produced each report.
186186
- name: Enforce the quality gates

‎CLAUDE.md‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,10 @@ any of them cannot skip it.
136136
`maven/cil-translator/`, `maven/unity-compat/`), scoped to the
137137
class or method it applies to and with a comment explaining why. Keep the
138138
generated report at zero rather than tolerating known noise.
139-
- PMD and Checkstyle still gate on their own lists in the same script.
139+
- **PMD is a zero-findings gate too**, in the same script: any rule fails the
140+
build. An intentional exception is a `//NOPMD <Rule> - <reason>` on the line.
141+
It once gated a fixed list of rules, and an unlisted one reached master.
142+
- Checkstyle gates on errors in the same script.
140143

141144
To reproduce the SpotBugs gate locally:
142145

@@ -443,12 +446,11 @@ The backend is held to the client's gates:
443446
- `///` markdown comments only, no `package.html`
444447
(`validate-java25-markdown-docs.sh` covers `vm/backend/src` and `impl/`);
445448
`package-info.java` per package; no since markers.
446-
- SpotBugs (zero findings, `maven/backend/spotbugs-exclude.xml`), PMD (the core's
447-
forbidden list) and Checkstyle (zero errors) run in `maven/backend`'s `verify`,
449+
- SpotBugs (zero findings, `maven/backend/spotbugs-exclude.xml`), PMD (zero findings) and Checkstyle (zero errors) run in `maven/backend`'s `verify`,
448450
using the core's own `pmd.xml` and `checkstyle.xml`. Only two of the three
449451
fail a local build: SpotBugs, and Checkstyle through its `check` execution
450452
(`checkstyle-zero-errors`). **PMD writes `maven/backend/target/pmd.xml` and
451-
judges nothing** -- its forbidden list lives in
453+
judges nothing** -- the zero-findings gate lives in
452454
`.github/scripts/generate-quality-report.py`, which PR CI runs afterwards. A
453455
green `mvn verify` therefore says nothing about PMD: read the report. Both
454456
reports once sat at seven and fifteen findings behind green local builds.
@@ -908,8 +910,11 @@ exceptions, so that the next finding is fixed rather than added to them:
908910
`get_position`, `GetComponent` and `Item1` are what translated bytecode
909911
calls, so SpotBugs' `NM_*` patterns are excluded for
910912
`com.codename1.unitycompat.{system,unityengine}` and each class with such
911-
members carries `@SuppressWarnings("PMD.MethodNamingConventions")`. A new
912-
runtime class needs that annotation; a Java-named helper never does.
913+
members carries `@SuppressWarnings("PMD.MethodNamingConventions")` -- plus
914+
`"PMD.ClassNamingConventions"` when the type itself has a C# name
915+
(`Action_1`, `Int32_`, `TMP_Text`). A new runtime class needs that
916+
annotation; a Java-named helper never does. An `extends Object` in
917+
`unityengine` is `UnityEngine.Object`, so it carries `// NOPMD ExtendsObject`.
913918
- **The translated classes are left out of SpotBugs by name**
914919
(`UnityEngine.*`): they are generated bytecode in `target/classes` with no
915920
Java source. PMD and Checkstyle read sources and never see them. A finding

‎maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Action_1.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
/// class, which `$sameType` cannot tell apart; the closed type a delegate
2929
/// was made as travels in `Delegate.$typeKey`, and `Delegate.Combine`
3030
/// compares that as well.
31-
@SuppressWarnings("PMD.MethodNamingConventions") // C# member names: translated code binds to them by name
31+
@SuppressWarnings({"PMD.MethodNamingConventions", "PMD.ClassNamingConventions"}) // C# member and type names: translated code binds to them by name
3232
public abstract class Action_1 extends MulticastDelegate {
3333
public abstract void Invoke(Object a0);
3434

‎maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Action_2.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
/// `System.Action`2`. Type arguments are erased, so every parameter is an
2626
/// `Object` and the generated subclass unboxes what the method it calls
2727
/// takes as a primitive.
28-
@SuppressWarnings("PMD.MethodNamingConventions") // C# member names: translated code binds to them by name
28+
@SuppressWarnings({"PMD.MethodNamingConventions", "PMD.ClassNamingConventions"}) // C# member and type names: translated code binds to them by name
2929
public abstract class Action_2 extends MulticastDelegate {
3030
public abstract void Invoke(Object a0, Object a1);
3131

‎maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Action_3.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
/// `System.Action`3`. Type arguments are erased, so every parameter is an
2626
/// `Object` and the generated subclass unboxes what the method it calls
2727
/// takes as a primitive.
28-
@SuppressWarnings("PMD.MethodNamingConventions") // C# member names: translated code binds to them by name
28+
@SuppressWarnings({"PMD.MethodNamingConventions", "PMD.ClassNamingConventions"}) // C# member and type names: translated code binds to them by name
2929
public abstract class Action_3 extends MulticastDelegate {
3030
public abstract void Invoke(Object a0, Object a1, Object a2);
3131

‎maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Array_.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
/// The members of `System.Array`, for the arrays that have no class of
2626
/// their own to put them on: a JVM array for one dimension, an [MdArray]
2727
/// for more. Which one an `Array` is, is only known when it gets here.
28-
@SuppressWarnings("PMD.MethodNamingConventions") // C# member names: translated code binds to them by name
28+
@SuppressWarnings({"PMD.MethodNamingConventions", "PMD.ClassNamingConventions"}) // C# member and type names: translated code binds to them by name
2929
public final class Array_ {
3030
private Array_() {
3131
}

‎maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Boolean_.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
/// The methods of `System.Boolean`, which is the primitive `boolean` here and so
2626
/// cannot carry them itself. The value comes first.
27-
@SuppressWarnings("PMD.MethodNamingConventions") // C# member names: translated code binds to them by name
27+
@SuppressWarnings({"PMD.MethodNamingConventions", "PMD.ClassNamingConventions"}) // C# member and type names: translated code binds to them by name
2828
public final class Boolean_ {
2929
private Boolean_() {
3030
}

‎maven/unity-compat/src/main/java/com/codename1/unitycompat/system/Byte_.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
/// The methods of `System.Byte`. A C# `byte` is unsigned and a JVM `byte`
2626
/// is not, so the value is masked on the way in.
27-
@SuppressWarnings("PMD.MethodNamingConventions") // C# member names: translated code binds to them by name
27+
@SuppressWarnings({"PMD.MethodNamingConventions", "PMD.ClassNamingConventions"}) // C# member and type names: translated code binds to them by name
2828
public final class Byte_ {
2929
private Byte_() {
3030
}

0 commit comments

Comments
 (0)