From e30defee2d1dae899397564aeb94f8078279d2a8 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Fri, 10 May 2024 21:05:58 +0700 Subject: [PATCH] [AK1006] Should not use `Persist` or `PersistAsync` in loop (#99) --- ...ldNotCallPersistInsideLoopAnalyzerSpecs.cs | 600 ++++++++++++++++++ .../ShouldNotCallPersistInsideLoopAnalyzer.cs | 65 ++ src/Akka.Analyzers/Context/AkkaContext.cs | 12 + .../Persistence/AkkaEventsourcedContext.cs | 71 +++ .../Persistence/AkkaPersistenceContext.cs | 68 ++ src/Akka.Analyzers/Utility/RuleDescriptors.cs | 8 + 6 files changed, 824 insertions(+) create mode 100644 src/Akka.Analyzers.Tests/Analyzers/AK1000/ShouldNotCallPersistInsideLoopAnalyzerSpecs.cs create mode 100644 src/Akka.Analyzers/AK1000/ShouldNotCallPersistInsideLoopAnalyzer.cs create mode 100644 src/Akka.Analyzers/Context/Persistence/AkkaEventsourcedContext.cs create mode 100644 src/Akka.Analyzers/Context/Persistence/AkkaPersistenceContext.cs 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()",