Summary
Add a source generator that produces a complete implementation of the Command pattern for consumer-defined operations.
The generator lives in PatternKit.Generators and emits self-contained, readable C# with no runtime PatternKit dependency.
Primary goals:
- Define commands as first-class, strongly-typed operations.
- Generate boilerplate for execution, optional undo, and optional async execution.
- Support deterministic ordering and composition (macro commands).
- Provide actionable diagnostics and test coverage.
Motivation / Problem
Command implementations often repeat the same scaffolding:
- ICommand interfaces + per-command classes
- argument and validation boilerplate
- consistent naming and dispatch wiring
- optional undo support and history handling
- sync/async duplication
This is ideal for source generation: generate the boring parts, keep consumer code focused on behavior.
Supported Targets (must-have)
The generator must support commands expressed as:
partial class / partial struct
partial record class / partial record struct
Two primary command models must be supported:
- Explicit command types (commands are real user types; generator emits executor adapters).
- Command host (a static/instance host groups multiple command methods).
Proposed User Experience
A) Command type model (recommended)
[Command]
public partial record struct RenameUser(Guid UserId, string NewName);
public sealed class UserService
{
[CommandHandler]
private void Handle(in RenameUser cmd) { /* ... */ }
}
Generated (representative shape):
public readonly partial struct RenameUserCommand
{
public static void Execute(UserService handler, in RenameUser cmd);
public static ValueTask ExecuteAsync(UserService handler, RenameUser cmd, CancellationToken ct = default);
}
B) Command host model (grouped commands)
[CommandHost]
public static partial class UserCommands
{
[CommandCase]
public static void Rename(UserService svc, Guid userId, string newName);
[CommandCase]
public static ValueTask DisableAsync(UserService svc, Guid userId, CancellationToken ct);
}
Generated:
- strongly-typed command wrapper types (optional)
- consistent
Execute/ExecuteAsync entrypoints
Attributes / Surface Area
Namespace: PatternKit.Generators.Command
Core
Optional grouping:
[CommandHost] on a static partial class
[CommandCase] on methods within a host
Undo/redo integration (optional v1, recommended v2):
[CommandUndo] on undo method
- caretaker/history generator can be a separate issue or follow-up.
Semantics (must-have)
Handler resolution
V1: no DI scanning.
- Generator wires a command to an explicitly annotated handler method.
- If multiple handlers match, error.
- If no handler found, diagnostic.
Execution signatures
Supported handler signatures:
void Handle(in TCommand cmd)
void Handle(TCommand cmd)
ValueTask HandleAsync(TCommand cmd, CancellationToken ct = default)
Generated entrypoints:
Execute(handler, in cmd) for sync handlers
ExecuteAsync(handler, cmd, ct) for async handlers (ValueTask)
Command composition (macro commands)
Optional v1 feature (but valuable):
- generate
MacroCommand<TContext> builder that executes multiple commands in order.
- deterministically executes in registration order.
Diagnostics (must-have)
Stable IDs, actionable:
PKCMD001 Type marked [Command] must be partial.
PKCMD002 No handler found for command type.
PKCMD003 Multiple handlers found for command type.
PKCMD004 Handler method signature invalid.
PKCMD005 Async handler detected but async generation disabled (enable GenerateAsync/ForceAsync).
PKCMD006 Command host marked [CommandHost] must be partial and static.
PKCMD007 Command case signature invalid.
Generated Code Layout
TypeName.Command.g.cs
TypeName.CommandHost.g.cs (if host feature enabled)
Determinism:
- stable ordering by fully-qualified symbol name for emission.
Testing Expectations
Acceptance Criteria
Summary
Add a source generator that produces a complete implementation of the Command pattern for consumer-defined operations.
The generator lives in
PatternKit.Generatorsand emits self-contained, readable C# with no runtime PatternKit dependency.Primary goals:
Motivation / Problem
Command implementations often repeat the same scaffolding:
This is ideal for source generation: generate the boring parts, keep consumer code focused on behavior.
Supported Targets (must-have)
The generator must support commands expressed as:
partial class/partial structpartial record class/partial record structTwo primary command models must be supported:
Proposed User Experience
A) Command type model (recommended)
Generated (representative shape):
B) Command host model (grouped commands)
Generated:
Execute/ExecuteAsyncentrypointsAttributes / Surface Area
Namespace:
PatternKit.Generators.CommandCore
[Command]on command typesstring? CommandTypeName(default:<TypeName>Command)bool GenerateAsync(default: inferred)bool ForceAsync(default: false)bool GenerateUndo(default: false)[CommandHandler]on methods that handle a commandType? CommandType(optional if inferrable)Optional grouping:
[CommandHost]on a static partial class[CommandCase]on methods within a hostUndo/redo integration (optional v1, recommended v2):
[CommandUndo]on undo methodSemantics (must-have)
Handler resolution
V1: no DI scanning.
Execution signatures
Supported handler signatures:
void Handle(in TCommand cmd)void Handle(TCommand cmd)ValueTask HandleAsync(TCommand cmd, CancellationToken ct = default)Generated entrypoints:
Execute(handler, in cmd)for sync handlersExecuteAsync(handler, cmd, ct)for async handlers (ValueTask)Command composition (macro commands)
Optional v1 feature (but valuable):
MacroCommand<TContext>builder that executes multiple commands in order.Diagnostics (must-have)
Stable IDs, actionable:
PKCMD001Type marked[Command]must bepartial.PKCMD002No handler found for command type.PKCMD003Multiple handlers found for command type.PKCMD004Handler method signature invalid.PKCMD005Async handler detected but async generation disabled (enable GenerateAsync/ForceAsync).PKCMD006Command host marked[CommandHost]must bepartialandstatic.PKCMD007Command case signature invalid.Generated Code Layout
TypeName.Command.g.csTypeName.CommandHost.g.cs(if host feature enabled)Determinism:
Testing Expectations
Command execution routes to correct handler.
Async handler uses
ValueTaskand respects cancellation.Diagnostics:
Optional macro command:
Acceptance Criteria
[Command]works for class/struct/record class/record struct.ExecuteandExecuteAsync(ValueTask) entrypoints when appropriate.