.net Roslyn 测试分析器 电脑版发表于:2024/10/8 14:15 ![](https://img.tnblog.net/arcimg/hb/12fd3b511cec4b60a83c422f92c4ed80.png) >#.net Roslyn 测试分析器 [TOC] tn2>关于项目的创建请参考:https://www.tnblog.net/hb/article/details/8473 简单测试方式 ------------ tn2>首先打开我们的`MyRoslynUnitTest`测试类。 ![](https://img.tnblog.net/arcimg/hb/113d2ef6cda84091bdac340a593c0eed.png) tn2>在头部引用相关的命名空间。 ```csharp using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using System.Collections.Immutable; using System; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis.Diagnostics; ``` tn2>然后我们通过在当前解决方案类中创建一个新的的项目,并在其中添加一个`File.cs`的类,把代码放入其中后,对其进行诊断。 具体代码如下: ```csharp private static async Task<ImmutableArray<Diagnostic>> GetDiagnostics(string code) { // 创建一个 AdhocWorkspace 实例,用于处理代码分析过程 AdhocWorkspace workspace = new AdhocWorkspace(); // 获取当前工作空间中的解决方案 var solution = workspace.CurrentSolution; // 创建一个新的项目 ID var projectId = ProjectId.CreateNewId(); // 在解决方案中添加一个新项目,命名为 "MyTestProject" solution = solution .AddProject( projectId, // 项目 ID "MyTestProject", // 项目名称 "MyTestProject", // 项目路径 LanguageNames.CSharp); // 项目语言为 C# // 为项目添加一个包含代码内容的文档 "File.cs" solution = solution .AddDocument(DocumentId.CreateNewId(projectId), // 文档 ID "File.cs", // 文档名称 code); // 文档内容,即传入的代码 // 从解决方案中获取刚刚创建的项目 var project = solution.GetProject(projectId); // 为项目添加一个元数据引用,引用系统的核心库 mscorlib (即 object 类所在的程序集) project = project.AddMetadataReference( MetadataReference.CreateFromFile( typeof(object).Assembly.Location)) // 添加其他必要的引用,用于解析 ImmutableArray 类型的依赖项 .AddMetadataReferences(GetAllReferencesNeededForType(typeof(ImmutableArray))); // 获取项目的编译结果 var compilation = await project.GetCompilationAsync(); // 创建一个包含自定义分析器的编译结果 var compilationWithAnalyzer = compilation.WithAnalyzers( ImmutableArray.Create<DiagnosticAnalyzer>( new CreationAnalyzer())); // 使用 CreationAnalyzer 分析器 // 获取并返回所有诊断结果 var diagnostics = await compilationWithAnalyzer.GetAllDiagnosticsAsync(); return diagnostics; } // 获取指定类型所需的所有元数据引用 private static MetadataReference[] GetAllReferencesNeededForType(Type type) { // 获取该类型所需的所有程序集文件,并转换为元数据引用数组 var files = GetAllAssemblyFilesNeededForType(type); return files.Select(x => MetadataReference.CreateFromFile(x)) // 创建元数据引用 .Cast<MetadataReference>() .ToArray(); // 转换为 MetadataReference 数组 } // 获取指定类型所需的所有程序集文件路径 private static ImmutableArray<string> GetAllAssemblyFilesNeededForType(Type type) { // 获取该类型的程序集及其引用的所有程序集 return type.Assembly.GetReferencedAssemblies() // 获取引用的所有程序集 .Select(x => Assembly.Load(x.FullName)) // 加载程序集 .Append(type.Assembly) // 包含自身的程序集 .Select(x => x.Location) // 获取程序集文件路径 .ToImmutableArray(); // 转换为 ImmutableArray<string> } ``` tn2>接下来我们定义一个`EmptyMethodGeneratesNoDiagnostics`测试方法,测试其中什么代码都不添加的情况下,诊断的数量为`0`。 ```csharp [TestMethod] public async Task EmptyMethodGeneratesNoDiagnostics() { // 定义一段空的 Main 方法代码,不执行任何操作 var code = @" public static class Program { public static void Main() { } }"; // 调用 GetDiagnostics 方法,对代码进行诊断,返回诊断结果 ImmutableArray<Diagnostic> diagnostics = await GetDiagnostics(code); // 断言诊断结果数量为 0,即空方法不应该产生任何诊断 Assert.AreEqual(0, diagnostics.Length); } ``` ![](https://img.tnblog.net/arcimg/hb/016e70323faf4854902c267e2f836b63.png) tn2>然后我们定义一个`CreatingAnImmutableArrayViaTheEmptyPropertyGeneratesOneDiagnostic`测试方法,这里肯定会触发我们的警告,所以我们断言了它的警告ID是否为`BadWayOfCreatingImmutableArray`,以及触发的行数在第7行。 ```csharp [TestMethod] public async Task CreatingAnImmutableArrayViaTheEmptyPropertyGeneratesOneDiagnostic() { // 定义一段代码,使用 ImmutableArray<int>.Empty 创建并添加元素 var code = @" using System.Collections.Immutable; public static class Program { public static void Main() { var array = ImmutableArray<int>.Empty.Add(1); } }"; // 调用 GetDiagnostics 方法,对代码进行诊断,返回诊断结果 ImmutableArray<Diagnostic> diagnostics = await GetDiagnostics(code); // 断言诊断结果数量为 1,即应该生成一个诊断信息 Assert.AreEqual(1, diagnostics.Length); // 获取诊断结果中的第一个诊断信息 var diagnostic = diagnostics[0]; // 断言诊断的 ID 是 "BadWayOfCreatingImmutableArray" Assert.AreEqual(diagnostic.Id, "BadWayOfCreatingImmutableArray"); // 获取诊断发生的位置 var location = diagnostic.Location; // 获取诊断的位置行信息 var lineSpan = location.GetLineSpan(); // 断言诊断发生在第 7 行代码,即 ImmutableArray 的创建行 Assert.AreEqual(7, lineSpan.StartLinePosition.Line); } ``` ![](https://img.tnblog.net/arcimg/hb/98d082bb22124e98bb4aa7934b2d3998.png) tn2>还定义了一个测试方法,由于我这里没有通过引用命名空间进行引用,而是直接通过命名空间点出来的类名,所以我们的分析根本找不到这个问题,也没办法弹出警告。自然在第一个断言时就报错。 代码如下: ```csharp [TestMethod] public async Task CreatingAnImmutableArrayViaTheEmptyPropertyWithoutOpeningTheImmutableNamespaceGeneratesOneDiagnostic() { // 定义一段代码,使用完整命名空间 System.Collections.Immutable.ImmutableArray 创建并添加元素 var code = @" public static class Program { public static void Main() { var array = System.Collections.Immutable.ImmutableArray<int>.Empty.Add(1); } }"; // 调用 GetDiagnostics 方法,对代码进行诊断,返回诊断结果 ImmutableArray<Diagnostic> diagnostics = await GetDiagnostics(code); // 断言诊断结果数量为 1,即应该生成一个诊断信息 Assert.AreEqual(1, diagnostics.Length); // 获取诊断结果中的第一个诊断信息 var diagnostic = diagnostics[0]; // 断言诊断的 ID 是 "BadWayOfCreatingImmutableArray" Assert.AreEqual(diagnostic.Id, "BadWayOfCreatingImmutableArray"); // 获取诊断发生的位置 var location = diagnostic.Location; // 获取诊断的位置行信息 var lineSpan = location.GetLineSpan(); // 断言诊断发生在第 5 行代码,即使用完整命名空间创建 ImmutableArray 的代码行 Assert.AreEqual(5, lineSpan.StartLinePosition.Line); } ``` ![](https://img.tnblog.net/arcimg/hb/18bef3f6e39948f5b587e66f3e47b321.png) tn2>我们可以通过判断它的命名符号(`Symbol`)来进行解决,当然验证的代码需要做一些修改。 ```csharp [DiagnosticAnalyzer(LanguageNames.CSharp)] public class CreationAnalyzer : DiagnosticAnalyzer { /// <summary> /// 定义诊断规则,包括诊断ID、标题、消息格式、分类、严重性等 /// </summary> private static DiagnosticDescriptor descriptor = new DiagnosticDescriptor( "BadWayOfCreatingImmutableArray", "Bad Way Of Creating Immutable Array", "Bad Way Of Creating Immutable Array", "Immutable arrays", DiagnosticSeverity.Warning, isEnabledByDefault: true ) ; public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(descriptor); public override void Initialize(AnalysisContext context) { context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression); } private void Analyze(SyntaxNodeAnalysisContext context) { // 获取当前的节点 var node = (InvocationExpressionSyntax)context.Node; // 我们肯定会根据 ImmutableArray<int>.Empty.Add(1); 找到这个特点 // 我们看到了ArgumentList是有(1)值的,所以小于一个参数的跳过 if (node.ArgumentList.Arguments.Count != 1) return; // 无法将表达式转换成成员、方法、属性的去掉 // 一般找都是从右往左去找 if (!(node.Expression is MemberAccessExpressionSyntax addAccess)) return; // 判断方法名是否胃Add if (addAccess.Name.Identifier.Text != "Add") return; // 获取上一个的成员、方法、属性 if (!(addAccess.Expression is MemberAccessExpressionSyntax emptyAccess)) return; // 判断是不是Empty,不是就直接返回 if (emptyAccess.Name.Identifier.Text != "Empty") return; // 判断符号类型的命名 if (!(context.SemanticModel.GetSymbolInfo(emptyAccess.Expression).Symbol is INamedTypeSymbol imSymbol)) return; // 检查符号的名称是否为 "ImmutableArray" 如果不是则返回 if (imSymbol.Name != "ImmutableArray") return; // 泛型参数为1 if (imSymbol.TypeArguments.Length != 1) return; // 获取符号所在的名称空间完整名称 var fullnameOfNamespace = GetFullname(imSymbol.ContainingNamespace); if (fullnameOfNamespace != "System.Collections.Immutable") return; //// 判断是不是GenericNameSyntax类型的 //if (!(emptyAccess.Expression is GenericNameSyntax ImmutableArrayAccess)) return; //// 判断是不是是否有一个泛型的类型 //if (ImmutableArrayAccess.TypeArgumentList.Arguments.Count != 1) return; //// 判断是否是ImmutableArray //if (ImmutableArrayAccess.Identifier.Text != "ImmutableArray") return; // 创建提示的消息 context.ReportDiagnostic(Diagnostic.Create(descriptor, node.GetLocation())); } private static string GetFullname(INamespaceSymbol containingNamespace) { // 如果是全局命名空间,返回空字符串 if (containingNamespace.IsGlobalNamespace) return ""; // 如果命名空间是全局命名空间的直接子命名空间,直接返回其中的名称 if (containingNamespace.ContainingNamespace.IsGlobalNamespace) return containingNamespace.Name; return GetFullname(containingNamespace.ContainingNamespace)+"."+containingNamespace.Name; } } ``` ![](https://img.tnblog.net/arcimg/hb/6c859018278b4938a1aa22315d45517b.png) ![](https://img.tnblog.net/arcimg/hb/c70bef27cf294ea38dc2a6ca8ea1d1ed.png) tn2>再次测试我们就能够通过了。 ![](https://img.tnblog.net/arcimg/hb/0b24fe1f96dd4a549965df6a48e937db.png) tn2>将类型空间用一个变量做代替也是能够检测出来的,但是需要注意我们的第二次验证修改为第`7`行。 ![](https://img.tnblog.net/arcimg/hb/56bfb50f790c42b89941422e62309e9b.png)