PMD und ESLint-Regeln einführen (Sonar-Regeln ohne Server) #154

Open
opened 2026-10-25 11:00:10 +00:00 by claude · 0 comments
Collaborator

Worum es geht

Ein grosser Teil der Regeln, die SonarQube prüft, soll auch hier gelten, ohne SonarQube-Server: PMD fürs Backend, ESLint mit eslint-plugin-sonarjs (Sonars eigene JS/TS-Regeln) fürs Frontend. Beide hängen sich in Schritte, die schon laufen (mvn -B clean verify, npm run lint). Neue Treffer machen den Build rot, der Altbestand wird eingefroren oder behoben.

Die Regeln hat Olli am 25.10.2026 einzeln ausgewählt, auf einer Swipe-Seite mit Treffern aus dem eigenen Code (Probelauf auf main 67413ac, PMD 7.28.0 mit allen Java-Regeln, eslint-plugin-sonarjs 4.2.2 mit allen Regeln). Die vollständige Entscheidungsliste hängt als decisions.json an, die Einstellungen aus dem Gespräch als einstellungen.md.

Befund vorab

ESLint ist in portal und content eingerichtet, läuft aber nirgends: nicht in der CI, nicht im Docker-Build. Darum ist es rot (am 25.10.: portal 46 Fehler, content 5). homepage und packages/shared haben gar kein ESLint.

Etappe 1: jetzt

