Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generating semantic code with roslyn

Tags:

c#

roslyn

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):

  1. Create file as compilation unit
  2. Add class MyClass to file
  3. Add method DoSomething to MyClass
  4. Set body of 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.

like image 507
Matthias Avatar asked Jan 08 '15 14:01

Matthias


2 Answers

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.

like image 67
Inspyro Avatar answered Oct 22 '22 03:10

Inspyro


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();
like image 32
Jeff D Avatar answered Oct 22 '22 04:10

Jeff D