如何在没有 "not found to remove" 异常的情况下获取重命名 class 的语义模型

How to get semantic model for renamed class without "not found to remove" exception

这是一个脚本

  1. 使用语义模型
  2. 识别class的类型信息
  3. 通过替换其标识符标记重命名 class
  4. 使用其语义模型
  5. 识别重命名的 class 的类型信息
  6. 在重命名的 class 上运行 SyntaxRewriter - 重写器将所有出现的旧类型信息替换为新类型信息。

(我希望)尽可能少:

static void Main(string[] args)
{
    // This class needs to be renamed. We want to use Roslyn and symbols,
    // because string-based replacement would change things that it's not supposed to.
    var someScript = "public class FooBar { public NotFooBar a; FooBar string b; public OtherNamespace.FooBar c; }";

    // Minimal Roslyn boilerplate
    var compilation = CSharpCompilation.Create("CodeGeneration")
        .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
        .AddSyntaxTrees(CSharpSyntaxTree.ParseText(someScript));

    var tree = compilation.SyntaxTrees[0];
    var classDeclaration = tree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
    var originalType = compilation.GetSemanticModel(classDeclaration.SyntaxTree).GetDeclaredSymbol(classDeclaration);
    var renamedClass = classDeclaration.ReplaceToken(
        classDeclaration.Identifier,
        SyntaxFactory.Identifier(classDeclaration.Identifier.Text.Replace("Bar", "Baz"))
    );
    var semanticModel = compilation.GetSemanticModel(renamedClass.SyntaxTree);
    var renamedType = semanticModel.GetDeclaredSymbol(renamedClass);
    var rewritten = new TestRewriter(semanticModel, originalType, renamedType).Visit(renamedClass);
    Console.Write(rewritten.GetText());
}

class TestRewriter : CSharpSyntaxRewriter
{
    private readonly SemanticModel _model;
    private readonly ITypeSymbol _replace;
    private readonly ITypeSymbol _replacement;

    public TestRewriter(SemanticModel model, ITypeSymbol replace, ITypeSymbol replacement)
    {
        _model = model;
        _replace = replace;
        _replacement = replacement;
    }

    public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
    {
        return base.VisitIdentifierName(
            _model.GetTypeInfo(node).Type.Equals(_replace)
                ? node.ReplaceToken(node.Identifier, SyntaxFactory.Identifier(_replacement.ToDisplayString()))
                : node
        );
    }
}

当运行这个程序时,compilation.GetSemanticModel(renamedClass.SyntaxTree)导致如下异常

System.ArgumentException: SyntaxTree public class FooBaz{ public NotFooBar a; FooBar string b; public OtherNamespace.FooBar c; } not found to remove

如果不是从重命名的 class' 树中获取语义模型,而是使用原始 class' 树中的语义模型,那么 GetTypeInfo(node) 将(如预期的那样) ) 抛出异常,因为 node 不是模型树的一部分。

我错过了什么步骤可以防止 "not found to remove" 异常?

您收到的错误是由于您在 SyntaxTree 中搜索不包含所述元素的元素。

当您调用 classDeclaration.ReplaceToken 时,您实际上并没有替换现有 SyntaxTree 中的标记,而是生成了一个与现有结构无关的新结构。当您尝试从重命名的类中获取语义模型时,您使用的语法树不在编译中,这会导致代码抛出异常。

为了解决这个问题,您可以替换现有CompilationUnit中的节点,然后替换Compilation中的SyntaxTree。但是,您将无法再次找到 ClassDeclaration,因为它变成了一个与现有 "renamedClass" 对象不对应的新对象。

