Zum Inhalt springen

Predicate catalog for querying code entities in rules

Everything you can ask of each code entity. Complete list of properties per code-model type.

CodeCharter analyses your solution once and hands the DSL a cached code model. This page lists what you can query, sorted by entity type.

When you write a rule you navigate the model starting from a root collection. Property reads, method calls, LINQ chains, and at the end you collect the matches.

Root collections

Every query starts at one of these.

Root Element Contents
Types TypeModel All classes, structs, interfaces, enums, records
Methods MethodModel All methods across all types (constructors are not included; they live in TypeModel.Constructors). Operator overloads and finalizers are ordinary entries here too — m.Name is "op_Addition", "op_Equality", etc. for an overloaded operator and "Finalize" for a ~Type() destructor, not the source-level spelling
Properties PropertyModel All properties
Fields FieldModel All fields
Events EventModel All events
Namespaces NamespaceModel All namespaces
Assemblies AssemblyModel One per project
TypeDependencies TypeDependency Directed type-to-type dependencies
Files FileModel One per source file: using directives and comment trivia
Diagnostics DiagnosticModel Whole-program analyzer findings — see Security diagnostics
AllTypesFlattened TypeModel Every type including nested types at any depth (Types stops at top-level types)
AllMethodsFlattened MethodModel Every method across AllTypesFlattened, including nested types
AllBodies see below Every method and property (including accessors) with a body, across AllTypesFlattened, ready for .Catches/.Invocations/etc. (see Body-level convenience collections) without navigating through Types and Methods yourself
AllLiteralSources see below Every method, property, and field that can carry literal expressions, across AllTypesFlattened, ready for .Literals
AllConstructorInjectableCreations ObjectCreationModel Every new expression in a constructor body or field initializer, across AllTypesFlattened, after the exclusions the new-in-constructor rule applies (value types, records, BCL/framework types, Exception/Args/Options-style suffixes)
DocumentableDeclarations see below Every type, method, property, field, and event across AllTypesFlattened, as one sequence — for a rule that checks XML-doc coverage uniformly instead of separately per entity kind

TypeModel

Identity:

Name                       string         # without namespace
FullName                   string         # Namespace.Type
Namespace                  NamespaceModel # object, not a string: use t.Namespace.FullName
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
IsValueType                bool           # true for struct, record struct, enum; false for class, record class/record, interface, delegate — the only way to tell a record struct apart from a record class, since both report Kind == "Record"
AccessModifier             string         # "Public" | "Internal" | "Private" | "Protected" | "ProtectedInternal" | "PrivateProtected"

Structural metrics:

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 and DistanceFromMainSequence are namespace-level metrics; you find them on NamespaceModel, not here.

Relationships:

BaseType                   TypeModel      # only set when the base type is declared in the analysed code; null for framework base types
DeclaredBaseTypeName       string         # fully qualified name of the DIRECT base type, e.g. "System.Exception" (set even for framework types); null when there is no explicit base
BaseTypeNames              collection<string>   # fully qualified names of the WHOLE inheritance chain (direct + transitive) up to System.Object; crosses the framework boundary, so use it for transitive base checks
Methods                    collection<MethodModel>   # for a Kind == "Delegate" type, holds exactly one synthesized "Invoke" entry carrying the delegate's real ReturnType/Parameters — the only way to reach a delegate's signature
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>
ImplementedInterfaceNames  collection<string>   # fully qualified names of every implemented interface (direct or inherited), erased to the interface's generic definition, e.g. "System.IEquatable<T>"
ImplementedInterfaceConstructedNames  collection<string>   # same set and order as ImplementedInterfaceNames, but keeping each interface's constructed type arguments, e.g. "System.IEquatable<Foo>" — use to tell IEquatable<Bar> apart from IEquatable<Foo>, which ImplementedInterfaceNames cannot
Attributes                 collection<AttributeModel>
TypeParameters             collection<TypeParameterModel>   # generic type parameters, e.g. the T in class Box<T>; empty when IsGeneric is false
Reaches(pattern, mode?)     bool           # see "Reachability" below
ReachedBy(pattern, mode?)   bool           # see "Reachability" below
InboundReferenceCount()     int            # see "Reachability" below
Syntax                      node           # the TYPE DECLARATION's own syntax — not its members' bodies, see AllBodies/a member's own Syntax for those. For a partial type, anchors at the same declaration SourceFile/LineNumber already anchor at (the last-processed part), so it never reflects the other parts. Empty for enum and delegate declarations. See "Syntax (statement-level navigation)" below.

