Explorar o código

更新ETTask CancelToken分析器 (#438)

* 更新ETTask CancelToken分析器

1.异步函数声明处的ETCancelToken参数禁止声明默认值
2.函数调用处的ETCancelToken参数禁止传入null
3.返回ETTask的异步函数必须含有ETCancelToken参数
susices %!s(int64=2) %!d(string=hai) anos
pai
achega
748d7b434b

+ 90 - 18
Share/Analyzer/Analyzer/ETCancellationTokenAnalyzer.cs

@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Immutable;
+using System.Collections.Immutable;
 using System.Linq;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp;
@@ -11,7 +10,10 @@ namespace ET.Analyzer
     [DiagnosticAnalyzer(LanguageNames.CSharp)]
     public class ETCancellationTokenAnalyzer: DiagnosticAnalyzer
     {
-        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule,AwaitExpressionCancelTokenParamAnalyzerRule.Rule);
+        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
+            CheckETCancellTokenAfterAwaitAnalyzerRule.Rule,
+            AwaitExpressionCancelTokenParamAnalyzerRule.Rule, AsyncMethodWithCancelTokenParamAnalyzerRule.Rule,
+            ExpressionWithCancelTokenParamAnalyzerRule.Rule, ETTaslAsyncMethodHasCancelTokenAnalyzerRule.Rule);
 
         public override void Initialize(AnalysisContext context)
         {
@@ -22,7 +24,7 @@ namespace ET.Analyzer
 
             context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
             context.EnableConcurrentExecution();
-            context.RegisterSyntaxNodeAction(this.AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration);
+            //context.RegisterSyntaxNodeAction(this.AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration);
         }
 
         private void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
@@ -36,25 +38,58 @@ namespace ET.Analyzer
             {
                 return;
             }
-            // 只检查异步函数
+
+            // 检查含有ETCancelToken参数的函数调用 是否传入null 或未赋值
+            foreach (InvocationExpressionSyntax? invocationExpressionSyntax in methodDeclarationSyntax.DescendantNodes<InvocationExpressionSyntax>())
+            {
+                if (this.CancelTokenArguIsNullOrNotSet(invocationExpressionSyntax, context))
+                {
+                    Diagnostic diagnostic = Diagnostic.Create(ExpressionWithCancelTokenParamAnalyzerRule.Rule,
+                        invocationExpressionSyntax.GetLocation());
+                    context.ReportDiagnostic(diagnostic);
+                }
+            }
+
+            // 忽略非异步函数
             IMethodSymbol? methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclarationSyntax);
             if (methodSymbol is not { IsAsync: true })
             {
                 return;
             }
 
-            // 只检查入参含有ETCancellationToken的函数
+            string methodReturnType = $"{methodSymbol.ReturnType.ContainingNamespace}.{methodSymbol.ReturnType.Name}";
+            if (methodReturnType != Definition.ETTaskFullName)
+            {
+                return;
+            }
+
+            // 检测是否含有cancelToken参数
             if (!methodSymbol.HasParameterType(Definition.ETCancellationToken, out IParameterSymbol? cancelTokenSymbol) || cancelTokenSymbol == null)
             {
+                Diagnostic diagnostic = Diagnostic.Create(ETTaslAsyncMethodHasCancelTokenAnalyzerRule.Rule,
+                    methodDeclarationSyntax.ParameterList.GetLocation());
+                context.ReportDiagnostic(diagnostic);
                 return;
             }
 
