diff --git a/Directory.Packages.props b/Directory.Packages.props
index e249a871f..5ea87d5a5 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -7,7 +7,7 @@
-
+
@@ -17,12 +17,14 @@
-
-
-
+
+
+
+
+
-
+
@@ -33,7 +35,7 @@
-
+
@@ -45,8 +47,8 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/all.sln b/all.sln
index eabc532e9..57601210d 100644
--- a/all.sln
+++ b/all.sln
@@ -155,7 +155,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JobsSample", "examples\Jobs
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Workflow.Test", "test\Dapr.Workflow.Test\Dapr.Workflow.Test.csproj", "{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Messaging", "Messaging", "{8DB002D2-19E9-4342-A86B-025A367DF3D1}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Analyzers", "src\Dapr.Actors.Analyzers\Dapr.Actors.Analyzers\Dapr.Actors.Analyzers.csproj", "{E49C822C-E921-48DF-897B-3E603CA596D2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Analyzers.Tests", "test\Dapr.Actors.Analyzers.Tests\Dapr.Actors.Analyzers.Tests.csproj", "{A2C0F203-11FF-4B7F-A94F-B9FD873573FE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -405,6 +407,14 @@ Global
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E49C822C-E921-48DF-897B-3E603CA596D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E49C822C-E921-48DF-897B-3E603CA596D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E49C822C-E921-48DF-897B-3E603CA596D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E49C822C-E921-48DF-897B-3E603CA596D2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A2C0F203-11FF-4B7F-A94F-B9FD873573FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A2C0F203-11FF-4B7F-A94F-B9FD873573FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A2C0F203-11FF-4B7F-A94F-B9FD873573FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A2C0F203-11FF-4B7F-A94F-B9FD873573FE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -478,8 +488,8 @@ Global
{D9697361-232F-465D-A136-4561E0E88488} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
{9CAF360E-5AD3-4C4F-89A0-327EEB70D673} = {D9697361-232F-465D-A136-4561E0E88488}
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4} = {DD020B34-460F-455F-8D17-CF4A949F100B}
- {8DB002D2-19E9-4342-A86B-025A367DF3D1} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
- {290D1278-F613-4DF3-9DF5-F37E38CDC363} = {8DB002D2-19E9-4342-A86B-025A367DF3D1}
+ {E49C822C-E921-48DF-897B-3E603CA596D2} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
+ {A2C0F203-11FF-4B7F-A94F-B9FD873573FE} = {DD020B34-460F-455F-8D17-CF4A949F100B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}
diff --git a/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/AnalyzerReleases.Shipped.md b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/AnalyzerReleases.Shipped.md
new file mode 100644
index 000000000..bc73dcb0b
--- /dev/null
+++ b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/AnalyzerReleases.Shipped.md
@@ -0,0 +1,9 @@
+
+
+## Release 1.16.0
+
+### New Rules
+
+Rule ID | Category | Severity | Notes
+--------|----------|----------|------------------------------------------------------------------------------------
+DAPR4001 | Usage | Warning | Actor timer method invocations require the named callback method to exist on type.
\ No newline at end of file
diff --git a/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/AnalyzerReleases.Unshipped.md b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/AnalyzerReleases.Unshipped.md
new file mode 100644
index 000000000..b96f00659
--- /dev/null
+++ b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/AnalyzerReleases.Unshipped.md
@@ -0,0 +1 @@
+; Unshipped analyzer release
\ No newline at end of file
diff --git a/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/AssemblyInfo.cs b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/AssemblyInfo.cs
new file mode 100644
index 000000000..dcc08b3c1
--- /dev/null
+++ b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/AssemblyInfo.cs
@@ -0,0 +1,16 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Dapr.Actors.Analyzers.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2" )]
diff --git a/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers.csproj b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers.csproj
new file mode 100644
index 000000000..a3c751c38
--- /dev/null
+++ b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers.csproj
@@ -0,0 +1,37 @@
+
+
+
+ netstandard2.0
+
+ false
+ enable
+ enable
+ This package contains Roslyn analyzers for Dapr.Actors.
+
+ true
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
diff --git a/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Properties/launchSettings.json b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Properties/launchSettings.json
new file mode 100644
index 000000000..a4b2b510e
--- /dev/null
+++ b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "DebugRoslynAnalyzers": {
+ "commandName": "DebugRoslynComponent",
+ "targetProject": "../Dapr.Actors.Analyzers.Sample/Dapr.Actors.Analyzers.Sample.csproj"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Readme.md b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Readme.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Resources.Designer.cs b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Resources.Designer.cs
new file mode 100644
index 000000000..af3dd797d
--- /dev/null
+++ b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Resources.Designer.cs
@@ -0,0 +1,80 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Dapr.Actors.Analyzers {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Dapr.Actors.Analyzers.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Actor timer method invocations require the named callback '{0}' method to exist on type '{1}'.
+ ///
+ internal static string DAPR4001MessageFormat {
+ get {
+ return ResourceManager.GetString("DAPR4001MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Actor timer method invocations require the named callback method to exist on type.
+ ///
+ internal static string DAPR4001Title {
+ get {
+ return ResourceManager.GetString("DAPR4001Title", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Resources.resx b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Resources.resx
new file mode 100644
index 000000000..47f6c667e
--- /dev/null
+++ b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/Resources.resx
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Actor timer method invocations require the named callback method to exist on type
+
+
+ Actor timer method invocations require the named callback '{0}' method to exist on type '{1}'
+
+
\ No newline at end of file
diff --git a/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/TimerMethodPresentAnalyzer.cs b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/TimerMethodPresentAnalyzer.cs
new file mode 100644
index 000000000..59640fd58
--- /dev/null
+++ b/src/Dapr.Actors.Analyzers/Dapr.Actors.Analyzers/TimerMethodPresentAnalyzer.cs
@@ -0,0 +1,105 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Dapr.Actors.Analyzers;
+
+///
+/// An analyzer for Dapr Actors that validates that whenever a register is registered, the method specified to invoke
+/// as the callback should actually exist on the type.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class TimerMethodPresentAnalyzer : DiagnosticAnalyzer
+{
+ ///
+ /// The rule validated by the analyzer.
+ ///
+ public static readonly DiagnosticDescriptor DaprTimerCallbackMethodRule = new(
+ id: "DAPR4001",
+ title: new LocalizableResourceString(nameof(Resources.DAPR4001Title), Resources.ResourceManager, typeof(Resources)),
+ messageFormat: new LocalizableResourceString(nameof(Resources.DAPR4001MessageFormat), Resources.ResourceManager, typeof(Resources)),
+ category: "Usage",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true
+ );
+
+ ///
+ public override ImmutableArray SupportedDiagnostics =>
+ ImmutableArray.Create(DaprTimerCallbackMethodRule);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.InvocationExpression);
+ }
+
+ private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context)
+ {
+ var invocationExpr = (InvocationExpressionSyntax)context.Node;
+
+ var methodSymbol = context.SemanticModel.GetSymbolInfo(invocationExpr.Expression).Symbol as IMethodSymbol;
+
+ if (methodSymbol is null
+ || methodSymbol.Name != "RegisterTimerAsync"
+ || methodSymbol.ContainingType.Name != "Actor"
+ || methodSymbol.ContainingType.ContainingNamespace.ToDisplayString() != "Dapr.Actors.Runtime")
+ {
+ return;
+ }
+
+ var ancestorType = invocationExpr.FirstAncestorOrSelf();
+ if (ancestorType is null)
+ {
+ return;
+ }
+ var ancestorTypeSymbol = context.SemanticModel.GetDeclaredSymbol(ancestorType);
+ if (ancestorTypeSymbol is null)
+ {
+ return;
+ }
+ var ancestorTypeName = ancestorTypeSymbol?.Name;
+
+ var arguments = invocationExpr.ArgumentList.Arguments;
+ if (arguments.Count < 2)
+ {
+ return;
+ }
+
+ var secondArgument = arguments[1].Expression;
+ var methodNameValue = context.SemanticModel.GetConstantValue(secondArgument);
+ if (!methodNameValue.HasValue || methodNameValue.Value is not string methodName)
+ {
+ return;
+ }
+
+ var members = ancestorTypeSymbol?.GetMembers().OfType().ToList();
+ var methodExists = members?.Any(m => m.Name == methodName) == true;
+ if (!methodExists)
+ {
+ //Get the type that contains our RegisterTimerAsync method
+
+ var diagnostic = Diagnostic.Create(SupportedDiagnostics[0], secondArgument.GetLocation(), methodName,
+ ancestorTypeName);
+ context.ReportDiagnostic(diagnostic);
+ }
+ }
+}
diff --git a/test/Dapr.Actors.Analyzers.Tests/Dapr.Actors.Analyzers.Tests.csproj b/test/Dapr.Actors.Analyzers.Tests/Dapr.Actors.Analyzers.Tests.csproj
new file mode 100644
index 000000000..d801c2dfd
--- /dev/null
+++ b/test/Dapr.Actors.Analyzers.Tests/Dapr.Actors.Analyzers.Tests.csproj
@@ -0,0 +1,35 @@
+
+
+
+ enable
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Dapr.Actors.Analyzers.Tests/TimerMethodPresentAnalyzerTests.cs b/test/Dapr.Actors.Analyzers.Tests/TimerMethodPresentAnalyzerTests.cs
new file mode 100644
index 000000000..bdcf0e469
--- /dev/null
+++ b/test/Dapr.Actors.Analyzers.Tests/TimerMethodPresentAnalyzerTests.cs
@@ -0,0 +1,148 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+
+namespace Dapr.Actors.Analyzers.Tests;
+
+public class TimerMethodPresentAnalyzerTests
+{
+ #if NET8_0
+ private static readonly ReferenceAssemblies assemblies = ReferenceAssemblies.Net.Net80;
+ #elif NET9_0
+ private static readonly ReferenceAssemblies assemblies = ReferenceAssemblies.Net.Net90;
+ #endif
+
+ [Fact]
+ public async Task TestActor_TimerRegistration_NotPresent()
+ {
+ var context = new CSharpAnalyzerTest();
+ context.ReferenceAssemblies = assemblies.AddPackages([
+ new ("Dapr.Actors", "1.15.3")
+ ]);
+
+ context.TestCode = """
+ using System;
+ using System.Threading.Tasks;
+ using Dapr.Actors.Runtime;
+ internal sealed class TestActorTimerRegistrationNotPresent(ActorHost host) : Actor(host)
+ {
+ public async Task DoSomethingAsync()
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(250));
+ }
+ }
+ """;
+
+ context.ExpectedDiagnostics.Clear();
+ await context.RunAsync();
+ }
+
+ [Fact]
+ public async Task TestActor_TimerRegistration_NameOfCallbackPresent()
+ {
+ var context = new CSharpAnalyzerTest();
+ context.ReferenceAssemblies = assemblies.AddPackages([
+ new ("Dapr.Actors", "1.15.3")
+ ]);
+
+
+ context.TestCode = """
+ using System;
+ using System.Threading.Tasks;
+ using Dapr.Actors.Runtime;
+ internal sealed class TestActorTimerRegistrationTimerCallbackPresent(ActorHost host) : Actor(host)
+ {
+ public async Task DoSomethingAsync()
+ {
+ await RegisterTimerAsync("MyTimer", nameof(TimerCallback), null, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(10));
+ }
+
+ private static async Task TimerCallback(byte[] data)
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(250));
+ }
+ }
+ """;
+
+ context.ExpectedDiagnostics.Clear();
+ await context.RunAsync();
+ }
+
+ [Fact]
+ public async Task TestActor_TimerRegistration_LiteralCallbackPresent()
+ {
+ var context = new CSharpAnalyzerTest();
+ context.ReferenceAssemblies = assemblies.AddPackages([
+ new ("Dapr.Actors", "1.15.3")
+ ]);
+
+
+ context.TestCode = """
+ using System;
+ using System.Threading.Tasks;
+ using Dapr.Actors.Runtime;
+ internal sealed class TestActorTimerRegistrationTimerCallbackPresent(ActorHost host) : Actor(host)
+ {
+ public async Task DoSomethingAsync()
+ {
+ await RegisterTimerAsync("MyTimer", "TimerCallback", null, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(10));
+ }
+
+ private static async Task TimerCallback(byte[] data)
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(250));
+ }
+ }
+ """;
+
+ context.ExpectedDiagnostics.Clear();
+ await context.RunAsync();
+ }
+
+ [Fact]
+ public async Task TestActor_TimerRegistration_CallbackNotPresent()
+ {
+ var context = new CSharpAnalyzerTest();
+ context.ReferenceAssemblies = assemblies.AddPackages([
+ new ("Dapr.Actors", "1.15.3")
+ ]);
+
+ context.TestCode = """
+ using System;
+ using System.Threading.Tasks;
+ using Dapr.Actors.Runtime;
+ internal sealed class TestActorTimerRegistrationTimerCallbackNotPresent(ActorHost host) : Actor(host)
+ {
+ public async Task DoSomethingAsync()
+ {
+ await RegisterTimerAsync("MyTimer", "TimerCallback", null, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(10));
+ }
+ }
+ """;
+
+ context.ExpectedDiagnostics.Add(new DiagnosticResult(TimerMethodPresentAnalyzer.DaprTimerCallbackMethodRule)
+ .WithSpan(8, 45, 8, 60)
+ .WithArguments("TimerCallback", "TestActorTimerRegistrationTimerCallbackNotPresent"));
+ await context.RunAsync();
+ }
+}
+
+
+
+
+
+