Skip to content

Syntax Overview

The DSL as a compact reference.

Relationship to other pages: this page covers everyday DSL usage — file anatomy, query forms, and the collection overview. The formal EBNF grammar is in DSL grammar. The full property catalog for every model type is in Predicate catalog.

The CodeCharter DSL is modeled on LINQ. If you know C#, you already know almost everything you need.

File Anatomy

@name "..."                // optional, defaults to filename
@severity warn             // optional, defaults to warn (info | warn | error)
@category "..."            // optional, defaults to "General"
@description "..."         // optional
@recommendation "..."      // optional

// the query body (no blank line required)
<linq-expression>

All directives are optional. The rule slug shown in findings is the filename without the .ccr extension@name is the human-readable display title only. Comments use //; there are no block comments.

Query Forms

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)

Both produce the same result. With more than one condition, query syntax is usually more readable.

Query syntax also supports let clauses to name intermediate values. An orderby clause (optionally followed by asc or desc) is accepted, but it does not change the order of the results.

Top-Level Collections

Collection Element Notes
Types TypeModel
Methods MethodModel Constructors are not included; they are reachable as Constructors on a TypeModel
Properties PropertyModel
Fields FieldModel
Events EventModel
Namespaces NamespaceModel
Assemblies AssemblyModel
TypeDependencies TypeDependency
AllBodies IHasBodySyntax Every method, constructor, and property accessor across every type, including nested types; use for a rule that scans statement-level facts (Catches, Invocations, MemberAccesses, Literals, BinaryExpressions, LocalDeclarations) without a nesting boundary
Files FileModel One per source file: using directives and comment trivia

AllBodies exists only as a top-level root collection; there is no AllBodies sub-collection on a method. To check the statement-level facts of a specific type or method, filter the root collection by the enclosing member's own DeclaringType and Name properties instead.

Operators

LINQ standard:

==  !=  <  <=  >  >=
&&  ||  !
+ - * / %

Strings:

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

Collections:

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

Both lists are closed: only the methods shown above exist. Other methods you may expect from C#, such as Trim, Replace, GroupBy, Skip, or FirstOrDefault, are not supported and fail at evaluation time. The consequence depends on the query form: the query (LINQ) form skips just the failing item, the method (fluent) form aborts the whole rule without findings. See DSL grammar for details.

Special Sub-Models

BaseType.FullName

where t.BaseType.FullName.Contains("ControllerBase")

BaseType can be null (for example on interfaces or types without a declared base class). Comparing against null (t.BaseType != null) is supported, and accessing a member on a null value safely yields null instead of failing.

BaseType is only populated when the base type is declared in the analysed code, so it is null for framework bases. To match a framework base, or to match a base anywhere up the inheritance chain, use DeclaredBaseTypeName (the direct base only) or BaseTypeNames (the whole chain — see below).

BaseTypeNames.Contains(...)

# Exempt every type that derives from System.Exception, at any depth
where !t.BaseTypeNames.Contains("System.Exception")

BaseTypeNames holds the fully qualified names of the entire inheritance chain — the direct base plus every transitive base — up to (but excluding) System.Object. It crosses the framework boundary, so "System.Exception" is present even though that type is not part of the analysed code. Prefer it over DeclaredBaseTypeName when an exemption must also cover indirect subclasses (e.g. DomainException : ExceptionBase where ExceptionBase : Exception); use DeclaredBaseTypeName only when you deliberately want the direct base alone.

Parameters.Any(...)

where m.Parameters.Any(p => p.TypeShortName == "CancellationToken")

m.Syntax — statement-level rules

m.Syntax navigates the statements inside a method body — loops, try/catch, throw, calls — by the same names C# uses for those constructs. Descendants reaches a construct at any depth; a construct name filters the set; named parts (.Block, .Condition, .Expression, …) drill in.

Methods.Where(m =>
  m.Syntax.Descendants.CatchClause.Any(c =>
    c.Block.Descendants.ThrowStatement.Any(t =>
      t.Expression.Text == c.Declaration.Identifier.Text)))

The full surface (readable members, common construct names, and the limits) is in Predicate catalog.

Full Property Catalog

The complete list of properties available on TypeModel, MethodModel, ParameterModel, PropertyModel, FieldModel, EventModel, and the typed statement-level facts (CatchClauseModel, InvocationModel, MemberAccessModel, LiteralModel, BinaryExpressionModel, LocalDeclarationModel, including collection helpers) lives in the Predicate catalog.