打造静态分析器(二)基于Asp.Net Core 3.0的AspectCore组件检测

上一篇,我们打造了一个简单的分析器,但是我们实际使用分析器就是为了对项目做分析检测,增加一些非语法的自检的

比如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;
        }
    }

 

posted @ 2019-10-16 19:22  沉迷代码的萌新  阅读(749)  评论(0编辑  收藏  举报