|
|
@@ -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;
|
|
|
+ }
|
|
|
}
|
|
|
}
|