Skip to content

Selectoren

Wie der erste Teil jeder Regel-Query festlegt was geprüft wird.

Der "Selector" ist der erste Teil einer Regel, die from-Klausel oder der initiale Collection-Zugriff. Er bestimmt auf welcher Sorte Code-Element die Regel arbeitet.

Top-Level-Collections

Diese Collections können Sie direkt als Startpunkt nutzen:

Collection Element-Typ Beschreibung
Types TypeModel Alle Klassen, Structs, Interfaces, Records, Enums
Methods MethodModel Alle Methoden; Konstruktoren sind nicht enthalten, diese erreichen Sie über t.Constructors am Typ
Properties PropertyModel Alle Properties
Fields FieldModel Alle Felder
Events EventModel Alle Events
Namespaces NamespaceModel Alle Namespaces
Assemblies AssemblyModel Alle analysierten Assemblies
TypeDependencies TypeDependency Coupling-Beziehungen zwischen Typen
Files FileModel Eine pro Quelldatei: using-Direktiven und Kommentar-Trivia

Zwei Schreibweisen

Die DSL unterstützt sowohl from x in Y (Query-Syntax) als auch Y.Where(x => ...) (Method-Syntax). Beide produzieren das gleiche Ergebnis, mit einem Unterschied im Fehlerverhalten: Schlägt die Auswertung eines Elements fehl (etwa durch eine falsch geschriebene Property), wird in der Query-Syntax nur dieses Element übersprungen, in der Method-Syntax bricht die gesamte Regel ab und meldet keine Verstöße.

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)

Wann welche? Bei mehreren where-Klauseln ist Query-Syntax lesbarer. Bei einer Bedingung oder einer Aggregation (.Count > 10, wobei Count eine Property ist) ist Method-Syntax kürzer.

Per-Element-Properties

Die Element-Typen haben jeweils Properties, die Sie in where-Klauseln und Sub-Queries nutzen können.

TypeModel (Auszug)

t.Name                 // einfacher Name
t.FullName             // mit Namespace
t.Namespace            // Namespace-Objekt; für den Namen t.Namespace.FullName nutzen
t.Kind                 // "Class", "Interface", "Struct", "Enum", "Record"
t.IsAbstract           // bool
t.IsSealed             // bool
t.IsStatic             // bool
t.IsRecord             // bool
t.LinesOfCode          // int
t.NumberOfMethods      // int
t.Methods              // Sammlung von MethodModel
t.Constructors         // Sammlung von MethodModel
t.NestedTypes          // Sammlung von TypeModel
t.DerivedTypes         // Sammlung von TypeModel
t.UsedTypes            // welche Typen referenziert dieser Typ
t.UsedByTypes          // welche Typen referenzieren diesen
t.BaseType.FullName    // Vater-Klasse

MethodModel (Auszug)

m.Name                 // Methodenname
m.AccessModifier       // "Public", "Private", "Protected", "Internal"
m.IsAsync              // bool
m.IsStatic             // bool
m.IsOverride           // bool
m.IsInterfaceImplementation // bool
m.Parameters           // Sammlung von ParameterModel
m.CalledMethods        // welche Methoden ruft diese auf
m.CalledByMethods      // welche Methoden rufen diese
m.LinesOfCode          // int
m.DeclaringType        // TypeModel

Die vollständige Referenz für Properties und Kind-Werte finden Sie im Predicate-Katalog; wenn Sie einen KI-Assistenten nutzen, liefert das get_authoring_docs-MCP-Tool (Topic predicates) dieselbe Referenz.

Composition mit Any / All / Count

Sub-Queries auf Collection-Properties. Für Any / All geben Sie meist einen Lambda-Ausdruck an (Any(m => ...)), ein Aufruf ohne Argumente wie t.Methods.Any() ist aber ebenfalls gültig und prüft nur, ob die Collection Elemente enthält. Count steht sowohl als Property (t.Methods.Count) als auch als Aufruf (t.Methods.Count()) zur Verfügung.

// Klassen mit mindestens einer async Methode ohne CancellationToken
from t in Types
where t.Methods.Any(m =>
    m.IsAsync &&
    !m.Parameters.Any(p => p.TypeShortName == "CancellationToken"))
select t
// Klassen mit mehr als 10 public Methoden
from t in Types
where t.Methods.Where(m => m.AccessModifier == "Public").Count > 10
select t

Statement-Level-Facts als Selector

Manche Pattern liegen innerhalb eines Methoden- oder Konstruktor-Bodys: eine catch-Klausel, ein Member-Zugriff, ein Literal, ein binärer Ausdruck. Diese stehen als typisierte Facts an jedem Body zur Verfügung (m.Catches, m.MemberAccesses, m.Literals, m.BinaryExpressions, m.Invocations, m.LocalDeclarations), erreichbar über die abgeflachte AllBodies-Collection, sodass eine Regel jede Methode, jeden Konstruktor und jeden Property-Accessor über alle Typen hinweg ohne Nesting-Grenze sieht:

@name "Direct DateTime usage"
@severity warn
@category "Testability"

AllBodies.SelectMany(m => m.MemberAccesses.Where(a => (a.Name == "Now" || a.Name == "UtcNow")
    && (a.ResolvedType.FullName == "System.DateTime" || a.ResolvedType.FullName == "System.DateTimeOffset")))

Der vollständige Katalog pro Fact-Typ steht im Predicate-Katalog.

Was als nächstes