在区域琐事与Roslyn之间插入代码

时间:2016-05-31 17:47:09

标签: c# roslyn

如何在Roslyn中的region指令后插入变量声明?我希望能够做到这样的事情:

class MyClass 
{
    #region myRegion
    #endregion
}

到此:

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

我似乎找不到任何以这种方式处理琐事的例子。

2 个答案:

答案 0 :(得分:3)

使用CSharpSyntaxRewriter非常棘手,因为#region <name>#endregion最终都在同一个SyntaxTriviaList中,而你{&#39}必须拆分并找出要创建的内容。不打扰所有错综复杂的最简单方法是创建相应的TextChange并修改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\";")));

之后,tree是:

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

答案 1 :(得分:0)

m0sa的答案适用于空白区域,但不能替换现有代码,这可能是需要这样做的原因,即重新运行代码生成工具。

要实现这一目标,需要找到整个区域。由于目标文件可能包含多个嵌套区域,因此这也很困难。为此,我处理所有指令并建立区域层次结构:

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;
    }
}