5

I'm new to Roslyn. I'm writing a code fix provider that transforms foreach blocks that iterate through the results of a Select, e.g.

foreach (var item in new int[0].Select(i => i.ToString()))
{
    ...
}

to

foreach (int i in new int[0])
{
    var item = i.ToString();
    ...
}

To do this, I need to insert a statement at the beginning of the BlockSyntax inside the ForEachStatementSyntax that represents the foreach block. Here is my code for that:

var blockStatement = forEach.Statement as BlockSyntax;
if (blockStatement == null)
{
    return document;
}
forEach = forEach.WithStatement(
    blockStatment.WithStatements(
        blockStatement.Statements.Insert(0, selectorStatement));

Unfortunately, doing that results in the whitespace being off:

            foreach (int i in new int[0])
            {
var item = i.ToString();
                ...
            }

I Googled solutions for this. I came across this answer, which recommended using either Formatter.Format or SyntaxNode.NormalizeWhitespace.

  • I can't use Formatter.Format because that takes a Workspace parameter, and it looks I don't have access to a Workspace per Roslyn: Current Workspace in Diagnostic with code fix project.

  • I tried using NormalizeWhitespace() on the syntax root of the document, but that invasively formatted other code not related to the fix. I tried using it on just the ForEachStatementSyntax associated with the foreach block, and then calling syntaxRoot = syntaxRoot.ReplaceNode(oldForEach, newForEach), but that results in the entire foreach block not being properly indented.

    namespace ConsoleApp1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var array = new int[0];
                int length = array.Length;
    
    foreach (int i in array)
    {
        string item = i.ToString();
    }        }
        }
    }
    

So is it possible to simply insert the statement with the correct indentation in the first place, without having to format other code?

Thanks.

Community
  • 1
  • 1
James Ko
  • 32,215
  • 30
  • 128
  • 239

1 Answers1

4

You can add the Formatter Annotation to the nodes that you want the formatter to run on using WithAdditionalAnnotations

blockStatement.Statements.Insert(0, selectorStatement.WithAdditionalAnnotations(Formatter.Annotation))

Jonathon Marolf
  • 2,071
  • 14
  • 16
  • Thanks so much! Worked like a charm. Out of curiosity, do you know *why* adding that annotation causes the statement to be formatted? When I look at the [Roslyn source code](http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Formatting/Formatter.cs,24) I see it's initialized as just a plain `new SyntaxAnnotation()`. – James Ko Mar 09 '17 at 02:09
  • SyntaxAnnotation increments a global id when created so that `Formatter.Annotation == someAnnotation ` will only be true if `someAnnotation` is actually a reference to the same instance. – Jonathon Marolf Mar 10 '17 at 02:55