diff --git a/src/Akka.Analyzers.Tests/Analyzers/AK1000/ShouldNotCallPersistInsideLoopAnalyzerSpecs.cs b/src/Akka.Analyzers.Tests/Analyzers/AK1000/ShouldNotCallPersistInsideLoopAnalyzerSpecs.cs
new file mode 100644
index 0000000..2724c65
--- /dev/null
+++ b/src/Akka.Analyzers.Tests/Analyzers/AK1000/ShouldNotCallPersistInsideLoopAnalyzerSpecs.cs
@@ -0,0 +1,600 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2013-2024 .NET Foundation
+//
+// -----------------------------------------------------------------------
+
+using Microsoft.CodeAnalysis;
+using Verify = Akka.Analyzers.Tests.Utility.AkkaVerifier;
+
+namespace Akka.Analyzers.Tests.Analyzers.AK1000;
+
+public class ShouldNotCallPersistInsideLoopAnalyzerSpecs
+{
+ public static readonly TheoryData SuccessCases = new()
+ {
+ // ReceivePersistenceActor calling Persist and PersistAsync methods outside of loop
+"""
+// 01
+using Akka.Persistence;
+using System.Linq;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ CommandAny(obj =>
+ {
+ foreach (var _ in Enumerable.Range(0, 10))
+ {
+ // Inconsequential loop
+ }
+
+ Persist(obj, o => {});
+ PersistAsync(obj, o => {});
+ PersistAll( new[]{ obj }, o => {});
+ PersistAllAsync( new[]{ obj }, o => {});
+ });
+ }
+}
+""",
+
+ // UntypedPersistenceActor calling Persist and PersistAsync methods outside of loop
+"""
+// 02
+using Akka.Persistence;
+using System.Linq;
+
+public class MyActor: UntypedPersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected override void OnCommand(object obj)
+ {
+ foreach (var _ in Enumerable.Range(0, 10))
+ {
+ // Inconsequential loop
+ }
+
+ Persist(obj, o => {});
+ PersistAsync(obj, o => {});
+ PersistAll( new[]{ obj }, o => {});
+ PersistAllAsync( new[]{ obj }, o => {});
+ }
+
+ protected override void OnRecover(object message) { }
+}
+""",
+
+ // ReceivePersistenceActor without Persist and PersistAsync methods calls
+"""
+// 03
+using Akka.Persistence;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+}
+""",
+
+ // UntypedPersistenceActor without Persist and PersistAsync methods calls
+"""
+// 04
+using Akka.Persistence;
+
+public class MyActor: UntypedPersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected override void OnCommand(object message) { }
+ protected override void OnRecover(object message) { }
+}
+""",
+
+ // Non-Actor class that implements methods that have the same methods fingerprints, we're not responsible for this.
+"""
+// 05
+using System;
+using System.Linq;
+
+public class MyNonActor
+{
+ public string PersistenceId { get; }
+
+ public MyNonActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected void OnCommand(object message)
+ {
+ foreach (var i in Enumerable.Range(0, 10))
+ {
+ Persist(i, o => {});
+ PersistAsync(i, o => {});
+ }
+ }
+
+ public void Persist(TEvent @event, Action handler) { }
+ public void PersistAsync(TEvent @event, Action handler) { }
+}
+""",
+ };
+
+ public static readonly
+ TheoryData<(string testData, (int startLine, int startColumn, int endLine, int endColumn) spanData, object[] arguments)>
+ FailureCases = new()
+ {
+ // ReceivePersistentActor calling Persist inside a foreach
+ (
+"""
+// 01
+using Akka.Persistence;
+using System.Linq;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ CommandAny(obj =>
+ {
+ foreach (var i in Enumerable.Range(0, 10))
+ {
+ Persist(i, o => {});
+ }
+ });
+ }
+}
+""", (15, 17, 15, 36), ["Persist", "PersistAll"]),
+
+ // ReceivePersistentActor calling Persist inside a for loop
+ (
+"""
+// 02
+using Akka.Persistence;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ CommandAny(obj =>
+ {
+ for (var i=0; i<10; i++)
+ {
+ Persist(i, o => {});
+ }
+ });
+ }
+}
+""", (14, 17, 14, 36), ["Persist", "PersistAll"]),
+
+ // ReceivePersistentActor calling Persist inside a while loop
+ (
+"""
+// 03
+using Akka.Persistence;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ CommandAny(obj =>
+ {
+ var i=0;
+ while(i<10)
+ {
+ Persist(i, o => {});
+ i++;
+ }
+ });
+ }
+}
+""", (15, 17, 15, 36), ["Persist", "PersistAll"]),
+
+ // ReceivePersistentActor calling Persist inside a do loop
+ (
+"""
+// 04
+using Akka.Persistence;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ CommandAny(obj =>
+ {
+ var i=0;
+ do
+ {
+ Persist(i, o => {});
+ i++;
+ } while(i<10);
+ });
+ }
+}
+""", (15, 17, 15, 36), ["Persist", "PersistAll"]),
+
+ // ReceivePersistentActor calling PersistAsync inside a foreach
+ (
+"""
+// 05
+using Akka.Persistence;
+using System.Linq;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ CommandAny(obj =>
+ {
+ foreach (var i in Enumerable.Range(0, 10))
+ {
+ PersistAsync(i, o => {});
+ }
+ });
+ }
+}
+""", (15, 17, 15, 41), ["PersistAsync", "PersistAllAsync"]),
+
+ // ReceivePersistentActor calling PersistAsync inside a for loop
+ (
+"""
+// 06
+using Akka.Persistence;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ CommandAny(obj =>
+ {
+ for (var i=0; i<10; i++)
+ {
+ PersistAsync(i, o => {});
+ }
+ });
+ }
+}
+""", (14, 17, 14, 41), ["PersistAsync", "PersistAllAsync"]),
+
+ // ReceivePersistentActor calling PersistAsync inside a while loop
+ (
+"""
+// 07
+using Akka.Persistence;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ CommandAny(obj =>
+ {
+ var i=0;
+ while(i<10)
+ {
+ PersistAsync(i, o => {});
+ i++;
+ }
+ });
+ }
+}
+""", (15, 17, 15, 41), ["PersistAsync", "PersistAllAsync"]),
+
+ // ReceivePersistentActor calling Persist inside a do loop
+ (
+"""
+// 08
+using Akka.Persistence;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ CommandAny(obj =>
+ {
+ var i=0;
+ do
+ {
+ PersistAsync(i, o => {});
+ i++;
+ } while(i<10);
+ });
+ }
+}
+""", (15, 17, 15, 41), ["PersistAsync", "PersistAllAsync"]),
+
+ // UntypedPersistentActor calling Persist inside a foreach
+ (
+"""
+// 09
+using Akka.Persistence;
+using System.Linq;
+
+public class MyActor: UntypedPersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected override void OnCommand(object message)
+ {
+ foreach (var i in Enumerable.Range(0, 10))
+ {
+ Persist(i, o => {});
+ }
+ }
+
+ protected override void OnRecover(object message) { }
+}
+""", (17, 13, 17, 32), ["Persist", "PersistAll"]),
+
+ // UntypedPersistentActor calling Persist inside a for loop
+ (
+"""
+// 10
+using Akka.Persistence;
+
+public class MyActor: UntypedPersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected override void OnCommand(object message)
+ {
+ for (var i=0; i<10; i++)
+ {
+ Persist(i, o => {});
+ }
+ }
+
+ protected override void OnRecover(object message) { }
+}
+""", (16, 13, 16, 32), ["Persist", "PersistAll"]),
+
+ // UntypedPersistentActor calling Persist inside a while loop
+ (
+"""
+// 11
+using Akka.Persistence;
+
+public class MyActor: UntypedPersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected override void OnCommand(object message)
+ {
+ var i=0;
+ while(i<10)
+ {
+ Persist(i, o => {});
+ i++;
+ }
+ }
+
+ protected override void OnRecover(object message) { }
+}
+""", (17, 13, 17, 32), ["Persist", "PersistAll"]),
+
+ // UntypedPersistentActor calling Persist inside a do loop
+ (
+"""
+// 12
+using Akka.Persistence;
+
+public class MyActor: UntypedPersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected override void OnCommand(object message)
+ {
+ var i=0;
+ do
+ {
+ Persist(i, o => {});
+ i++;
+ } while(i<10);
+ }
+
+ protected override void OnRecover(object message) { }
+}
+""", (17, 13, 17, 32), ["Persist", "PersistAll"]),
+
+ // UntypedPersistentActor calling PersistAsync inside a foreach
+ (
+"""
+// 13
+using Akka.Persistence;
+using System.Linq;
+
+public class MyActor: UntypedPersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected override void OnCommand(object message)
+ {
+ foreach (var i in Enumerable.Range(0, 10))
+ {
+ PersistAsync(i, o => {});
+ }
+ }
+
+ protected override void OnRecover(object message) { }
+}
+""", (17, 13, 17, 37), ["PersistAsync", "PersistAllAsync"]),
+
+ // UntypedPersistentActor calling PersistAsync inside a for loop
+ (
+"""
+// 14
+using Akka.Persistence;
+
+public class MyActor: UntypedPersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected override void OnCommand(object message)
+ {
+ for (var i=0; i<10; i++)
+ {
+ PersistAsync(i, o => {});
+ }
+ }
+
+ protected override void OnRecover(object message) { }
+}
+""", (16, 13, 16, 37), ["PersistAsync", "PersistAllAsync"]),
+
+ // UntypedPersistentActor calling PersistAsync inside a while loop
+ (
+"""
+// 15
+using Akka.Persistence;
+
+public class MyActor: UntypedPersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected override void OnCommand(object message)
+ {
+ var i=0;
+ while(i<10)
+ {
+ PersistAsync(i, o => {});
+ i++;
+ }
+ }
+
+ protected override void OnRecover(object message) { }
+}
+""", (17, 13, 17, 37), ["PersistAsync", "PersistAllAsync"]),
+
+ // UntypedPersistentActor calling Persist inside a do loop
+ (
+"""
+// 16
+using Akka.Persistence;
+
+public class MyActor: UntypedPersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ }
+
+ protected override void OnCommand(object message)
+ {
+ var i=0;
+ do
+ {
+ PersistAsync(i, o => {});
+ i++;
+ } while(i<10);
+ }
+
+ protected override void OnRecover(object message) { }
+}
+""", (17, 13, 17, 37), ["PersistAsync", "PersistAllAsync"]),
+
+ // ReceivePersistentActor calling Persist inside a message handler delegate
+ (
+"""
+// 17
+using Akka.Persistence;
+using System.Linq;
+
+public class MyActor: ReceivePersistentActor
+{
+ public override string PersistenceId { get; }
+ public MyActor(string persistenceId)
+ {
+ PersistenceId = persistenceId;
+ CommandAny(MessageHandler);
+ }
+
+ private void MessageHandler(object obj)
+ {
+ foreach (var i in Enumerable.Range(0, 10))
+ {
+ Persist(i, o => {});
+ }
+ }
+}
+""", (18, 13, 18, 32), ["Persist", "PersistAll"]),
+ };
+
+ [Theory]
+ [MemberData(nameof(SuccessCases))]
+ public async Task SuccessCase(string testCode)
+ {
+ await Verify.VerifyAnalyzer(testCode).ConfigureAwait(true);
+ }
+
+ [Theory]
+ [MemberData(nameof(FailureCases))]
+ public Task FailureCase(
+ (string testCode, (int startLine, int startColumn, int endLine, int endColumn) spanData, object[] arguments) d)
+ {
+ var expected = Verify.Diagnostic()
+ .WithSpan(d.spanData.startLine, d.spanData.startColumn, d.spanData.endLine, d.spanData.endColumn)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments(d.arguments);
+
+ return Verify.VerifyAnalyzer(d.testCode, expected);
+ }
+}
\ No newline at end of file
diff --git a/src/Akka.Analyzers/AK1000/ShouldNotCallPersistInsideLoopAnalyzer.cs b/src/Akka.Analyzers/AK1000/ShouldNotCallPersistInsideLoopAnalyzer.cs
new file mode 100644
index 0000000..44f72b2
--- /dev/null
+++ b/src/Akka.Analyzers/AK1000/ShouldNotCallPersistInsideLoopAnalyzer.cs
@@ -0,0 +1,65 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2013-2024 .NET Foundation
+//
+// -----------------------------------------------------------------------
+
+using Akka.Analyzers.Context;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Akka.Analyzers;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public class ShouldNotCallPersistInsideLoopAnalyzer(): AkkaDiagnosticAnalyzer(RuleDescriptors.Ak1006ShouldNotUsePersistInsideLoop)
+{
+ public override void AnalyzeCompilation(CompilationStartAnalysisContext context, AkkaContext akkaContext)
+ {
+ Guard.AssertIsNotNull(context);
+ Guard.AssertIsNotNull(akkaContext);
+
+ context.RegisterSyntaxNodeAction(ctx =>
+ {
+ // No need to check if Akka.Persistence is not installed
+ if (!akkaContext.HasAkkaPersistenceInstalled)
+ return;
+
+ var invocationExpression = (InvocationExpressionSyntax)ctx.Node;
+ var semanticModel = ctx.SemanticModel;
+
+ // Get the member symbol from the invocation expression
+ if(semanticModel.GetSymbolInfo(invocationExpression.Expression).Symbol is not IMethodSymbol methodInvocationSymbol)
+ return;
+
+ var persistenceContext = akkaContext.AkkaPersistence;
+
+ // Check if the method name is `Persist` or `PersistAsync`
+ var eventsourcedContext = persistenceContext.Eventsourced;
+ var refMethods = eventsourcedContext.Persist.AddRange(eventsourcedContext.PersistAsync);
+ if (!methodInvocationSymbol.MatchesAny(refMethods))
+ return;
+
+ // Traverse up the parent nodes to see if any of them are loops
+ var parent = invocationExpression.Parent;
+ while (parent != null)
+ {
+ if (parent is ForStatementSyntax or WhileStatementSyntax or DoStatementSyntax or ForEachStatementSyntax)
+ {
+ // If found, report a diagnostic
+ var methodName = methodInvocationSymbol.Name;
+ var replacementName = methodName.EndsWith("Async", StringComparison.InvariantCulture)
+ ? "PersistAllAsync" : "PersistAll";
+ var diagnostic = Diagnostic.Create(
+ descriptor: RuleDescriptors.Ak1006ShouldNotUsePersistInsideLoop,
+ location: invocationExpression.GetLocation(),
+ methodName,
+ replacementName);
+ ctx.ReportDiagnostic(diagnostic);
+ }
+ parent = parent.Parent;
+ }
+ }, SyntaxKind.InvocationExpression);
+ }
+}
\ No newline at end of file
diff --git a/src/Akka.Analyzers/Context/AkkaContext.cs b/src/Akka.Analyzers/Context/AkkaContext.cs
index d388cdc..e20c7ed 100644
--- a/src/Akka.Analyzers/Context/AkkaContext.cs
+++ b/src/Akka.Analyzers/Context/AkkaContext.cs
@@ -7,6 +7,7 @@
using Akka.Analyzers.Context.Cluster;
using Akka.Analyzers.Context.ClusterSharding;
using Akka.Analyzers.Context.Core;
+using Akka.Analyzers.Context.Persistence;
using Akka.Analyzers.Context.System;
using Microsoft.CodeAnalysis;
@@ -30,6 +31,7 @@ public AkkaContext(Compilation compilation)
AkkaCore = AkkaCoreContext.Get(compilation);
AkkaCluster = AkkaClusterContext.Get(compilation);
AkkaClusterSharding = AkkaClusterShardingContext.Get(compilation);
+ AkkaPersistence = AkkaPersistenceContext.Get(compilation);
SystemThreadingTasks = SystemThreadingTasksContext.Get(compilation);
}
@@ -63,5 +65,15 @@ public AkkaContext(Compilation compilation)
///
public bool HasAkkaClusterShardingInstalled => AkkaClusterSharding != EmptyAkkaClusterShardingContext.Instance;
+ ///
+ /// Symbol data and availability for Akka.Persistence.
+ ///
+ public IAkkaPersistenceContext AkkaPersistence { get; }
+
+ ///
+ /// Does the current compilation context have Akka.Persistence installed?
+ ///
+ public bool HasAkkaPersistenceInstalled => AkkaPersistence != EmptyPersistenceContext.Instance;
+
public ISystemThreadingTasksContext SystemThreadingTasks { get; }
}
\ No newline at end of file
diff --git a/src/Akka.Analyzers/Context/Persistence/AkkaEventsourcedContext.cs b/src/Akka.Analyzers/Context/Persistence/AkkaEventsourcedContext.cs
new file mode 100644
index 0000000..74199ac
--- /dev/null
+++ b/src/Akka.Analyzers/Context/Persistence/AkkaEventsourcedContext.cs
@@ -0,0 +1,71 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2013-2024 .NET Foundation
+//
+// -----------------------------------------------------------------------
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+
+namespace Akka.Analyzers.Context.Persistence;
+
+public interface IEventsourcedContext
+{
+ #region Methods
+
+ public ImmutableArray Persist { get; }
+ public ImmutableArray PersistAsync { get; }
+ public ImmutableArray PersistAll { get; }
+ public ImmutableArray PersistAllAsync { get; }
+
+ #endregion
+}
+
+public sealed class EmptyEventsourcedContext : IEventsourcedContext
+{
+ public static EmptyEventsourcedContext Instance => new();
+
+ private EmptyEventsourcedContext() { }
+
+ public ImmutableArray Persist => ImmutableArray.Empty;
+ public ImmutableArray PersistAsync => ImmutableArray.Empty;
+ public ImmutableArray PersistAll => ImmutableArray.Empty;
+ public ImmutableArray PersistAllAsync => ImmutableArray.Empty;
+}
+
+public class EventsourcedContext: IEventsourcedContext
+{
+ private readonly Lazy> _lazyPersist;
+ private readonly Lazy> _lazyPersistAsync;
+ private readonly Lazy> _lazyPersistAll;
+ private readonly Lazy> _lazyPersistAllAsync;
+
+ private EventsourcedContext(AkkaPersistenceContext context)
+ {
+ Guard.AssertIsNotNull(context.EventsourcedType);
+
+ _lazyPersist = new Lazy>(() => context.EventsourcedType!
+ .GetMembers(nameof(Persist))
+ .Select(m => (IMethodSymbol)m).ToImmutableArray());
+ _lazyPersistAsync = new Lazy>(() => context.EventsourcedType!
+ .GetMembers(nameof(PersistAsync))
+ .Select(m => (IMethodSymbol)m).ToImmutableArray());
+ _lazyPersistAll = new Lazy>(() => context.EventsourcedType!
+ .GetMembers(nameof(PersistAll))
+ .Select(m => (IMethodSymbol)m).ToImmutableArray());
+ _lazyPersistAllAsync = new Lazy>(() => context.EventsourcedType!
+ .GetMembers(nameof(PersistAllAsync))
+ .Select(m => (IMethodSymbol)m).ToImmutableArray());
+ }
+
+ public ImmutableArray Persist => _lazyPersist.Value;
+ public ImmutableArray PersistAsync => _lazyPersistAsync.Value;
+ public ImmutableArray PersistAll => _lazyPersistAll.Value;
+ public ImmutableArray PersistAllAsync => _lazyPersistAllAsync.Value;
+
+ public static EventsourcedContext Get(AkkaPersistenceContext context)
+ {
+ Guard.AssertIsNotNull(context);
+ return new EventsourcedContext(context);
+ }
+}
\ No newline at end of file
diff --git a/src/Akka.Analyzers/Context/Persistence/AkkaPersistenceContext.cs b/src/Akka.Analyzers/Context/Persistence/AkkaPersistenceContext.cs
new file mode 100644
index 0000000..b728c28
--- /dev/null
+++ b/src/Akka.Analyzers/Context/Persistence/AkkaPersistenceContext.cs
@@ -0,0 +1,68 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2013-2024 .NET Foundation
+//
+// -----------------------------------------------------------------------
+
+using Akka.Analyzers.Context.Core;
+using Microsoft.CodeAnalysis;
+
+namespace Akka.Analyzers.Context.Persistence;
+
+public interface IAkkaPersistenceContext
+{
+ Version Version { get; }
+
+ INamedTypeSymbol? PersistenceType { get; }
+ INamedTypeSymbol? EventsourcedType { get; }
+
+ IEventsourcedContext Eventsourced { get; }
+}
+
+public sealed class EmptyPersistenceContext : IAkkaPersistenceContext
+{
+ public static EmptyPersistenceContext Instance => new();
+
+ private EmptyPersistenceContext()
+ {
+ }
+
+ public Version Version => new();
+ public INamedTypeSymbol? PersistenceType => null;
+ public INamedTypeSymbol? EventsourcedType => null;
+ public IEventsourcedContext Eventsourced => EmptyEventsourcedContext.Instance;
+}
+
+public class AkkaPersistenceContext: IAkkaPersistenceContext
+{
+ public const string PersistenceNamespace = AkkaCoreContext.AkkaNamespace + ".Persistence";
+
+ private readonly Lazy _lazyPersistenceType;
+ private readonly Lazy _lazyEventsourcedType;
+
+ private AkkaPersistenceContext(Compilation compilation, Version version)
+ {
+ Version = version;
+ _lazyPersistenceType = new Lazy(() => compilation.GetTypeByMetadataName($"{PersistenceNamespace}.Persistence"));
+ _lazyEventsourcedType = new Lazy(() => compilation.GetTypeByMetadataName($"{PersistenceNamespace}.Eventsourced"));
+ Eventsourced = EventsourcedContext.Get(this);
+ }
+
+ public static IAkkaPersistenceContext Get(Compilation compilation, Version? versionOverride = null)
+ {
+ // assert that compilation is not null
+ Guard.AssertIsNotNull(compilation);
+
+ var version = versionOverride ?? compilation
+ .ReferencedAssemblyNames
+ .FirstOrDefault(a => a.Name.Equals(PersistenceNamespace, StringComparison.OrdinalIgnoreCase))?
+ .Version;
+
+ return version is null ? EmptyPersistenceContext.Instance : new AkkaPersistenceContext(compilation, version);
+ }
+
+ public Version Version { get; }
+ public INamedTypeSymbol? PersistenceType => _lazyPersistenceType.Value;
+ public INamedTypeSymbol? EventsourcedType => _lazyEventsourcedType.Value;
+ public IEventsourcedContext Eventsourced { get; }
+}
\ No newline at end of file
diff --git a/src/Akka.Analyzers/Utility/RuleDescriptors.cs b/src/Akka.Analyzers/Utility/RuleDescriptors.cs
index 6cca89e..0d2ae1d 100644
--- a/src/Akka.Analyzers/Utility/RuleDescriptors.cs
+++ b/src/Akka.Analyzers/Utility/RuleDescriptors.cs
@@ -72,6 +72,14 @@ private static DiagnosticDescriptor Rule(
"method is invoked, because there is no guarantee that asynchronous context will be preserved " +
"when the lambda is invoked inside the method.");
+ public static DiagnosticDescriptor Ak1006ShouldNotUsePersistInsideLoop { get; } = Rule(
+ id: "AK1006",
+ title: "Should not call `Persist` inside a loop",
+ category: AnalysisCategory.ActorDesign,
+ defaultSeverity: DiagnosticSeverity.Warning,
+ messageFormat: "Calling `{0}()` inside a loop is discouraged as it is non-performant. Collect all of your " +
+ "changes inside the loop and then call `{1}()` after the loop instead.");
+
public static DiagnosticDescriptor Ak1007MustNotUseIWithTimersInPreRestart { get; } = Rule(
id: "AK1007",
title: "Timers.StartSingleTimer() and Timers.StartPeriodicTimer() must not be used inside AroundPreRestart() or PreRestart()",