Skip to content

Commit

Permalink
Rewrite CallBaseOnInitialized analyzer (#520)
Browse files Browse the repository at this point in the history
* Rewrite CallBaseOnInitialized analyzer

* Doc and note OriginalDefinition
  • Loading branch information
Youssef1313 authored Jan 2, 2025
1 parent 67210b3 commit 4f38fea
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 73 deletions.
119 changes: 47 additions & 72 deletions Source/Lib/Fluxor.Blazor.Web.Analyzers/CallBaseOnInitialized.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<IOperation> 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<ExpressionStatementSyntax>()
.Select(s => s.Expression)
.Select(expr =>
expr is AwaitExpressionSyntax awaitExpr
? awaitExpr.Expression
: expr)
.OfType<InvocationExpressionSyntax>()
.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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
</ItemGroup>

</Project>

0 comments on commit 4f38fea

Please sign in to comment.