James Morrison
James Morrison

Reputation: 2122

Roslyn - fix error in document by replacing text span in syntax root

I've got a bit of a specific one here, basically, I'm loading in an adhoc workspace and getting the errors relating to a specific value in a compilation because I know exactly what text I need to replace but not where. Code below.

public static async Task<Solution> UpdateEntityReferences(Solution solution, ProjectId servicesId, string oldValue, string newValue)
{
     var project = solution.GetProject(servicesId);
     var compilation = await project.GetCompilationAsync();
     var diagnostics = compilation.GetDiagnostics().Where(diag => diag.GetMessage().Contains($"'{oldValue}'"));

     foreach (var diagnostic in diagnostics)
     {
         var errorLineSpan = diagnostic.Location.GetLineSpan();
         var document = project.FindDocumentByName(Path.GetFileName(errorLineSpan.Path));
         var syntaxRoot = await document.GetSyntaxRootAsync();
         var errorSpan = errorLineSpan.Span;
     }
     return solution;
}

So with the code so far I get the location of the error and basically want to return a new version of that document with the "errorSpan" replaced with the "newValue" text but can't find a way to do it, is this possible?

EDIT: with the help of Get the SyntaxNode given the linenumber in a SyntaxTree I'm able to get the SyntaxNode from the SyntaxTree and should be able to replace the text span (the for loop becomes the below) but this doesn't work.

foreach (var diagnostic in diagnostics)
{
    var errorLineSpan = diagnostic.Location.GetLineSpan();
    var document = project.FindDocumentByName(Path.GetFileName(errorLineSpan.Path));
    var syntaxTree = await document.GetSyntaxTreeAsync();

    var errorSpan = errorLineSpan.Span;
    var lineSpan = syntaxTree.GetText().Lines[errorSpan.Start.Line].Span;

    var node = syntaxTree.GetRoot().DescendantNodes(lineSpan)
                .First(n => lineSpan.Contains(n.FullSpan));

    var errorTextSpan = TextSpan.FromBounds(errorSpan.Start.Character, errorSpan.End.Character);

    var newNodeText = node.GetText().Replace(errorTextSpan, newValue);
}

The text is replaced (incorrectly half the time) and then I'm left with a SourceText object which I can't figure out how to replace in the document. Any ideas?

Upvotes: 1

Views: 939

Answers (1)

James Morrison
James Morrison

Reputation: 2122

Got there in the end - so you can't replace the textspan and get the document back without using something like document editor because you need to replace the SyntaxNode and there's no guarantee the character placement of the LineSpan is still correct ones the node is grabbed.

The solution is simply to use the Line number in the LineSpan to grab the node on that line and then filter down to the token using LINQ. Once the exact node is grabbed it can be replaced in the parent node and then the syntaxRoot and finally the document. Code below.

foreach (var diagnostic in diagnostics)
{
     servicesProject = solution.GetProject(servicesId);
     var errorLineSpan = diagnostic.Location.GetLineSpan();
     var document = servicesProject.FindDocumentByName(Path.GetFileName(errorLineSpan.Path));
     var syntaxTree = await document.GetSyntaxTreeAsync();
     var errorSpan = errorLineSpan.Span;
     var lineSpan = syntaxTree.GetText().Lines[errorSpan.Start.Line].Span;

     var node = syntaxTree.GetRoot().DescendantNodes(lineSpan)
.First(n => lineSpan.Contains(n.FullSpan)).DescendantNodes()
.OfType<IdentifierNameSyntax>().FirstOrDefault(c => c.Identifier.Text == oldValue);

        var newNode = node.ReplaceNode(node, SyntaxFactory.IdentifierName(newValue));
        var newSyntaxRoot = syntaxTree.GetRoot().ReplaceNode(node, newNode);
        document = document.WithSyntaxRoot(newSyntaxRoot);
        solution = document.Project.Solution;           
}

Upvotes: 1

Related Questions