-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AK1004 - Add ScheduleTell to IWithTimers analyzer (#81)
* AK1004 - Add ScheduleTell to IWithTimers analyzer * Fix merge problems * Implement fix (unit test not working) * Fix fixer code * Remove code fixer * Clean up solution files * Cleanup solution files --------- Co-authored-by: Aaron Stannard <[email protected]>
- Loading branch information
1 parent
9266142
commit 4ff3a14
Showing
11 changed files
with
1,214 additions
and
1 deletion.
There are no files selected for viewing
889 changes: 889 additions & 0 deletions
889
...nalyzers.Tests/Analyzers/AK1000/ShouldUseIWithTimersInsteadOfScheduleTellAnalyzerSpecs.cs
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
src/Akka.Analyzers/AK1000/ShouldUseIWithTimersInsteadOfScheduleTellAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
// ----------------------------------------------------------------------- | ||
// <copyright file="ShouldUseIWithTimerInsteadOfITellScheduler.cs" company="Akka.NET Project"> | ||
// Copyright (C) 2013-2024 .NET Foundation <https://github.com/akkadotnet/akka.net> | ||
// </copyright> | ||
// ----------------------------------------------------------------------- | ||
|
||
using Akka.Analyzers.Context; | ||
using Akka.Analyzers.Context.Core.Actor; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
namespace Akka.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class ShouldUseIWithTimersInsteadOfScheduleTellAnalyzer(): AkkaDiagnosticAnalyzer(RuleDescriptors.Ak1004ShouldUseIWithTimersInsteadOfScheduleTell) | ||
{ | ||
public override void AnalyzeCompilation(CompilationStartAnalysisContext context, AkkaContext akkaContext) | ||
{ | ||
Guard.AssertIsNotNull(context); | ||
Guard.AssertIsNotNull(akkaContext); | ||
|
||
context.RegisterSyntaxNodeAction(ctx => | ||
{ | ||
var invocationExpr = (InvocationExpressionSyntax)ctx.Node; | ||
var semanticModel = ctx.SemanticModel; | ||
|
||
var classDeclaration = invocationExpr.FirstAncestorOrSelf<ClassDeclarationSyntax>(); | ||
if (classDeclaration is null) | ||
return; | ||
var classBase = semanticModel.GetDeclaredSymbol(classDeclaration)?.BaseType; | ||
var coreContext = akkaContext.AkkaCore; | ||
|
||
// Check that the class declaration inherits from ActorBase | ||
if (classBase is null || !classBase.IsDerivedOrImplements(coreContext.Actor.ActorBaseType!)) | ||
return; | ||
|
||
// invocation must be a member access expression | ||
if (invocationExpr.Expression is not MemberAccessExpressionSyntax memberAccessExpr) | ||
return; | ||
|
||
// Get the member symbol from the invocation expression | ||
if(semanticModel.GetSymbolInfo(memberAccessExpr).Symbol is not IMethodSymbol methodSymbol) | ||
return; | ||
|
||
// Check if the method name is `ScheduleTellOnce` or `ScheduleTellRepeatedly` | ||
ArgumentSyntax? receiver = null; | ||
ArgumentSyntax? sender = null; | ||
var refSymbols = akkaContext.AkkaCore.Actor.ITellScheduler.ScheduleTellOnce; | ||
if (refSymbols.Any(s => ReferenceEquals(methodSymbol, s))) | ||
{ | ||
receiver = invocationExpr.ArgumentList.Arguments[1]; | ||
sender = invocationExpr.ArgumentList.Arguments[3]; | ||
} | ||
else | ||
{ | ||
refSymbols = akkaContext.AkkaCore.Actor.ITellScheduler.ScheduleTellRepeatedly; | ||
if (refSymbols.Any(s => ReferenceEquals(methodSymbol, s))) | ||
{ | ||
receiver = invocationExpr.ArgumentList.Arguments[2]; | ||
sender = invocationExpr.ArgumentList.Arguments[4]; | ||
} | ||
} | ||
|
||
// Check that both receiver and sender is Self or if sender is Nobody or NoSender | ||
if (!IsReferenceToSelf(receiver, semanticModel, coreContext.Actor) || | ||
!IsReferenceToSelfOrNobody(sender, semanticModel, coreContext.Actor)) | ||
return; | ||
|
||
var diagnostic = Diagnostic.Create( | ||
descriptor: RuleDescriptors.Ak1004ShouldUseIWithTimersInsteadOfScheduleTell, | ||
location: invocationExpr.GetLocation(), | ||
"ScheduleTell invocation"); | ||
ctx.ReportDiagnostic(diagnostic); | ||
|
||
}, SyntaxKind.InvocationExpression); | ||
} | ||
|
||
private static bool IsReferenceToSelfOrNobody(ArgumentSyntax? argument, SemanticModel semanticModel, IAkkaCoreActorContext context) | ||
{ | ||
if (argument is null) | ||
return false; | ||
|
||
var expression = argument.Expression; | ||
|
||
// null is considered as NoSender | ||
if (expression is LiteralExpressionSyntax literal && literal.Kind() == SyntaxKind.NullLiteralExpression) | ||
return true; | ||
|
||
// argument must be an identifier | ||
var identifier = expression.DescendantNodesAndSelf(node => node is IdentifierNameSyntax).FirstOrDefault(); | ||
if (identifier is null) | ||
return false; | ||
|
||
// Check for field symbols | ||
if (semanticModel.GetSymbolInfo(identifier).Symbol is IFieldSymbol fieldSymbol) | ||
{ | ||
// Argument is `ActorRefs.Nobody` | ||
if (ReferenceEquals(fieldSymbol, context.ActorRefs.Nobody)) | ||
return true; | ||
|
||
// Argument is `ActorRefs.NoSender` | ||
if (ReferenceEquals(fieldSymbol, context.ActorRefs.NoSender)) | ||
return true; | ||
} | ||
|
||
// identifier must be a property | ||
if (semanticModel.GetSymbolInfo(identifier).Symbol is not IPropertySymbol propertySymbol) | ||
return false; | ||
|
||
// Argument is `ActorBase.Self` | ||
if (ReferenceEquals(propertySymbol, context.ActorBase.Self)) | ||
return true; | ||
|
||
// Argument is `IActorRef.Self` | ||
if (ReferenceEquals(propertySymbol, context.IActorContext.Self)) | ||
return true; | ||
|
||
return false; | ||
} | ||
|
||
private static bool IsReferenceToSelf(ArgumentSyntax? argument, SemanticModel semanticModel, IAkkaCoreActorContext context) | ||
{ | ||
if (argument is null) | ||
return false; | ||
|
||
var expression = argument.Expression; | ||
|
||
// argument must be an identifier | ||
var identifier = expression.DescendantNodesAndSelf(node => node is IdentifierNameSyntax).FirstOrDefault(); | ||
if (identifier is null) | ||
return false; | ||
|
||
// identifier must be a property | ||
if (semanticModel.GetSymbolInfo(identifier).Symbol is not IPropertySymbol propertySymbol) | ||
return false; | ||
|
||
// Argument is `ActorBase.Self` | ||
if (ReferenceEquals(propertySymbol, context.ActorBase.Self)) | ||
return true; | ||
|
||
// Argument is `IActorRef.Self` | ||
if (ReferenceEquals(propertySymbol, context.IActorContext.Self)) | ||
return true; | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// ----------------------------------------------------------------------- | ||
// <copyright file="ActorRefsContext.cs" company="Akka.NET Project"> | ||
// Copyright (C) 2013-2024 .NET Foundation <https://github.com/akkadotnet/akka.net> | ||
// </copyright> | ||
// ----------------------------------------------------------------------- | ||
|
||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Akka.Analyzers.Context.Core.Actor; | ||
|
||
public interface IActorRefsContext | ||
{ | ||
public IFieldSymbol? Nobody { get; } | ||
public IFieldSymbol? NoSender { get; } | ||
} | ||
|
||
public sealed class EmptyActorRefsContext : IActorRefsContext | ||
{ | ||
public static readonly EmptyActorRefsContext Empty = new(); | ||
|
||
private EmptyActorRefsContext() { } | ||
|
||
public IFieldSymbol? Nobody => null; | ||
public IFieldSymbol? NoSender => null; | ||
} | ||
|
||
public sealed class ActorRefsContext : IActorRefsContext | ||
{ | ||
private readonly Lazy<IFieldSymbol> _lazyNobody; | ||
private readonly Lazy<IFieldSymbol> _lazyNoSender; | ||
|
||
private ActorRefsContext(IAkkaCoreActorContext context) | ||
{ | ||
_lazyNobody = new Lazy<IFieldSymbol>(() => (IFieldSymbol) context.ActorRefsType! | ||
.GetMembers("Nobody").First()); | ||
_lazyNoSender = new Lazy<IFieldSymbol>(() => (IFieldSymbol) context.ActorRefsType! | ||
.GetMembers("NoSender").First()); | ||
} | ||
|
||
public IFieldSymbol? Nobody => _lazyNobody.Value; | ||
public IFieldSymbol? NoSender => _lazyNoSender.Value; | ||
|
||
public static ActorRefsContext Get(IAkkaCoreActorContext context) | ||
=> new(context); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
src/Akka.Analyzers/Context/Core/Actor/ITellSchedulerFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// ----------------------------------------------------------------------- | ||
// <copyright file="ITellSchedulerFactory.cs" company="Akka.NET Project"> | ||
// Copyright (C) 2013-2024 .NET Foundation <https://github.com/akkadotnet/akka.net> | ||
// </copyright> | ||
// ----------------------------------------------------------------------- | ||
|
||
using System.Collections.Immutable; | ||
using Akka.Analyzers.Context.Core; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Akka.Analyzers.Core.Actor; | ||
|
||
// ReSharper disable once InconsistentNaming | ||
public static class ITellSchedulerFactory | ||
{ | ||
public static ImmutableArray<ISymbol> ScheduleTellOnce(Compilation compilation) | ||
=> ActorSymbolFactory.TellSchedulerInterface(compilation)! | ||
.GetMembers("ScheduleTellOnce"); | ||
|
||
public static ImmutableArray<ISymbol> ScheduleTellRepeatedly(Compilation compilation) | ||
=> ActorSymbolFactory.TellSchedulerInterface(compilation)! | ||
.GetMembers("ScheduleTellRepeatedly"); | ||
} |
45 changes: 45 additions & 0 deletions
45
src/Akka.Analyzers/Context/Core/Actor/TellSchedulerInterfaceContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// ----------------------------------------------------------------------- | ||
// <copyright file="TellSchedulerContext.cs" company="Akka.NET Project"> | ||
// Copyright (C) 2013-2024 .NET Foundation <https://github.com/akkadotnet/akka.net> | ||
// </copyright> | ||
// ----------------------------------------------------------------------- | ||
|
||
using System.Collections.Immutable; | ||
using Akka.Analyzers.Core.Actor; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Akka.Analyzers.Context.Core.Actor; | ||
|
||
public interface ITellSchedulerInterfaceContext | ||
{ | ||
public ImmutableArray<ISymbol> ScheduleTellOnce { get; } | ||
public ImmutableArray<ISymbol> ScheduleTellRepeatedly { get; } | ||
} | ||
|
||
public class EmptyTellSchedulerInterfaceContext : ITellSchedulerInterfaceContext | ||
{ | ||
private EmptyTellSchedulerInterfaceContext() { } | ||
|
||
public static readonly EmptyTellSchedulerInterfaceContext Instance = new(); | ||
|
||
public ImmutableArray<ISymbol> ScheduleTellOnce => new(); | ||
public ImmutableArray<ISymbol> ScheduleTellRepeatedly => new(); | ||
} | ||
|
||
public class TellSchedulerInterfaceContext: ITellSchedulerInterfaceContext | ||
{ | ||
private readonly Lazy<ImmutableArray<ISymbol>> _lazyScheduleTellOnce; | ||
private readonly Lazy<ImmutableArray<ISymbol>> _lazyScheduleTellRepeatedly; | ||
|
||
private TellSchedulerInterfaceContext(Compilation compilation) | ||
{ | ||
_lazyScheduleTellOnce = new Lazy<ImmutableArray<ISymbol>>(() => ITellSchedulerFactory.ScheduleTellOnce(compilation)); | ||
_lazyScheduleTellRepeatedly = new Lazy<ImmutableArray<ISymbol>>(() => ITellSchedulerFactory.ScheduleTellRepeatedly(compilation)); | ||
} | ||
|
||
public ImmutableArray<ISymbol> ScheduleTellOnce => _lazyScheduleTellOnce.Value; | ||
public ImmutableArray<ISymbol> ScheduleTellRepeatedly => _lazyScheduleTellRepeatedly.Value; | ||
|
||
public static ITellSchedulerInterfaceContext Get(Compilation compilation) | ||
=> new TellSchedulerInterfaceContext(compilation); | ||
} |
Oops, something went wrong.