Bläddra i källkod

添加ETTask CancelToken分析器 修复分析器AllModel程序集未包含UnityModelViewCodes的问题 (#437)

* 添加ETCancellationToken分析器
含有ETCancellationToken参数的异步函数内调用await表达式必须提前判断CancelToken.IsCancel
添加ETTask CancelToken分析器 修复分析器AllModel程序集未包含UnityModelViewCodes的问题
susices 3 år sedan
förälder
incheckning
fdebb766be

+ 133 - 0
Share/Analyzer/Analyzer/ETCancellationTokenAnalyzer.cs

@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace ET.Analyzer
+{
+    [DiagnosticAnalyzer(LanguageNames.CSharp)]
+    public class ETCancellationTokenAnalyzer: DiagnosticAnalyzer
+    {
+        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule,AwaitExpressionCancelTokenParamAnalyzerRule.Rule);
+
+        public override void Initialize(AnalysisContext context)
+        {
+            if (!AnalyzerGlobalSetting.EnableAnalyzer)
+            {
+                return;
+            }
+
+            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+            context.EnableConcurrentExecution();
+            context.RegisterSyntaxNodeAction(this.AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration);
+        }
+
+        private void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
+        {
+            if (!AnalyzerHelper.IsAssemblyNeedAnalyze(context.Compilation.AssemblyName, AnalyzeAssembly.AllHotfix))
+            {
+                return;
+            }
+
+            if (context.Node is not MethodDeclarationSyntax methodDeclarationSyntax)
+            {
+                return;
+            }
+            // 只检查异步函数
+            IMethodSymbol? methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclarationSyntax);
+            if (methodSymbol is not { IsAsync: true })
+            {
+                return;
+            }
+
+            // 只检查入参含有ETCancellationToken的函数
+            if (!methodSymbol.HasParameterType(Definition.ETCancellationToken, out IParameterSymbol? cancelTokenSymbol) || cancelTokenSymbol == null)
+            {
+                return;
+            }
+
+            
+            foreach (AwaitExpressionSyntax? awaitExpressionSyntax in methodDeclarationSyntax.DescendantNodes<AwaitExpressionSyntax>())
+            {
+                // 跳过await 返回值不是ETTask的
+                var awaitExpressionTypeSymbol = context.SemanticModel.GetTypeInfo(awaitExpressionSyntax.Expression);
+                if (awaitExpressionTypeSymbol.Type==null)
+                {
+                    continue;
+                }
+
+                string awaitExpressionType = $"{awaitExpressionTypeSymbol.Type.ContainingNamespace}.{awaitExpressionTypeSymbol.Type.Name}";
+                if (awaitExpressionType != Definition.ETTaskFullName)
+                {
+                    continue;
+                }
+                
+                // await函数调用后 是否判断了canceltoken
+                if (!this.HasCheckCancelTokenAfterAwait(awaitExpressionSyntax, cancelTokenSymbol,context))
+                {
+                    Diagnostic diagnostic = Diagnostic.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
+                    context.ReportDiagnostic(diagnostic);
+                }
+
+                // 跳过await字段的表达式
+                InvocationExpressionSyntax? awaitInvocationSyntax = awaitExpressionSyntax.Expression as InvocationExpressionSyntax;
+                if (awaitInvocationSyntax==null)
+                {
+                    continue;
+                }
+                
+                // await 函数是否带canceltoken参数
+                if (!HasCancelTokenInAwait(awaitInvocationSyntax,cancelTokenSymbol,context))
+                {
+                    Diagnostic diagnostic = Diagnostic.Create(AwaitExpressionCancelTokenParamAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
+                    context.ReportDiagnostic(diagnostic);
+                }
+            }
+        }
+        
+        /// <summary>
+        /// 调用await函数后,是否判断了所在函数传入的canceltoken参数是否取消 
+        /// </summary>
+        bool HasCheckCancelTokenAfterAwait(AwaitExpressionSyntax awaitExpression,IParameterSymbol cancelTokenSymbol, SyntaxNodeAnalysisContext context)
+        {
+            // 检查await表达式的下一个表达式是否为判断 cancelToken.IsCancel()
+            StatementSyntax? statementSyntax = awaitExpression.GetNeareastAncestor<StatementSyntax>();
+
+            if (statementSyntax == null)
+            {
+                return true;
+            }
+
+            SyntaxNode? nextNode = statementSyntax.NextNode();
+            if (nextNode is not IfStatementSyntax ifStatementSyntax)
+            {
+                return false;
+            }
+
+            string conditionStr = ifStatementSyntax.Condition.ToString().Replace(" ", string.Empty);
+            if (conditionStr!= $"{cancelTokenSymbol.Name}.IsCancel()")
+            {
+                return false;
+            }
+
+            // 检查判断表达式内是否直接return
+            if (ifStatementSyntax.Statement.GetFirstChild<ReturnStatementSyntax>() == null)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// 调用await函数时, 是否带了所在函数传入的canceltoken参数
+        /// </summary>
+        bool HasCancelTokenInAwait(InvocationExpressionSyntax awaitInvocationSyntax,IParameterSymbol cancelTokenSymbol, SyntaxNodeAnalysisContext context)
+        {
+            return awaitInvocationSyntax.ArgumentList.Arguments.Any(x => x.ToString() == cancelTokenSymbol.Name);
+        }
+    }
+}

+ 1 - 1
Share/Analyzer/Config/AnalyzeAssembly.cs

@@ -26,7 +26,7 @@
         public static readonly string[] AllModel =
         {
             DotNetModel, UnityModel, 
-            UnityModelView, UnityModel, UnityModelCodes
+            UnityModelView, UnityModelViewCodes, UnityModelCodes
         };
 
         public static readonly string[] AllModelHotfix =

+ 6 - 0
Share/Analyzer/Config/Definition.cs

@@ -6,6 +6,8 @@
         
         public const string ETTask = "ETTask";
 
+        public const string ETTaskFullName = "ET.ETTask";
+
         public static readonly string[] AddChildMethods = { "AddChild", "AddChildWithId" };
 
         public static readonly string[] ComponentMethod = {"AddComponent","GetComponent"};
@@ -29,6 +31,10 @@
         public const string EnableAccessEntiyChildAttribute = "ET.EnableAccessEntiyChildAttribute";
 
         public const string StaticFieldAttribute = "ET.StaticFieldAttribute";
+
+        public const string ETCancellationToken = "ET.ETCancellationToken";
+
+        public const string ETTaskCompleteTask = "ETTask.CompletedTask";
     }
 }
 

