Mirrors a C# source tree 1:1 but empties every method/property/accessor body, keeping signatures, type layout, doc comments, constants and enum values.
Use case: hand an AI (or a person) the stripped source so they write integration tests against the public contract without reading the implementation. Tests still compile and run against the real build - the stripped tree is only context.
public int Add(int a, int b) // in
{
_log.Info("adding");
return a + b;
}
public int Add(int a, int b) => throw null; // outIt is a Roslyn CSharpSyntaxRewriter plus throw null - the same technique Microsoft.DotNet.GenAPI
uses, but source to source: exact file/type structure, private members kept if you want, and
optional statement-level redaction. Reference-assembly tools (GenAPI, <ProduceReferenceAssembly>,
Refasmer) only give you a DLL or a reconstructed API surface.
dotnet build -c Release
dotnet run --project src/CodeStripper -- --input <src-dir> --output <out-dir> [--config <file>] [--verbose]Output must be a different directory from input; stripping in place is refused.
strip-for-ai.config.json- the anti-cheating case. Every body emptied (incl. private and internal), field initializers stripped so they cannot leak logic.buildable-mirror.config.json- same, plus copies csproj/props/assets and drops a build override, so the stripped tree still compiles with a plaindotnet build(see below).redact-noise.config.json- the opposite: keep the real logic, but remove logging calls and unwraptry/catchfor clean review.
| Key | Values | Default | Meaning |
|---|---|---|---|
bodyReplacement |
throwNull | notImplemented | emptyForVoid |
throwNull |
What an emptied body becomes. notImplemented avoids the CS8597 warning. |
computedProperties |
strip | keepTrivial | keepAll |
strip |
Expression-bodied properties/indexers (=> expr). keepTrivial keeps ones with no call/new/await (e.g. Id > 0). |
publicMembers / protectedMembers / internalMembers / privateMembers |
keepFull | emptyBody | removeEntirely |
emptyBody |
Per-accessibility handling. removeEntirely drops the member. |
keepXmlDocComments |
bool | true |
Keep /// docs. |
keepAttributes |
bool | true |
Keep attributes. |
keepConstants |
bool | true |
Keep const values even when initializers are stripped. |
keepEnumMembers |
bool | true |
Keep enum member values. |
stripFieldInitializers |
bool | false |
Remove = expr on fields/properties (they can leak logic). |
statementRules |
list | [] |
Only affect keepFull bodies (see below). |
include / exclude |
glob list | **/*.cs / build+generated |
File selection, relative to input root. |
stripBuildTooling |
bool | false |
Strip generator/analyzer projects too. Off = mirror them verbatim (see below). |
mirrorNonSourceFiles |
bool | false |
Copy every non-.cs file verbatim, for a complete tree. |
emitBuildOverride |
bool | false |
Write a Directory.Build.targets that relaxes the strict build. |
runCSharpier |
bool | false |
Format output with CSharpier (dotnet tool install -g csharpier). |
type |
Options | Effect |
|---|---|---|
removeLoggingCalls |
matchMembers (receiver names) |
Removes _log.Info(...), Log.Warning(...), await _log.XAsync(...). |
unwrapTryCatch |
- | Replaces try { X } catch/finally { ... } with { X } (drops finally). |
Rules run before body-stripping, so they only mark bodies that survive (keepFull). Add one by
implementing IStatementRule and registering it in StatementRuleFactory.
These are build tooling: emptying them stops them emitting code, breaking every consumer. CodeStripper
detects them and mirrors them verbatim - no config needed. A project counts as tooling when its
csproj sets IsRoslynComponent / EnforceExtendedAnalyzerRules, when it is referenced with
OutputItemType="Analyzer", or when a file matches IIncrementalGenerator / [Generator] /
DiagnosticAnalyzer. Set stripBuildTooling: true to strip them anyway.
A stripped tree inherently has unused parameters, unused members and throw null, which
TreatWarningsAsErrors + analyzers turn into errors (a ~116-file solution produced 278 such errors).
That is what stripping is, not a bug. emitBuildOverride writes a Directory.Build.targets at the
output root (TreatWarningsAsErrors=false, RunAnalyzers=false, Nullable=disable, plus NoWarn);
because targets are imported after every Directory.Build.props, its values win. With
buildable-mirror.config.json the mirror compiles under a plain dotnet build. Validated on five
in-house solutions (865 .cs files, one with a source generator).
- Types are always kept;
removeEntirelyapplies to members, not whole types. - Accessibility is resolved syntactically (modifiers + containing declaration) - no compilation needed.
- Output is valid C# but is meant as reference, not a build target; build and test against the original.