Skip to content

Prädikat-Katalog

Was Sie an jeder Code-Entität abfragen können. Vollständige Liste der Properties pro Model-Typ.

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

  • .Count statt .Count(): Sub-Collections nutzen Count als 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 .Count als Property und übergeben Sie Any / First eine Lambda.
  • Kind == "Class" ist case-sensitive: Großbuchstabe ist Pflicht.
  • AccessModifier == "Public": wir folgen der C#-Pascal-Konvention.
  • Parameters enthält keinen this-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