C# Roslyn修改代码
源app
class Program { /// <summary> /// 方法入口123 /// </summary> /// <param name="args"></param> static void Main(string[] args) { Program.Plus(3,3); var c1 = Plus(1, 2); var c2 = Plus(3, 4); var c3 = Plus(5,55); var c4 = Plus(6, 666); } static int Plus(int a,int b) { return a + b; } } }
roslyn代码:
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Build.Locator; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.MSBuild; namespace _2021_12_14_Roslyn批量修改代码 { class Program { private static void Workspace_WorkspaceFailed(object sender, Microsoft.CodeAnalysis.WorkspaceDiagnosticEventArgs e) { var x = e.Diagnostic.Message; //Console.WriteLine(x); //无法打开项目,因为语言“C#”不受支持 //Microsoft.CodeAnalysis //处理文件“D:\测试代码\2021-12-14-Roslyn批量修改代码-测试解决方案\2021-12-14-Roslyn批量修改代码-测试解决方案\2021-12-14-Roslyn批量修改代码-测试解决方案.csproj”时 Msbuild 失败,出现消息: The tools version "Current" is unrecognized. Available tools versions are "14.0", "2.0", "3.5", "4.0". D:\测试代码\2021-12-14-Roslyn批量修改代码-测试解决方案\2021-12-14-Roslyn批量修改代码-测试解决方案\2021-12-14-Roslyn批量修改代码-测试解决方案.csproj //修改build版本: //Microsoft.Build 16.10.0 //Microsoft.Build.Framework 16.10.0 //Microsoft.Build.Locator 1.4.1 //Microsoft.CodeAnalysis.Workspaces.MSBuild 3.9.0 //Microsoft.Net.Compilers } static void Main(string[] args) { //安装 MSBuildLocator MSBuildLocator.RegisterDefaults(); var solutionPath = @"D:\测试代码\2022-06-27-DemoApp\2022-06-27-DemoApp.sln"; var projectName = "2022-06-27-DemoApp"; var csFileName = "Program.cs"; var className1 = "Program"; CheckMethod(solutionPath, projectName, csFileName, className1, "Plus", "(int a,int b)", 1); Console.WriteLine(DateTime.Now.ToString() + " 完成"); Console.Read(); } private static void CheckMethod(string solutionPath, string projectName, string csFileName, string className1, string methodName, string methodParListString, int maxArgumentCount) { //安装Microsoft.CodeAnalysis.Workspaces.MSBuild //安装Microsoft.Build.Framework using (var workspace = MSBuildWorkspace.Create()) { workspace.SkipUnrecognizedProjects = true; workspace.LoadMetadataForReferencedProjects = false; workspace.WorkspaceFailed += Workspace_WorkspaceFailed; // Selects a Solution File //安装Microsoft.Build var solution = workspace.OpenSolutionAsync(solutionPath).Result; //项目库 var findProject = solution.Projects.FirstOrDefault(x => x.Name == projectName); var csFileDoc = findProject.Documents.FirstOrDefault(x => x.Name == csFileName); var semanticModel2 = csFileDoc.GetSemanticModelAsync().Result; var rootNode2 = csFileDoc.GetSyntaxRootAsync().Result; var nodeClass = rootNode2.DescendantNodes().OfType<ClassDeclarationSyntax>() .FirstOrDefault(x => x.Identifier.ToString() == className1); var nodeMethod = nodeClass.DescendantNodes() .OfType<MethodDeclarationSyntax>().FirstOrDefault(x => x.Identifier.ToString() == methodName && x.ParameterList.ToString() == methodParListString); var sampleMethodSymbol1 = semanticModel2.GetDeclaredSymbol(nodeMethod); //查找引用 var referencesToSampleMethod2 = SymbolFinder.FindReferencesAsync(sampleMethodSymbol1, solution).Result; var groupByFileName = GetReferenceFileLocations(referencesToSampleMethod2).GroupBy(x => x.Document.FilePath); referencesToSampleMethod2.GroupBy(x => x.Locations.First().Document.Name); foreach (var gpFile in groupByFileName) { Console.WriteLine(DateTime.Now.ToString() + " 个数=" + gpFile.Count() + " 找到引用源:" + gpFile.Key); var first = gpFile.FirstOrDefault(); //文档 var docId = first.Document.Id; var sourceTree = first.Location.SourceTree; //root var root = sourceTree.GetRoot(); var expressionFullTextList = GetExpressionFullText(gpFile.ToList(), root); var reviter = new VisitExpressionStatementChanger(className1, methodName, expressionFullTextList, maxArgumentCount); root = reviter.Visit(root); solution = solution.WithDocumentSyntaxRoot(docId, root); } var result = workspace.TryApplyChanges(solution); } } private static List<string> GetExpressionFullText(List<ReferenceLocation> refList, SyntaxNode root) { var expressionList = new List<string>(); foreach (var referenceLocation in refList) { var nodeFind = root.FindNode(referenceLocation.Location.SourceSpan); //method node var parentMethod = GetParentMethod<ExpressionStatementSyntax>(nodeFind); if (parentMethod==null) { continue; } var txt = parentMethod.Expression.ToFullString(); expressionList.Add(txt); } return expressionList; } private static List<ReferenceLocation> GetReferenceFileLocations(IEnumerable<ReferencedSymbol> referencesToSampleMethod2) { var result = new List<ReferenceLocation>(); foreach (var referencedSymbol in referencesToSampleMethod2) { foreach (var location in referencedSymbol.Locations) { result.Add(location); } } return result; } private static T GetParentMethod<T>(SyntaxNode nodeFind) where T : class { if (nodeFind.Parent == null) { return null; } if (nodeFind.Parent is T mm) { return mm; } else { return GetParentMethod<T>(nodeFind.Parent); } } } /// <summary> /// The CSharpSyntaxRewriter allows to rewrite the Syntax of a node /// </summary> public class VisitExpressionStatementChanger : CSharpSyntaxRewriter { private readonly string _methodName; private readonly int _argumentCountLimit; private readonly string _className; private readonly List<string> _referenceExpressionList; public VisitExpressionStatementChanger(string methodClassName, string methodName, List<string> expressionFullTxtList, int argumentCountLimitValue) { _className = methodClassName; _methodName = methodName; _referenceExpressionList = expressionFullTxtList; _argumentCountLimit = argumentCountLimitValue; } public override SyntaxNode VisitExpressionStatement(ExpressionStatementSyntax node) { var nodeParentIsMethod = _referenceExpressionList.Contains(node.Expression.ToFullString()); if (nodeParentIsMethod) { //Console.WriteLine(node.Expression.ToFullString()); var leadingTrivia = node.GetLeadingTrivia(); var trailingTrivia = node.GetTrailingTrivia(); //判断参数是不是多于2个 var invocationFirst = node.DescendantNodes().OfType<InvocationExpressionSyntax>().First(); var argumentList = invocationFirst.ArgumentList.Arguments.ToList(); if (argumentList.Count > _argumentCountLimit) { //计算空格 var firstToken = node.DescendantTokens().First(); var whiteSpaceTrivia = firstToken.LeadingTrivia.FirstOrDefault(x => x.IsKind(SyntaxKind.WhitespaceTrivia)); var whiteSpaceLength = whiteSpaceTrivia.FullSpan.Length; var whiteSpace = string.Join("", Enumerable.Range(1, whiteSpaceLength).Select(x => " ")); if (_argumentCountLimit <= 0) { //不要参数,所有参数都忽略 var replaceNode = SyntaxFactory.ParseStatement($"{whiteSpace}{_className}.{_methodName}();\r\n"); Console.WriteLine("忽略参数:" + node.Expression + " 到新的->>>>>" + replaceNode.ToFullString().Replace('\r', ' ').Replace('\n', '\\')); return replaceNode; } else if (_argumentCountLimit >= 1) { var firstParam = argumentList[0].Expression; var secondParam = argumentList[1].Expression; var replaceNode = SyntaxFactory.ParseStatement($"{leadingTrivia.ToFullString()}var a = (int)({firstParam});\r\n{whiteSpace}var b= (int)({secondParam});\r\n {whiteSpace}{_className}.{_methodName}(a,b);{trailingTrivia.ToFullString()}\r\n"); Console.WriteLine("修改参数:" + node.Expression + " 到新的->>>>>" + replaceNode.ToFullString().Replace('\r', ' ').Replace('\n', '\\')); return replaceNode; } } } return base.VisitExpressionStatement(node); } } }
效果:
class Program { /// <summary> /// 方法入口123 /// </summary> /// <param name="args"></param> static void Main(string[] args) { var a = (int)(3); var b= (int)(3); Program.Plus(a,b); var c1 = Plus(1, 2); var c2 = Plus(3, 4); var c3 = Plus(5,55); var c4 = Plus(6, 666); } static int Plus(int a,int b) { return a + b; } }
nuget包:
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="Humanizer.Core" version="2.13.14" targetFramework="net472" /> <package id="Microsoft.Bcl.AsyncInterfaces" version="6.0.0" targetFramework="net472" /> <package id="Microsoft.Build" version="16.10.0" targetFramework="net472" /> <package id="Microsoft.Build.Framework" version="16.10.0" targetFramework="net472" /> <package id="Microsoft.Build.Locator" version="1.4.1" targetFramework="net462" /> <package id="Microsoft.CodeAnalysis" version="3.9.0" targetFramework="net472" /> <package id="Microsoft.CodeAnalysis.Analyzers" version="3.3.3" targetFramework="net472" developmentDependency="true" /> <package id="Microsoft.CodeAnalysis.Common" version="3.9.0" targetFramework="net472" /> <package id="Microsoft.CodeAnalysis.CSharp" version="3.9.0" targetFramework="net472" /> <package id="Microsoft.CodeAnalysis.CSharp.Workspaces" version="3.9.0" targetFramework="net472" /> <package id="Microsoft.CodeAnalysis.VisualBasic" version="3.9.0" targetFramework="net472" /> <package id="Microsoft.CodeAnalysis.VisualBasic.Workspaces" version="3.9.0" targetFramework="net472" /> <package id="Microsoft.CodeAnalysis.Workspaces.Common" version="3.9.0" targetFramework="net472" /> <package id="Microsoft.CodeAnalysis.Workspaces.MSBuild" version="3.9.0" targetFramework="net472" /> <package id="Microsoft.Net.Compilers" version="4.0.1" targetFramework="net472" developmentDependency="true" /> <package id="Microsoft.NET.StringTools" version="1.0.0" targetFramework="net472" /> <package id="Microsoft.VisualStudio.Setup.Configuration.Interop" version="3.0.4492" targetFramework="net472" developmentDependency="true" /> <package id="System.Buffers" version="4.5.1" targetFramework="net472" /> <package id="System.Collections.Immutable" version="6.0.0" targetFramework="net472" /> <package id="System.Composition" version="6.0.0" targetFramework="net472" /> <package id="System.Composition.AttributedModel" version="6.0.0" targetFramework="net472" /> <package id="System.Composition.Convention" version="6.0.0" targetFramework="net472" /> <package id="System.Composition.Hosting" version="6.0.0" targetFramework="net472" /> <package id="System.Composition.Runtime" version="6.0.0" targetFramework="net472" /> <package id="System.Composition.TypedParts" version="6.0.0" targetFramework="net472" /> <package id="System.Configuration.ConfigurationManager" version="6.0.0" targetFramework="net472" /> <package id="System.IO.Pipelines" version="6.0.0" targetFramework="net472" /> <package id="System.Memory" version="4.5.4" targetFramework="net472" /> <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" /> <package id="System.Reflection.Metadata" version="6.0.0" targetFramework="net472" /> <package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net472" /> <package id="System.Security.AccessControl" version="6.0.0" targetFramework="net472" /> <package id="System.Security.Permissions" version="6.0.0" targetFramework="net472" /> <package id="System.Security.Principal.Windows" version="5.0.0" targetFramework="net472" /> <package id="System.Text.Encoding.CodePages" version="6.0.0" targetFramework="net472" /> <package id="System.Text.Encodings.Web" version="6.0.0" targetFramework="net472" /> <package id="System.Text.Json" version="6.0.0" targetFramework="net472" /> <package id="System.Threading.Tasks.Dataflow" version="6.0.0" targetFramework="net472" /> <package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" /> <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" /> </packages>