[英]Replacing nested nodes in a Roslyn SyntaxTree
作為自定義編譯過程的一部分,我將替換SyntaxTree中的各個節點以生成有效的C#。 當替換的節點嵌套時,會發生問題,因為所有類型的不變性意味着,一旦交換出一個節點,其層次結構就不再平等。
關於SO ,已經存在一個類似的問題 ,但是它似乎針對的是Roslyn的較舊版本,並依賴於現在私有的某些方法。 我已經有了SyntaxTree
和SemanticModel
,但是到目前為止,我並不需要Document
, Project
或Solution
,所以我一直猶豫要走那條路線。
假設我有以下字符串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.