From 4f38feac5cbbe5d78066617c2026260febd3ad82 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 2 Jan 2025 10:07:42 +0100 Subject: [PATCH] Rewrite CallBaseOnInitialized analyzer (#520) * Rewrite CallBaseOnInitialized analyzer * Doc and note OriginalDefinition --- .../CallBaseOnInitialized.cs | 119 +++++++----------- .../Fluxor.Blazor.Web.Analyzers.csproj | 2 +- 2 files changed, 48 insertions(+), 73 deletions(-) diff --git a/Source/Lib/Fluxor.Blazor.Web.Analyzers/CallBaseOnInitialized.cs b/Source/Lib/Fluxor.Blazor.Web.Analyzers/CallBaseOnInitialized.cs index 3d58966b..9e099400 100644 --- a/Source/Lib/Fluxor.Blazor.Web.Analyzers/CallBaseOnInitialized.cs +++ b/Source/Lib/Fluxor.Blazor.Web.Analyzers/CallBaseOnInitialized.cs @@ -1,8 +1,7 @@ -using System.Collections.Immutable; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; namespace Fluxor.Blazor.Web.Analyzers; @@ -23,91 +22,67 @@ public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.EnableConcurrentExecution(); - context.RegisterSyntaxNodeAction( - action: AnalyzeMethod, - syntaxKinds: SyntaxKind.MethodDeclaration); + context.RegisterCompilationStartAction(context => + { + var fluxorComponent = context.Compilation.GetTypeByMetadataName("Fluxor.Blazor.Web.Components.FluxorComponent"); + var fluxorLayout = context.Compilation.GetTypeByMetadataName("Fluxor.Blazor.Web.Components.FluxorLayout"); + context.RegisterOperationBlockAction(context => AnalyzeOperationBlock(context, fluxorComponent, fluxorLayout)); + }); } - - private static bool IsFluxorComponentBase(INamedTypeSymbol? symbol) => - symbol is not null - && ( - IsFluxorType(symbol, "Fluxor.Blazor.Web.Components.FluxorComponent") - || IsFluxorType(symbol, "Fluxor.Blazor.Web.Components.FluxorLayout") - ); - - private static bool IsFluxorType(INamedTypeSymbol symbol, string fullyQualifiedName) => - symbol.ToString() == fullyQualifiedName - || ( - symbol.BaseType is not null - && IsFluxorType(symbol.BaseType, fullyQualifiedName) - ); - - private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) + private void AnalyzeOperationBlock(OperationBlockAnalysisContext context, INamedTypeSymbol? fluxorComponent, INamedTypeSymbol? fluxorLayout) { - if (context.Node is not MethodDeclarationSyntax methodDecl) + if (context.OwningSymbol is not IMethodSymbol { IsOverride: true, Name: "OnInitialized" or "OnInitializedAsync" } method || + !IsFluxorComponentBase(method.ContainingType, fluxorComponent, fluxorLayout)) + { return; + } - if (methodDecl.Modifiers.All(m => m.Text != "override")) - return; + if (!CallsBase(context.OperationBlocks, method.OverriddenMethod)) + context.ReportDiagnostic(Diagnostic.Create(Rule, method.Locations[0])); + } - string methodName = methodDecl.Identifier.Text; - if (methodName != "OnInitialized" && methodName != "OnInitializedAsync") - return; + private bool CallsBase(ImmutableArray operations, IMethodSymbol? overriddenMethod) + { + foreach (var operation in operations) + { + if (CallsBase(operation, overriddenMethod)) + return true; + } - SemanticModel semanticModel = context.SemanticModel; - INamedTypeSymbol? containingType = semanticModel.GetDeclaredSymbol(methodDecl)?.ContainingType; - if (containingType is null) - return; + return false; + } - if (!IsFluxorComponentBase(containingType)) - return; + private bool CallsBase(IOperation operation, IMethodSymbol? overriddenMethod) + { + if (operation is IBlockOperation blockOperation) + return CallsBase(blockOperation.Operations, overriddenMethod); - bool callsBase = - methodDecl.Body is not null - && methodDecl.Body.Statements - .OfType() - .Select(s => s.Expression) - .Select(expr => - expr is AwaitExpressionSyntax awaitExpr - ? awaitExpr.Expression - : expr) - .OfType() - .Any(inv => IsBaseCall(inv, methodName, semanticModel)); - - if (!callsBase && methodDecl.ExpressionBody is null) - { - context.ReportDiagnostic(Diagnostic.Create(Rule, methodDecl.Identifier.GetLocation())); - return; - } + if (operation is IExpressionStatementOperation expressionStatementOperation) + operation = expressionStatementOperation.Operation; - if (methodDecl.ExpressionBody is not null) - { - ExpressionSyntax expr = methodDecl.ExpressionBody.Expression; - if ( - expr is InvocationExpressionSyntax exprInv - && IsBaseCall(exprInv, methodName, semanticModel) - ) - { - return; - } - context.ReportDiagnostic(Diagnostic.Create(Rule, methodDecl.Identifier.GetLocation())); - } + if (operation is IAwaitOperation awaitOperation) + operation = awaitOperation.Operation; + + return operation is IInvocationOperation invocation && + invocation.TargetMethod.Equals(overriddenMethod, SymbolEqualityComparer.Default); } - private static bool IsBaseCall( - InvocationExpressionSyntax invocation, - string methodName, - SemanticModel semanticModel) + private static bool IsFluxorComponentBase(INamedTypeSymbol symbol, INamedTypeSymbol? fluxorComponent, INamedTypeSymbol? fluxorLayout) + => DerivesFrom(symbol, fluxorComponent) || DerivesFrom(symbol, fluxorLayout); + + private static bool DerivesFrom(INamedTypeSymbol symbol, INamedTypeSymbol? candidateBaseType) { - if ( - invocation.Expression is MemberAccessExpressionSyntax memberAccess - && memberAccess.Expression is BaseExpressionSyntax) + // Note: Generics may require special handling. See OriginalDefinition documentation. + var baseType = symbol.BaseType; + while (baseType is not null) { - var symbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol; - if (symbol is not null && symbol.Name == methodName) + if (SymbolEqualityComparer.Default.Equals(baseType, candidateBaseType)) return true; + + baseType = baseType.BaseType; } + return false; } } diff --git a/Source/Lib/Fluxor.Blazor.Web.Analyzers/Fluxor.Blazor.Web.Analyzers.csproj b/Source/Lib/Fluxor.Blazor.Web.Analyzers/Fluxor.Blazor.Web.Analyzers.csproj index d76bd56a..f53e4f3e 100644 --- a/Source/Lib/Fluxor.Blazor.Web.Analyzers/Fluxor.Blazor.Web.Analyzers.csproj +++ b/Source/Lib/Fluxor.Blazor.Web.Analyzers/Fluxor.Blazor.Web.Analyzers.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file