-            
+            // 函数定义处 ETcanceltoken参数 是否有默认值
+            if (cancelTokenSymbol.HasExplicitDefaultValue)
+            {
+                SyntaxNode? cancelTokenParamSyntax = cancelTokenSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax();
+                if (cancelTokenParamSyntax == null)
+                {
+                    cancelTokenParamSyntax = methodDeclarationSyntax;
+                }
+
+                Diagnostic diagnostic = Diagnostic.Create(AsyncMethodWithCancelTokenParamAnalyzerRule.Rule, cancelTokenParamSyntax.GetLocation());
+                context.ReportDiagnostic(diagnostic);
+            }
+
             foreach (AwaitExpressionSyntax? awaitExpressionSyntax in methodDeclarationSyntax.DescendantNodes<AwaitExpressionSyntax>())
             {
                 // 跳过await 返回值不是ETTask的
-                var awaitExpressionTypeSymbol = context.SemanticModel.GetTypeInfo(awaitExpressionSyntax.Expression);
-                if (awaitExpressionTypeSymbol.Type==null)
+                TypeInfo awaitExpressionTypeSymbol = context.SemanticModel.GetTypeInfo(awaitExpressionSyntax.Expression);
+                if (awaitExpressionTypeSymbol.Type == null)
                 {
                     continue;
                 }
@@ -64,9 +99,9 @@ namespace ET.Analyzer
                 {
                     continue;
                 }
-                
+
                 // await函数调用后 是否判断了canceltoken
-                if (!this.HasCheckCancelTokenAfterAwait(awaitExpressionSyntax, cancelTokenSymbol,context))
+                if (!this.HasCheckCancelTokenAfterAwait(awaitExpressionSyntax, cancelTokenSymbol, context))
                 {
                     Diagnostic diagnostic = Diagnostic.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
                     context.ReportDiagnostic(diagnostic);
@@ -74,24 +109,25 @@ namespace ET.Analyzer
 
                 // 跳过await字段的表达式
                 InvocationExpressionSyntax? awaitInvocationSyntax = awaitExpressionSyntax.Expression as InvocationExpressionSyntax;
-                if (awaitInvocationSyntax==null)
+                if (awaitInvocationSyntax == null)
                 {
                     continue;
                 }
-                
+
                 // await 函数是否带canceltoken参数
-                if (!HasCancelTokenInAwait(awaitInvocationSyntax,cancelTokenSymbol,context))
+                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)
+        private bool HasCheckCancelTokenAfterAwait(AwaitExpressionSyntax awaitExpression, IParameterSymbol cancelTokenSymbol,
+        SyntaxNodeAnalysisContext context)
         {
             // 检查await表达式的下一个表达式是否为判断 cancelToken.IsCancel()
             StatementSyntax? statementSyntax = awaitExpression.GetNeareastAncestor<StatementSyntax>();
@@ -108,7 +144,7 @@ namespace ET.Analyzer
             }
 
             string conditionStr = ifStatementSyntax.Condition.ToString().Replace(" ", string.Empty);
-            if (conditionStr!= $"{cancelTokenSymbol.Name}.IsCancel()")
+            if (conditionStr != $"{cancelTokenSymbol.Name}.IsCancel()")
             {
                 return false;
             }
@@ -125,9 +161,45 @@ namespace ET.Analyzer
         /// <summary>
         /// 调用await函数时, 是否带了所在函数传入的canceltoken参数
         /// </summary>
-        bool HasCancelTokenInAwait(InvocationExpressionSyntax awaitInvocationSyntax,IParameterSymbol cancelTokenSymbol, SyntaxNodeAnalysisContext context)
+        private bool HasCancelTokenInAwait(InvocationExpressionSyntax awaitInvocationSyntax, IParameterSymbol cancelTokenSymbol,
+        SyntaxNodeAnalysisContext context)
         {
             return awaitInvocationSyntax.ArgumentList.Arguments.Any(x => x.ToString() == cancelTokenSymbol.Name);
         }
+
+        /// <summary>
+        /// 表达式canceltoken参数是否为null或未赋值
+        /// </summary>
+        private bool CancelTokenArguIsNullOrNotSet(InvocationExpressionSyntax invocationExpressionSyntax, SyntaxNodeAnalysisContext context)
+        {
+            IMethodSymbol? methodSymbol = context.SemanticModel.GetSymbolInfo(invocationExpressionSyntax).Symbol as IMethodSymbol;
+            if (methodSymbol == null)
+            {
+                return false;
+            }
+
+            // 忽略没有ETCancellationToken参数的表达式
+            if (!methodSymbol.HasParameterType(Definition.ETCancellationToken, out IParameterSymbol? cancelTokenSYmbol))
+            {
+                return false;
+            }
+
+            var arguments = invocationExpressionSyntax.ArgumentList.Arguments;
+            for (int i = 0; i < arguments.Count; i++)
+            {
+                ITypeSymbol? typeInfo = context.SemanticModel.GetTypeInfo(arguments[i].Expression).Type;
+                if (typeInfo == null)
+                {
+                    continue;
+                }
+
+                if (typeInfo.ToString() == Definition.ETCancellationToken)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
     }
 }

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

@@ -36,5 +36,11 @@
 
         public const string AwaitExpressionCancelTokenParamAnalyzerRuleId = "ET0017";
 
+        public const string AsyncMethodWithCancelTokenParamAnalyzerRuleId = "ET0018";
+
+        public const string ExpressionWithCancelTokenParamAnalyzerRuleId = "ET0019";
+
+        public const string ETTaslAsyncMethodHasCancelTokenAnalyzerRuleId = "ET0020";
+
     }
 }

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

