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.