簡體   English   中英

替換Roslyn語法樹中的嵌套節點

[英]Replacing nested nodes in a Roslyn SyntaxTree

作為自定義編譯過程的一部分,我將替換SyntaxTree中的各個節點以生成有效的C#。 當替換的節點嵌套時,會發生問題,因為所有類型的不變性意味着,一旦交換出一個節點,其層次結構就不再平等。

關於SO ,已經存在一個類似的問題 ,但是它似乎針對的是Roslyn的較舊版本,並依賴於現在私有的某些方法。 我已經有了SyntaxTreeSemanticModel ,但是到目前為止,我並不需要DocumentProjectSolution ,所以我一直猶豫要走那條路線。

假設我有以下字符串public void Test() { cosh(x); } public void Test() { cosh(x); } ,我想將其轉換為public void Test() { MathNet.Numerics.Trig.Cosh(__resolver["x"]); } public void Test() { MathNet.Numerics.Trig.Cosh(__resolver["x"]); }

我第一次使用ReplaceNodes()嘗試失敗了,因為一旦進行了一次替換,樹的變化就足以使第二次比較失敗。 因此,僅進行cosh替換, x保持不變:

public static void TestSyntaxReplace()
{
  const string code = "public void Test() { cosh(x); }";
  var tree = CSharpSyntaxTree.ParseText(code);
  var root = tree.GetRoot();
  var swap = new Dictionary<SyntaxNode, SyntaxNode>();

  foreach (var node in root.DescendantNodes())
    if (node is InvocationExpressionSyntax oldInvocation)
    {
      var newExpression = ParseExpression("MathNet.Numerics.Trig.Cosh");
      var newInvocation = InvocationExpression(newExpression, oldInvocation.ArgumentList);
      swap.Add(node, newInvocation);
    }

  foreach (var node in root.DescendantNodes())
    if (node is IdentifierNameSyntax identifier)
      if (identifier.ToString() == "x")
      {
        var resolver = IdentifierName("__resolver");
        var literal = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(identifier.ToString()));
        var argument = BracketedArgumentList(SingletonSeparatedList(Argument(literal)));
        var resolverCall = ElementAccessExpression(resolver, argument);
        swap.Add(node, resolverCall);
      }

  root = root.ReplaceNodes(swap.Keys, (n1, n2) => swap[n1]);
  var newCode = root.ToString();
}

我很欣賞在這種情況下可能無需執行任何操作, ReplaceNodes根本無法處理嵌套的替換。


根據以上鏈接中的答案,我切換到SyntaxVisitor ,它根本無法做任何事情。 我的重寫方法從不調用, Visit()方法返回一個空節點:

public static void TestSyntaxVisitor()
{
  const string code = "public void Test() { cosh(x); }";
  var tree = CSharpSyntaxTree.ParseText(code);
  var root = tree.GetRoot();

  var replacer = new NodeReplacer();
  var newRoot = replacer.Visit(root); // This just returns null.
  var newCode = newRoot.ToString();
}
private sealed class NodeReplacer : CSharpSyntaxVisitor<SyntaxNode>
{
  public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
  {
    if (node.ToString().Contains("cosh"))
    {
      var newExpression = ParseExpression("MathNet.Numerics.Trig.Cosh");
      node = InvocationExpression(newExpression, node.ArgumentList);
    }
    return base.VisitInvocationExpression(node);
  }

  public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
  {
    if (node.ToString() == "x")
    {
      var resolver = IdentifierName("__resolver");
      var literal = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(node.ToString()));
      var argument = BracketedArgumentList(SingletonSeparatedList(Argument(literal)));
      return ElementAccessExpression(resolver, argument);
    }

    return base.VisitIdentifierName(node);
  }
}

問題: CSharpSyntaxVisitor是否正確? 如果是這樣,如何使它起作用?


回答由George Alexandria提供的,至關重要的是首先調用基本的Visit方法,否則將無法再使用SemanticModel。 這是對我有用的SyntaxRewriter:

private sealed class NonCsNodeRewriter : CSharpSyntaxRewriter
{
  private readonly SemanticModel _model;
  public NonCsNodeRewriter(SemanticModel model)
  {
    _model = model;
  }

  public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
  {
    var invocation = (InvocationExpressionSyntax)base.VisitInvocationExpression(node);
    var symbol = _model.GetSymbolInfo(node);
    if (symbol.Symbol == null)
      if (!symbol.CandidateSymbols.Any())
      {
        var methodName = node.Expression.ToString();
        if (_methodMap.TryGetValue(methodName, out var mapped))
          return InvocationExpression(mapped, invocation.ArgumentList);
      }

    return invocation;
  }
  public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
  {
    var identifier = base.VisitIdentifierName(node);
    var symbol = _model.GetSymbolInfo(node);
    if (symbol.Symbol == null)
      if (!symbol.CandidateSymbols.Any())
      {
        // Do not replace unknown methods, only unknown variables.
        if (node.Parent.IsKind(SyntaxKind.InvocationExpression))
          return identifier;

        return CreateResolverIndexer(node.Identifier);
      }
    return identifier;
  }

  private static SyntaxNode CreateResolverIndexer(SyntaxToken token)
  {
    var literal = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(token.ToString()));
    var argument = BracketedArgumentList(SingletonSeparatedList(Argument(literal)));
    var indexer = ElementAccessExpression(IdentifierName("__resolver"), argument);
    return indexer;
  }
}

是否需要ReplaceNode() ,但應該從深度替換節點,因此在當前深度級別中,只有一個更改可用於比較。

您可以重寫第一個示例,保存交換順序並保存中間的SyntaxTree ,它將起作用。 但是Roslyn具有深度一階重寫的CSharpSyntaxRewriter實現– CSharpSyntaxRewriter並且在您發布@JoshVarty的鏈接中指向CSharpSyntaxRewriter

第二個示例不起作用,因為您使用了自定義CSharpSyntaxVisitor<SyntaxNode> ,該自定義CSharpSyntaxVisitor<SyntaxNode>設計和調用replacer.Visit(root);來深入了解replacer.Visit(root); 您只需調用VisitCompilationUnit(...) 取而代之的是, CSharpSyntaxRewriter轉到子節點,並將為所有子節點調用Visit*()方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM