8

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.

Matthias
  • 15,919
  • 5
  • 39
  • 84
  • Your goal is to emit code, right? not compile it? – i3arnon Jan 08 '15 at 14:21
  • Yes, but without explicitly specifying trivia. I also have some experience using the ReSharper SDK, which basically has the same field of application. They *do* have factories that are oblivious to trivia, and it does print all the generated code clean and compilable. I crossed some comments of other users suggesting to use CodeDOM, and that Roslyn is supposedly not suitable for this, but this would seem like the biggest fail ever :) – Matthias Jan 08 '15 at 14:29
  • Well... Roslyn, being a compiler, is meant to consume code, not produce it. – i3arnon Jan 08 '15 at 14:36
  • 3
    I'm not sure about this. Roslyn was also built to support better tooling in refactorings, etc.. As an author of such refactorings, I don't want to deal with trivia. As I pointed out, JetBrains did a great job there. Do you really suggest that in the end, code generation is still a matter of CodeDOM and StringBuilder? Doesn't seem right for me. Don't get me wrong, I would accept this as an answer, but then I still declare this as a big fail. – Matthias Jan 08 '15 at 14:42
  • I'm not sure what you mean by trivia. And Roslyn of course supports manipulating code, but it's not **meant** for code generation, it's more of a side effect. Good luck anyway... – i3arnon Jan 08 '15 at 14:45
  • Trivia are whitespaces for instance. [This article](http://stackoverflow.com/a/7854697/568266) also points out, that Roslyn is meant to be the successor of CodeDOM. – Matthias Jan 08 '15 at 14:47
  • Roslyn is meant for code generation. It will power VS and all its refactorings. – usr Jan 08 '15 at 14:56
  • Hope you don't mind me going off-topic on your question, but I find it very interesting that you have experience with using ReSharper for refactoring, and are now looking at Roslyn. Would you mind taking a quick look at this, and letting me know your thoughts? Thanks. http://stackoverflow.com/questions/15193793/looking-for-program-to-format-c-sharp-source-so-methods-are-in-order-of-call-dep – RenniePet Jan 08 '15 at 14:56
  • @usr: Can you elaborate? – Matthias Jan 08 '15 at 15:00
  • @Matthias not sure what to say. Roslyn understands code. You can query what it knows. You can build AST's and format them to strings. That's all that is needed to build refactorings out of this. Watch a Roslyn Channel 9 talk. They explain it all. – usr Jan 08 '15 at 15:05
  • 2
    Using Formatter is definitely a good way to go. If you want to avoid the Workspace layer, you could also use `SyntaxNode.NormalizeWhitespace()` – Kevin Pilch Jan 08 '15 at 15:31

2 Answers2

12

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.

Inspyro
  • 358
  • 1
  • 8
1

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();
Jeff D
  • 11
  • 1