
.net Roslyn 测试分析器
关于项目的创建请参考:https://www.tnblog.net/hb/article/details/8473
简单测试方式
首先打开我们的MyRoslynUnitTest
测试类。
在头部引用相关的命名空间。
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;
然后我们通过在当前解决方案类中创建一个新的的项目,并在其中添加一个File.cs
的类,把代码放入其中后,对其进行诊断。
具体代码如下:
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>
}
接下来我们定义一个EmptyMethodGeneratesNoDiagnostics
测试方法,测试其中什么代码都不添加的情况下,诊断的数量为0
。
[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);
}
然后我们定义一个CreatingAnImmutableArrayViaTheEmptyPropertyGeneratesOneDiagnostic
测试方法,这里肯定会触发我们的警告,所以我们断言了它的警告ID是否为BadWayOfCreatingImmutableArray
,以及触发的行数在第7行。
[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);
}
还定义了一个测试方法,由于我这里没有通过引用命名空间进行引用,而是直接通过命名空间点出来的类名,所以我们的分析根本找不到这个问题,也没办法弹出警告。自然在第一个断言时就报错。
代码如下:
[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);
}
我们可以通过判断它的命名符号(Symbol
)来进行解决,当然验证的代码需要做一些修改。
[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;
}
}
再次测试我们就能够通过了。
将类型空间用一个变量做代替也是能够检测出来的,但是需要注意我们的第二次验证修改为第7
行。
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739
评价
排名
2
文章
634
粉丝
44
评论
93
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 :
好是好,这个对效率影响大不大哇,效率高不高
一个bug让程序员走上法庭 索赔金额达400亿日元
剑轩 : 有点可怕
ASP.NET Core 服务注册生命周期
剑轩 :
http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:
50010702506256


欢迎加群交流技术