为了跟踪元素,您可以使用 ,它允许您向节点添加信息,以便稍后检索。这可能看起来像这样:

    static void ReplaceClassName()
    {
        // This class needs to be renamed. We want to use Roslyn and symbols,
        // because string-based replacement would change things that it's not supposed to.
        var someScript = "public class FooBar { public NotFooBar a; FooBar b; public OtherNamespace.FooBar c; }";

        // Minimal Roslyn boilerplate
        var compilation = CSharpCompilation.Create("CodeGeneration")
            .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
            .AddSyntaxTrees(CSharpSyntaxTree.ParseText(someScript));
        var oldSyntaxTree = compilation.SyntaxTrees[0];
        var root = oldSyntaxTree.GetRoot();
        var classDeclaration = root.DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
        var originalType = compilation.GetSemanticModel(classDeclaration.SyntaxTree).GetDeclaredSymbol(classDeclaration);
        var renamedClass = classDeclaration.ReplaceToken(
            classDeclaration.Identifier,
            SyntaxFactory.Identifier(classDeclaration.Identifier.Text.Replace("Bar", "Baz"))
        ).WithAdditionalAnnotations(new SyntaxAnnotation("ReplacedClass"));
        root = root.ReplaceNode(classDeclaration, renamedClass);
        compilation = compilation.ReplaceSyntaxTree(oldSyntaxTree, renamedClass.SyntaxTree);
        renamedClass = compilation.SyntaxTrees[0].GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault(curr => curr.GetAnnotations("ReplacedClass").Any());
        var semanticModel = compilation.GetSemanticModel(compilation.SyntaxTrees[0]);
        var renamedType = semanticModel.GetDeclaredSymbol(renamedClass);
        var rewritten = new TestRewriter(semanticModel, originalType, renamedType).Visit(renamedClass);
        Console.Write(rewritten.GetText());
    }

遗憾的是,这并不能真正解决您的问题。虽然您现在有一个新的 SemanticModel 可用,但您将无法像在 SyntaxRewriter 中那样比较两个不同模型的 TypeInfo。

为了保留您的类型信息,我建议您使用 SyntaxRewriter 更改 IdentifierName 和 ClassDeclarationSyntax,因为 SyntaxRewriter 将首先访问节点,然后才替换它们。这可以按如下方式实现:

static void ReplaceClassName()
{
    // This class needs to be renamed. We want to use Roslyn and symbols,
    // because string-based replacement would change things that it's not supposed to.
    var someScript = "public class FooBar { public NotFooBar a; FooBar b; public OtherNamespace.FooBar c; }";

    // Minimal Roslyn boilerplate
    var compilation = CSharpCompilation.Create("CodeGeneration")
        .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
        .AddSyntaxTrees(CSharpSyntaxTree.ParseText(someScript));
    var oldSyntaxTree = compilation.SyntaxTrees[0];
    var root = oldSyntaxTree.GetRoot();
    var classDeclaration = root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
    var semanticModel = compilation.GetSemanticModel(classDeclaration.SyntaxTree);
    var originalType = semanticModel.GetDeclaredSymbol(classDeclaration);
    var rewritten = new TestRewriter(semanticModel, originalType).Visit(root);
    Console.Write(rewritten.GetText());
}

class TestRewriter : CSharpSyntaxRewriter
{
    private readonly SemanticModel _model;
    private readonly ITypeSymbol _replace;
    public TestRewriter(SemanticModel model, ITypeSymbol replace)
    {
        _model = model;
        _replace = replace;
    }

    public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
    {
        node = base.VisitIdentifierName(node) as IdentifierNameSyntax;
        if (_model.GetTypeInfo(node).Type.Equals(_replace))
        {
            var oldNode = node;
            node = node.ReplaceToken(

                        node.Identifier,
                        SyntaxFactory.Identifier(node.Identifier.Text.Replace("Bar", "Baz"))
                ).WithLeadingTrivia(oldNode.GetLeadingTrivia()).WithTrailingTrivia(oldNode.GetTrailingTrivia());
        }
        return node;
    }

    public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        var oldNode = node;
        node = base.VisitClassDeclaration(node) as ClassDeclarationSyntax;
        if (_model.GetDeclaredSymbol(oldNode).Equals(_replace))
        {
            return node.WithIdentifier(SyntaxFactory.Identifier(node.Identifier.Text.Replace("Bar", "Baz")));
        }
        return node;
    }
}

如果您确实有可用的解决方案,您可以使用 Renamer class,它应该会为您完成所有工作。