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.