Zum Inhalt springen

Nur bei neuen Findings mit dem Baseline-Gate scheitern

Nur an neuen Findings scheitern, indem bestehende als Baseline akzeptiert werden.

Das Baseline-Gate lässt CodeCharter einen Build nur an neuen Findings scheitern, während ein bekannter Satz bestehender Findings geduldet wird. Das ist der ideale Weg, CodeCharter in einer bestehenden Codebasis einzuführen: die heutigen Findings als Baseline akzeptieren und neue zuverlässig draußen halten.

Unterschied zum Diff-Modus

  • Diff-Modus (--diff / --git-ref) beschränkt auf die geänderten Zeilen („neu in diesem PR, zeilenbasiert").
  • Baseline-Modus (--baseline) beschränkt auf Findings, die nicht in einem akzeptierten Snapshot stehen („neu gegenüber der Baseline, identitätsbasiert"). Erfasst auch ein neues Finding auf einer Zeile, die der PR nicht angefasst hat.

Beides kombiniert: mit beiden gesetzt muss ein Finding auf einer geänderten Zeile liegen und nicht in der Baseline stehen, um gemeldet zu werden.

Fingerprints

Jedes Finding erhält einen stabilen Fingerprint aus seiner Identität (Regel, Entitätsart, Entitätsname, Kategorie), seinem Dateipfad (repo-relativ, wenn --workspace-root gesetzt ist) und, bei einem Finding auf einer Methode, den Parametertypen der Methode: zwei Überladungen mit demselben Namen (etwa Process(string) und Process(int)) erhalten dadurch unterschiedliche Fingerprints, statt nur über ihre Position in der Datei auseinandergehalten zu werden. Ein Vorkommenszähler hält verbleibende gleichartige Findings in derselben Datei unterscheidbar. Der Fingerprint ist zeilenunabhängig und übersteht damit Zeilenverschiebungen und Reformatierungen. Eine Datei verschieben oder die Entität umbenennen ändert den Fingerprint, sodass das Finding als neu erscheint. Baseline neu generieren, um es wieder zu akzeptieren. Der Fingerprint steht auch in der JSON-Ausgabe und als SARIF partialFingerprints["codecharter/v1"]; die Deklarationsidentität weiter unten wird zusätzlich als codecharter/v2 ausgegeben, wenn das Finding eine besitzt.

Deklarationsidentität (verschiebungsstabiler Abgleich)

Für ein Finding auf einem Typ, einer Methode, einer Property, einem Feld, einem Event oder einem Namespace zeichnet die Baseline zusätzlich eine Deklarationsidentität auf, die vom .NET-Compiler stammt (Roslyns DocumentationCommentId), eine stabile ID wie M:MyApp.Service.Do(System.Int32), die überhaupt nicht vom Dateipfad abhängt. Wird die Datei verschoben, in der dieses Finding deklariert ist, passt diese Identität weiterhin, auch wenn der Fingerprint (der den Pfad kodiert) nicht mehr passt. Das Finding wird also nicht allein deshalb als neu gemeldet, weil die Datei verschoben wurde. Ein Finding wird unterdrückt, wenn entweder seine Identität oder sein Fingerprint in der Baseline steht. Eine Baseline, die vor dieser Funktion geschrieben wurde, funktioniert unverändert weiter, allein über Fingerprints abgeglichen.

Das gilt nur für diese sechs Entitätsarten. Ein Finding auf Vorkommensebene (eine catch-Klausel, ein Literal, ein Kommentar und Ähnliches) hat keine Deklaration, an der sich eine Identität verankern ließe, und wird daher weiterhin ausschließlich über den Fingerprint abgeglichen, genau wie bisher. Ein Umbenennen der Entität ist weiterhin nicht abgedeckt: Die Identität wird aus dem aktuellen Namen der Deklaration gebildet, sodass eine Umbenennung das Finding weiterhin als neu erscheinen lässt (die Deklarationsidentität unterscheidet aber auch überladene Methoden, dieselbe Disambiguierung, die die Parametertypen im Fingerprint bereits leisten).

Umbenennungs-Migration (baseline migrate)

Ein Umbenennen ist von der Deklarationsidentität selbst weiterhin nicht abgedeckt (siehe oben) - codecharter baseline migrate schließt diese Lücke für den häufigen Fall: eine Entität, die zusammen mit ihrer Datei umbenannt wurde. Es wird nie allein aufgrund von Namensähnlichkeit geraten; ein Mapping für eine veraltete Identität wird nur vorgeschlagen, wenn Gits eigene Rename-Erkennung (git diff -M20% --name-status) sagt, dass alte und neue Datei "dieselbe Datei, verschoben" sind. Die Schwelle von 20% ist bewusst großzügig gewählt - eine Umbenennung, bei der zusätzlich der Großteil des Dateiinhalts überarbeitet wurde, zählt hier weiterhin als Hinweis, denn jeder so gefundene Kandidat wird im nächsten Schritt gegen die tatsächlichen Entitäten am neuen Pfad geprüft; ein Fehltreffer von Git mit geringer Ähnlichkeit ergibt dort dann einfach keinen oder mehrere mehrdeutige Kandidaten statt eines falschen Mappings.

codecharter baseline migrate --baseline .codecharter/baseline.json --git-ref main..HEAD
  • Führt git diff -M20% --name-status über --git-ref aus, um umbenannte oder verschobene Dateien zu finden, und prüft dann jede Baseline-Identität gegen die aktuelle Kompilierung: eine Identität, die weiterhin auflöst, braucht nichts; eine, die nicht mehr auflöst (NotFound), ist ein Migrationskandidat.
  • Für jeden Kandidaten wird nach Entitäten derselben Art (Typ, Methode, Property, Feld oder Event) am neuen Ort der umbenannten Datei gesucht. Genau ein noch nicht beanspruchter Treffer schlägt ein Mapping vor - Konfidenz High, wenn sich nur der Container verschoben hat (der einfache Name ist unverändert), Konfidenz Renamed, wenn sich auch der Name der Entität geändert hat. Kein Treffer, mehr als ein Treffer, oder ein Treffer, der bereits von einem anderen Mapping im selben Lauf beansprucht wurde (ein Split oder Merge), werden als ungelöst gemeldet, mit allen Kandidaten aufgelistet, nie stillschweigend geraten.
  • Gibt jedes vorgeschlagene Mapping und jede ungelöste Identität aus und fragt dann vor dem Schreiben nach Bestätigung. Mit --yes wird ohne Rückfrage angewendet (in der CI oder jedem nicht-interaktiven Kontext erforderlich - der Befehl verweigert eine Rückfrage gegen eine nicht-interaktive Standardeingabe und beendet sich ohne Änderungen).
  • Das Anwenden einer Migration schreibt die veralteten Identitäten in der Baseline um und hängt einen Eintrag an ein append-only Migrationsjournal an, das in derselben Datei aufgezeichnet wird (wann gelaufen, aus welchem --git-ref-Bereich die Belege stammen, und jedes angewendete Mapping) - die eine Stelle, an der diese sonst zeitstempelfreie Datei bewusst einen führt, damit ein Rename-Mapping nie ein stilles, unnachvollziehbares Überschreiben ist.
  • Exit-Codes: 0 saubere Migration (jede veraltete Identität aufgelöst, oder nichts zu migrieren), 1 ungelöste/mehrdeutige Identitäten bleiben übrig (oder der Lauf wurde abgebrochen - nichts wurde geschrieben), 64 Bedienungsfehler (fehlende Baseline-Datei, kein auflösbares Analyseziel, oder ein fehlgeschlagener Git-/Analyseschritt).

Ablauf

# 1. Aktuelle Findings als Baseline akzeptieren und einchecken.
codecharter analyze MySolution.sln --write-baseline .codecharter/baseline.json
git add .codecharter/baseline.json && git commit -m "chore: codecharter baseline"

# 2. In der CI nur an neuen Findings gaten.
codecharter analyze MySolution.sln --baseline .codecharter/baseline.json --fail-on error
  • Die Baseline mit den gleichen Optionen erzeugen, mit denen Sie gaten (Regeln, --severity), damit der aufgezeichnete Satz zum später ausgewerteten passt.
  • Beim Erzeugen und Gaten --workspace-root <Repo-Root> übergeben (oder aus dem Repo-Root heraus laufen), damit Fingerprints repo-relative Pfade nutzen und die Baseline maschinen- und CI-übergreifend portabel ist. Die GitHub Action setzt das automatisch.
  • Die Baseline-Datei ist deterministisch (sortiert, ohne Timestamps) und erzeugt saubere Diffs in der Versionsverwaltung.
  • Um neu eingeführte Findings zu akzeptieren (oder behobene zu entfernen), die Datei mit --write-baseline neu generieren und einchecken.

Verhalten

  • Eine fehlende --baseline-Datei analysiert alles und gibt eine Warnung aus; sie lässt nicht still durch. (Nützlich, bevor die erste Baseline eingecheckt ist.)
  • Eine kaputte Datei oder eine Datei mit einer Schemaversion, die die CLI nicht kennt, ist ein harter Fehler (Exit 2).
  • --write-baseline schreibt nur und endet mit 0; es meldet keine Findings und löst kein --fail-on aus.
  • --write-baseline zeichnet immer das vollständige (nach Severity gefilterte) Analyseergebnis auf: in Kombination mit --diff/--git-ref wird die geschriebene Baseline nicht auf den Diff beschränkt.