Roslyn 分析一个方法是否为 async void、未使用 await

2023年11月23日 1470点热度 0人点赞 0条评论
内容纲要

继承 DiagnosticAnalyzer 。

检测 async void

注册监听器:

        public override void Initialize(AnalysisContext context)
        {
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.EnableConcurrentExecution();
            // 注册分析类型,只分析方法
            context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration);
        }

分析代码节点:

        private void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var node = context.Node;
            if (node.IsKind(SyntaxKind.MethodDeclaration))
            {
                var syntax = node as MethodDeclarationSyntax;
                if (syntax is null) return;
                AnalyzeMethodDeclarationSyntax(context, syntax);
            }
        }

判断节点是否为 async void 方法:

        private void AnalyzeMethodDeclarationSyntax(SyntaxNodeAnalysisContext context, MethodDeclarationSyntax syntax)
        {
            var returnType = syntax.ReturnType as PredefinedTypeSyntax;
            if (returnType == null) return;
            if (returnType.Keyword.ValueText != SyntaxFactory.Token(SyntaxKind.VoidKeyword).ValueText) return;

            if (syntax.Modifiers.Any(x => x.ValueText == "async"))
            {
                context.ReportDiagnostic(Diagnostic.Create(
                    descriptor: Rule,
                    location: returnType.GetLocation(),
                    messageArgs: "async void"));
            }
        }

如果开发者使用了 async void 代码,会提示报错。
file

检测使用异步方法未使用 await

注册监听器:

        public override void Initialize(AnalysisContext context)
        {
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.EnableConcurrentExecution();
            context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ExpressionStatement);
        }

分析节点:

        private void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var node = context.Node;
            var ex = node as ExpressionStatementSyntax;
            if (ex is null) return;
            var syntax = ex.Expression as InvocationExpressionSyntax;
            if (syntax is null) return;
            AnalyzeMethodDeclarationSyntax(context, syntax);
        }

针对多种情况进行检测分析:

        private static string[] BlockingCalls = new string[] { "GetAwaiter", "Result", "Wait" };
        private void AnalyzeMethodDeclarationSyntax(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax syntax)
        {
            // 检查被调用的方法是否为 async Task 方法
            var invokeMethod = context.SemanticModel.GetSymbolInfo(syntax).Symbol as IMethodSymbol;
            if (invokeMethod == null || !ReturnTask(invokeMethod))
            {
                return;
            }

            // 获取被调用方法的名称
            var methodSymbol = context
                .SemanticModel
                .GetSymbolInfo(syntax, context.CancellationToken)
                .Symbol as IMethodSymbol;

            // 查找此方法引用
            var syntaxReference = methodSymbol
                .DeclaringSyntaxReferences
                .FirstOrDefault();

            // 获取此方法在语法树中的定义
            var methodDeclaration = syntaxReference.GetSyntax(context.CancellationToken) as MethodDeclarationSyntax;
            if (methodDeclaration == null) return;
            // 如果不是异步方法
            if (!methodDeclaration.Modifiers.Any(x => x.ValueText == "async")) return;

            // 已经使用了 await 等待
            var isAwaited = syntax.Ancestors().OfType<AwaitExpressionSyntax>().Any();
            if (isAwaited)
            {
                return;
            }

            //var isUnderLambda = FirstAncestorOrSelfUnderGivenNode<LambdaExpressionSyntax>(syntax, node) != null;
            //if (isUnderLambda)
            //{
            //    return;
            //}

            // 检查是否使用了 await、_ = xxx、BlockingCalls
            bool isInvocationWaited = false;

            foreach (var parent in syntax.Ancestors())
            {
                var assignment = parent as AssignmentExpressionSyntax;
                // 已经使用了弃元 _ = xxx
                if (assignment != null)
                {
                    return;
                }

                var parentMemberAccess = parent as MemberAccessExpressionSyntax;
                if (parentMemberAccess?.Name != null)
                {
                    if (BlockingCalls.Any(a => a.Equals(parentMemberAccess.Name.Identifier.ValueText, StringComparison.OrdinalIgnoreCase)))
                    {
                        isInvocationWaited = true;
                        break;
                    }
                }

                if (isInvocationWaited)
                {
                    return;
                }
            }

            context.ReportDiagnostic(Diagnostic.Create(
                descriptor: Rule,
                location: syntax.GetLocation()));
        }

        public static T FirstAncestorOrSelfUnderGivenNode<T>(SyntaxNode node, SyntaxNode parent) where T : SyntaxNode
        {
            var current = node;

            while (current != null && current != parent)
            {
                var temp = current as T;
                if (temp != null)
                {
                    return temp;
                }

                current = current.Parent;
            }

            return null;
        }

        public static bool IsTask(ITypeSymbol type)
        {
            return type.ContainingNamespace?.ToDisplayString() == "System.Threading.Tasks" &&
                (type.Name == "Task" || type.Name == "ValueTask");
        }
        public static bool ReturnTask(IMethodSymbol symbol)
        {
            return !symbol.ReturnsVoid && IsTask(symbol.ReturnType);
        }

痴者工良

高级程序员劝退师

文章评论