Reachability

t.Reaches(pattern, mode?) and t.ReachedBy(pattern, mode?) answer a question the member-level UsedTypes/UsedByTypes collections above cannot: whether a directed path of edges connects t to some type matching pattern, however many hops away. Reaches walks outward from t; ReachedBy is the transpose, true when some type matching pattern can reach t. A type does not reach a pattern it matches itself unless it sits on a genuine cycle.

pattern is a glob, not a regex: * matches any run of characters, ? matches exactly one, matched against the candidate type's full name. Do not paste a regex here — a pattern containing ^, \, or $ is flagged at lint time, since those are regex syntax that means nothing in a glob. Use Matches("regex") (see String helpers) when you need an actual regular expression instead.

The optional mode argument selects the edge set the path is computed over. The default and most common mode is "declared": exactly the six edge kinds a compile-time scan of the source can see — inheritance, interface implementation, member/parameter/return-type usage, a resolved call, an applied attribute, and an object-creation expression. This is a compile-time-only question, so a declared hit is always a genuine dependency chain, which makes it the right mode for a prohibition rule such as "domain must not reach infrastructure":

from t in Types
where t.FullName.Matches("App.Domain\\..*")
where t.Reaches("App.Infra.*", "declared")
select t

If App.Domain.OrderService only holds an IRepository interface and App.Infra.SqlRepository implements it elsewhere, declared correctly stays silent — that is the DIP-correct shape, and declared never follows dependency-injection wiring from an interface to its resolved implementation. This is also its blind spot: reflection, serializers, source generators, and string-keyed DI resolution are all invisible to it, so declared can under-report a requirement rule ("X must reach Y") the same way. The mode set is closed: the only valid names are "declared" and "dispatch", where "dispatch" additionally follows interface and virtual dispatch at every resolved call site; any other string is an authoring error flagged at lint time.

t.InboundReferenceCount() is the depth-1 sibling of these predicates: the number of distinct solution types with at least one direct declared edge into t, over the same six edge kinds. It takes no arguments and counts direct referrers only, not transitive reachability — t.AccessModifier != "Public" && t.InboundReferenceCount() == 0 flags an internal type nothing in the solution references.

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
IsNew                      bool           # declared with the `new` modifier, hiding a same-signature base member; read from the declaration syntax, since Roslyn has no direct "is new" symbol flag
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>
TypeParameters             collection<TypeParameterModel>   # the method's own generic type parameters, e.g. the T in void Map<T>(); does not include the declaring type's — empty for a non-generic method
SourceFile                 string
LineNumber                 int
Syntax                     node           # the method body, for statement-level rules — see below

Syntax (statement-level navigation)

The properties above describe a method from the outside. m.Syntax lets you query the statements inside the body — loops, try/catch, throw, calls, and so on. You start at m.Syntax and walk down to the construct you want.

m.Syntax is a node. Reading a member gives you another node, a node set (a collection of nodes), or a token (a leaf such as an identifier).

Members of a node

Every node — m.Syntax included — exposes exactly these:

Kind          string     # the construct name, e.g. "CatchClause" (see "Construct kinds")
Text          string     # the verbatim source text of this construct
Line          int        # 1-based start line
Column        int        # 1-based start column
Descendants   node set   # every construct below this node, at any depth
Children      node set   # the direct child constructs only (one level down)
ResolvedType  TypeInfo   # the semantically resolved type of this node, or null
ResolvedSymbol string    # the fully qualified name of the symbol it binds to, or null
<slot>        node | node set | token   # a named part, see "Child slots per construct"

A token (for example the Identifier of a catch declaration) exposes Kind, Text, Line, and Column only. It is a leaf: it has no Descendants, Children, or child slots.

Members of a node set

A node set is a collection. It exposes Count and the same closed collection helpers (.Any, .Where, .Count, .Select, …) as every other collection, plus:

Count        int        # number of nodes in the set
Descendants  node set   # all descendants of every node in the set
Children     node set   # direct children of every node in the set
<Kind>       node set   # the nodes of that kind, e.g. .Descendants.CatchClause

.<Kind> filters by construct name (see below). A name that is not a known construct is reported as an error, not a silent empty result.

Construct kinds

The exact names you use as a kind filter (.Descendants.<Kind>) or compare with Kind. They are the names of the C# language constructs.

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

These follow C#'s own grammar; any other construct in the language is reachable by its grammar name, no matter how deeply nested. The grammar is large — the kinds above are the ones rules normally need.

Child slots per construct

A construct's named parts. Each yields a node, a node set, or a token. A slot that is absent in the source (an if with no else, a bare throw;, a catch {} with no declaration) yields empty and is safe to read.

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)

Example

Flag throw ex;, which discards the original error location:

@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)))

The finding points at the exact throw statement, even when it sits deep inside if branches or loops, not at the whole method.

Resolving types and symbols