Vom Backend-Umbau (ADR 0033, #122) unabhängig.

Frontend

  • Eine gemeinsame ESLint-Konfiguration für alle vier Workspaces (homepage über eslint-plugin-astro), npm run lint als CI-Schritt.
  • eslint-plugin-sonarjs: die 20 behaltenen Regeln mit Treffern und 209 ohne Treffer (Voreinstellung).
  • curly: ["error", "all"]: immer Klammern, wie in Java (21 Treffer, per --fix).
  • Altbestand über ESLints Bulk Suppressions (eslint-suppressions.json); die Datei darf nur schrumpfen, wie der ArchUnit-Store.
Regel Was sie prüft Produktivcode Tests
no-duplicate-string Doppelte Strings (Frontend) 5 332
prefer-read-only-props React-Props schreibgeschützt 147 2
no-nested-conditional Keine verschachtelten Ternaries 66 9
deprecation Veraltete APIs 33 1
cognitive-complexity Cognitive Complexity (Frontend) 25 2
strings-comparison Kein < oder > bei Strings 18 0
max-lines-per-function Funktion zu lang (Frontend) 1 16
no-alphabetical-sort sort() ohne Vergleichsfunktion 8 1
no-nested-template-literals Keine verschachtelten Template-Strings 8 0
no-clear-text-protocols Kein http:// 1 3
prefer-specific-assertions Passendere Assertion (Frontend) 0 3
array-constructor Kein new Array() 2 0
function-return-type Einheitlicher Rückgabetyp 2 0
nested-control-flow Zu tief verschachtelt 2 0
no-hardcoded-passwords Kein Passwort im Code 0 2
prefer-regexp-exec RegExp.exec statt match 1 1
no-hardcoded-ip Keine festen IP-Adressen 0 1
no-misleading-array-reverse sort/reverse verändert das Array 1 0
no-undefined-argument Kein undefined als Argument 0 1
table-header Tabellen brauchen Kopfzeilen 1 0

Backend

  • maven-pmd-plugin in mvn -B clean verify, eine Regeldatei im Repo.
  • 42 behaltene Regeln mit Treffern (ohne die vier aus Etappe 2) und 88 ohne Treffer (Voreinstellung).
  • Einstellungen: OnlyOneReturn mit allowGuardIfs=true (Etappe 2), MethodNamingConventions mit junit5TestPattern=[a-z][a-zA-Z0-9_]*, UseExplicitTypes mit allowCtors/allowLiterals/allowCasts, AvoidLiteralsInIfCondition ohne weitere Ausnahmen.
  • Eigene Regel: !x.isEmpty() auf Collections → CollectionUtils.isNotEmpty(x) (neue Abhängigkeit commons-collections4), auf Strings → StringUtils.isNotEmpty(s). Mit Gegentest, damit sie nicht still nichts findet. Heute 28 Stellen im Produktivcode, 9 in Tests.
  • TestIsolation und TestDataInitializer umbenennen (Regel TestClassWithoutTestCases: „Test“ heisst JUnit-Test); Verweise in Code, META-INF/services, CLAUDE.md und docs/README.md nachziehen, alte Pläne und Specs nicht.
Regel Was sie prüft Produktivcode Tests
AvoidDuplicateLiterals Doppelte Textliterale 29 468
UseExplicitTypes Kein var 21 95
FieldDeclarationsShouldBeAtStartOfClass Felder an den Anfang 19 57
RelianceOnDefaultCharset Zeichensatz angeben 1 46
LinguisticNaming Name passt nicht zum Verhalten 12 21
AvoidLiteralsInIfCondition Keine Zahlen im if 13 9
AddEmptyString Kein + "" 0 18
PreserveStackTrace Ursache beim Weiterwerfen behalten 14 0
AssignmentInOperand Keine Zuweisung im Ausdruck 4 9
UseLocaleWithCaseConversions Locale bei toUpperCase/toLowerCase 9 4
SimplifiableTestAssertion Passendere Assertion 0 12
AvoidCatchingGenericException Keine pauschalen catch-Blöcke 6 5
CloseResource Ressource schliessen 0 9
LiteralsFirstInComparisons Literal zuerst beim Vergleich 4 4
UnnecessaryFullyQualifiedName Überflüssiger voller Klassenname 0 7
UnusedFormalParameter Unbenutzter Parameter 0 7
LambdaCanBeMethodReference Methodenreferenz statt Lambda 2 4
LooseCoupling Interface statt Implementierung 1 5
VariableCanBeInlined Variable direkt zurückgeben 6 0
MissingOverride @Override fehlt 5 0
SimplifyBooleanReturns Boolean direkt zurückgeben 5 0
DanglingJavadoc Verwaister Javadoc 3 1
TestClassWithoutTestCases Test-Klasse ohne Tests 0 4
UseCollectionIsEmpty isEmpty() statt size() 0 4
AvoidReassigningParameters Parameter nicht neu zuweisen 3 0
InstantiableUtilityClass Hilfsklasse mit privatem Konstruktor 1 2
AvoidAccessibilityAlteration Kein setAccessible 0 2
AvoidThrowingRawExceptionTypes Keine nackte RuntimeException 1 1
CheckResultSet ResultSet.next() prüfen 0 2
UnusedPrivateMethod Unbenutzte private Methode 0 2
ClassWithOnlyPrivateConstructorsShouldBeFinal Klasse final machen 1 0
CompareObjectsWithEquals Objekte mit equals vergleichen 1 0
ControlStatementBraces Klammern bei if/for/while 0 1
EmptyCatchBlock Leerer catch-Block 1 0
ExceptionAsFlowControl Exception als Sprung 0 1
LocalVariableNamingConventions Lokale Namen nach Muster 0 1
MissingStaticMethodInNonInstantiatableClass Unbenutzbare Klasse 1 0
NonExhaustiveSwitch switch ohne default 1 0
ReturnEmptyCollectionRatherThanNull Leere Liste statt null 1 0
SingularField Feld könnte lokal sein 0 1
UnusedLocalVariable Unbenutzte lokale Variable 0 1
MethodNamingConventions Methodennamen nach Muster 0 0

CLAUDE.md

  • Klammerregel gilt auch fürs Frontend.
  • Neue Testmethoden heissen in snake_case (Bestand: #153).
  • Wo die Regeldateien liegen und dass ein roter Lauf im Code behoben wird, nicht in der Ausnahmedatei.

Etappe 2: nach #122 und dem Abschied von Active Record

Der Service-Zuschnitt teilt genau die betroffenen Methoden neu auf. Jetzt eingefroren, würden sie beim Umbau als neuer Code gelten.

Regel Was sie prüft Produktivcode Tests
OnlyOneReturn Nur ein return pro Methode 164 ?
CognitiveComplexity Cognitive Complexity (Java) 23 2
GodClass Gottklasse 12 11
NPathComplexity Zu viele Ausführungspfade 9 1

(OnlyOneReturn mit allowGuardIfs=true gemessen: 164 Treffer in 54 Dateien.)

Gestrichen

Regeln mit Treffern, die nicht gelten: 46 PMD, 25 sonarjs. Begründung je Regel in decisions.json bzw. auf den Karten; die grossen sind LocalVariableCouldBeFinal/MethodArgumentCouldBeFinal (über 8000 Treffer zusammen, Parameter-Neuzuweisung verbietet stattdessen AvoidReassigningParameters), LawOfDemeter, die Kommentar-Regeln und arrow-function-convention.

Regeln ohne Treffer nach Voreinstellung (PMD-Schnellstart bzw. Sonar-Empfehlung): 297 behalten, 148 gestrichen.

Offen

  • Java-Bestand beheben oder einfrieren? Etappe 1 hat 165 Treffer im Produktivcode und 803 in Tests. PMD friert nur grob je Klasse ein (excludeFromFailureFile). Entscheidung im Plan.
  • Doppelte Strings in Tests: AvoidDuplicateLiterals (468) und no-duplicate-string (332) treffen fast nur Tests. Behalten ist entschieden, ob Tests ausgenommen werden, nicht.
  • continue ganz verbieten? Besprochen, nicht entschieden: ESLint no-continue (Frontend 10 Treffer), für Java bräuchte es eine eigene Regel (85 Treffer).
  • Die Zahlen stammen vom 25.10. und werden vor dem Bau neu gemessen.
## Worum es geht Ein grosser Teil der Regeln, die SonarQube prüft, soll auch hier gelten, ohne SonarQube-Server: **PMD** fürs Backend, **ESLint mit `eslint-plugin-sonarjs`** (Sonars eigene JS/TS-Regeln) fürs Frontend. Beide hängen sich in Schritte, die schon laufen (`mvn -B clean verify`, `npm run lint`). Neue Treffer machen den Build rot, der Altbestand wird eingefroren oder behoben. Die Regeln hat Olli am 25.10.2026 einzeln ausgewählt, auf einer Swipe-Seite mit Treffern aus dem eigenen Code (Probelauf auf `main` `67413ac`, PMD 7.28.0 mit allen Java-Regeln, eslint-plugin-sonarjs 4.2.2 mit allen Regeln). Die vollständige Entscheidungsliste hängt als `decisions.json` an, die Einstellungen aus dem Gespräch als `einstellungen.md`. ## Befund vorab ESLint ist in `portal` und `content` eingerichtet, läuft aber nirgends: nicht in der CI, nicht im Docker-Build. Darum ist es rot (am 25.10.: portal 46 Fehler, content 5). `homepage` und `packages/shared` haben gar kein ESLint. ## Etappe 1: jetzt Vom Backend-Umbau (ADR 0033, #122) unabhängig. **Frontend** - Eine gemeinsame ESLint-Konfiguration für alle vier Workspaces (homepage über `eslint-plugin-astro`), `npm run lint` als CI-Schritt. - `eslint-plugin-sonarjs`: die 20 behaltenen Regeln mit Treffern und 209 ohne Treffer (Voreinstellung). - `curly: ["error", "all"]`: immer Klammern, wie in Java (21 Treffer, per `--fix`). - Altbestand über ESLints Bulk Suppressions (`eslint-suppressions.json`); die Datei darf nur schrumpfen, wie der ArchUnit-Store. | Regel | Was sie prüft | Produktivcode | Tests | |---|---|---|---| | `no-duplicate-string` | Doppelte Strings (Frontend) | 5 | 332 | | `prefer-read-only-props` | React-Props schreibgeschützt | 147 | 2 | | `no-nested-conditional` | Keine verschachtelten Ternaries | 66 | 9 | | `deprecation` | Veraltete APIs | 33 | 1 | | `cognitive-complexity` | Cognitive Complexity (Frontend) | 25 | 2 | | `strings-comparison` | Kein < oder > bei Strings | 18 | 0 | | `max-lines-per-function` | Funktion zu lang (Frontend) | 1 | 16 | | `no-alphabetical-sort` | sort() ohne Vergleichsfunktion | 8 | 1 | | `no-nested-template-literals` | Keine verschachtelten Template-Strings | 8 | 0 | | `no-clear-text-protocols` | Kein http:// | 1 | 3 | | `prefer-specific-assertions` | Passendere Assertion (Frontend) | 0 | 3 | | `array-constructor` | Kein new Array() | 2 | 0 | | `function-return-type` | Einheitlicher Rückgabetyp | 2 | 0 | | `nested-control-flow` | Zu tief verschachtelt | 2 | 0 | | `no-hardcoded-passwords` | Kein Passwort im Code | 0 | 2 | | `prefer-regexp-exec` | RegExp.exec statt match | 1 | 1 | | `no-hardcoded-ip` | Keine festen IP-Adressen | 0 | 1 | | `no-misleading-array-reverse` | sort/reverse verändert das Array | 1 | 0 | | `no-undefined-argument` | Kein undefined als Argument | 0 | 1 | | `table-header` | Tabellen brauchen Kopfzeilen | 1 | 0 | **Backend** - `maven-pmd-plugin` in `mvn -B clean verify`, eine Regeldatei im Repo. - 42 behaltene Regeln mit Treffern (ohne die vier aus Etappe 2) und 88 ohne Treffer (Voreinstellung). - Einstellungen: `OnlyOneReturn` mit `allowGuardIfs=true` (Etappe 2), `MethodNamingConventions` mit `junit5TestPattern=[a-z][a-zA-Z0-9_]*`, `UseExplicitTypes` mit `allowCtors`/`allowLiterals`/`allowCasts`, `AvoidLiteralsInIfCondition` ohne weitere Ausnahmen. - **Eigene Regel:** `!x.isEmpty()` auf Collections → `CollectionUtils.isNotEmpty(x)` (neue Abhängigkeit `commons-collections4`), auf Strings → `StringUtils.isNotEmpty(s)`. Mit Gegentest, damit sie nicht still nichts findet. Heute 28 Stellen im Produktivcode, 9 in Tests. - `TestIsolation` und `TestDataInitializer` umbenennen (Regel `TestClassWithoutTestCases`: „Test“ heisst JUnit-Test); Verweise in Code, `META-INF/services`, CLAUDE.md und docs/README.md nachziehen, alte Pläne und Specs nicht. | Regel | Was sie prüft | Produktivcode | Tests | |---|---|---|---| | `AvoidDuplicateLiterals` | Doppelte Textliterale | 29 | 468 | | `UseExplicitTypes` | Kein var | 21 | 95 | | `FieldDeclarationsShouldBeAtStartOfClass` | Felder an den Anfang | 19 | 57 | | `RelianceOnDefaultCharset` | Zeichensatz angeben | 1 | 46 | | `LinguisticNaming` | Name passt nicht zum Verhalten | 12 | 21 | | `AvoidLiteralsInIfCondition` | Keine Zahlen im if | 13 | 9 | | `AddEmptyString` | Kein + "" | 0 | 18 | | `PreserveStackTrace` | Ursache beim Weiterwerfen behalten | 14 | 0 | | `AssignmentInOperand` | Keine Zuweisung im Ausdruck | 4 | 9 | | `UseLocaleWithCaseConversions` | Locale bei toUpperCase/toLowerCase | 9 | 4 | | `SimplifiableTestAssertion` | Passendere Assertion | 0 | 12 | | `AvoidCatchingGenericException` | Keine pauschalen catch-Blöcke | 6 | 5 | | `CloseResource` | Ressource schliessen | 0 | 9 | | `LiteralsFirstInComparisons` | Literal zuerst beim Vergleich | 4 | 4 | | `UnnecessaryFullyQualifiedName` | Überflüssiger voller Klassenname | 0 | 7 | | `UnusedFormalParameter` | Unbenutzter Parameter | 0 | 7 | | `LambdaCanBeMethodReference` | Methodenreferenz statt Lambda | 2 | 4 | | `LooseCoupling` | Interface statt Implementierung | 1 | 5 | | `VariableCanBeInlined` | Variable direkt zurückgeben | 6 | 0 | | `MissingOverride` | @Override fehlt | 5 | 0 | | `SimplifyBooleanReturns` | Boolean direkt zurückgeben | 5 | 0 | | `DanglingJavadoc` | Verwaister Javadoc | 3 | 1 | | `TestClassWithoutTestCases` | Test-Klasse ohne Tests | 0 | 4 | | `UseCollectionIsEmpty` | isEmpty() statt size() | 0 | 4 | | `AvoidReassigningParameters` | Parameter nicht neu zuweisen | 3 | 0 | | `InstantiableUtilityClass` | Hilfsklasse mit privatem Konstruktor | 1 | 2 | | `AvoidAccessibilityAlteration` | Kein setAccessible | 0 | 2 | | `AvoidThrowingRawExceptionTypes` | Keine nackte RuntimeException | 1 | 1 | | `CheckResultSet` | ResultSet.next() prüfen | 0 | 2 | | `UnusedPrivateMethod` | Unbenutzte private Methode | 0 | 2 | | `ClassWithOnlyPrivateConstructorsShouldBeFinal` | Klasse final machen | 1 | 0 | | `CompareObjectsWithEquals` | Objekte mit equals vergleichen | 1 | 0 | | `ControlStatementBraces` | Klammern bei if/for/while | 0 | 1 | | `EmptyCatchBlock` | Leerer catch-Block | 1 | 0 | | `ExceptionAsFlowControl` | Exception als Sprung | 0 | 1 | | `LocalVariableNamingConventions` | Lokale Namen nach Muster | 0 | 1 | | `MissingStaticMethodInNonInstantiatableClass` | Unbenutzbare Klasse | 1 | 0 | | `NonExhaustiveSwitch` | switch ohne default | 1 | 0 | | `ReturnEmptyCollectionRatherThanNull` | Leere Liste statt null | 1 | 0 | | `SingularField` | Feld könnte lokal sein | 0 | 1 | | `UnusedLocalVariable` | Unbenutzte lokale Variable | 0 | 1 | | `MethodNamingConventions` | Methodennamen nach Muster | 0 | 0 | **CLAUDE.md** - Klammerregel gilt auch fürs Frontend. - Neue Testmethoden heissen in snake_case (Bestand: #153). - Wo die Regeldateien liegen und dass ein roter Lauf im Code behoben wird, nicht in der Ausnahmedatei. ## Etappe 2: nach #122 und dem Abschied von Active Record Der Service-Zuschnitt teilt genau die betroffenen Methoden neu auf. Jetzt eingefroren, würden sie beim Umbau als neuer Code gelten. | Regel | Was sie prüft | Produktivcode | Tests | |---|---|---|---| | `OnlyOneReturn` | Nur ein return pro Methode | 164 | ? | | `CognitiveComplexity` | Cognitive Complexity (Java) | 23 | 2 | | `GodClass` | Gottklasse | 12 | 11 | | `NPathComplexity` | Zu viele Ausführungspfade | 9 | 1 | (`OnlyOneReturn` mit `allowGuardIfs=true` gemessen: 164 Treffer in 54 Dateien.) ## Gestrichen Regeln mit Treffern, die nicht gelten: 46 PMD, 25 sonarjs. Begründung je Regel in `decisions.json` bzw. auf den Karten; die grossen sind `LocalVariableCouldBeFinal`/`MethodArgumentCouldBeFinal` (über 8000 Treffer zusammen, Parameter-Neuzuweisung verbietet stattdessen `AvoidReassigningParameters`), `LawOfDemeter`, die Kommentar-Regeln und `arrow-function-convention`. Regeln ohne Treffer nach Voreinstellung (PMD-Schnellstart bzw. Sonar-Empfehlung): 297 behalten, 148 gestrichen. ## Offen - **Java-Bestand beheben oder einfrieren?** Etappe 1 hat 165 Treffer im Produktivcode und 803 in Tests. PMD friert nur grob je Klasse ein (`excludeFromFailureFile`). Entscheidung im Plan. - **Doppelte Strings in Tests:** `AvoidDuplicateLiterals` (468) und `no-duplicate-string` (332) treffen fast nur Tests. Behalten ist entschieden, ob Tests ausgenommen werden, nicht. - **`continue` ganz verbieten?** Besprochen, nicht entschieden: ESLint `no-continue` (Frontend 10 Treffer), für Java bräuchte es eine eigene Regel (85 Treffer). - Die Zahlen stammen vom 25.10. und werden vor dem Bau neu gemessen.
Sign in to join this conversation.
No description provided.