C# 替换Roslyn语法树中的嵌套节点
作为自定义编译过程的一部分,我将替换SyntaxTree中的各种节点,以生成有效的C。当替换的节点嵌套时,就会出现问题,因为所有类型的不变性意味着一旦替换出一个节点,它的层次结构中就不再平等 然而,它似乎已经瞄准了旧版本的Roslyn,并且依赖于一些现在是私有的方法。我已经有了一个SyntaxTree和一个SemanticModel,但到目前为止我还不需要文档、项目或解决方案,所以我一直在犹豫是否要走这条路 假设我有以下字符串public void Test{coshx;},我想将其转换为public void Test{MathNet.Numerics.Trig.Cosh_u解析器[x];} 我第一次尝试使用ReplaceNodes失败,因为一旦进行了一次替换,树就会发生足够的变化,导致第二次比较失败。因此,仅进行cosh替换,x保持不变:C# 替换Roslyn语法树中的嵌套节点,c#,roslyn,abstract-syntax-tree,C#,Roslyn,Abstract Syntax Tree,作为自定义编译过程的一部分,我将替换SyntaxTree中的各种节点,以生成有效的C。当替换的节点嵌套时,就会出现问题,因为所有类型的不变性意味着一旦替换出一个节点,它的层次结构中就不再平等 然而,它似乎已经瞄准了旧版本的Roslyn,并且依赖于一些现在是私有的方法。我已经有了一个SyntaxTree和一个SemanticModel,但到目前为止我还不需要文档、项目或解决方案,所以我一直在犹豫是否要走这条路 假设我有以下字符串public void Test{coshx;},我想将其转换为pub
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();
}
问题:CSharpSyntaxVisitor是正确的方法吗?如果是这样的话,我们如何让它工作呢
答案正如George Alexandria所提供的,首先调用基本访问方法是至关重要的,否则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,在您发布的@JoshVarty指向CSharpSyntaxRewriter的链接中
您的第二个示例不起作用,因为您使用了定制的CSharpSyntaxVisitor,它在调用replacer.visitrot时不会深入到deep;您仅调用VisitComplationUnit。。。没有别的了。相反,CSharpSyntaxRewriter将转到子节点,并为所有子节点调用Visit*方法。ReplaceNode是您需要的,但您应该从深度替换节点,以便在当前深度级别中,只有一个用于比较的更改。为了达到更深层次的一阶重写,Roslyn在上面的链接中有一个链接,您在@JoshVarty上发布的链接指向CSharpSyntaxRewriter。您还可以重写第一个示例,保存交换顺序并保存一个中间语法树,它会起作用。@GeorgeAlexandria我的第二次尝试使用CSharpSyntaxRewriter,但它似乎根本不起作用。我将尝试修改ReplaceNodes方法,以迭代所有交换对,并首先执行嵌套最深的交换对。谢谢你的想法。不,你第二次尝试使用定制的CSharpSyntaxVisitor,它不会通过设计CSharpSyntaxVisitor而深入。因此,当您调用var newRoot=replace.visitrot时;您真正调用的是VisitComplationIntrootSyntax,而不是其他任何东西。CSharpSyntaxRewriter将代替CSharpSyntaxRewriter转到子节点。@GeorgeAlexandria啊!重写者与访问者。就这样,谢谢!如果你把它作为答案贴出来,我可以接受。
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;
}
}