From afba9a9441cbc6d1c6e26f317de7488067d045bd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 17 Dec 2025 00:19:49 +0000
Subject: [PATCH 1/8] Initial plan
From 70b84c3fd9556b6452decc9ca2f663a90da3c8bc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 17 Dec 2025 00:23:37 +0000
Subject: [PATCH 2/8] Update global.json to use available .NET SDK version
Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
---
global.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/global.json b/global.json
index 332b6c45..4b560e68 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "10.0.101",
+ "version": "10.0.100",
"rollForward": "latestFeature"
},
"msbuild-sdks": {
From 0fc65758a624cb4fd34418b1b16377ce3375bbd2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 17 Dec 2025 00:34:01 +0000
Subject: [PATCH 3/8] Add TimeProvider support to DateTimeOrchestrationAnalyzer
Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
---
src/Analyzers/KnownTypeSymbols.Net.cs | 6 +
.../DateTimeOrchestrationAnalyzer.cs | 31 +++-
.../DateTimeOrchestrationFixer.cs | 155 +++++++++++++-----
.../DateTimeOrchestrationAnalyzerTests.cs | 124 ++++++++++++++
test/Analyzers.Tests/Verifiers/References.cs | 2 +-
5 files changed, 279 insertions(+), 39 deletions(-)
diff --git a/src/Analyzers/KnownTypeSymbols.Net.cs b/src/Analyzers/KnownTypeSymbols.Net.cs
index 9803273a..f39ce260 100644
--- a/src/Analyzers/KnownTypeSymbols.Net.cs
+++ b/src/Analyzers/KnownTypeSymbols.Net.cs
@@ -23,6 +23,7 @@ public sealed partial class KnownTypeSymbols
INamedTypeSymbol? cancellationToken;
INamedTypeSymbol? environment;
INamedTypeSymbol? httpClient;
+ INamedTypeSymbol? timeProvider;
///
/// Gets a Guid type symbol.
@@ -75,4 +76,9 @@ public sealed partial class KnownTypeSymbols
/// Gets an HttpClient type symbol.
///
public INamedTypeSymbol? HttpClient => this.GetOrResolveFullyQualifiedType(typeof(HttpClient).FullName, ref this.httpClient);
+
+ ///
+ /// Gets a TimeProvider type symbol.
+ ///
+ public INamedTypeSymbol? TimeProvider => this.GetOrResolveFullyQualifiedType("System.TimeProvider", ref this.timeProvider);
}
diff --git a/src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs b/src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs
index d6555481..55571730 100644
--- a/src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs
+++ b/src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs
@@ -10,7 +10,7 @@
namespace Microsoft.DurableTask.Analyzers.Orchestration;
///
-/// Analyzer that reports a warning when a non-deterministic DateTime or DateTimeOffset property is used in an orchestration method.
+/// Analyzer that reports a warning when a non-deterministic DateTime, DateTimeOffset, or TimeProvider method is used in an orchestration method.
///
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class DateTimeOrchestrationAnalyzer : OrchestrationAnalyzer
@@ -41,12 +41,14 @@ public sealed class DateTimeOrchestrationVisitor : MethodProbeOrchestrationVisit
{
INamedTypeSymbol systemDateTimeSymbol = null!;
INamedTypeSymbol? systemDateTimeOffsetSymbol;
+ INamedTypeSymbol? systemTimeProviderSymbol;
///
public override bool Initialize()
{
this.systemDateTimeSymbol = this.Compilation.GetSpecialType(SpecialType.System_DateTime);
this.systemDateTimeOffsetSymbol = this.Compilation.GetTypeByMetadataName("System.DateTimeOffset");
+ this.systemTimeProviderSymbol = this.Compilation.GetTypeByMetadataName("System.TimeProvider");
return true;
}
@@ -85,6 +87,33 @@ protected override void VisitMethod(SemanticModel semanticModel, SyntaxNode meth
reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, operation.Syntax, methodSymbol.Name, property.ToString(), orchestrationName));
}
}
+
+ // Check for TimeProvider method invocations
+ if (this.systemTimeProviderSymbol is not null)
+ {
+ foreach (IInvocationOperation operation in methodOperation.Descendants().OfType())
+ {
+ IMethodSymbol invokedMethod = operation.TargetMethod;
+
+ // Check if the method is called on TimeProvider type
+ bool isTimeProvider = invokedMethod.ContainingType.Equals(this.systemTimeProviderSymbol, SymbolEqualityComparer.Default);
+
+ if (!isTimeProvider)
+ {
+ continue;
+ }
+
+ // Check for non-deterministic TimeProvider methods: GetUtcNow, GetLocalNow, GetTimestamp
+ bool isNonDeterministicMethod = invokedMethod.Name is "GetUtcNow" or "GetLocalNow" or "GetTimestamp";
+
+ if (isNonDeterministicMethod)
+ {
+ // e.g.: "The method 'Method1' uses 'System.TimeProvider.GetUtcNow()' that may cause non-deterministic behavior when invoked from orchestration 'MyOrchestrator'"
+ string timeProviderMethodName = $"{invokedMethod.ContainingType}.{invokedMethod.Name}()";
+ reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, operation.Syntax, methodSymbol.Name, timeProviderMethodName, orchestrationName));
+ }
+ }
+ }
}
}
}
diff --git a/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs b/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
index fd233d1b..6ea59d5e 100644
--- a/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
+++ b/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
@@ -26,51 +26,87 @@ public sealed class DateTimeOrchestrationFixer : OrchestrationContextFixer
///
protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationCodeFixContext orchestrationContext)
{
- // Parses the syntax node to see if it is a member access expression (e.g. DateTime.Now or DateTimeOffset.Now)
- if (orchestrationContext.SyntaxNodeWithDiagnostic is not MemberAccessExpressionSyntax dateTimeExpression)
- {
- return;
- }
-
// Gets the name of the TaskOrchestrationContext parameter (e.g. "context" or "ctx")
string contextParameterName = orchestrationContext.TaskOrchestrationContextSymbol.Name;
-
- // Use semantic analysis to determine if this is a DateTimeOffset expression
SemanticModel semanticModel = orchestrationContext.SemanticModel;
- ITypeSymbol? typeSymbol = semanticModel.GetTypeInfo(dateTimeExpression.Expression).Type;
- bool isDateTimeOffset = typeSymbol?.ToDisplayString() == "System.DateTimeOffset";
-
- bool isDateTimeToday = dateTimeExpression.Name.ToString() == "Today";
- // Build the recommendation text
- string recommendation;
- if (isDateTimeOffset)
+ // Handle DateTime/DateTimeOffset property access (e.g. DateTime.Now or DateTimeOffset.Now)
+ if (orchestrationContext.SyntaxNodeWithDiagnostic is MemberAccessExpressionSyntax dateTimeExpression)
{
- // For DateTimeOffset, we always just cast CurrentUtcDateTime
- recommendation = $"(DateTimeOffset){contextParameterName}.CurrentUtcDateTime";
+ // Use semantic analysis to determine if this is a DateTimeOffset expression
+ ITypeSymbol? typeSymbol = semanticModel.GetTypeInfo(dateTimeExpression.Expression).Type;
+ bool isDateTimeOffset = typeSymbol?.ToDisplayString() == "System.DateTimeOffset";
+
+ bool isDateTimeToday = dateTimeExpression.Name.ToString() == "Today";
+
+ // Build the recommendation text
+ string recommendation;
+ if (isDateTimeOffset)
+ {
+ // For DateTimeOffset, we always just cast CurrentUtcDateTime
+ recommendation = $"(DateTimeOffset){contextParameterName}.CurrentUtcDateTime";
+ }
+ else
+ {
+ // For DateTime, we may need to add .Date for Today
+ string dateTimeTodaySuffix = isDateTimeToday ? ".Date" : string.Empty;
+ recommendation = $"{contextParameterName}.CurrentUtcDateTime{dateTimeTodaySuffix}";
+ }
+
+ // e.g: "Use 'context.CurrentUtcDateTime' instead of 'DateTime.Now'"
+ // e.g: "Use 'context.CurrentUtcDateTime.Date' instead of 'DateTime.Today'"
+ // e.g: "Use '(DateTimeOffset)context.CurrentUtcDateTime' instead of 'DateTimeOffset.Now'"
+ string title = string.Format(
+ CultureInfo.InvariantCulture,
+ Resources.UseInsteadFixerTitle,
+ recommendation,
+ dateTimeExpression.ToString());
+
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: title,
+ createChangedDocument: c => ReplaceDateTime(context.Document, orchestrationContext.Root, dateTimeExpression, contextParameterName, isDateTimeToday, isDateTimeOffset),
+ equivalenceKey: title), // This key is used to prevent duplicate code fixes.
+ context.Diagnostics);
}
- else
+
+ // Handle TimeProvider method invocations (e.g. TimeProvider.System.GetUtcNow())
+ else if (orchestrationContext.SyntaxNodeWithDiagnostic is InvocationExpressionSyntax timeProviderInvocation)
{
- // For DateTime, we may need to add .Date for Today
- string dateTimeTodaySuffix = isDateTimeToday ? ".Date" : string.Empty;
- recommendation = $"{contextParameterName}.CurrentUtcDateTime{dateTimeTodaySuffix}";
- }
+ // Determine the method being called
+ if (semanticModel.GetSymbolInfo(timeProviderInvocation).Symbol is IMethodSymbol methodSymbol)
+ {
+ string methodName = methodSymbol.Name;
+
+ // Check if the method returns DateTimeOffset
+ bool returnsDateTimeOffset = methodSymbol.ReturnType.ToDisplayString() == "System.DateTimeOffset";
- // e.g: "Use 'context.CurrentUtcDateTime' instead of 'DateTime.Now'"
- // e.g: "Use 'context.CurrentUtcDateTime.Date' instead of 'DateTime.Today'"
- // e.g: "Use '(DateTimeOffset)context.CurrentUtcDateTime' instead of 'DateTimeOffset.Now'"
- string title = string.Format(
- CultureInfo.InvariantCulture,
- Resources.UseInsteadFixerTitle,
- recommendation,
- dateTimeExpression.ToString());
-
- context.RegisterCodeFix(
- CodeAction.Create(
- title: title,
- createChangedDocument: c => ReplaceDateTime(context.Document, orchestrationContext.Root, dateTimeExpression, contextParameterName, isDateTimeToday, isDateTimeOffset),
- equivalenceKey: title), // This key is used to prevent duplicate code fixes.
- context.Diagnostics);
+ // Build the recommendation based on the method name
+ string recommendation = methodName switch
+ {
+ "GetUtcNow" when returnsDateTimeOffset => $"(DateTimeOffset){contextParameterName}.CurrentUtcDateTime",
+ "GetUtcNow" => $"{contextParameterName}.CurrentUtcDateTime",
+ "GetLocalNow" when returnsDateTimeOffset => $"(DateTimeOffset){contextParameterName}.CurrentUtcDateTime.ToLocalTime()",
+ "GetLocalNow" => $"{contextParameterName}.CurrentUtcDateTime.ToLocalTime()",
+ "GetTimestamp" => $"{contextParameterName}.CurrentUtcDateTime.Ticks",
+ _ => $"{contextParameterName}.CurrentUtcDateTime",
+ };
+
+ // e.g: "Use 'context.CurrentUtcDateTime' instead of 'TimeProvider.System.GetUtcNow()'"
+ string title = string.Format(
+ CultureInfo.InvariantCulture,
+ Resources.UseInsteadFixerTitle,
+ recommendation,
+ timeProviderInvocation.ToString());
+
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: title,
+ createChangedDocument: c => ReplaceTimeProvider(context.Document, orchestrationContext.Root, timeProviderInvocation, contextParameterName, methodName, returnsDateTimeOffset),
+ equivalenceKey: title),
+ context.Diagnostics);
+ }
+ }
}
static Task ReplaceDateTime(Document document, SyntaxNode oldRoot, MemberAccessExpressionSyntax incorrectDateTimeSyntax, string contextParameterName, bool isDateTimeToday, bool isDateTimeOffset)
@@ -106,4 +142,49 @@ static Task ReplaceDateTime(Document document, SyntaxNode oldRoot, Mem
return Task.FromResult(newDocument);
}
+
+ static Task ReplaceTimeProvider(Document document, SyntaxNode oldRoot, InvocationExpressionSyntax incorrectTimeProviderSyntax, string contextParameterName, string methodName, bool returnsDateTimeOffset)
+ {
+ // Build the correct expression based on the method name
+ ExpressionSyntax correctExpression = methodName switch
+ {
+ "GetUtcNow" => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(contextParameterName),
+ IdentifierName("CurrentUtcDateTime")),
+ "GetLocalNow" => InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(contextParameterName),
+ IdentifierName("CurrentUtcDateTime")),
+ IdentifierName("ToLocalTime"))),
+ "GetTimestamp" => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(contextParameterName),
+ IdentifierName("CurrentUtcDateTime")),
+ IdentifierName("Ticks")),
+ _ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(contextParameterName),
+ IdentifierName("CurrentUtcDateTime")),
+ };
+
+ // If the method returns DateTimeOffset, we need to cast the DateTime to DateTimeOffset
+ if (returnsDateTimeOffset)
+ {
+ correctExpression = CastExpression(
+ IdentifierName("DateTimeOffset"),
+ correctExpression);
+ }
+
+ // Replaces the old invocation with the new expression
+ SyntaxNode newRoot = oldRoot.ReplaceNode(incorrectTimeProviderSyntax, correctExpression);
+ Document newDocument = document.WithSyntaxRoot(newRoot);
+
+ return Task.FromResult(newDocument);
+ }
}
diff --git a/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs b/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs
index 4c5a1889..6989b86a 100644
--- a/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs
+++ b/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs
@@ -453,6 +453,130 @@ public async Task FuncOrchestratorWithDateTimeOffsetHasDiag()
await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
}
+ [Theory]
+ [InlineData("TimeProvider.System.GetUtcNow()")]
+ [InlineData("TimeProvider.System.GetLocalNow()")]
+ public async Task DurableFunctionOrchestrationUsingTimeProviderNonDeterministicMethodsHasDiag(string expression)
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration($@"
+[Function(""Run"")]
+DateTimeOffset Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{{
+ return {{|#0:{expression}|}};
+}}
+");
+
+ string expectedReplacement = expression.Contains("GetLocalNow")
+ ? "(DateTimeOffset)context.CurrentUtcDateTime.ToLocalTime()"
+ : "(DateTimeOffset)context.CurrentUtcDateTime";
+
+ string fix = Wrapper.WrapDurableFunctionOrchestration($@"
+[Function(""Run"")]
+DateTimeOffset Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{{
+ return {expectedReplacement};
+}}
+");
+
+ // The analyzer reports the method name as "System.TimeProvider.GetUtcNow()" or "System.TimeProvider.GetLocalNow()"
+ string methodName = expression.Contains("GetLocalNow") ? "System.TimeProvider.GetLocalNow()" : "System.TimeProvider.GetUtcNow()";
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", methodName, "Run");
+
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
+ }
+
+ [Fact]
+ public async Task DurableFunctionOrchestrationUsingTimeProviderGetTimestampHasDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+long Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ return {|#0:TimeProvider.System.GetTimestamp()|};
+}
+");
+
+ string fix = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+long Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ return context.CurrentUtcDateTime.Ticks;
+}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", "System.TimeProvider.GetTimestamp()", "Run");
+
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
+ }
+
+ [Fact]
+ public async Task TaskOrchestratorUsingTimeProviderHasDiag()
+ {
+ string code = Wrapper.WrapTaskOrchestrator(@"
+public class MyOrchestrator : TaskOrchestrator
+{
+ public override Task RunAsync(TaskOrchestrationContext context, string input)
+ {
+ return Task.FromResult({|#0:TimeProvider.System.GetUtcNow()|});
+ }
+}
+");
+
+ string fix = Wrapper.WrapTaskOrchestrator(@"
+public class MyOrchestrator : TaskOrchestrator
+{
+ public override Task RunAsync(TaskOrchestrationContext context, string input)
+ {
+ return Task.FromResult((DateTimeOffset)context.CurrentUtcDateTime);
+ }
+}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("RunAsync", "System.TimeProvider.GetUtcNow()", "MyOrchestrator");
+
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
+ }
+
+ [Fact]
+ public async Task FuncOrchestratorWithTimeProviderHasDiag()
+ {
+ string code = Wrapper.WrapFuncOrchestrator(@"
+tasks.AddOrchestratorFunc(""HelloSequence"", context =>
+{
+ return {|#0:TimeProvider.System.GetUtcNow()|};
+});
+");
+
+ string fix = Wrapper.WrapFuncOrchestrator(@"
+tasks.AddOrchestratorFunc(""HelloSequence"", context =>
+{
+ return (DateTimeOffset)context.CurrentUtcDateTime;
+});
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Main", "System.TimeProvider.GetUtcNow()", "HelloSequence");
+
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
+ }
+
+ [Fact]
+ public async Task DurableFunctionOrchestrationInvokingMethodWithTimeProviderHasDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+DateTimeOffset Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ return GetTime();
+}
+
+DateTimeOffset GetTime() => {|#0:TimeProvider.System.GetUtcNow()|};
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("GetTime", "System.TimeProvider.GetUtcNow()", "Run");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ }
+
static DiagnosticResult BuildDiagnostic()
{
return VerifyCS.Diagnostic(DateTimeOrchestrationAnalyzer.DiagnosticId);
diff --git a/test/Analyzers.Tests/Verifiers/References.cs b/test/Analyzers.Tests/Verifiers/References.cs
index 1df31e76..b6b33d93 100644
--- a/test/Analyzers.Tests/Verifiers/References.cs
+++ b/test/Analyzers.Tests/Verifiers/References.cs
@@ -11,7 +11,7 @@ public static class References
public static ReferenceAssemblies CommonAssemblies => durableAssemblyReferences.Value;
- static ReferenceAssemblies BuildReferenceAssemblies() => ReferenceAssemblies.Net.Net60.AddPackages([
+ static ReferenceAssemblies BuildReferenceAssemblies() => ReferenceAssemblies.Net.Net80.AddPackages([
new PackageIdentity("Azure.Storage.Blobs", "12.17.0"),
new PackageIdentity("Azure.Storage.Queues", "12.17.0"),
new PackageIdentity("Azure.Data.Tables", "12.8.3"),
From 32c472b8ec7fb9b0d712447ff92da2081d9f807f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 17 Dec 2025 00:39:09 +0000
Subject: [PATCH 4/8] Update comment to include TimeProvider in visitor
description
Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
---
src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs b/src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs
index 55571730..94b30e90 100644
--- a/src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs
+++ b/src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs
@@ -35,7 +35,7 @@ public sealed class DateTimeOrchestrationAnalyzer : OrchestrationAnalyzer SupportedDiagnostics => [Rule];
///
- /// Visitor that inspects the method body for DateTime and DateTimeOffset properties.
+ /// Visitor that inspects the method body for DateTime and DateTimeOffset properties, and TimeProvider method invocations.
///
public sealed class DateTimeOrchestrationVisitor : MethodProbeOrchestrationVisitor
{
From 340e16366c9705e82e8a286c543acc473e2e33b6 Mon Sep 17 00:00:00 2001
From: wangbill
Date: Tue, 30 Dec 2025 17:39:26 -0800
Subject: [PATCH 5/8] Update global.json
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
global.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/global.json b/global.json
index 4b560e68..332b6c45 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "10.0.100",
+ "version": "10.0.101",
"rollForward": "latestFeature"
},
"msbuild-sdks": {
From 1ceb9129259b11d51a05ee342701c96a6b14fb06 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 31 Dec 2025 01:55:28 +0000
Subject: [PATCH 6/8] Address review comments: fix XML docs, remove redundant
ToString(), combine if statements, restore test files
Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
---
src/Analyzers/KnownTypeSymbols.Net.cs | 8 +-
.../DateTimeOrchestrationFixer.cs | 65 +++++-----
.../DateTimeOrchestrationAnalyzerTests.cs | 112 ++++++++++++++----
.../CSharpCodeFixVerifier.Durable.cs | 52 +++++++-
test/Analyzers.Tests/Verifiers/References.cs | 17 +++
5 files changed, 193 insertions(+), 61 deletions(-)
diff --git a/src/Analyzers/KnownTypeSymbols.Net.cs b/src/Analyzers/KnownTypeSymbols.Net.cs
index b063dffb..8e327319 100644
--- a/src/Analyzers/KnownTypeSymbols.Net.cs
+++ b/src/Analyzers/KnownTypeSymbols.Net.cs
@@ -79,10 +79,12 @@ public sealed partial class KnownTypeSymbols
public INamedTypeSymbol? HttpClient => this.GetOrResolveFullyQualifiedType(typeof(HttpClient).FullName, ref this.httpClient);
///
- /// Gets a TimeProvider type symbol.
- ///
- public INamedTypeSymbol? TimeProvider => this.GetOrResolveFullyQualifiedType("System.TimeProvider", ref this.timeProvider);
/// Gets an ILogger type symbol.
///
public INamedTypeSymbol? ILogger => this.GetOrResolveFullyQualifiedType("Microsoft.Extensions.Logging.ILogger", ref this.iLogger);
+
+ ///
+ /// Gets a TimeProvider type symbol.
+ ///
+ public INamedTypeSymbol? TimeProvider => this.GetOrResolveFullyQualifiedType("System.TimeProvider", ref this.timeProvider);
}
diff --git a/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs b/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
index 6ea59d5e..a7b3402f 100644
--- a/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
+++ b/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
@@ -60,7 +60,7 @@ protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationC
CultureInfo.InvariantCulture,
Resources.UseInsteadFixerTitle,
recommendation,
- dateTimeExpression.ToString());
+ dateTimeExpression);
context.RegisterCodeFix(
CodeAction.Create(
@@ -71,41 +71,38 @@ protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationC
}
// Handle TimeProvider method invocations (e.g. TimeProvider.System.GetUtcNow())
- else if (orchestrationContext.SyntaxNodeWithDiagnostic is InvocationExpressionSyntax timeProviderInvocation)
+ else if (orchestrationContext.SyntaxNodeWithDiagnostic is InvocationExpressionSyntax timeProviderInvocation &&
+ semanticModel.GetSymbolInfo(timeProviderInvocation).Symbol is IMethodSymbol methodSymbol)
{
- // Determine the method being called
- if (semanticModel.GetSymbolInfo(timeProviderInvocation).Symbol is IMethodSymbol methodSymbol)
+ string methodName = methodSymbol.Name;
+
+ // Check if the method returns DateTimeOffset
+ bool returnsDateTimeOffset = methodSymbol.ReturnType.ToDisplayString() == "System.DateTimeOffset";
+
+ // Build the recommendation based on the method name
+ string recommendation = methodName switch
{
- string methodName = methodSymbol.Name;
-
- // Check if the method returns DateTimeOffset
- bool returnsDateTimeOffset = methodSymbol.ReturnType.ToDisplayString() == "System.DateTimeOffset";
-
- // Build the recommendation based on the method name
- string recommendation = methodName switch
- {
- "GetUtcNow" when returnsDateTimeOffset => $"(DateTimeOffset){contextParameterName}.CurrentUtcDateTime",
- "GetUtcNow" => $"{contextParameterName}.CurrentUtcDateTime",
- "GetLocalNow" when returnsDateTimeOffset => $"(DateTimeOffset){contextParameterName}.CurrentUtcDateTime.ToLocalTime()",
- "GetLocalNow" => $"{contextParameterName}.CurrentUtcDateTime.ToLocalTime()",
- "GetTimestamp" => $"{contextParameterName}.CurrentUtcDateTime.Ticks",
- _ => $"{contextParameterName}.CurrentUtcDateTime",
- };
-
- // e.g: "Use 'context.CurrentUtcDateTime' instead of 'TimeProvider.System.GetUtcNow()'"
- string title = string.Format(
- CultureInfo.InvariantCulture,
- Resources.UseInsteadFixerTitle,
- recommendation,
- timeProviderInvocation.ToString());
-
- context.RegisterCodeFix(
- CodeAction.Create(
- title: title,
- createChangedDocument: c => ReplaceTimeProvider(context.Document, orchestrationContext.Root, timeProviderInvocation, contextParameterName, methodName, returnsDateTimeOffset),
- equivalenceKey: title),
- context.Diagnostics);
- }
+ "GetUtcNow" when returnsDateTimeOffset => $"(DateTimeOffset){contextParameterName}.CurrentUtcDateTime",
+ "GetUtcNow" => $"{contextParameterName}.CurrentUtcDateTime",
+ "GetLocalNow" when returnsDateTimeOffset => $"(DateTimeOffset){contextParameterName}.CurrentUtcDateTime.ToLocalTime()",
+ "GetLocalNow" => $"{contextParameterName}.CurrentUtcDateTime.ToLocalTime()",
+ "GetTimestamp" => $"{contextParameterName}.CurrentUtcDateTime.Ticks",
+ _ => $"{contextParameterName}.CurrentUtcDateTime",
+ };
+
+ // e.g: "Use 'context.CurrentUtcDateTime' instead of 'TimeProvider.System.GetUtcNow()'"
+ string title = string.Format(
+ CultureInfo.InvariantCulture,
+ Resources.UseInsteadFixerTitle,
+ recommendation,
+ timeProviderInvocation);
+
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: title,
+ createChangedDocument: c => ReplaceTimeProvider(context.Document, orchestrationContext.Root, timeProviderInvocation, contextParameterName, methodName, returnsDateTimeOffset),
+ equivalenceKey: title),
+ context.Diagnostics);
}
}
diff --git a/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs b/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs
index 4404dff8..e42fa379 100644
--- a/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs
+++ b/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs
@@ -453,56 +453,128 @@ public async Task FuncOrchestratorWithDateTimeOffsetHasDiag()
await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
}
+ [Theory]
+ [InlineData("TimeProvider.System.GetUtcNow()")]
+ [InlineData("TimeProvider.System.GetLocalNow()")]
+ public async Task DurableFunctionOrchestrationUsingTimeProviderNonDeterministicMethodsHasDiag(string expression)
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration($@"
+[Function(""Run"")]
+DateTimeOffset Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{{
+ return {{|#0:{expression}|}};
+}}
+");
+
+ string expectedReplacement = expression.Contains("GetLocalNow")
+ ? "(DateTimeOffset)context.CurrentUtcDateTime.ToLocalTime()"
+ : "(DateTimeOffset)context.CurrentUtcDateTime";
+
+ string fix = Wrapper.WrapDurableFunctionOrchestration($@"
+[Function(""Run"")]
+DateTimeOffset Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{{
+ return {expectedReplacement};
+}}
+");
+
+ // The analyzer reports the method name as "System.TimeProvider.GetUtcNow()" or "System.TimeProvider.GetLocalNow()"
+ string methodName = expression.Contains("GetLocalNow") ? "System.TimeProvider.GetLocalNow()" : "System.TimeProvider.GetUtcNow()";
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", methodName, "Run");
+
+ await VerifyCS.VerifyNet80CodeFixAsync(code, expected, fix);
+ }
+
[Fact]
- public async Task TaskOrchestratorSdkOnlyHasDiag()
+ public async Task DurableFunctionOrchestrationUsingTimeProviderGetTimestampHasDiag()
{
- // Tests that the analyzer works with SDK-only references (without Azure Functions assemblies)
- string code = Wrapper.WrapTaskOrchestratorSdkOnly(@"
-public class MyOrchestrator : TaskOrchestrator
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+long Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
- public override Task RunAsync(TaskOrchestrationContext context, string input)
+ return {|#0:TimeProvider.System.GetTimestamp()|};
+}
+");
+
+ string fix = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+long Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ return context.CurrentUtcDateTime.Ticks;
+}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", "System.TimeProvider.GetTimestamp()", "Run");
+
+ await VerifyCS.VerifyNet80CodeFixAsync(code, expected, fix);
+ }
+
+ [Fact]
+ public async Task TaskOrchestratorUsingTimeProviderHasDiag()
{
- return Task.FromResult({|#0:DateTime.Now|});
+ string code = Wrapper.WrapTaskOrchestrator(@"
+public class MyOrchestrator : TaskOrchestrator
+{
+ public override Task RunAsync(TaskOrchestrationContext context, string input)
+ {
+ return Task.FromResult({|#0:TimeProvider.System.GetUtcNow()|});
}
}
");
- string fix = Wrapper.WrapTaskOrchestratorSdkOnly(@"
-public class MyOrchestrator : TaskOrchestrator
+ string fix = Wrapper.WrapTaskOrchestrator(@"
+public class MyOrchestrator : TaskOrchestrator
{
- public override Task RunAsync(TaskOrchestrationContext context, string input)
+ public override Task RunAsync(TaskOrchestrationContext context, string input)
{
- return Task.FromResult(context.CurrentUtcDateTime);
+ return Task.FromResult((DateTimeOffset)context.CurrentUtcDateTime);
}
}
");
- DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("RunAsync", "System.DateTime.Now", "MyOrchestrator");
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("RunAsync", "System.TimeProvider.GetUtcNow()", "MyOrchestrator");
- await VerifyCS.VerifySdkOnlyCodeFixAsync(code, expected, fix);
+ await VerifyCS.VerifyNet80CodeFixAsync(code, expected, fix);
}
[Fact]
- public async Task FuncOrchestratorSdkOnlyWithLambdaHasDiag()
+ public async Task FuncOrchestratorWithTimeProviderHasDiag()
{
- // Tests that the analyzer works with SDK-only references (without Azure Functions assemblies)
- string code = Wrapper.WrapFuncOrchestratorSdkOnly(@"
+ string code = Wrapper.WrapFuncOrchestrator(@"
tasks.AddOrchestratorFunc(""HelloSequence"", context =>
{
- return {|#0:DateTime.Now|};
+ return {|#0:TimeProvider.System.GetUtcNow()|};
});
");
- string fix = Wrapper.WrapFuncOrchestratorSdkOnly(@"
+ string fix = Wrapper.WrapFuncOrchestrator(@"
tasks.AddOrchestratorFunc(""HelloSequence"", context =>
{
- return context.CurrentUtcDateTime;
+ return (DateTimeOffset)context.CurrentUtcDateTime;
});
");
- DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Main", "System.DateTime.Now", "HelloSequence");
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Main", "System.TimeProvider.GetUtcNow()", "HelloSequence");
+
+ await VerifyCS.VerifyNet80CodeFixAsync(code, expected, fix);
+ }
+
+ [Fact]
+ public async Task DurableFunctionOrchestrationInvokingMethodWithTimeProviderHasDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+DateTimeOffset Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ return GetTime();
+}
+
+DateTimeOffset GetTime() => {|#0:TimeProvider.System.GetUtcNow()|};
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("GetTime", "System.TimeProvider.GetUtcNow()", "Run");
- await VerifyCS.VerifySdkOnlyCodeFixAsync(code, expected, fix);
+ await VerifyCS.VerifyNet80AnalyzerAsync(code, expected);
}
static DiagnosticResult BuildDiagnostic()
diff --git a/test/Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.Durable.cs b/test/Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.Durable.cs
index b69fae93..552f7a13 100644
--- a/test/Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.Durable.cs
+++ b/test/Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.Durable.cs
@@ -1,7 +1,4 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
@@ -89,6 +86,53 @@ public static async Task VerifySdkOnlyCodeFixAsync(
References.SdkOnlyAssemblies, configureTest);
}
+ ///
+ /// Runs analyzer test with .NET 8.0 references for testing APIs only available in .NET 8+.
+ /// Used for TimeProvider and other .NET 8+ specific tests.
+ ///
+ public static Task VerifyNet80AnalyzerAsync(string source, params DiagnosticResult[] expected)
+ {
+ return VerifyNet80AnalyzerAsync(source, null, expected);
+ }
+
+ ///
+ /// Runs analyzer test with .NET 8.0 references for testing APIs only available in .NET 8+.
+ /// Used for TimeProvider and other .NET 8+ specific tests.
+ ///
+ public static async Task VerifyNet80AnalyzerAsync(
+ string source, Action? configureTest = null, params DiagnosticResult[] expected)
+ {
+ await RunAsync(expected, new Test()
+ {
+ TestCode = source,
+ }, References.Net80Assemblies, configureTest);
+ }
+
+ ///
+ /// Runs code fix test with .NET 8.0 references for testing APIs only available in .NET 8+.
+ /// Used for TimeProvider and other .NET 8+ specific tests.
+ ///
+ public static Task VerifyNet80CodeFixAsync(
+ string source, DiagnosticResult expected, string fixedSource, Action? configureTest = null)
+ {
+ return VerifyNet80CodeFixAsync(source, [expected], fixedSource, configureTest);
+ }
+
+ ///
+ /// Runs code fix test with .NET 8.0 references for testing APIs only available in .NET 8+.
+ /// Used for TimeProvider and other .NET 8+ specific tests.
+ ///
+ public static async Task VerifyNet80CodeFixAsync(
+ string source, DiagnosticResult[] expected, string fixedSource, Action? configureTest = null)
+ {
+ await RunAsync(expected, new Test()
+ {
+ TestCode = source,
+ FixedCode = fixedSource,
+ },
+ References.Net80Assemblies, configureTest);
+ }
+
static async Task RunAsync(DiagnosticResult[] expected, Test test, ReferenceAssemblies referenceAssemblies, Action? configureTest = null)
{
test.ReferenceAssemblies = referenceAssemblies;
diff --git a/test/Analyzers.Tests/Verifiers/References.cs b/test/Analyzers.Tests/Verifiers/References.cs
index 59de0dda..701a04cf 100644
--- a/test/Analyzers.Tests/Verifiers/References.cs
+++ b/test/Analyzers.Tests/Verifiers/References.cs
@@ -9,6 +9,7 @@ public static class References
{
static readonly Lazy durableAssemblyReferences = new(() => BuildReferenceAssemblies());
static readonly Lazy durableSdkOnlyReferences = new(() => BuildSdkOnlyReferenceAssemblies());
+ static readonly Lazy durableNet80References = new(() => BuildNet80ReferenceAssemblies());
public static ReferenceAssemblies CommonAssemblies => durableAssemblyReferences.Value;
@@ -18,6 +19,12 @@ public static class References
///
public static ReferenceAssemblies SdkOnlyAssemblies => durableSdkOnlyReferences.Value;
+ ///
+ /// Gets assembly references targeting .NET 8.0 for testing APIs only available in .NET 8+.
+ /// Used for TimeProvider and other .NET 8+ specific tests.
+ ///
+ public static ReferenceAssemblies Net80Assemblies => durableNet80References.Value;
+
static ReferenceAssemblies BuildReferenceAssemblies() => ReferenceAssemblies.Net.Net60.AddPackages([
new PackageIdentity("Azure.Storage.Blobs", "12.17.0"),
new PackageIdentity("Azure.Storage.Queues", "12.17.0"),
@@ -37,4 +44,14 @@ static ReferenceAssemblies BuildSdkOnlyReferenceAssemblies() => ReferenceAssembl
new PackageIdentity("Microsoft.DurableTask.Abstractions", "1.3.0"),
new PackageIdentity("Microsoft.DurableTask.Worker", "1.3.0"),
]);
+
+ static ReferenceAssemblies BuildNet80ReferenceAssemblies() => ReferenceAssemblies.Net.Net80.AddPackages([
+ new PackageIdentity("Azure.Storage.Blobs", "12.17.0"),
+ new PackageIdentity("Azure.Storage.Queues", "12.17.0"),
+ new PackageIdentity("Azure.Data.Tables", "12.8.3"),
+ new PackageIdentity("Microsoft.Azure.Cosmos", "3.39.1"),
+ new PackageIdentity("Microsoft.Azure.Functions.Worker", "1.21.0"),
+ new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.DurableTask", "1.1.1"),
+ new PackageIdentity("Microsoft.Data.SqlClient", "5.2.0"),
+ ]);
}
From 2948bf40297370e4e133989967a77c549f06df38 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 31 Dec 2025 10:02:34 +0000
Subject: [PATCH 7/8] Fix code fixer not showing for TimeProvider diagnostics
by finding parent InvocationExpressionSyntax
Co-Authored-By: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
---
.../DateTimeOrchestrationFixer.cs | 28 +++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)
diff --git a/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs b/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
index a7b3402f..8ddb1aaf 100644
--- a/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
+++ b/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
@@ -4,6 +4,7 @@
using System.Collections.Immutable;
using System.Composition;
using System.Globalization;
+using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
@@ -68,14 +69,37 @@ protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationC
createChangedDocument: c => ReplaceDateTime(context.Document, orchestrationContext.Root, dateTimeExpression, contextParameterName, isDateTimeToday, isDateTimeOffset),
equivalenceKey: title), // This key is used to prevent duplicate code fixes.
context.Diagnostics);
+ return;
}
// Handle TimeProvider method invocations (e.g. TimeProvider.System.GetUtcNow())
- else if (orchestrationContext.SyntaxNodeWithDiagnostic is InvocationExpressionSyntax timeProviderInvocation &&
- semanticModel.GetSymbolInfo(timeProviderInvocation).Symbol is IMethodSymbol methodSymbol)
+ // The node might be the invocation itself or a child node, so we need to find the InvocationExpressionSyntax
+ InvocationExpressionSyntax? timeProviderInvocation = orchestrationContext.SyntaxNodeWithDiagnostic as InvocationExpressionSyntax
+ ?? orchestrationContext.SyntaxNodeWithDiagnostic.AncestorsAndSelf().OfType().FirstOrDefault();
+
+ if (timeProviderInvocation != null &&
+ semanticModel.GetSymbolInfo(timeProviderInvocation).Symbol is IMethodSymbol methodSymbol)
{
string methodName = methodSymbol.Name;
+ // Check if this is one of the TimeProvider methods we handle
+ // The diagnostic was already reported by the analyzer, so we know it's valid
+ if (methodName is not ("GetUtcNow" or "GetLocalNow" or "GetTimestamp"))
+ {
+ return;
+ }
+
+ // Optionally verify it's actually TimeProvider if the symbol is available
+ if (orchestrationContext.KnownTypeSymbols.TimeProvider != null)
+ {
+ bool isTimeProviderType = methodSymbol.ContainingType.Equals(orchestrationContext.KnownTypeSymbols.TimeProvider, SymbolEqualityComparer.Default);
+ if (!isTimeProviderType)
+ {
+ // This might be a different type with the same method name
+ return;
+ }
+ }
+
// Check if the method returns DateTimeOffset
bool returnsDateTimeOffset = methodSymbol.ReturnType.ToDisplayString() == "System.DateTimeOffset";
From 68e3f101ca680bb90f1549213d07410c7c203218 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Wed, 31 Dec 2025 02:27:38 -0800
Subject: [PATCH 8/8] Revert "Make TimeProvider code fixer more lenient for
conditional compilation scenarios"
This reverts commit c48f1d85cad780fc334602ee68b7d3f3601fe4d7.
---
.../DateTimeOrchestrationFixer.cs | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs b/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
index 8ddb1aaf..221d6048 100644
--- a/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
+++ b/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
@@ -82,24 +82,6 @@ protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationC
{
string methodName = methodSymbol.Name;
- // Check if this is one of the TimeProvider methods we handle
- // The diagnostic was already reported by the analyzer, so we know it's valid
- if (methodName is not ("GetUtcNow" or "GetLocalNow" or "GetTimestamp"))
- {
- return;
- }
-
- // Optionally verify it's actually TimeProvider if the symbol is available
- if (orchestrationContext.KnownTypeSymbols.TimeProvider != null)
- {
- bool isTimeProviderType = methodSymbol.ContainingType.Equals(orchestrationContext.KnownTypeSymbols.TimeProvider, SymbolEqualityComparer.Default);
- if (!isTimeProviderType)
- {
- // This might be a different type with the same method name
- return;
- }
- }
-
// Check if the method returns DateTimeOffset
bool returnsDateTimeOffset = methodSymbol.ReturnType.ToDisplayString() == "System.DateTimeOffset";