By default m.Syntax matches the written form of the code. When you need to match the real type behind an expression — regardless of aliased usings or partial names — read ResolvedType (a TypeInfo) or ResolvedSymbol (the symbol's fully qualified name). Both are null when resolution is unavailable, so guard accordingly.

# Flag MD5/SHA1 by their real type, however the code spells them:
Methods.Where(m =>
  m.Syntax.Descendants.ObjectCreationExpression.Any(n =>
    n.ResolvedType.FullName == "System.Security.Cryptography.MD5"))

ResolvedSymbol on a fluent extension-method call renders the reduced form, qualified by the receiver's static type — never by the declaring static class. xs.Count() resolves to "System.Collections.Generic.IEnumerable<int>.Count<int>()", not "System.Linq.Enumerable.Count<int>(...)"; logger.LogError(...) resolves to "Microsoft.Extensions.Logging.ILogger.LogError(...)", not a LoggerExtensions. prefix. Pin the exact rendered string for the overload you target (parameter lists are part of the display name) rather than guessing a prefix.

Limits

m.Syntax does not follow a value across statements — var e2 = ex; throw e2; reads as a different variable — and it is scoped to a single method body. For checks that span several methods, use the structural properties above. Syntax is empty for members without a body (abstract or interface methods); navigating it then yields no matches.

Body-level convenience collections

Walking raw syntax with .Descendants.<Kind> covers everything, but for the most common patterns MethodModel exposes them pre-extracted and already typed, saving you the tree walk. The same collections are available on a property's accessor bodies (PropertyModel.Catches, PropertyModel.Invocations, and so on — empty for an auto-implemented property), and Literals/ObjectCreations are also available on FieldModel for its initializer expression.

Catches               collection<CatchClauseModel>
Invocations           collection<InvocationModel>
ObjectCreations       collection<ObjectCreationModel>
MemberAccesses        collection<MemberAccessModel>
Literals              collection<LiteralModel>
BinaryExpressions     collection<BinaryExpressionModel>
LocalDeclarations     collection<LocalDeclarationModel>
UnusedParameters      collection<ParameterModel>   # this method's parameters never referenced in its body, after the same exclusions the built-in unused-parameter rule applies (underscore-prefixed names, CancellationToken, the (sender, e) event-handler shape); empty for abstract/virtual/override/partial/explicit-interface/empty-bodied methods
Shape                 MethodShapeModel             # coarse control-flow shape, see below

CatchClauseModel — a single catch clause:

IsEmpty                    bool     # no statements in the block: it silently swallows the exception
ExceptionType               TypeInfo # resolved caught type, null for a bare `catch { }` or unresolved
RethrowsOriginal            bool     # a bare `throw;` anywhere in the block (not `throw ex;`)
SourceFile, Line, Column

InvocationModel — a single method call expression:

Target                      string   # verbatim invoked expression, e.g. "_repo.Save"
ResolvedType                 TypeInfo # resolved return type of the invoked method
Arguments                   collection<string>   # verbatim argument text, in call order
IsUnobservedStatement        bool     # this call IS the whole statement: not awaited, assigned, returned, or passed as an argument
SourceFile, Line, Column

Fire-and-forget check: i.IsUnobservedStatement && i.ResolvedType.FullName.StartsWith("System.Threading.Tasks.Task").

ObjectCreationModel — a new Foo(...) or target-typed new(...):

ResolvedType                 TypeInfo   # resolved created type, null when unresolved
SourceFile, Line, Column

MemberAccessModel — a member access such as DateTime.Now:

Name                        string     # accessed member's simple name, e.g. "Now"
ResolvedType                 TypeInfo
SourceFile, Line, Column

LiteralModel — a literal expression:

Value                        string   # verbatim source text of the literal token
IsNumeric                    bool
IsNamedConstant               bool     # inside a const/static readonly field, const local, or enum member declaration
IsTrivialMagicExemptValue     bool     # numeric value is 0, 1, or 2, across suffixed/hex/binary/real forms
IsInMagicNumberExcludedContext bool    # any of: named-constant context, attribute argument, array index
SourceFile, Line, Column

BinaryExpressionModela == b, x + y, and similar:

Operator                     string   # e.g. "==", "!=", "+", "&&"
Left, Right                  BinaryOperandModel
IsInNestedScope               bool     # nested inside a lambda body or LINQ query expression
IsConcatenationRoot           bool     # this `+` is the topmost node of its string-concatenation chain
ConcatenationOperatorCount   int      # number of `+` operators in the chain rooted here
SourceFile, Line, Column

BinaryOperandModel (each side of Left/Right):

ResolvedType                 TypeInfo   # null for the `null` literal too
IsNull                       bool
IsStringLiteral               bool     # a string literal, not merely a value resolving to System.String

LocalDeclarationModel — a local variable declaration:

IsVar                        bool
DeclaredTypeName              string   # verbatim declared type text, e.g. "Foo" or "var"
DeclaredType                  TypeInfo # resolved declared type; for `var`, the inferred type
InitializerKind               string   # "ObjectCreation" | "Cast" | "None" | "Other"
InitializerType                TypeInfo
SourceFile, Line, Column

MethodShapeModel (m.Shape) — the coarse control-flow shape of a body:

BodyStatementCount            int      # top-level statements directly in the body block
BodyLinesOfCode                int      # non-whitespace lines in the body block
FirstStatementIsGuardIf        bool     # first executable statement (after leading local declarations) is an if with no else
FirstGuardIfBodyLineRatio      double   # fraction (0.0-1.0) of body lines inside that guard if; 0 when the flag above is false

Missing-guard-clause example: m.Shape.BodyStatementCount >= 3 && m.Shape.FirstStatementIsGuardIf && m.Shape.BodyLinesOfCode > 5 && m.Shape.FirstGuardIfBodyLineRatio > 0.7.

ParameterModel

Name                       string
Type                       string         # fully qualified type name
TypeShortName              string         # e.g. "CancellationToken"
HasDefaultValue            bool
IsParams                   bool
IsOut                      bool
IsRef                      bool           # ref, including ref readonly
IsIn                       bool           # in (read-only by-reference)
IsTypeInterface            bool
IsTypeClass                bool
IsTypeRecord               bool
IsTypeEnum                 bool
IsTypeStruct               bool
IsTypePrimitive            bool
IsTypeAbstract             bool
ResolvedType               TypeInfo
TypeBaseTypeNames          collection<string>   # base type names of the parameter type
Attributes                 collection<AttributeModel>

PropertyModel

Name                       string
Type                       string         # type name
AccessModifier             string
IsStatic                   bool
IsAutoProperty             bool
IsAbstract                 bool
IsVirtual                  bool
IsOverride                 bool
HasGetter                  bool
HasSetter                  bool           # set or init
HasInitOnlySetter          bool           # true when the setter is init-only
IsInterfaceImplementation  bool
IsNew                      bool           # declared with the `new` modifier, hiding a same-name base member; read from the declaration syntax, since Roslyn has no direct "is new" symbol flag
DeclaringType              TypeModel
Attributes                 collection<AttributeModel>
ResolvedType               TypeInfo
SourceFile                 string
LineNumber                 int

FieldModel

Name                       string
Type                       string         # type name
AccessModifier             string
IsStatic                   bool
IsReadonly                 bool           # note the lowercase "o": property names are case-sensitive
IsConst                    bool
IsVolatile                 bool           # declared `volatile`; double-checked-locking rules use this to avoid flagging an already-safely-published field
DeclaringType              TypeModel
Attributes                 collection<AttributeModel>
ResolvedType               TypeInfo
SourceFile                 string
LineNumber                 int

EventModel

Name                       string
DelegateType               string         # delegate type of the event
IsStatic                   bool
DeclaringType              TypeModel
SourceFile                 string
LineNumber                 int

NamespaceModel

Name                       string         # last segment
FullName                   string
Assembly                   AssemblyModel
Types                      collection<TypeModel>   # directly contained types
NumberOfTypes              int
Abstractness               double
Instability                double
DistanceFromMainSequence   double

AssemblyModel

Name                       string
Version                    string
TargetFramework            string
IsTestProject              bool         # resolved via build property, test-framework reference, or name suffix
Namespaces                 collection<NamespaceModel>

TypeDependency

Source                     TypeModel
Target                     TypeModel
Kind                       string         # "Inherits" | "Implements" | "Uses"

AttributeModel

Name                       string         # keeps the compiler-visible "Attribute" suffix, e.g. "ObsoleteAttribute"
FullName                   string         # e.g. "System.ObsoleteAttribute"
ArgumentValues             collection<string>   # positional/constructor argument values, in order, as TypedConstant.ToCSharpString() display strings
NamedArguments             collection<AttributeArgumentModel>   # named arguments, in source declaration order

Every argument value is rendered with Roslyn's TypedConstant.ToCSharpString(): a string constant renders quoted ("reason"), a number or bool as its literal (1, true), an enum member qualified with its declaring type (N.Color.Green), a typeof(...) argument as typeof(string), and an array argument as a brace-delimited element list ({1, 2, 3}). Each rendered value is capped at 256 characters (with a trailing "…" marker), and an over-long array argument (more than 32 elements, or a combined element length past the cap) renders only a bounded prefix of its elements — the list then ends in , …} — so a hostile multi-megabyte attribute literal cannot balloon the analyzed model. ArgumentValues and NamedArguments are always empty collections, never null, when the attribute application supplies no arguments of that kind.

The DSL has no Zip/ElementAt to correlate two parallel collections by index, so NamedArguments pairs each argument's name with its value in one AttributeArgumentModel entry rather than exposing separate name/value collections.

AttributeArgumentModel

A single named argument of an attribute application (e.g. the Skip = "reason" in [Fact(Skip = "reason")]).

Name                       string         # the named argument's name, as written in source
Value                      string         # the named argument's rendered value — same format and 256-character cap as AttributeModel.ArgumentValues

Detect a placeholder skip reason: m.Attributes.Any(a => a.Name == "FactAttribute" && a.NamedArguments.Any(na => na.Name == "Skip" && na.Value == "\"TODO\"")).

TypeParameterModel

Available via TypeParameters on TypeModel and MethodModel — a generic type parameter such as the T in class Box<T> or void Map<T>().

Name                       string         # e.g. "T"
Ordinal                    int            # zero-based position in the declaring type's/method's type parameter list
Variance                   string         # "None" (invariant) | "In" (contravariant, e.g. IComparer<in T>) | "Out" (covariant, e.g. IEnumerable<out T>)
HasReferenceTypeConstraint bool           # class constraint
HasValueTypeConstraint     bool           # struct constraint
HasNotNullConstraint       bool           # notnull constraint
HasConstructorConstraint   bool           # new() constraint
ConstraintTypeNames        collection<string>   # base class and/or interface constraint names, in declaration order

TypeInfo

Returned by ResolvedType / ResolvedReturnType. Unlike TypeModel it also works for types outside your solution:

FullName                   string
ShortName                  string
IsClass                    bool           # classes excluding records
IsRecord                   bool
IsInterface                bool
IsEnum                     bool
IsStruct                   bool
IsPrimitive                bool
IsAbstract                 bool
IsTypeParameter            bool           # an unresolved generic type parameter such as `T` in `List<T>`
IsSealed                   bool           # also true for structs, enums, and delegates (not static classes)

String helpers

When you read a string property you can chain:

.StartsWith("...")
.EndsWith("...")
.Contains("...")
.Matches("regex")
.ToLower()
.ToUpper()
.Substring(start)          # to the end of the string
.Substring(start, length)
.Length                    # property, not a method

Comparisons are case-sensitive unless the helper documents otherwise.

This list is closed: exactly the methods above are available. Calling any other string method you may know from C#, such as Trim or Replace, fails at evaluation time.

+ also works between strings (see Syntax overview for the full operator list), so you can build a comparison value from parts, e.g. "Get" + p.Name == m.Name to flag a GetFoo() method sitting beside a Foo property (CA1721):

from t in Types
from m in t.Methods
where t.Properties.Any(p => m.Name == "Get" + p.Name && m.Parameters.Count == 0)
select m

Collection helpers

All root collections and sub-collections (Methods, Parameters, Properties, ...) support exactly these methods:

.Where(x => ...)
.Select(x => ...)
.SelectMany(x => ...)
.Any(x => ...)
.All(x => ...)
.Count                     # property, not a method
.Contains(value)
.First(x => ...)
.Sum(x => ...)
.Min(x => ...)
.Max(x => ...)
.Average(x => ...)
.OrderBy(x => ...)
.OrderByDescending(x => ...)
.Take(n)
.Distinct()

This list is closed as well: calling any method outside it, including common LINQ operators such as GroupBy, Skip, or FirstOrDefault, fails at evaluation time. What happens then depends on the query form: the LINQ form skips just the failing item, the fluent form aborts the whole rule without findings. See DSL grammar for details.

Common pitfalls

  • .Count instead of .Count(): sub-collections use Count as a property.
  • Parentheses, not argument count, decide whether a call is a method call: .Any(), .Count(), and .ToLower() work as method calls both as steps of the fluent chain and inside nested expressions (m.Name.ToLower()); leaving the parentheses off (m.Name.ToLower) is a property read instead and fails, since ToLower is not a property.
  • Kind == "Class" is case-sensitive: the capital letter is required.
  • AccessModifier == "Public": we follow the C# Pascal-case convention.
  • Parameters does not contain the this receiver for instance methods.
  • .First() and .First(x => ...) throw when nothing matches, exactly like LINQ's First. Guard with .Any(...), or use .Where(...).Count > 0 as the guard condition, rather than assuming an empty result is returned silently.

Requesting a property

If your use case needs a property that this catalog does not list yet, please tell us. The DSL grows along real demand.

Where to go next