Skip to content

Predicate catalog

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

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
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>
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           # 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"))

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.

ParameterModel

Name                       string
Type                       string         # fully qualified type name
TypeShortName              string         # e.g. "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>   # base type names of the parameter type

PropertyModel

Name                       string
Type                       string         # type 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         # type name
AccessModifier             string
IsStatic                   bool
IsReadonly                 bool           # note the lowercase "o": property names are case-sensitive
IsConst                    bool
DeclaringType              TypeModel
Attributes                 collection<AttributeModel>
ResolvedType               TypeInfo
SourceFile                 string
LineNumber                 int

EventModel

Name                       string
DelegateType               string         # delegate type of the event
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
Namespaces                 collection<NamespaceModel>

TypeDependency

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

AttributeModel

Name                       string         # without "Attribute" suffix, e.g. "Obsolete"
FullName                   string         # e.g. "System.ObsoleteAttribute"

Only Name and FullName are exposed; constructor arguments and named arguments of an attribute (e.g. the message of [Obsolete("...")]) are not accessible.

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

String helpers

When you read a string property you can chain:

.StartsWith("...")
.EndsWith("...")
.Contains("...")
.Matches("regex")
.ToLower()
.ToUpper()
.Length                    # property, not a method

Comparisons are case-sensitive unless the helper documents otherwise.

This list is closed: only the methods above exist. Other string methods you may know from C#, such as Trim or Replace, are not supported and fail at evaluation time.

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: any method not listed here, including common LINQ operators such as GroupBy, Skip, or FirstOrDefault, does not exist in the DSL and 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.
  • Zero-argument calls like .Any(), .First(), .Count() only work as steps of the top-level fluent chain: inside nested expressions they are read as member access and fail. Use .Count as a property and pass a lambda to Any / First.
  • 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.

What is missing

If your use case needs a property that is not listed here, please file an issue. The DSL grows on demand, not on speculation.

Where to go next