@@ -181,4 +181,60 @@ namespace ET.Analyzer
                     true,
                     Description);
     }
+    
+    public static class AsyncMethodWithCancelTokenParamAnalyzerRule
+    {
+        private const string Title = "异步函数声明处的ETCancelToken参数禁止声明默认值";
+    
+        private const string MessageFormat = "异步函数声明处的ETCancelToken参数禁止声明默认值";
+    
+        private const string Description = "异步函数声明处的ETCancelToken参数禁止声明默认值.";
+    
+        public static readonly DiagnosticDescriptor Rule =
+                new DiagnosticDescriptor(DiagnosticIds.AsyncMethodWithCancelTokenParamAnalyzerRuleId,
+                    Title,
+                    MessageFormat,
+                    DiagnosticCategories.All,
+                    DiagnosticSeverity.Error,
+                    true,
+                    Description);
+    }
+    
+    
+    public static class ExpressionWithCancelTokenParamAnalyzerRule
+    {
+        private const string Title = "函数调用处的ETCancelToken参数禁止传入null";
+    
+        private const string MessageFormat = "函数调用处的ETCancelToken参数禁止传入null";
+    
+        private const string Description = "函数调用处的ETCancelToken参数禁止传入null.";
+    
+        public static readonly DiagnosticDescriptor Rule =
+                new DiagnosticDescriptor(DiagnosticIds.ExpressionWithCancelTokenParamAnalyzerRuleId,
+                    Title,
+                    MessageFormat,
+                    DiagnosticCategories.All,
+                    DiagnosticSeverity.Error,
+                    true,
+                    Description);
+    }
+
+    public static class ETTaslAsyncMethodHasCancelTokenAnalyzerRule
+    {
+        private const string Title = "返回ETTask的异步函数必须含有ETCancelToken参数";
+    
+        private const string MessageFormat = "返回ETTask的异步函数必须含有ETCancelToken参数";
+    
+        private const string Description = "返回ETTask的异步函数必须含有ETCancelToken参数.";
+    
+        public static readonly DiagnosticDescriptor Rule =
+                new DiagnosticDescriptor(DiagnosticIds.ETTaslAsyncMethodHasCancelTokenAnalyzerRuleId,
+                    Title,
+                    MessageFormat,
+                    DiagnosticCategories.All,
+                    DiagnosticSeverity.Error,
+                    true,
+                    Description);
+    }
+
 }

+ 1 - 0
Unity/Assets/Scripts/ThirdParty/ETTask/ETCancellationToken.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 
 namespace ET
 {