Relationship to other pages: this tutorial walks you through writing your first rule step by step. Syntax overview is the compact everyday reference; DSL grammar is the formal appendix.
The simplest meaningful CodeCharter rule is nine lines long. Here it is:
@name "Class must not be named Manager"
@severity warn
@category "Naming"
@description "Classes called *Manager are vague, name them after what they actually do"
@recommendation "Pick a verb-based name: OrderProcessor, EmailDispatcher, PriceCalculator"
from t in Types
where t.Kind == "Class"
where t.Name.EndsWith("Manager")
select t
Save this as rules/no-manager-suffix.ccr in your repo. If you prefer not to
start from scratch, codecharter init creates the rules/ directory with two
example rules and declares it under rules: in .codecharter/config.yml. If
you created the directory yourself, declare it once with
codecharter config add rules rules. Then run the analysis from the repo root — analyze
expects the path to a .sln, .slnx, or .csproj file:
codecharter analyze MySolution.sln
The run reads the declared rules/ directory, and the Rules: line on
stderr confirms it. If you have a class with a Manager suffix, it will
appear as a Finding.
What's Happening Here
Five lines of metadata and four lines of query. Let's walk through it.
Metadata
All directives are optional and have sensible defaults. The rule slug comes from
the file name (no-manager-suffix), not from @name. @name is the
human-readable title shown in the CI log and output formats; when omitted it
defaults to the file name. @severity defaults to warn when omitted —
unrecognized values also fall back to warn, so watch out for typos.
@name "Class must not be named Manager"
@severity warn
@category "Naming"
@description "..."
@recommendation "..."
| Directive | Required | Default | Description |
|---|---|---|---|
@name |
no | filename | Human-readable title |
@severity |
no | warn |
info, warn, or error |
@category |
no | General |
Freely chosen; groups related rules |
@description |
no | — | What the rule checks |
@recommendation |
no | — | How to fix it |
All are optional, but filling in at least @name, @description, and
@recommendation makes findings much more actionable.
Query
The body is a LINQ query against the code model:
from t in Types // all types
where t.Kind == "Class" // classes only
where t.Name.EndsWith("Manager") // with Manager suffix
select t // returns all matches as findings
Comments in .ccr files use //. select t is the required closing clause.
Whatever you select becomes a Finding — t is a TypeModel, so the Finding
will point to the file and line where t is declared.
Variant: Banning Several Suffixes
@name "No vague class suffixes"
@severity warn
from t in Types
where t.Kind == "Class"
where t.Name.EndsWith("Manager")
|| t.Name.EndsWith("Helper")
|| t.Name.EndsWith("Util")
select t
Conditions combine with && and ||. The query language supports a fixed set
of LINQ-style methods such as Where, Any, OrderBy, and Count; the
Syntax overview lists what is available.
Variant: Combining Multiple Conditions
A rule that finds only classes that are too large AND have too many fields:
@name "Large class with many fields, split candidate"
@severity warn
@category "Design"
from t in Types
where t.Kind == "Class"
where t.LinesOfCode > 400
where t.Fields.Count > 15
select t
Testing Your Rule
codecharter test rules/no-manager-suffix.ccr --scaffold writes a starter spec
file rules/no-manager-suffix.spec.md next to the rule. Fill in code snippets
the rule should and should not flag, then run codecharter test rules/ to verify
the rule against them.
More Complete Examples
The Rule examples page has a gallery of ready-to-copy rules by category, including the full Manager-suffix and async/CancellationToken patterns. Refer there once you are comfortable with the basics above.
Where to Go Next
- File Structure — how the
rules/directory is organized - Syntax Overview — collections overview and query forms
- Predicate catalog — full property catalog for all model types
- Best Practices — what separates good rules from bad ones