I’m currently working on an Icon Pack for my Material Design library and need to generate an enum containing a list of all the icons. Before you delve into this code you should know that the same result could no doubt be achieved using simple string manipulation in a lot less time. But I took this as an opportunity to learn a little about Roslyn, so if that’s what you’re after then read on.
I set about creating a code template for the enum with the intention of using Roslyn to clear the dummy enum values out and re-populate the enum with a new list. Sourcing the list of values is the easy part and not discussed here.
To begin with, this is my starting code template:
namespace MaterialDesignThemes.Wpf
{
/// ******************************************
/// This code is auto generated. Do not amend.
/// ******************************************
/// <summary>
/// List of available icons for use with <see cref="Icon"/>.
/// </summary>
/// <remarks>
/// All icons sourced from Material Design Icons Font - <see cref="https://materialdesignicons.com/"> - in accordance of
/// <see cref="https://github.com/Templarian/MaterialDesign/blob/master/license.txt"/>.
/// </remarks>;
public enum IconType
{
AutoGeneratedDoNotAmend
}
}
First lesson. If you want to do something similar, install the .Net Compiler Platform SDK. Stupidly I didn’t do this until I had the code 75% complete. The solution below looks pretty simple but as is oft the way, figuring it out was the hard part. The Syntax Visualizer Visual Studio add-in included in the SDK would have got me there a lot quicker.
If I had of used the Visualizer in VS 2015 earlier (View > Other Windows > Syntax Visualizer), upon highlighting my above code template I would have seen this, which gives you a pretty good idea of what we’re working with:

Second lesson. The nuget package you are probably after is (took me a bit of digging to get the right one):
Microsoft.CodeAnalysis.CSharp.Workspaces
Also, I found ReSharper’s Hierarchy tool pretty useful to start understanding the class relationships; soon realising that we’re going to be spending a lot of time working with SyntaxNode objects and derivations thereof:

One of the first things to learn about Roslyn SyntaxNode objects is that they are immutable. If you make a change then you’ll get a new one. Obviously what we want to do is remove our AutoGeneratedDoNotAmend enum value (EnumMemberDeclarationSyntax) from the owning enum (EnumDeclarationSyntax) and replace it with a bunch of new members. Meaning we’ll end up with a new enum/EnumDeclarationSyntax, in turn meaning we need to swap it out in the parent (NamespaceDeclaration) which again give us a new namespace, so we’ll have to swap that in the root’s children giving us a new root. Effectively we start at the bottom and replace/renew everything walking up the ancestry of the tree.
Maybe there’s a better way, but this is my first crack at Roslyn and is very much a learning experience.
My first cut of the code (done prior to me installing the Visualizer/SDK, and using some pretend enum values) turned out like this:
private void UpdateEnum(string sourceFile)
{
var sourceText = SourceText.From(new FileStream(sourceFile, FileMode.Open));
var syntaxTree = CSharpSyntaxTree.ParseText(sourceText);
var rootNode = syntaxTree.GetRoot();
var namespaceDeclarationNode = rootNode.ChildNodes().Single();
var enumDeclarationSyntaxNode = namespaceDeclarationNode.ChildNodes().OfType&amp;amp;amp;amp;lt;EnumDeclarationSyntax&amp;amp;amp;amp;gt;().Single();
var emptyEnumDeclarationSyntaxNode = enumDeclarationSyntaxNode.RemoveNodes(enumDeclarationSyntaxNode.ChildNodes().OfType&amp;amp;amp;amp;lt;EnumMemberDeclarationSyntax&amp;amp;amp;amp;gt;(), SyntaxRemoveOptions.KeepDirectives);
var generatedEnumDeclarationSyntax = emptyEnumDeclarationSyntaxNode.AddMembers(
SyntaxFactory.EnumMemberDeclaration("Aston"),
SyntaxFactory.EnumMemberDeclaration("Villa"));
var generatedNamespaceDeclarationSyntaxNode = namespaceDeclarationNode.ReplaceNode(enumDeclarationSyntaxNode, generatedEnumDeclarationSyntax);
var generatedRootNode = rootNode.ReplaceNode(namespaceDeclarationNode, generatedNamespaceDeclarationSyntaxNode);
Console.WriteLine(generatedRootNode.ToFullString());
}
The end result was pretty good, we retain the namespace declaration, comments, but we’ve lost something in the formatting:
namespace MaterialDesignThemes.Wpf
{
/// ******************************************
/// This code is auto generated. Do not amend.
/// ******************************************
/// <summary>
/// List of available icons for use with <see cref="Icon"/>.
/// </summary>
/// <remarks>
/// All icons sourced from Material Design Icons Font - <see cref="https://materialdesignicons.com/"> - in accordance of
/// <see cref="https://github.com/Templarian/MaterialDesign/blob/master/license.txt"/>.
/// </remarks>
public enum IconType
{
Aston,Villa }
}
More digging and I learn how to create my enum member with “trivia” consisting of leading white space:
var leadingTriviaList = SyntaxTriviaList.Create(SyntaxFactory.Whitespace(" "));
var generatedEnumDeclarationSyntax = emptyEnumDeclarationSyntaxNode.AddMembers(
SyntaxFactory.EnumMemberDeclaration(SyntaxFactory.Identifier(leadingTriviaList, "Aston", SyntaxTriviaList.Empty)),
SyntaxFactory.EnumMemberDeclaration(SyntaxFactory.Identifier(leadingTriviaList, "Villa", SyntaxTriviaList.Empty)));
Current result:
public enum IconType
{
Aston, Villa }
This is good, but I now realised I needed to add a line feed, but to do this I needed the sibling “CommaToken” node to have the correct trailing trivia. I discovered this by tweaking my initial template to include two enum values and taking a look at the Syntax Visualizer:

