Как часть пользовательского процесса компиляции, я заменяю различные узлы в SyntaxTree для генерации действительного C #.Проблема возникает, когда заменяемые узлы являются вложенными, поскольку неизменность всех типов означает, что как только один узел заменяется, в его иерархии больше нет равенства.
Уже существует aаналогичный вопрос для SO , однако он, похоже, нацелен на более старую версию Roslyn и полагается на некоторые методы, которые сейчас являются закрытыми.У меня уже есть SyntaxTree
и SemanticModel
, но пока мне не нужно Document
с, Project
с или Solution
с, поэтому я не решался идти по этому маршруту.
Предположим, у меня есть следующая строка public void Test() { cosh(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
правильный подход?И если да, то как можно заставить его работать?
Ответ, предоставленный Джорджем Александрией, жизненно важно, чтобы базовый метод 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;
}
}