上一篇,我们打造了一个简单的分析器,但是我们实际使用分析器就是为了对项目做分析检测,增加一些非语法的自检的
比如Asp.Net Core 3.0的替换依赖注入检测
设计分析
我们创建一个默认的Asp.Net Core 3.0的项目
打开Startup.cs
大致结构如下
我们要针对Startup分析,第一方法ConfigureServices的返回类型必须是void
这是Asp.Net Core 3.0的改动之一,所以,当Startup.ConfigureServices返回类型不等于void的时候,我们就抛出错误提示
我们编写一个Startup的监视代码
public class StartupAnalyzerContext : BaseAnalyzContext { private static DiagnosticDescriptor NotFindConfigureServices = new DiagnosticDescriptor("Class", "Startup", "未找到方法ConfigureServices", "Error", DiagnosticSeverity.Error, true); public override DiagnosticDescriptor[] SupportedDiagnostics => new DiagnosticDescriptor[] { NotFindConfigureServices }; public override void Execute(SyntaxNodeAnalysisContext context) { if (context.Node.Kind() == SyntaxKind.ClassDeclaration && context.Node is ClassDeclarationSyntax classDeclaration && classDeclaration.Identifier.Text.Equals("Startup") ) { var methods = classDeclaration.Members.OfType<MethodDeclarationSyntax>(); if (!methods.Any(_method => _method.Identifier.Text.Equals("ConfigureServices"))) context.ReportDiagnostic(Diagnostic.Create(NotFindConfigureServices, classDeclaration.GetLocation())); else { } } } }
如果没在Startup类里找到ConfigureServices方法则抛出错误
我们注释掉ConfigureServices方法
看看结果
已经可以正常分析方法了
下一步,我们分析ConfigureServices方法的返回值是否是void
else { var voidSymbol = context.Compilation.GetTypeByMetadataName(typeof(void).FullName); var configureServices = methods.FirstOrDefault(_method => _method.Identifier.Text.Equals("ConfigureServices")); var returnType = configureServices.ReturnType; var typeInfo = context.SemanticModel.GetTypeInfo(returnType); if (!typeInfo.Type.Equals(voidSymbol)) context.ReportDiagnostic(Diagnostic.Create(ConfigureServicesReturnType, configureServices.GetLocation())); }
我们刚才的else部分逻辑,找到了ConfigureServices方法才进入这部分逻辑,我们修改一下ConfigureServices方法返回值,改为Asp.Net Core 3.0一下,常见的IServiceProvider
完善AspectCore的依赖注入替换分析
项目引用AspectCore.Extensions.DependencyInjection
判断Program.CreateHostBuilder是否存在
分析CreateHostBuilder的代码体,是否存在一个UseServiceProviderFactory方法,存在则判断是否是泛型AspectCore.Injector.IServiceContainer的类
public class ProgramAnalyzerContext : BaseAnalyzContext { private static DiagnosticDescriptor NotFindCreateHostBuilder = new DiagnosticDescriptor("Class", "Program", "未找到方法CreateHostBuilder", "Error", DiagnosticSeverity.Error, true); private static DiagnosticDescriptor CreateHostBuilderReturnType = new DiagnosticDescriptor("Class", "Program", "无法分析返回值类型非IHostBuilder的CreateHostBuilder方法", "Error", DiagnosticSeverity.Error, true); private static DiagnosticDescriptor NotFindUseServiceProviderFactory = new DiagnosticDescriptor("Class", "Program", "未找到UseServiceProviderFactory方法", "Error", DiagnosticSeverity.Error, true); private static DiagnosticDescriptor AspectCoreServiceProviderFactory = new DiagnosticDescriptor("Class", "Program", "请Nuget安装AspectCore.Extensions.DependencyInjection", "Error", DiagnosticSeverity.Error, true); private static DiagnosticDescriptor UseServiceProviderFactoryType = new DiagnosticDescriptor("Class", "Program", "UseServiceProviderFactory(new AspectCoreServiceProviderFactory())", "Error", DiagnosticSeverity.Error, true); public override DiagnosticDescriptor[] SupportedDiagnostics => new DiagnosticDescriptor[] { NotFindCreateHostBuilder, CreateHostBuilderReturnType, NotFindUseServiceProviderFactory, AspectCoreServiceProviderFactory, UseServiceProviderFactoryType }; public override void Execute(SyntaxNodeAnalysisContext context) { if (context.Node.Kind() == SyntaxKind.ClassDeclaration && context.Node is ClassDeclarationSyntax classDeclaration && classDeclaration.Identifier.Text.Equals("Program") ) { var methods = classDeclaration.Members.OfType<MethodDeclarationSyntax>(); if (!methods.Any(_method => _method.Identifier.Text.Equals("CreateHostBuilder"))) context.ReportDiagnostic(Diagnostic.Create(NotFindCreateHostBuilder, classDeclaration.GetLocation())); else { var aspectCoreServiceProviderFactorySymbol = context.Compilation.GetTypeByMetadataName("AspectCore.Extensions.DependencyInjection.AspectCoreServiceProviderFactory"); var iServiceContainerSymbol = context.Compilation.GetTypeByMetadataName("AspectCore.Injector.IServiceContainer"); if (aspectCoreServiceProviderFactorySymbol == null) { context.ReportDiagnostic(Diagnostic.Create(AspectCoreServiceProviderFactory, classDeclaration.GetLocation())); return; } var createHostBuilder = methods.FirstOrDefault(_method => _method.Identifier.Text.Equals("CreateHostBuilder")); var expressionBody = createHostBuilder.ExpressionBody as ArrowExpressionClauseSyntax; if (expressionBody != null) { var expressions = ConvertArrowExpression(expressionBody); if (!expressions.Any(expression=> ((MemberAccessExpressionSyntax)expression.Expression).Name.Identifier.Text.Equals("UseServiceProviderFactory"))) context.ReportDiagnostic(Diagnostic.Create(NotFindUseServiceProviderFactory, createHostBuilder.GetLocation())); else { var useServiceProviderFactoryExpression = expressions.FirstOrDefault(expression => ((MemberAccessExpressionSyntax)expression.Expression).Name.Identifier.Text.Equals("UseServiceProviderFactory")); var method = context.SemanticModel.GetSymbolInfo(useServiceProviderFactoryExpression.Expression).Symbol as IMethodSymbol; if (!method.TypeArguments.Any(_param => _param.Equals(iServiceContainerSymbol))) context.ReportDiagnostic(Diagnostic.Create(UseServiceProviderFactoryType, createHostBuilder.GetLocation())); } } } } } private List<InvocationExpressionSyntax> ConvertArrowExpression(ArrowExpressionClauseSyntax expresionBody) { var result = new List<InvocationExpressionSyntax>(); var firstExpresson = (InvocationExpressionSyntax) expresionBody.Expression; var expression = firstExpresson; result.Add(expression); while (IsNext(expression)) { expression = Next(expression); result.Add(expression); } return result; } private InvocationExpressionSyntax Next(InvocationExpressionSyntax expression) { var method = (MemberAccessExpressionSyntax) expression.Expression; Console.WriteLine($"{method.Name}"); return (InvocationExpressionSyntax)method.Expression; } private bool IsNext(InvocationExpressionSyntax expression) { var method = (MemberAccessExpressionSyntax)expression.Expression; return method.Expression.Kind() == SyntaxKind.InvocationExpression; } }