Skip to content

Instantly share code, notes, and snippets.

@DanielShuey
Created August 27, 2025 23:08
Show Gist options
  • Select an option

  • Save DanielShuey/32d2e4f785fcd5b9adcd0fb1b7a611f3 to your computer and use it in GitHub Desktop.

Select an option

Save DanielShuey/32d2e4f785fcd5b9adcd0fb1b7a611f3 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Namespace {
public static class DelegateAllRewriter {
private static List<SyntaxNode> Search(this SyntaxNode root, SyntaxKind kind, int levels) {
return new NodeSearch(root, kind).Call(levels);
}
private static SyntaxNode Get(this SyntaxNode root, SyntaxKind kind, int levels) {
return new NodeSearch(root, kind).Call(levels) [0];
}
public static CSharpCompilation Call(CSharpCompilation compilation) {
return new Compiler(compilation).Call();
// TODO: Visitor pattern get attributes (instead of manual)
}
private class Compiler {
public CSharpCompilation compilation;
public Dictionary<string, SyntaxNode> classes = new Dictionary<string, SyntaxNode>();
public Compiler(CSharpCompilation compilation) {
this.compilation = compilation;
}
public CSharpCompilation Call() {
foreach (var old_tree in compilation.SyntaxTrees) {
compilation = compilation.ReplaceSyntaxTree(old_tree, new SyntaxTreeHandler(this, old_tree).Call());
}
return compilation;
}
public void MapClassesToDict(IEnumerable<SyntaxNode> class_nodes, SemanticModel semantic) =>
class_nodes.Each(x => classes[semantic.GetDeclaredSymbol(x).Name] = x);
}
private class SyntaxTreeHandler {
private Compiler compiler;
private CSharpCompilation compilation => compiler.compilation;
private Dictionary<string, SyntaxNode> classes => compiler.classes;
private SemanticModel semantic;
private SyntaxTree tree;
private SyntaxNode root;
public SyntaxTreeHandler(Compiler compiler, SyntaxTree tree) {
this.compiler = compiler;
this.tree = tree;
semantic = compilation.GetSemanticModel(tree, ignoreAccessibility : true);
root = tree.GetCompilationUnitRoot();
}
private SyntaxNode WriteDelegatesFor(SyntaxNode to_class, SyntaxNode for_field) {
var field_name = for_field.Get(SyntaxKind.VariableDeclarator, 1).ToString();
var field_type = for_field.Get(SyntaxKind.IdentifierName, 1).ToString();
var method_data = new ClassParser().Tap(x => x.Visit(classes[field_type])).Data;
var new_nodes = method_data.Select(x => {
var member_access = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName(field_name),
SyntaxFactory.IdentifierName(x.Identifier.ToString())
);
var arguments = SyntaxFactory.ArgumentList(
SyntaxFactory.SeparatedList(
x.Parameters.Parameters.Select(p =>
SyntaxFactory.Argument(SyntaxFactory.IdentifierName(p.Identifier.ToString()))
)
)
);
var method_call = SyntaxFactory.InvocationExpression(member_access, arguments);
var arrow_exp = SyntaxFactory.ArrowExpressionClause(method_call);
return SyntaxFactory.MethodDeclaration(x.ReturnType, x.Identifier)
.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)))
.WithParameterList(x.Parameters)
.WithReturnType(x.ReturnType)
.WithExpressionBody(arrow_exp)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
});
return to_class.InsertNodesAfter(to_class.ChildNodes().Last(), new_nodes);
}
public SyntaxTree Call() {
var class_nodes = root.Search(SyntaxKind.ClassDeclaration, 1);
compiler.MapClassesToDict(class_nodes, semantic);
foreach (var c in class_nodes) {
foreach (var f in c.Search(SyntaxKind.FieldDeclaration, 0))
foreach (var a in f.Search(SyntaxKind.Attribute, 1)) {
if (a.ToString() == "DelegateAll")
root = root.ReplaceNode(c, WriteDelegatesFor(c, f));
}
}
return tree.WithRootAndOptions(root, RoslynCompiler.ParseOptions);
}
}
private class ClassParser : CSharpSyntaxRewriter {
public List<MethodData> Data = new List<MethodData>();
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) {
if (node.Modifiers.Any(x => x.ToString().Equals("public"))) {
Data.Add(new MethodData {
Identifier = node.Identifier,
Parameters = node.ParameterList,
ReturnType = node.ReturnType,
Modifiers = node.Modifiers
});
}
return base.VisitMethodDeclaration(node);
}
}
private struct MethodData {
public SyntaxToken Identifier;
public ParameterListSyntax Parameters;
public TypeSyntax ReturnType;
public SyntaxTokenList Modifiers;
}
private class NodeSearch {
private int max_level;
private SyntaxNode root;
private SyntaxKind kind;
private List<SyntaxNode> nodes;
public NodeSearch(SyntaxNode root, SyntaxKind kind) {
this.kind = kind;
this.root = root;
}
private void Traverse(SyntaxNode n, int current_level) {
if (current_level > max_level) return;
foreach (var x in n.ChildNodes()) {
if (x.IsKind(kind)) nodes.Add(x);
Traverse(x, current_level + 1);
}
}
public List<SyntaxNode> Call(int levels) {
max_level = levels;
nodes = new List<SyntaxNode>();
Traverse(root, 0);
return nodes;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment