roslyn-rewriters
Skill2 starsChanged 44 days agoMITView on GitHub
Use for predictable, structural, repo-wide C# code changes — removing/renaming attributes, rewriting call patterns, converting one API shape to another across many files. Write a throwaway Roslyn program (ideally a single-file `dotnet run rewrite.cs` app) that parses the real syntax tree and rewrites it, instead of regex/sed/Python guessing at C# structure. Roslyn understands the code; text tools don't.
In this repository56 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- benchmarkingSkill
- commit-messagesSkill
- csharp-15-syntaxSkill
- csharp-aot-trimmingSkill
- csharp-api-designSkill
- csharp-assembly-inspectionSkill
- csharp-asyncSkill
- csharp-collections-modernSkill
- csharp-concurrency-primitivesSkill
- csharp-data-layoutSkill
- csharp-debuggingSkill
- csharp-discardsSkill
- csharp-disposable-patternsSkill
- csharp-docsSkill
- csharp-error-handlingSkill
- csharp-extension-membersSkill
- csharp-generics-modernSkill
- csharp-guard-clausesSkill
- csharp-json-source-generationSkill
- csharp-language-versionsSkill
- csharp-logging-diagnosticsSkill
- csharp-lspSkill
- csharp-modern-typesSkill
- csharp-nullabilitySkill
- csharp-nullable-migrationSkill
- csharp-nunitSkill
- csharp-pattern-matchingSkill
- csharp-performanceSkill
- csharp-polyfillsSkill
- csharp-source-generatorsSkill
- csharp-static-lambdasSkill
- csharp-string-handlingSkill
- csharp-tunitSkill
- csharp-verificationSkill
- dispatching-subagentsSkill
- dotnet-11-librariesSkill
- dotnet-analyzersSkill
- dotnet-central-package-managementSkill
- dotnet-file-based-appsSkill
- dotnet-msbuild-usingsSkill
- dotnet-multi-targetingSkill
- dotnet-project-hygieneSkill
- dotnet-runSkill
- dotnet-runtime-asyncSkill
- dotnet-slnxSkill
- dotnet-test-platformsSkill
- executing-plansSkill
- performance-bounded-deduplicationSkill
- roslyn-analyzer-performanceSkill
- roslyn-discoverySkill
- roslyn-duplicate-detectionSkill
- roslyn-guard-rewritersSkill
- subagent-driven-developmentSkill
- tddSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 50
Run it: dotnet run rewrite.cs -- /path/to/src.
- Very specific: line 91
Edit the tree, not the text: Use ReplaceNode/ReplaceNodes, WithAttributeLists, SyntaxFactory, or a CSharpSyntaxRewriter.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Throwaway Roslyn rewriters for structural C# changes
For a mechanical change that's the same shape across many files — strip a set of attributes, swap one API for another, rename a pattern, reshape method signatures — a regex or a Python script is guessing at C# syntax and will mangle edge cases (nested generics, trivia, string literals, conditional compilation). A small Roslyn program parses the actual syntax tree and edits it structurally, so the change is correct and the formatting/trivia survives. It's the reliable way to do predictable bulk edits.
Reach for this when: the change is structural and repeatable, affects many files, and a wrong text-match would silently corrupt code. For one-off edits or genuinely judgment-heavy changes, just edit directly (or drive subagents — see subagent-driven-development).
The lightweight path: a single-file dotnet run app
Since .NET 10 you don't need a project — a single .cs file with directives runs directly. This is the ideal throwaway harness: one file, no scaffolding, delete it when done.
#:package Microsoft.CodeAnalysis.CSharp@5.0.0
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
foreach (var path in Directory.EnumerateFiles(args[0], "*.cs", SearchOption.AllDirectories))
{
if (path.Contains("/obj/") || path.Contains("/bin/")) continue;
var text = await File.ReadAllTextAsync(path);
var tree = CSharpSyntaxTree.ParseText(text);
var root = await tree.GetRootAsync();
var newRoot = new MyRewriter().Visit(root);
if (newRoot == root) continue; // nothing changed — skip
await File.WriteAllTextAsync(path, newRoot.ToFullString());
Console.WriteLine($"rewrote {path}");
}
// A CSharpSyntaxRewriter visits nodes and returns replacements; unvisited nodes
// (and all their trivia) are preserved exactly.
sealed class MyRewriter : CSharpSyntaxRewriter
{
public override SyntaxNode? VisitMethodDeclaration(MethodDeclarationSyntax node)
{
// e.g. strip specific attributes, returning the edited node.
return base.VisitMethodDeclaration(node);
}
}Run it: dotnet run rewrite.cs -- /path/to/src. Add more #:package lines as needed. Convert to a real project only if it outgrows one file (dotnet project convert rewrite.cs).
Runnable examples
./examples/ has three complete single-file apps you can copy and adapt — a progression from safest to most powerful:
01-survey-attributes.cs— read-only reconnaissance. Walks every.csfile and prints the distinct attribute names it finds. Do this first: understand what's actually in the code before you rewrite it. Syntax-only.02-remove-attributes.cs— a syntax-onlyCSharpSyntaxRewriterthat strips a configured set of attributes off methods and rescues their comments. Defaults to a dry run;--writeapplies. Covers the common case.03-remove-redundant-call.cs— the semantic case: removes a redundant.AsQueryable()only when the receiver is alreadyIQueryable<T>, which you can only know from the semantic model. Loads the solution viaMSBuildWorkspace. Also dry-run by default.
Two habits baked into the examples worth keeping: dry-run by default (print what would change, require --write to touch disk), and skip obj//bin/.
When you need semantics: load the workspace
Syntax-only parsing (above) is enough for most attribute/signature/call-shape edits and is far simpler. Step up to a full workspace only when the change needs the semantic model — resolving what a symbol actually binds to, types, overloads, "is this the System.Text.Json one or ours." Load the solution through MSBuildWorkspace:
MSBuildLocator.RegisterDefaults(); // before any Roslyn MSBuild call
using var workspace = MSBuildWorkspace.Create();
workspace.RegisterWorkspaceFailedHandler(e => Console.Error.WriteLine(e.Diagnostic));
var solution = await workspace.OpenSolutionAsync(slnPath);
foreach (var project in solution.Projects.Where(p => p.Name == "Target"))
foreach (var doc in project.Documents.Where(d =>
d.FilePath?.EndsWith(".cs") == true &&
!d.FilePath.Contains("/obj/") && !d.FilePath.Contains("/bin/")))
{
var model = await doc.GetSemanticModelAsync(); // semantic info lives here
var root = await doc.GetSyntaxRootAsync();
// ...rewrite, then File.WriteAllText(doc.FilePath, newRoot.ToFullString());
}Needs Microsoft.Build.Locator + Microsoft.CodeAnalysis.Workspaces.MSBuild (+ .CSharp.Features for code-fix-style edits). RegisterDefaults() must run before any MSBuild-backed Roslyn call.
Use the latest stable Roslyn
Default to the newest non-preview Microsoft.CodeAnalysis.* (the 5.0.0 line) for these throwaway tools. There's no downside: the harness is disposable, doesn't ship, and never has to match the target repo's compiler version — it just reads/parses .cs text. Newer Roslyn also pulls a patched MSBuild, so the workspace example restores with no vulnerability advisories, whereas the older 4.x line drags in a flagged Microsoft.Build transitively. Pin all the Microsoft.CodeAnalysis.* packages to the same version. Don't reach for preview builds unless you specifically need an unreleased language feature.
Doing the edit right
- Edit the tree, not the text. Use
ReplaceNode/ReplaceNodes,WithAttributeLists,SyntaxFactory, or aCSharpSyntaxRewriter. Nodes are immutable — every "change" returns a new node; reassign. - Preserve trivia. Whitespace, comments, and newlines are trivia attached to tokens. When you remove or replace a node, carry the leading/trailing trivia across (as
AttributeRemoverdoes) so you don't leave dangling blank lines or eat comments. - Bail when nothing changed.
if (newRoot == root) continue;— don't rewrite (and reformat/touch) files the transform didn't actually affect. - Normalize output deliberately. Decide on line endings and trailing whitespace and apply consistently (for example normalize to
\n, trim line ends, and write UTF-8 without a BOM). Or rundotnet formatafterward instead of hand-normalizing.
After running it
- Build is the verdict.
dotnet build+ analyzers + tests (seecsharp-verification). The rewriter is only correct if the result compiles clean and tests pass — confirm, don't assume. - Diff before trusting. Skim
git difffor a few representative files and any odd cases, and check that the exact spans changed are the ones you intended — a boundary-less text sweep (regex/sed) can splice a comment off its method or rename an identifier inside a string literal, and nothing says so until the build breaks. A structural rewrite is predictable, which makes a wrong assumption show up uniformly — easy to catch in a sample. Rebuild after. - Delete the harness. It's throwaway tooling — don't commit
rewrite.csinto the product repo unless the team wants to keep it. (Keep it in a scratch/tools location if it's worth re-running.)
Anti-patterns
- Regex/sed/Python rewriting C# structure — mangles generics, trivia, strings, and
#ifblocks; use the parser. - Mutating
.ToFullString()text after parsing instead of editing the tree — throws away the whole point. - Dropping trivia, leaving blank-line craters or deleting comments.
- Loading a full
MSBuildWorkspacewhen syntax-only parsing would do — slower and more setup for no benefit unless you need the semantic model. - Forgetting
MSBuildLocator.RegisterDefaults()before MSBuild-backed calls (cryptic load failures). - Declaring success on "it ran" — only
dotnet build+ tests confirm the rewrite was correct.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

