Verhältnis zu anderen Seiten: diese Seite behandelt den alltäglichen DSL-Einsatz: Datei-Aufbau, Query-Formen und die Collections-Übersicht. Die formale EBNF-Grammatik steht in der DSL-Grammatik. Den vollständigen Property-Katalog aller Modell-Typen finden Sie im Prädikat-Katalog.
Die CodeCharter-DSL ist an LINQ angelehnt. Wenn Sie C# kennen, kennen Sie fast alles, was Sie brauchen.
Datei-Anatomie
@name "..." // optional, Default: Dateiname
@severity warn // optional, Default: warn (info | warn | error)
@category "..." // optional, Default: "General"
@description "..." // optional
@recommendation "..." // optional
// der Query-Body (keine Leerzeile erforderlich)
<linq-expression>
Alle Direktiven sind optional. Der Regel-Slug in den Findings ist der
Dateiname ohne .ccr-Endung. @name ist nur der menschenlesbare
Anzeigetitel. Kommentare nutzen //; es gibt keine Block-Kommentare.
Query-Formen
Query-Syntax
from t in Types
where t.Kind == "Class"
where t.IsAbstract
select t
Method-Syntax
Types.Where(t => t.Kind == "Class" && t.IsAbstract)
Beide ergeben das gleiche Ergebnis. Bei mehr als einer Bedingung ist Query-Syntax meist lesbarer.
Die Query-Syntax unterstützt zusätzlich let-Klauseln, um Zwischenwerte zu
benennen. Eine orderby-Klausel (optional gefolgt von asc oder desc)
wird akzeptiert, ändert die Reihenfolge der Ergebnisse aber nicht.
Top-Level-Collections
| Collection | Element | Hinweis |
|---|---|---|
Types |
TypeModel |
|
Methods |
MethodModel |
Konstruktoren sind nicht enthalten; sie sind als Constructors auf einem TypeModel erreichbar |
Properties |
PropertyModel |
|
Fields |
FieldModel |
|
Events |
EventModel |
|
Namespaces |
NamespaceModel |
|
Assemblies |
AssemblyModel |
|
TypeDependencies |
TypeDependency |
|
AllBodies |
IHasBodySyntax |
Jede Methode, jeder Konstruktor und jeder Property-Accessor über alle Typen, inklusive verschachtelter Typen; für Regeln, die Statement-Level-Facts (Catches, Invocations, MemberAccesses, Literals, BinaryExpressions, LocalDeclarations) ohne Nesting-Grenze auswerten |
Files |
FileModel |
Eine pro Quelldatei: using-Direktiven und Kommentar-Trivia |
AllBodies existiert ausschließlich als Top-Level-Root-Collection; es
gibt keine AllBodies-Sub-Collection auf einer Methode. Um die
Statement-Level-Facts eines bestimmten Typs oder einer bestimmten Methode zu
prüfen, filtern Sie die Root-Collection über die Properties DeclaringType
und Name des umschließenden Members.
Operatoren
LINQ-Standard:
== != < <= > >=
&& || !
+ - * / %
Strings:
.StartsWith("...")
.EndsWith("...")
.Contains("...")
.Matches("regex") // Regex-Match
.ToLower()
.ToUpper()
.Length // Property, nicht Methode
Collections:
.Any(x => ...)
.All(x => ...)
.Count // Property, nicht Methode
.Where(x => ...).Count
.Select(x => ...)
.SelectMany(x => ...)
.First(x => ...)
.Contains(value)
.Distinct()
.Take(n)
.OrderBy(x => ...)
.OrderByDescending(x => ...)
.Sum(x => ...) .Average(x => ...) .Min(x => ...) .Max(x => ...)
Beide Listen sind abgeschlossen: nur die oben gezeigten Methoden
existieren. Andere Methoden, die Sie aus C# erwarten würden, etwa
Trim, Replace, GroupBy, Skip oder FirstOrDefault, werden
nicht unterstützt und schlagen zur Auswertungszeit fehl. Die Folge
hängt von der Query-Form ab: die Query-Form (LINQ) überspringt nur das
fehlschlagende Element, die Method-Form (fluent) bricht die gesamte
Regel ohne Findings ab. Details stehen in der
DSL-Grammatik.
Spezielle Sub-Modelle
BaseType.FullName
where t.BaseType.FullName.Contains("ControllerBase")
BaseType kann null sein (zum Beispiel bei Interfaces oder Typen ohne
deklarierte Basisklasse). Vergleiche gegen null (t.BaseType != null) werden
unterstützt, und ein Member-Zugriff auf einen null-Wert liefert sicher null,
statt einen Fehler auszulösen.
BaseType ist nur gesetzt, wenn der Basistyp im analysierten Code deklariert
ist, bei Framework-Basistypen also null. Um einen Framework-Basistyp zu treffen
oder einen Basistyp irgendwo in der Vererbungskette, nutzen Sie
DeclaredBaseTypeName (nur der direkte Basistyp) oder BaseTypeNames (die
gesamte Kette, siehe unten).
BaseTypeNames.Contains(...)
# Jeden Typ ausnehmen, der in beliebiger Tiefe von System.Exception erbt
where !t.BaseTypeNames.Contains("System.Exception")
BaseTypeNames enthält die voll qualifizierten Namen der gesamten
Vererbungskette, den direkten plus jeden transitiven Basistyp, bis (exklusive)
System.Object. Es überschreitet die Framework-Grenze, sodass
"System.Exception" enthalten ist, obwohl dieser Typ nicht Teil des
analysierten Codes ist. Bevorzugen Sie es gegenüber DeclaredBaseTypeName, wenn eine
Ausnahme auch indirekte Unterklassen abdecken muss (z.B.
DomainException : ExceptionBase mit ExceptionBase : Exception); nutzen Sie
DeclaredBaseTypeName nur, wenn Sie bewusst ausschließlich den direkten Basistyp
meinen.
Parameters.Any(...)
where m.Parameters.Any(p => p.TypeShortName == "CancellationToken")
m.Syntax, Regeln auf Anweisungsebene
m.Syntax navigiert die Anweisungen im Methodenrumpf, also Schleifen,
try/catch, throw, Aufrufe, über dieselben Namen, die C# für diese
Konstrukte verwendet. Descendants erreicht ein Konstrukt in beliebiger Tiefe,
ein Konstruktname filtert die Menge, benannte Teile (.Block, .Condition,
.Expression, ...) gehen hinein.
Methods.Where(m =>
m.Syntax.Descendants.CatchClause.Any(c =>
c.Block.Descendants.ThrowStatement.Any(t =>
t.Expression.Text == c.Declaration.Identifier.Text)))
Die vollständige Oberfläche (lesbare Member, gängige Konstruktnamen und die Grenzen) steht im Prädikat-Katalog.
Vollständiger Property-Katalog
Die vollständige Liste aller Properties auf TypeModel, MethodModel,
ParameterModel, PropertyModel, FieldModel, EventModel und den
typisierten Statement-Level-Facts (CatchClauseModel, InvocationModel,
MemberAccessModel, LiteralModel, BinaryExpressionModel,
LocalDeclarationModel, einschließlich Collection-Helfer) steht im
Prädikat-Katalog.