Verhältnis zu anderen Seiten: diese Seite ist der formale Grammatik-Anhang. Für den alltäglichen DSL-Einsatz (Datei-Aufbau, Query-Formen, Collections-Übersicht) gibt es die Syntax-Übersicht. Alle abfragbaren Properties aller Modell-Typen stehen im Prädikat-Katalog.
Diese Seite ist die formale Wahrheit. Wenn Sie unsicher sind, ob ein Konstrukt erlaubt ist, schauen Sie hier nach. Für den alltäglichen Sprachgebrauch gibt es die kompakte Syntax-Übersicht, die nach Anwendungsfall sortiert ist.
Datei-Struktur
<file> ::= <directive>* <query>
<directive> ::= '@' <name> <directive-value>
<directive-value> ::= <string-literal> | <identifier>
<query> ::= <query-syntax> | <fluent-syntax>
Eine .ccr-Datei enthält null oder mehr Direktiven, gefolgt von
genau einer Query. Der Parser lehnt nachfolgenden Inhalt nicht ab:
alles nach der ersten Query wird stillschweigend ignoriert. Schreiben
Sie deshalb nur eine Query pro Datei.
Direktiven
Direktiven setzen Metadaten. Alle sind optional. Unbekannte Direktiv-Namen werden ignoriert, damit Regeln zukunftskompatibel bleiben.
| Direktive | Pflicht | Wert | Beschreibung |
|---|---|---|---|
@name |
nein | string | Menschenlesbarer Titel der Regel. Default: Dateiname ohne Endung |
@severity |
nein | enum | info, warn (auch warning), error. Default warn; unbekannte Werte fallen ebenfalls auf warn zurück |
@category |
nein | string | Frei wählbare Gruppe (z.B. Async, Design). Default General |
@description |
nein | string | Eine Zeile, was die Regel prüft |
@recommendation |
nein | string | Eine Zeile, wie man es behebt |
Strings nutzen C-Escapes für \", \\, \n, \r, \t, \0.
Unbekannte Escapes bleiben unverändert stehen, damit "Handler\d*$"
für Regex-Patterns natürlich bleibt.
@category ist Freitext: Die Engine validiert ihn nie gegen eine feste
Liste, also können eigene Regeln jederzeit eine neue Kategorie einführen.
Der eingebaute Regelkatalog gruppiert seine Regeln in Kategorien wie
Design, Naming, Async, Style, Performance, Architecture,
ErrorHandling, Documentation und Complexity; neue Kategorien kommen
hinzu, sobald die eingebauten Regeln neue Bereiche der Codebasis abdecken.
Die aktuelle, automatisch erzeugte Liste nach Kategorie steht unter
Alle eingebauten Regeln.
Konfigurierbare Parameter (@param)
Eine Regel kann statt eines fest codierten Schwellenwerts einen oder mehrere einstellbare Parameter deklarieren:
<param-directive> ::= '@param' <id> ':' <param-type> ('=' <default>)?
<range-clause>? <aliases-clause>? <enum-clause>?
<param-type> ::= 'int' | 'float' | 'bool' | 'string' | 'enum'
<range-clause> ::= '[range:' <number> '..' <number> ']'
<aliases-clause> ::= '[aliases:' <id> (',' <id>)* ']'
<enum-clause> ::= '[enum:' <value> (',' <value>)* ']'
@param maxLines: int = 60 [range: 1..500]
Im Query-Body ist der Parameter ein ganz normaler Identifier: maxLines
im Beispiel oben löst zu seinem konfigurierten oder default Wert auf und
hat Vorrang vor allem außer einer gleichnamigen let-Variable oder
Schleifenvariable:
Methods.Where(m => m.LinesOfCode > maxLines)
range schränkt den Default und jeden konfigurierten Wert eines
int/float-Parameters auf die inklusiven Grenzen ein; enum listet die
erlaubten Werte für einen string-Parameter, der als Enumeration genutzt
wird; aliases fügt alternative Identifier-Namen hinzu, die der Query-Body
austauschbar mit dem deklarierten Namen verwenden kann. Ein Wert, der gegen
Typ, Bereich oder Enum-Vorgabe verstößt, wird beim Laden der Regel
abgelehnt. Repository-Verantwortliche überschreiben den Default von außen,
siehe params in der
Konfigurationsdatei-Referenz.
Query-Syntax (LINQ-Form)
<query-syntax> ::= 'from' <id> 'in' <id>
( <let-clause> | <where-clause> )*
<orderby-clause>?
'select' <expression>
<let-clause> ::= 'let' <id> '=' <expression>
<where-clause> ::= 'where' <expression>
<orderby-clause> ::= 'orderby' <expression> ('asc' | 'desc')?
Zwei semantische Hinweise:
orderbywird vom Parser akzeptiert, hat in dieser Form aber derzeit keine Wirkung: die Ergebnisse behalten die Reihenfolge der Collection. Zum Sortieren nutzen Sie stattdessen die Fluent-MethodenOrderBy/OrderByDescending.- Alle
let-Klauseln werden vor allenwhere-Klauseln ausgewertet, auch wenn sie im Quelltext gemischt stehen.
Beispiel:
from t in Types
where t.Kind == "Class"
let methodCount = t.Methods.Count
where methodCount > 20
orderby methodCount desc
select t
Fluent-Syntax (Method-Form)
<fluent-syntax> ::= <id> <method-call>+
<method-call> ::= '.' <id> ( '(' ( <argument> (',' <argument>)* )? ')' )?
<argument> ::= <expression> | <lambda>
<lambda> ::= <id> '=>' <expression>
Argumente werden durch Kommas getrennt. Aufrufe ohne Argumente wie
.Count(), .Any() oder .ToLower() funktionieren sowohl als
Schritte der äußeren Fluent-Kette als auch innerhalb von Ausdrücken
oder Lambda-Bodys, etwa in einer where-Bedingung: m.Name.ToLower()
wird als Methodenaufruf geparst, nicht als Zugriff auf ein Property.
Beispiel:
Methods.Where(m => m.IsAsync && m.Parameters.Count > 5)
Bei reinen Filtern treffen beide Formen dieselben Entitäten, sie sind
aber nicht vollständig austauschbar: Sortieren kann nur die
Fluent-Form (siehe den Hinweis zu orderby oben), und bei einem
Auswertungsfehler überspringt die LINQ-Form nur das betroffene
Element, während die Fluent-Form die ganze Regel ohne Findings
abbricht. Bei mehreren where-Bedingungen liest sich die LINQ-Form
meist besser. Bei kurzen Filter-Ketten ist Fluent oft kompakter.
Ausdrucksgrammatik
Operatoren von festester zu lockerster Bindung:
- Primär: Literale, Identifier,
( expr ) - Member-Access und Method-Call:
a.b,a.b(args) - Unär:
!expr,-expr - Multiplikativ:
*,/,% - Additiv:
+,- - Vergleich:
==,!=,>,>=,<,<= - Logisches UND:
&& - Logisches ODER:
||
Literale die der Lexer kennt: Integer, Float, String ("..."),
true, false.
Ein Identifier (<id>) beginnt mit einem Buchstaben oder Unterstrich,
gefolgt von beliebig vielen Buchstaben, Ziffern oder Unterstrichen.
null ist kein Lexer-Literal, sondern wird bei der Auswertung als
spezieller Identifier aufgelöst. Vergleiche wie t.BaseType == null
sind daher erlaubt.
Whitespace und Kommentare
- Whitespace zwischen Tokens ist bedeutungslos.
- Kommentare beginnen mit
//und gehen bis Zeilenende.
Zusicherungen der Sprache
Die DSL ist absichtlich klein und seiteneffektfrei:
- Ein Query-Body liest das gecachte Code-Modell und sonst nichts.
- Jedes Property ist ein Read, die Auswertung bleibt damit frei von mutablem State.
- Die Auswertung läuft synchron über dem gecachten Code-Modell.
Wenn Sie ein Prädikat brauchen, das der Katalog noch nicht führt, melden Sie uns den Anwendungsfall bitte als kurze Anfrage, statt auf Reflection auszuweichen. Die DSL wächst absichtlich, nicht zufällig.
Wo geht's weiter
- Prädikat-Katalog: vollständige Liste der Properties auf jeder Entität
- Syntax-Übersicht: kompakte, anwendungsfallorientierte Variante dieser Seite
- Regel-Beispiele: praktische Anwendungen der Grammatik