Inserting Code Between Region Trivia With Roslyn

自古美人都是妖i 提交于 2019-12-11 04:44:56

问题


How can I insert a variable declaration after a region directive in Roslyn? I'd like to be able to do something like going from this:

class MyClass 
{
    #region myRegion
    #endregion
}

to this:

class MyClass 
{
    #region myRegion
    private const string myData = "somedata";
    #endregion 
}

I can't seem to find any examples that deal with trivia in this manner.


回答1:


That's quite tricky to do with a CSharpSyntaxRewriter, because both the #region <name> and #endregion end up in the same SyntaxTriviaList, which you'd have to split out and figure out what to create instead. The simplest way to not bother with all the intricacies, is to create the corresponding TextChange and modify the SourceText.

var tree = SyntaxFactory.ParseSyntaxTree(
@"class MyClass
{
    #region myRegion
    #endregion
}");

// get the region trivia
var region = tree.GetRoot()
    .DescendantNodes(descendIntoTrivia: true)
    .OfType<RegionDirectiveTriviaSyntax>()
    .Single();

// modify the source text
tree = tree.WithChangedText(
    tree.GetText().WithChanges(
        new TextChange(
            region.GetLocation().SourceSpan,
            region.ToFullString() + "private const string myData = \"somedata\";")));

After that, tree is:

class MyClass 
{
    #region myRegion
private const string myData = "somedata";
    #endregion
}



回答2:


m0sa's answer works for empty regions but it cannot replace existing code which is the likely reason to need to this i.e. rerunning a code-generation tool.

Achieving this requires finding the full extent of the region. This is also made difficult because the target file can contain multiple nested regions. To do this I process all directives and build a hierarchy of regions:

public class RegionInfo
{
    public RegionDirectiveTriviaSyntax Begin;
    public EndRegionDirectiveTriviaSyntax End;
    public RegionInfo Parent;
    public List<RegionInfo> Children = new List<RegionInfo>();
    public string Name => this.Begin.EndOfDirectiveToken.ToFullString().Trim();
}

public static class CodeMutator
{   
    public static string ReplaceRegion(string existingCode, string regionName, string newCode)
    {
        var syntaxTree = CSharpSyntaxTree.ParseText(existingCode);

        var region = CodeMutator.GetRegion(syntaxTree, regionName);

        if (region == null)
        {
            throw new Exception($"Cannot find region named {regionName}");
        }

        return 
            existingCode
                .Substring(0, region.Begin.FullSpan.End) +
            newCode +
            Environment.NewLine +
            existingCode
                .Substring(region.End.FullSpan.Start);
    }

    static RegionInfo GetRegion(SyntaxTree syntaxTree, string regionName) =>
        CodeMutator.GetRegions(syntaxTree)
            .FirstOrDefault(x => x.Name == regionName);

    static List<RegionInfo> GetRegions(SyntaxTree syntaxTree)
    {
        var directives = syntaxTree
            .GetRoot()
            .DescendantNodes(descendIntoTrivia: true)
            .OfType<DirectiveTriviaSyntax>()
            .Select(x => (x.GetLocation().SourceSpan.Start, x))
            .OrderBy(x => x.Item1)
            .ToList();

        var allRegions = new List<RegionInfo>();
        RegionInfo parent = null;

        foreach (var directive in directives)
        {
            if (directive.Item2 is RegionDirectiveTriviaSyntax begin)
            {
                var next = new RegionInfo() {Begin = begin, Parent = parent};

                allRegions.Add(next);
                parent?.Children.Add(next);

                parent = next;
            }
            else if (directive.Item2 is EndRegionDirectiveTriviaSyntax end)
            {
                if (parent == null)
                {
                    Log.Error("Unmatched end region");
                }
                else
                {
                    parent.End = end;
                    parent = parent.Parent;
                }
            }
        }

        return allRegions;
    }
}


来源:https://stackoverflow.com/questions/37552426/inserting-code-between-region-trivia-with-roslyn

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!