CodeCharter analysiert Ihre Solution einmal und stellt der DSL ein gecachtes Code-Modell zur Verfügung. Diese Seite listet, was Sie darauf abfragen können, sortiert nach Entitäts-Typ.
Wenn Sie eine Regel formulieren, wandern Sie von einer Root-Collection ausgehend durch das Modell. Property-Reads, Method-Aufrufe, LINQ-Ketten, am Ende sammeln Sie die Treffer ein.
Root-Collections
Jede Query startet bei einer dieser Sammlungen.
| Root | Element | Inhalt |
|---|---|---|
Types |
TypeModel |
Alle Klassen, Structs, Interfaces, Enums, Records |
Methods |
MethodModel |
Alle Methoden über alle Typen (Konstruktoren sind nicht enthalten, die stehen in TypeModel.Constructors) |
Properties |
PropertyModel |
Alle Properties |
Fields |
FieldModel |
Alle Felder |
Events |
EventModel |
Alle Events |
Namespaces |
NamespaceModel |
Alle Namespaces |
Assemblies |
AssemblyModel |
Eines pro Projekt |
TypeDependencies |
TypeDependency |
Gerichtete Type-zu-Type-Abhängigkeiten |
Files |
FileModel |
Eine pro Quelldatei: using-Direktiven und Kommentar-Trivia |
TypeModel
Identität:
Name string # ohne Namespace
FullName string # Namespace.Type
Namespace NamespaceModel # Objekt, kein String: für den String t.Namespace.FullName nutzen
Kind string # "Class" | "Interface" | "Struct" | "Enum" | "Record" | "Delegate"
SourceFile string
LineNumber int
Modifiers:
IsAbstract bool
IsSealed bool
IsStatic bool
IsPartial bool
IsGeneric bool
IsRecord bool
AccessModifier string # "Public" | "Internal" | "Private" | "Protected" | "ProtectedInternal" | "PrivateProtected"
Strukturelle Metriken:
LinesOfCode int
NumberOfMethods int
NumberOfFields int
NumberOfProperties int
NumberOfDerivedTypes int
WeightedMethodsPerClass int
DepthOfInheritance int
LackOfCohesion double
ResponseForClass int
Instability double
MaintainabilityIndex double
CouplingEfferent int
CouplingAfferent int
Abstractness und DistanceFromMainSequence sind Namespace-Metriken;
Sie finden sie auf NamespaceModel, nicht hier.
Beziehungen:
BaseType TypeModel # nur gesetzt, wenn der Basistyp im analysierten Code deklariert ist; bei Framework-Basistypen null
DeclaredBaseTypeName string # voll qualifizierter Name des DIREKTEN Basistyps, z.B. "System.Exception" (auch für Framework-Typen gesetzt); null ohne expliziten Basistyp
BaseTypeNames collection<string> # voll qualifizierte Namen der GESAMTEN Vererbungskette (direkt + transitiv) bis System.Object; überschreitet die Framework-Grenze, also für transitive Basistyp-Prüfungen verwenden
Methods collection<MethodModel>
Constructors collection<MethodModel>
Properties collection<PropertyModel>
Fields collection<FieldModel>
Events collection<EventModel>
NestedTypes collection<TypeModel>
DerivedTypes collection<TypeModel>
UsedTypes collection<TypeModel>
UsedByTypes collection<TypeModel>
ImplementedInterfaces collection<TypeModel>
Attributes collection<AttributeModel>
MethodModel
Name string
FullName string
AccessModifier string
IsAsync bool
IsStatic bool
IsAbstract bool
IsSealed bool
IsOverride bool
IsInterfaceImplementation bool
IsConstructor bool
IsExtensionMethod bool
IsVirtual bool
ReturnType string
ReturnTypeShortName string
IsReturnTypeClass bool
IsReturnTypeInterface bool
IsReturnTypeRecord bool
ResolvedReturnType TypeInfo
Parameters collection<ParameterModel>
LinesOfCode int
CognitiveComplexity int
CyclomaticComplexity int
NestingDepth int
NumberOfLocalVariables int
NumberOfParameters int
NumberOfOverloads int
Overloads collection<MethodModel>
DeclaringType TypeModel
CalledMethods collection<MethodModel>
CalledByMethods collection<MethodModel>
Attributes collection<AttributeModel>
SourceFile string
LineNumber int
Syntax node # der Methodenrumpf, für Regeln auf Anweisungsebene, siehe unten
Syntax (Navigation auf Anweisungsebene)
Die Properties oben beschreiben eine Methode von außen. Mit m.Syntax fragen Sie
die Anweisungen im Rumpf ab, also Schleifen, try/catch, throw, Aufrufe und
so weiter. Sie starten bei m.Syntax und gehen hinunter zum Konstrukt, das Sie
brauchen.
m.Syntax ist ein node. Das Lesen eines Members liefert wieder einen node,
ein node set (eine Menge von Knoten) oder ein token (ein Blatt wie ein
Bezeichner).
Member eines node
Jeder node, auch m.Syntax selbst, bietet genau diese:
Kind string # der Konstruktname, z. B. "CatchClause" (siehe "Konstrukt-Kinds")
Text string # der wörtliche Quelltext dieses Konstrukts
Line int # 1-basierte Startzeile
Column int # 1-basierte Startspalte
Descendants node set # jedes Konstrukt unterhalb dieses Knotens, beliebig tief
Children node set # nur die direkten Kind-Konstrukte (eine Ebene tiefer)
ResolvedType TypeInfo # der semantisch aufgelöste Typ dieses Knotens, oder null
ResolvedSymbol string # der voll qualifizierte Name des gebundenen Symbols, oder null
<slot> node | node set | token # ein benannter Teil, siehe "Kind-Slots je Konstrukt"
Ein token (zum Beispiel der Identifier einer catch-Deklaration) bietet nur
Kind, Text, Line und Column. Es ist ein Blatt: es hat keine
Descendants, Children oder Kind-Slots.
Member eines node set
Ein node set ist eine Collection. Es bietet Count und dieselben geschlossenen
Collection-Helfer (.Any, .Where, .Count, .Select,
...) wie jede andere Collection, dazu:
Count int # Anzahl der Knoten in der Menge
Descendants node set # alle Nachfahren aller Knoten der Menge
Children node set # direkte Kinder aller Knoten der Menge
<Kind> node set # die Knoten dieser Art, z. B. .Descendants.CatchClause
.<Kind> filtert über den Konstruktnamen (siehe unten). Ein Name, der kein
bekanntes Konstrukt ist, wird als Fehler gemeldet, nicht als stilles leeres
Ergebnis.
Konstrukt-Kinds
Die exakten Namen, die Sie als Kind-Filter (.Descendants.<Kind>) oder im
Vergleich mit Kind verwenden. Es sind die Namen der C#-Sprachkonstrukte.
Statements Block ExpressionStatement LocalDeclarationStatement
IfStatement ElseClause SwitchStatement SwitchSection
ForStatement ForEachStatement WhileStatement DoStatement
TryStatement ThrowStatement ReturnStatement YieldStatement
UsingStatement LockStatement
BreakStatement ContinueStatement GotoStatement
Clauses CatchClause CatchDeclaration CatchFilterClause FinallyClause
Expressions InvocationExpression MemberAccessExpression ObjectCreationExpression
BinaryExpression AssignmentExpression ConditionalExpression
CastExpression AwaitExpression IdentifierName
LiteralExpression NumericLiteralExpression StringLiteralExpression
ArgumentList Argument
Declarations VariableDeclaration VariableDeclarator EqualsValueClause
Sie folgen C#s eigener Grammatik; jedes andere Konstrukt der Sprache ist über seinen Grammatiknamen erreichbar, egal wie tief verschachtelt. Die Grammatik ist umfangreich, die Kinds oben sind die, die Regeln normalerweise brauchen.
Kind-Slots je Konstrukt
Die benannten Teile eines Konstrukts. Jeder liefert einen node, ein node set oder
ein token. Ein Slot, der im Quelltext fehlt (ein if ohne else, ein bloßes
throw;, ein catch {} ohne Deklaration), liefert leer und ist sicher lesbar.
TryStatement Block (node) Catches (node set) Finally (node)
CatchClause Declaration (node) Filter (node) Block (node)
CatchDeclaration Type (node) Identifier (token)
CatchFilterClause FilterExpression (node)
FinallyClause Block (node)
IfStatement Condition (node) Statement (node) Else (node)
ElseClause Statement (node)
ForStatement Declaration (node) Condition (node) Incrementors (node set) Statement (node)
ForEachStatement Type (node) Identifier (token) Expression (node) Statement (node)
WhileStatement Condition (node) Statement (node)
DoStatement Statement (node) Condition (node)
SwitchStatement Expression (node) Sections (node set)
SwitchSection Labels (node set) Statements (node set)
ThrowStatement Expression (node)
ReturnStatement Expression (node)
Block Statements (node set)
ExpressionStatement Expression (node)
LocalDeclarationStatement Declaration (node)
UsingStatement Declaration (node) Expression (node) Statement (node)
LockStatement Expression (node) Statement (node)
InvocationExpression Expression (node) ArgumentList (node)
ArgumentList Arguments (node set)
Argument Expression (node)
MemberAccessExpression Expression (node) Name (node)
ObjectCreationExpression Type (node) ArgumentList (node) Initializer (node)
BinaryExpression Left (node) Right (node)
AssignmentExpression Left (node) Right (node)
ConditionalExpression Condition (node) WhenTrue (node) WhenFalse (node)
CastExpression Type (node) Expression (node)
AwaitExpression Expression (node)
IdentifierName Identifier (token)
LiteralExpression Token (token)
VariableDeclaration Type (node) Variables (node set)
VariableDeclarator Identifier (token) Initializer (node)
EqualsValueClause Value (node)
Beispiel
throw ex; anstreichen, das den ursprünglichen Fehlerort verwirft:
@name "Rethrow loses the original location"
@severity error
@category "ErrorHandling"
@recommendation "Use a bare throw; to keep the original stack trace"
Methods.Where(m =>
m.Syntax.Descendants.CatchClause.Any(c =>
c.Block.Descendants.ThrowStatement.Any(t =>
t.Expression.Text == c.Declaration.Identifier.Text)))
Das Finding zeigt auf die genaue throw-Anweisung, auch wenn sie tief in
if-Zweigen oder Schleifen steckt, nicht auf die ganze Methode.
Typen und Symbole auflösen
Standardmäßig trifft m.Syntax die geschriebene Form des Codes. Wenn Sie den
echten Typ hinter einem Ausdruck treffen möchten, unabhängig von Alias-Usings oder
Teilnamen, lesen Sie ResolvedType (ein TypeInfo) oder ResolvedSymbol (der voll
qualifizierte Name des Symbols). Beide sind null, wenn keine Auflösung möglich ist,
also entsprechend absichern.
# MD5/SHA1 über ihren echten Typ treffen, egal wie der Code sie schreibt:
Methods.Where(m =>
m.Syntax.Descendants.ObjectCreationExpression.Any(n =>
n.ResolvedType.FullName == "System.Security.Cryptography.MD5"))
Grenzen
m.Syntax verfolgt keinen Wert über Anweisungen hinweg (var e2 = ex; throw e2;
liest sich als andere Variable) und bleibt auf einen Methodenrumpf beschränkt. Für
Prüfungen, die über mehrere Methoden gehen, nutzen Sie die strukturellen Properties oben.
Syntax ist leer bei Membern ohne Rumpf (abstrakte oder Interface-Methoden); die
Navigation liefert dann keine Treffer.
ParameterModel
Name string
Type string # voll qualifizierter Typ-Name
TypeShortName string # z.B. "CancellationToken"
HasDefaultValue bool
IsParams bool
IsOut bool
IsRef bool
IsTypeInterface bool
IsTypeClass bool
IsTypeRecord bool
IsTypeEnum bool
IsTypeStruct bool
IsTypePrimitive bool
IsTypeAbstract bool
ResolvedType TypeInfo
TypeBaseTypeNames collection<string> # Basistyp-Namen des Parameter-Typs
PropertyModel
Name string
Type string # Typ-Name
AccessModifier string
IsStatic bool
IsAutoProperty bool
IsAbstract bool
IsVirtual bool
IsOverride bool
HasGetter bool
HasSetter bool
DeclaringType TypeModel
Attributes collection<AttributeModel>
ResolvedType TypeInfo
SourceFile string
LineNumber int
FieldModel
Name string
Type string # Typ-Name
AccessModifier string
IsStatic bool
IsReadonly bool # kleines "o" beachten: Property-Namen sind case-sensitive
IsConst bool
DeclaringType TypeModel
Attributes collection<AttributeModel>
ResolvedType TypeInfo
SourceFile string
LineNumber int
EventModel
Name string
DelegateType string # Delegate-Typ des Events
DeclaringType TypeModel
SourceFile string
LineNumber int
NamespaceModel
Name string # letztes Segment
FullName string
Assembly AssemblyModel
Types collection<TypeModel> # direkt enthaltene Typen
NumberOfTypes int
Abstractness double
Instability double
DistanceFromMainSequence double
AssemblyModel
Name string
Version string
TargetFramework string
Namespaces collection<NamespaceModel>
TypeDependency
Source TypeModel
Target TypeModel
Kind string # "Inherits" | "Implements" | "Uses"
AttributeModel
Name string # ohne "Attribute"-Suffix, z.B. "Obsolete"
FullName string # z.B. "System.ObsoleteAttribute"
Es sind nur Name und FullName verfügbar; Konstruktor-Argumente und
benannte Argumente eines Attributs (z.B. die Message von
[Obsolete("...")]) sind nicht zugreifbar.
TypeInfo
Kommt aus ResolvedType / ResolvedReturnType zurück. Anders als
TypeModel funktioniert es auch für Typen außerhalb Ihrer Solution:
FullName string
ShortName string
IsClass bool # Klassen ohne Records
IsRecord bool
IsInterface bool
IsEnum bool
IsStruct bool
IsPrimitive bool
IsAbstract bool
String-Helfer
Wenn Sie ein string-Property lesen, können Sie verketten:
.StartsWith("...")
.EndsWith("...")
.Contains("...")
.Matches("regex")
.ToLower()
.ToUpper()
.Length # Property, nicht Methode
Vergleiche sind case-sensitive sofern der Helper nichts anderes sagt.
Diese Liste ist abgeschlossen: nur die oben aufgeführten Methoden
existieren. Andere String-Methoden, die Sie aus C# kennen, etwa Trim
oder Replace, werden nicht unterstützt und schlagen zur
Auswertungszeit fehl.
Collection-Helfer
Alle Root-Collections und Sub-Collections (Methods, Parameters,
Properties, ...) unterstützen genau diese Methoden:
.Where(x => ...)
.Select(x => ...)
.SelectMany(x => ...)
.Any(x => ...)
.All(x => ...)
.Count # Property, nicht Methode
.Contains(value)
.First(x => ...)
.Sum(x => ...)
.Min(x => ...)
.Max(x => ...)
.Average(x => ...)
.OrderBy(x => ...)
.OrderByDescending(x => ...)
.Take(n)
.Distinct()
Auch diese Liste ist abgeschlossen: jede Methode, die hier nicht
steht, existiert in der DSL nicht und schlägt zur Auswertungszeit fehl.
Das gilt auch für gängige LINQ-Operatoren wie GroupBy, Skip oder
FirstOrDefault. Die Folge hängt von der Query-Form ab: die LINQ-Form
überspringt nur das fehlschlagende Element, die Fluent-Form bricht die
gesamte Regel ohne Findings ab. Details stehen in der
DSL-Grammatik.
Häufige Fallen
.Countstatt.Count(): Sub-Collections nutzenCountals Property.- Aufrufe ohne Argument wie
.Any(),.First(),.Count()funktionieren nur als Schritte der Top-Level-Fluent-Kette: in geschachtelten Ausdrücken werden sie als Property-Zugriff gelesen und schlagen fehl. Verwenden Sie.Countals Property und übergeben SieAny/Firsteine Lambda. Kind == "Class"ist case-sensitive: Großbuchstabe ist Pflicht.AccessModifier == "Public": wir folgen der C#-Pascal-Konvention.Parametersenthält keinenthis-Receiver bei Instanz-Methoden.
Was fehlt
Wenn Ihr Use-Case ein Property braucht, das hier nicht steht, eröffnen Sie bitte ein Issue. Die DSL wächst auf Nachfrage, nicht ins Blaue.
Wo geht's weiter
- DSL-Grammatik: wie die Sprache strukturell aufgebaut ist
- Regel-Beispiele: praktische Anwendungen der Prädikate
- Syntax-Übersicht: kompaktere Variante dieses Katalogs