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-refaus, 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), KonfidenzRenamed, 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
--yeswird 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:
0saubere Migration (jede veraltete Identität aufgelöst, oder nichts zu migrieren),1ungelöste/mehrdeutige Identitäten bleiben übrig (oder der Lauf wurde abgebrochen - nichts wurde geschrieben),64Bedienungsfehler (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-baselineneu 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-baselineschreibt nur und endet mit0; es meldet keine Findings und löst kein--fail-onaus.--write-baselinezeichnet immer das vollständige (nach Severity gefilterte) Analyseergebnis auf: in Kombination mit--diff/--git-refwird die geschriebene Baseline nicht auf den Diff beschränkt.