Zum Inhalt springen

Compact syntax reference for the CodeCharter rule DSL

Compact reference for the CodeCharter DSL, covering file anatomy, query syntax, operators, and top-level collections.

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 start with // and run to the end of the line.

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 as a top-level root collection. 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:

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

+ is arithmetic addition for numbers, and ordinal string concatenation when either operand is a string — e.g. "Get" + p.Name. Both operands are taken as written: concatenating a string with a non-string value (e.g. "x" + 1) fails at evaluation time instead of silently producing "x1".

Strings:

.StartsWith("...")
.EndsWith("...")
.Contains("...")
.Matches("regex")           // regex match
.ToLower()
.ToUpper()
.Substring(start)           // to the end of the string
.Substring(start, length)
.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: exactly the methods shown above are available. Calling any other method you may know from C#, such as Trim, Replace, GroupBy, Skip, or FirstOrDefault, fails 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.

TypeModel exposes the same facade as MethodModel, accessible as t.Syntax and anchored at the type declaration itself rather than a method body — use it for type-level facts such as an attribute's argument list. It does not reach into member bodies; use t.AllBodies or a member's own Syntax for those. For a partial type it anchors at the same declaration t.SourceFile/t.LineNumber already anchor at, so it never reflects the other parts' syntax.

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.