George Mamaladze
George Mamaladze

Reputation: 7931

Replace all class usages using roslyn

What I am trying to do: Let's assume we have a class Class1 inside assembly OldAssembly. This assembly is referenced by a number of unknown projects. I am going to move this class into a new assembly NewAssembly, eventually name and namespace would also change. Now all usages must be adjusted. I want to create a tool which will automate these code adjustments.

What I did so far: I have played around with roslyn Renamer:

var workspace = MSBuildWorkspace.Create();
var originalSln = workspace.OpenSolutionAsync(@"D:\spikes\ToBeFixed\ToBeFixed.sln").Result;

var project = originalSln.Projects.Single();
var compilation = project.GetCompilationAsync().Result;

var renameFrom = compilation.GetSymbolsWithName(s => s.Contains("Class1")).Single();
const string renameTo = "Class2";
var optionSet = originalSln.Workspace.Options;
var modifiedSln = Renamer.RenameSymbolAsync(originalSln, renameFrom, renameTo, optionSet).Result;

workspace.TryApplyChanges(modifiedSln);

but it renames also the source class. So I have looked into the Renamer code and tried to adapt it for my use case, but failed due to some internals used in there.

Question: How can I automate code adjustments after moving a class from one assembly into another.

Upvotes: 2

Views: 2390

Answers (1)

JoshVarty
JoshVarty

Reputation: 9426

You can achieve what you're looking for by:

  1. Renaming all usages of the class throughout the entire solution
  2. Replacing the renamed class itself with its original self.

    public async static Task TestingRenamer()
    {
        var code = @"
        using System;
    
        //We do not want to rename MyClass (we want to keep it the same)
        public class MyClass
        {
            public MyClass()
            {
            }
        }
    
        public class Program
        {
            public static void Main()
            {
                //We want to rename this usage
                var x = new MyClass();  
            }   
        }";
    
        var newClassName = "MY_NEW_CLASS_NAME";
        var document = getDocumentForCode(code);
        var compilation = await document.Project.GetCompilationAsync();
    
        var root = await document.GetSyntaxRootAsync();
        var originalClass = root.DescendantNodesAndSelf().OfType<ClassDeclarationSyntax>().First();
    
        var model = compilation.GetSemanticModel(root.SyntaxTree);
        var originalSymbol = model.GetDeclaredSymbol(originalClass);
    
        //Rename all 
        var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, originalSymbol, newClassName, document.Project.Solution.Workspace.Options);
    
        //Revert the original class
        var newDocument = newSolution.GetDocument(document.Id);
        var newSyntaxRoot = await newDocument.GetSyntaxRootAsync();
        var newClass = newSyntaxRoot.DescendantNodes().OfType<ClassDeclarationSyntax>().Where(n => n.Identifier.ToString() == newClassName).Single();
        newSyntaxRoot = newSyntaxRoot.ReplaceNode(newClass, originalClass);
        newDocument = newDocument.WithSyntaxRoot(newSyntaxRoot);
    
        //We've now renamed all usages and reverted the class back to its original self.
        var finalSolution = newDocument.Project.Solution;
    }
    
    //Helper method to build Document
    private static Document getDocumentForCode(string code)
    {
        var ws = new AdhocWorkspace();
        var Mscorlib = MetadataReference.CreateFromAssembly(typeof(object).Assembly);
        var references = new List<MetadataReference>() { Mscorlib };
        var projInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Default, "MyProject", "MyAssembly", "C#", metadataReferences: references);
        var project = ws.AddProject(projInfo);
    
        var text = SourceText.From(code);
        var myDocument = ws.AddDocument(project.Id, "MyDocument.cs", text);
        return myDocument;
    }
    

Upvotes: 4

Related Questions