Yikes. This is starting to give me a headache.
Remembering that these toys we are playing with are immutable, I concocted a new method to pull all the comma tokens, and replace with new comma tokens with trailing line feed trivia:
. . .
generatedEnumDeclarationSyntax = AddLineFeedsToCommas(generatedEnumDeclarationSyntax);
. . .
private static EnumDeclarationSyntax AddLineFeedsToCommas(EnumDeclarationSyntax enumDeclarationSyntax)
{
var none = new SyntaxToken();
var trailingTriviaList = SyntaxTriviaList.Create(SyntaxFactory.ElasticCarriageReturnLineFeed);
Func<EnumDeclarationSyntax, SyntaxToken> next = enumSyntax => enumSyntax.ChildNodesAndTokens()
.Where(nodeOrToken => nodeOrToken.IsToken)
.Select(nodeOrToken => nodeOrToken.AsToken())
.FirstOrDefault(
token =>
token.Value.Equals(",") &&
(!token.HasTrailingTrivia || !token.TrailingTrivia.Any(SyntaxKind.EndOfLineTrivia)));
SyntaxToken current;
while ((current = next(enumDeclarationSyntax)) != none)
{
enumDeclarationSyntax = enumDeclarationSyntax.ReplaceToken(current,
SyntaxFactory.Identifier(SyntaxTriviaList.Empty, ",", trailingTriviaList)
);
}
return enumDeclarationSyntax;
}
Our result now is looking much better:
namespace MaterialDesignThemes.Wpf
{
/// ******************************************
/// This code is auto generated. Do not amend.
/// ******************************************
/// <summary>;
/// List of available icons for use with <see cref="Icon">.
/// </summary>
/// <remarks>
/// All icons sourced from Material Design Icons Font - <see cref="https://materialdesignicons.com/"/> - in accordance of
/// <see cref="https://github.com/Templarian/MaterialDesign/blob/master/license.txt"/>.
/// </remarks>
public enum IconType
{
Aston,
Villa }
}
There’s just that last curly bracket which needs knocking down a line, but I guess I’ll just have to get back to that…