Every example is a complete .ccr file. Copy what you need, adjust
the thresholds or strings, and commit it into rules/ in your repo.
Note on built-in rules: Several examples below cover patterns that CodeCharter already ships as built-in rules (120+ rules out of the box). If a built-in rule already covers the pattern at the threshold you want, you do not need a custom rule — enabling the built-in is enough. Adding a custom rule for the same pattern will produce duplicate findings. The note "(built-in rule available)" on individual examples indicates this.
Async
Async method missing the Async suffix
Built-in rule available: Async method missing Async suffix. Use this example only if you need a custom severity or scope.
@name "Async method missing Async suffix"
@severity warn
@category "Async"
@description "Async methods should end with 'Async' so callers spot await sites"
@recommendation "Rename Foo to FooAsync when the method returns Task or ValueTask"
from m in Methods
where m.IsAsync
where !m.Name.EndsWith("Async")
select m
Public async without a CancellationToken
Built-in rule available: Async method without CancellationToken. Use this example only if you need a different scope or severity.
@name "Public async method missing CancellationToken"
@severity warn
@category "Async"
@description "Public async APIs should accept a CancellationToken so callers can cancel"
@recommendation "Add CancellationToken parameter with default value"
from m in Methods
where m.IsAsync
where m.AccessModifier == "Public"
where !m.Parameters.Any(p => p.TypeShortName == "CancellationToken")
select m
Fire-and-forget async
Built-in rule available: Fire-and-forget async call. Use this example only if you need a different severity or message.
@name "Async call not awaited"
@severity error
@category "Async"
@description "Calling an async method without awaiting silently drops exceptions"
@recommendation "Await the result, or assign to '_ = ...' if discard is intentional"
AllBodies
.SelectMany(m => m.Invocations.Where(i => i.IsUnobservedStatement
&& (i.ResolvedType.FullName == "System.Threading.Tasks.Task"
|| i.ResolvedType.FullName.StartsWith("System.Threading.Tasks.Task<")
|| i.ResolvedType.FullName == "System.Threading.Tasks.ValueTask"
|| i.ResolvedType.FullName.StartsWith("System.Threading.Tasks.ValueTask<"))))
Error handling
Empty catch block
Built-in rule available: Empty catch block. Use this example only if you need a custom severity or scope.
@name "Empty catch block"
@severity error
@category "ErrorHandling"
@description "Empty catch silently swallows exceptions"
@recommendation "Log and rethrow, or catch the specific exception you can recover from"
AllBodies.SelectMany(m => m.Catches.Where(c => c.IsEmpty))
Generic exception catch without rethrow
Built-in rule available: Catching System.Exception directly. Use this example only if you need a different severity.
@name "Generic exception caught without rethrow"
@severity warn
@category "ErrorHandling"
@description "Catching Exception swallows everything, including bugs"
@recommendation "Catch the narrowest exception type the caller can recover from"
AllBodies
.SelectMany(m => m.Catches.Where(c => c.ExceptionType.FullName == "System.Exception"))
Rethrow that loses the stack trace
Built-in rule available: Rethrow loses the original stack trace. This example also shows how to write a statement-level rule using
m.Syntax. Use it only if you need a different severity or wording.
@name "Rethrow loses the original location"
@severity error
@category "ErrorHandling"
@description "throw ex; resets the original stack trace and hides where the error came from"
@recommendation "Use a bare throw; to keep the original stack trace"
Methods.Where(m =>
m.Syntax.Descendants.CatchClause.Any(c =>
c.Block.Descendants.ThrowStatement.Any(t =>
t.Expression.Text == c.Declaration.Identifier.Text)))
The finding points at the exact throw statement, even when it sits deep inside
if branches or loops. See the
Predicate catalog for the full m.Syntax
surface.
Design
Constructor with too many parameters
Built-in rule available: Constructor has too many parameters (threshold: more than 4 parameters, not counting cross-cutting dependencies such as
ILoggerorCancellationToken). Use this example only if you need a different threshold or no exclusions.
Constructors are not part of the Methods collection; reach them through
Types and the Constructors property.
@name "Constructor with too many parameters"
@severity warn
@category "Design"
@description "Constructors with 5+ parameters usually indicate too many responsibilities"
@recommendation "Split the class or use a parameter object"
from t in Types
where t.Constructors.Any(c => c.NumberOfParameters > 4)
select t
Class too large
Built-in rule available: Class too large (threshold: 500 lines). Use this example if you want a different threshold.
@name "Class too large"
@severity warn
@category "Design"
@description "Classes over 500 lines tend to mix responsibilities"
@recommendation "Extract collaborators or split by responsibility"
from t in Types
where t.Kind == "Class"
where t.LinesOfCode > 500
select t
Static class with non-extension public methods
Built-in rule available: Static class has public non-extension methods. The built-in only applies to public static classes; this example also flags internal ones. Use this example only if you need a different severity or scope.
@name "Static class has public non-extension methods"
@severity warn
@category "Design"
@description "Static utility classes should host extension methods only"
@recommendation "Convert to instance methods on a real type, or mark the parameter with 'this'"
from t in Types
where t.IsStatic
where t.Kind == "Class"
where t.Methods.Any(m => m.AccessModifier == "Public" && !m.IsExtensionMethod)
select t
Naming
Boolean property missing verb prefix
Built-in rule available: Boolean property missing verb prefix. Use this example only if you need a custom set of allowed prefixes.
@name "Boolean property missing verb prefix"
@severity warn
@category "Naming"
@description "Booleans should be readable as questions: Is, Has, Can, Should, Enable, Disable"
@recommendation "Prefix the property to convey the state it represents"
from p in Properties
where p.Type == "bool"
where !p.Name.StartsWith("Is") && !p.Name.StartsWith("Has")
where !p.Name.StartsWith("Can") && !p.Name.StartsWith("Should")
where !p.Name.StartsWith("Enable") && !p.Name.StartsWith("Disable")
select p
Interface missing the I prefix
Built-in rule available: Interface not prefixed with I. Use this example only if you need a different severity.
@name "Interface missing I prefix"
@severity warn
@category "Naming"
@description "Interface names should start with I, following C# convention"
@recommendation "Rename Foo to IFoo and update implementations"
from t in Types
where t.Kind == "Interface"
where !t.Name.StartsWith("I")
select t
Class with Manager suffix
Built-in rule available: Avoid 'Manager' class suffix. Use this example only if you need a different severity or want to combine with other predicates.
@name "Class named with vague Manager suffix"
@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
Determinism
Direct DateTime usage
Built-in rule available: Direct DateTime/DateTimeOffset usage instead of TimeProvider. Use this example only if you need a different severity or recommendation.
@name "Direct DateTime.Now / DateTime.UtcNow usage"
@severity error
@category "Determinism"
@description "Direct DateTime access makes tests non-deterministic"
@recommendation "Inject IClock and call _clock.UtcNow instead"
AllBodies.SelectMany(m => m.MemberAccesses.Where(a => (a.Name == "Now" || a.Name == "UtcNow")
&& (a.ResolvedType.FullName == "System.DateTime" || a.ResolvedType.FullName == "System.DateTimeOffset")))
Constructor calls new
Built-in rule available: Direct instantiation in constructor or field initializer. Use this example only if you need a different severity.
@name "Constructor allocates dependency directly"
@severity warn
@category "Determinism"
@description "Allocating dependencies in a constructor bypasses DI and breaks testability"
@recommendation "Inject the dependency through the constructor"
AllConstructorInjectableCreations
.Where(c => !c.SourceFile.Contains(".g.cs") && !c.SourceFile.Contains(".Designer.cs"))
Architecture
Domain depending on Infrastructure
@name "Domain layer references Infrastructure"
@severity error
@category "Architecture"
@description "Domain layer must not depend on infrastructure concerns"
@recommendation "Move the call behind an interface in Domain that Infrastructure implements"
from t in Types
where t.Namespace.FullName.Contains(".Domain.")
where t.UsedTypes.Any(u => u.Namespace.FullName.Contains(".Infrastructure."))
select t
Controller calling a Repository directly
@name "Controller calls Repository directly"
@severity warn
@category "Architecture"
@description "Controllers should go through the application layer, not access data directly"
@recommendation "Introduce an application service that wraps the repository call"
from t in Types
where t.Name.EndsWith("Controller")
where t.UsedTypes.Any(u => u.Name.EndsWith("Repository"))
select t
Complexity
Method too long
Built-in rule available: Method too large (threshold: 80 lines). Use this example if you want a different threshold (e.g. 60 lines).
@name "Method too long"
@severity info
@category "Style"
@description "Methods over 60 lines are harder to read and harder to test"
@recommendation "Extract sub-routines once a method passes ~60 lines"
from m in Methods
where m.LinesOfCode > 60
select m
High cyclomatic complexity
Built-in rule available: High cyclomatic complexity (threshold: 15). Use this example if you want a different threshold.
@name "High cyclomatic complexity"
@severity warn
@category "Style"
@description "Methods with branching depth above 10 are hard to test and reason about"
@recommendation "Extract guard clauses, replace nested ifs with early returns, or split the method"
from m in Methods
where m.CyclomaticComplexity > 10
select m
High cognitive complexity
Built-in rule available: High cognitive complexity (threshold: 15). Use this example if you want a different threshold.
@name "High cognitive complexity"
@severity warn
@category "Style"
@description "Cognitive complexity above 15 indicates the method is hard for humans to follow"
@recommendation "Flatten nesting, extract intermediate variables with meaningful names"
from m in Methods
where m.CognitiveComplexity > 15
select m
Where to go next
- Getting started: signposts to the rest of the rule-writing docs.
- Predicate catalog: full property list.
- Best practices: what makes a good rule.
- codecharter test: put a
<rule>.spec.mdnext to your.ccrand runcodecharter testto verify a copied or adjusted rule actually fires.