+ 4 - 0
Share/Analyzer/Config/DiagnosticIds.cs

@@ -32,5 +32,9 @@
 
         public const string StaticFieldDeclarationAnalyzerRule = "ET0015";
 
+        public const string ETCancellationTokenAnalyzerRuleId = "ET0016";
+
+        public const string AwaitExpressionCancelTokenParamAnalyzerRuleId = "ET0017";
+
     }
 }

+ 36 - 0
Share/Analyzer/Config/DiagnosticRules.cs

@@ -145,4 +145,40 @@ namespace ET.Analyzer
                     true,
                     Description);
     }
+    
+    public static class CheckETCancellTokenAfterAwaitAnalyzerRule
+    {
+        private const string Title = "含有ETCancelToken参数的异步函数内调用await表达式后必须判断CancelToken.IsCancel";
+
+        private const string MessageFormat = "含有ETCancelToken参数的异步函数内调用await表达式后必须判断CancelToken.IsCancel";
+
+        private const string Description = "含有ETCancelToken参数的异步函数内调用await表达式后必须判断CancelToken.IsCancel.";
+
+        public static readonly DiagnosticDescriptor Rule =
+                new DiagnosticDescriptor(DiagnosticIds.ETCancellationTokenAnalyzerRuleId,
+                    Title,
+                    MessageFormat,
+                    DiagnosticCategories.All, 
+                    DiagnosticSeverity.Error,
+                    true,
+                    Description);
+    }
+    
+    public static class AwaitExpressionCancelTokenParamAnalyzerRule
+    {
+        private const string Title = "含有ETCancelToken参数的异步函数内调用await表达式必须传入同一个CancelToken";
+    
+        private const string MessageFormat = "含有ETCancelToken参数的异步函数内调用await表达式必须传入同一个CancelToken";
+    
+        private const string Description = "含有ETCancelToken参数的异步函数内调用await表达式必须传入同一个CancelToken.";
+    
+        public static readonly DiagnosticDescriptor Rule =
+                new DiagnosticDescriptor(DiagnosticIds.AwaitExpressionCancelTokenParamAnalyzerRuleId,
+                    Title,
+                    MessageFormat,
+                    DiagnosticCategories.All, 
+                    DiagnosticSeverity.Error,
+                    true,
+                    Description);
+    }
 }

+ 90 - 1
Share/Analyzer/Extension/AnalyzerHelper.cs

@@ -1,4 +1,5 @@
-using System.Linq;
+using System.Collections.Generic;
+using System.Linq;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
 
@@ -231,5 +232,93 @@ namespace ET.Analyzer
             }
             return null ;
         }
+        
+        /// <summary>
+        /// 判断函数是否是否含有指定类型的参数
+        /// </summary>
+        public static bool HasParameterType(this IMethodSymbol methodSymbol, string parameterType, out IParameterSymbol? cencelTokenSymbol)
+        {
+            foreach (var parameterSymbol in methodSymbol.Parameters)
+            {
+                if (parameterSymbol.Type.ToString() == parameterType)
+                {
+                    cencelTokenSymbol = parameterSymbol;
+                    return true;
+                }
+            }
+            cencelTokenSymbol = null;
+            return false;
+        }
+
+        /// <summary>
+        /// 获取所有指定类型的子节点
+        /// </summary>
+        public static IEnumerable<T> DescendantNodes<T>(this SyntaxNode syntaxNode) where T : SyntaxNode
+        {
+            foreach (var descendantNode in syntaxNode.DescendantNodes())
+            {
+                if (descendantNode is T node)
+                {
+                    yield return node;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 获取与该语法节点同层级的上一个节点
+        /// </summary>
+        public static SyntaxNode? PreviousNode(this SyntaxNode syntaxNode)
+        {
+            if (syntaxNode.Parent==null)
+            {
+                return null;
+            }
+            
+            int index = 0;
+            foreach (var childNode in syntaxNode.Parent.ChildNodes())
+            {
+                if (childNode == syntaxNode)
+                {
+                    break;
+                }
+                index++;
+            }
+
+            if (index==0)
+            {
+                return null;
+            }
+            
+            return syntaxNode.Parent.ChildNodes().ElementAt(index-1);
+        }
+
+        /// <summary>
+        /// 获取与该语法节点同层级的下一个节点
+        /// </summary>
+        public static SyntaxNode? NextNode(this SyntaxNode syntaxNode)
+        {
+            if (syntaxNode.Parent==null)
+            {
+                return null;
+            }
+            
+            int index = 0;
+            
+            foreach (var childNode in syntaxNode.Parent.ChildNodes())
+            {
+                if (childNode == syntaxNode)
+                {
+                    break;
+                }
+                index++;
+            }
+
+            if (index == syntaxNode.Parent.ChildNodes().Count()-1)
+            {
+                return null;
+            }
+            
+            return syntaxNode.Parent.ChildNodes().ElementAt(index+1);
+        }
     }
 }