We try to figure out how to generate code with Roslyn. I'm not speaking about something like CSharpSyntaxTree.ParseText
that will take some strings and convert them into an AST. Instead, I would like to build my model somehow like this (pseudo code):
file
as compilation unitMyClass
to file
DoSomething
to MyClass
DoSomething
in a similar fashion like System.Linq.Expressions
We recently discovered Microsoft.CodeAnalysis.CSharp.SyntaxFactory
, and it seemed to be promising. However, obviously we have to add trivia ourselves.
After building a tree with SyntaxFactory.CompilationUnit()
and adding some members back and forth, the output of ToFullString()
is just a bunch of text, that is neither readable, nor compilable (e.g., missing braces). Do we miss something when generating the text from the model?
EDIT:
When using workspaces, you can set options affecting the whitespace behavior:
public string Generate (CompilationNode rootNode)
{
var cw = new CustomWorkspace();
cw.Options.WithChangedOption (CSharpFormattingOptions.IndentBraces, true);
var formattedCode = Formatter.Format (CreateFile(rootNode), cw);
return formattedCode.ToFullString();
}
This already yields a better result. Can someone confirm this as a good solution or is it rather a hack?
One problem remains. We want to generate an auto-property, currently using SF.AccessorDeclaration
but it misses the semicolon when converting to the full string.
You basically have to add block definitions, then Roslyn handles the trivia for you as long as you use the Formatter (as you have written)
Here is an example for a simple class that is generated correctly without myself having to specify any trivia
var consoleWriteLine = Syntax.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
Syntax.IdentifierName("Console"),
name: Syntax.IdentifierName("WriteLine"));
var arguments = Syntax.ArgumentList (
Syntax.SeparatedList (
new[]
{
Syntax.Argument (
Syntax.LiteralExpression (
SyntaxKind.StringLiteralExpression,
Syntax.Literal (@"""Goodbye everyone!""", "Goodbye everyone!")))
}));
var consoleWriteLineStatement = Syntax.ExpressionStatement (Syntax.InvocationExpression (consoleWriteLine, arguments));
var voidType = Syntax.ParseTypeName ("void");
var method = Syntax.MethodDeclaration (voidType, "Method").WithBody (Syntax.Block(consoleWriteLineStatement));
var intType = Syntax.ParseTypeName ("int");
var getterBody = Syntax.ReturnStatement (Syntax.DefaultExpression (intType));
var getter = Syntax.AccessorDeclaration (SyntaxKind.GetAccessorDeclaration, Syntax.Block (getterBody));
var property = Syntax.PropertyDeclaration (intType, "Property").WithAccessorList (Syntax.AccessorList (Syntax.SingletonList (getter)));
var @class = Syntax.ClassDeclaration ("MyClass").WithMembers (Syntax.List (new MemberDeclarationSyntax[] { method, property }));
var cw = new CustomWorkspace();
cw.Options.WithChangedOption (CSharpFormattingOptions.IndentBraces, true);
var formattedCode = Formatter.Format (@class, cw);
Console.WriteLine (formattedCode.ToFullString());
Note: Syntax = Microsoft.CodeAnalysis.CSharp.SyntaxFactory
This generates the following class definiton:
class MyClass
{
void Method()
{
Console.WriteLine("Goodbye everyone!");
}
int Property
{
get
{
return default(int);
}
}
}
Seems fine.
I had this same problem and found CustomWorkspace is now called AdhocWorkspace.
var cw = new AdhocWorkspace();
cw.Options.WithChangedOption(CSharpFormattingOptions.IndentBraces, true);
var formatter = Formatter.Format(cu, cw);
StringBuilder sb = new StringBuilder();
using (StringWriter writer = new StringWriter(sb))
{
formatter.WriteTo(writer);
}
var code = sb.ToString();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With