Skip to content

Commit 17bd79c

Browse files
authored
Merge branch 'main' into dependabot/nuget/BenchmarkDotNet-0.15.8
2 parents 8007a0a + 310c8e9 commit 17bd79c

File tree

10 files changed

+354
-34
lines changed

10 files changed

+354
-34
lines changed

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
4343
</ItemGroup>
4444
<ItemGroup>
45-
<PackageVersion Include="Google.Protobuf" Version="3.33.1" />
45+
<PackageVersion Include="Google.Protobuf" Version="3.33.2" />
4646
<PackageVersion Include="Grpc.Core" Version="2.46.6" />
4747
<PackageVersion Include="Grpc.Net.Client" Version="2.67.0" />
4848
<PackageVersion Include="Grpc.Tools" Version="2.76.0" />
@@ -71,7 +71,7 @@
7171
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
7272
<PackageVersion Include="Moq" Version="4.20.72" />
7373
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
74-
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
74+
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
7575
<PackageVersion Include="xunit" Version="2.9.2" />
7676
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
7777
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2"/>

samples/AzureFunctionsApp/AzureFunctionsApp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
</ItemGroup>
2121

2222
<ItemGroup>
23+
<PackageReference Include="Google.Protobuf" VersionOverride="3.33.2" />
2324
<None Update="host.json">
2425
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2526
</None>

samples/NetFxConsoleApp/NetFxConsoleApp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
</PropertyGroup>
77

88
<ItemGroup>
9+
<PackageReference Include="Google.Protobuf" />
910
<PackageReference Include="Microsoft.Extensions.Hosting" />
1011
<PackageReference Include="Microsoft.DurableTask.Client.Grpc" />
1112
<PackageReference Include="Microsoft.DurableTask.Worker.Grpc" />

src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
namespace Microsoft.DurableTask.Analyzers.Orchestration;
1111

1212
/// <summary>
13-
/// Analyzer that reports a warning when a non-deterministic DateTime property is used in an orchestration method.
13+
/// Analyzer that reports a warning when a non-deterministic DateTime or DateTimeOffset property is used in an orchestration method.
1414
/// </summary>
1515
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1616
public sealed class DateTimeOrchestrationAnalyzer : OrchestrationAnalyzer<DateTimeOrchestrationVisitor>
@@ -35,16 +35,18 @@ public sealed class DateTimeOrchestrationAnalyzer : OrchestrationAnalyzer<DateTi
3535
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
3636

3737
/// <summary>
38-
/// Visitor that inspects the method body for DateTime properties.
38+
/// Visitor that inspects the method body for DateTime and DateTimeOffset properties.
3939
/// </summary>
4040
public sealed class DateTimeOrchestrationVisitor : MethodProbeOrchestrationVisitor
4141
{
4242
INamedTypeSymbol systemDateTimeSymbol = null!;
43+
INamedTypeSymbol? systemDateTimeOffsetSymbol;
4344

4445
/// <inheritdoc/>
4546
public override bool Initialize()
4647
{
4748
this.systemDateTimeSymbol = this.Compilation.GetSpecialType(SpecialType.System_DateTime);
49+
this.systemDateTimeOffsetSymbol = this.Compilation.GetTypeByMetadataName("System.DateTimeOffset");
4850
return true;
4951
}
5052

@@ -61,14 +63,25 @@ protected override void VisitMethod(SemanticModel semanticModel, SyntaxNode meth
6163
{
6264
IPropertySymbol property = operation.Property;
6365

64-
if (!property.ContainingSymbol.Equals(this.systemDateTimeSymbol, SymbolEqualityComparer.Default))
66+
bool isDateTime = property.ContainingSymbol.Equals(this.systemDateTimeSymbol, SymbolEqualityComparer.Default);
67+
bool isDateTimeOffset = this.systemDateTimeOffsetSymbol is not null &&
68+
property.ContainingSymbol.Equals(this.systemDateTimeOffsetSymbol, SymbolEqualityComparer.Default);
69+
70+
if (!isDateTime && !isDateTimeOffset)
6571
{
66-
return;
72+
continue;
6773
}
6874

69-
if (property.Name is nameof(DateTime.Now) or nameof(DateTime.UtcNow) or nameof(DateTime.Today))
75+
// Check for non-deterministic properties
76+
// DateTime has: Now, UtcNow, Today
77+
// DateTimeOffset has: Now, UtcNow (but not Today)
78+
bool isNonDeterministic = property.Name is nameof(DateTime.Now) or nameof(DateTime.UtcNow) ||
79+
(isDateTime && property.Name == nameof(DateTime.Today));
80+
81+
if (isNonDeterministic)
7082
{
71-
// e.g.: "The method 'Method1' uses 'System.Date.Now' that may cause non-deterministic behavior when invoked from orchestration 'MyOrchestrator'"
83+
// e.g.: "The method 'Method1' uses 'System.DateTime.Now' that may cause non-deterministic behavior when invoked from orchestration 'MyOrchestrator'"
84+
// e.g.: "The method 'Method1' uses 'System.DateTimeOffset.Now' that may cause non-deterministic behavior when invoked from orchestration 'MyOrchestrator'"
7285
reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, operation.Syntax, methodSymbol.Name, property.ToString(), orchestrationName));
7386
}
7487
}

src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public sealed class DateTimeOrchestrationFixer : OrchestrationContextFixer
2626
/// <inheritdoc/>
2727
protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationCodeFixContext orchestrationContext)
2828
{
29-
// Parses the syntax node to see if it is a member access expression (e.g. DateTime.Now)
29+
// Parses the syntax node to see if it is a member access expression (e.g. DateTime.Now or DateTimeOffset.Now)
3030
if (orchestrationContext.SyntaxNodeWithDiagnostic is not MemberAccessExpressionSyntax dateTimeExpression)
3131
{
3232
return;
@@ -35,12 +35,30 @@ protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationC
3535
// Gets the name of the TaskOrchestrationContext parameter (e.g. "context" or "ctx")
3636
string contextParameterName = orchestrationContext.TaskOrchestrationContextSymbol.Name;
3737

38+
// Use semantic analysis to determine if this is a DateTimeOffset expression
39+
SemanticModel semanticModel = orchestrationContext.SemanticModel;
40+
ITypeSymbol? typeSymbol = semanticModel.GetTypeInfo(dateTimeExpression.Expression).Type;
41+
bool isDateTimeOffset = typeSymbol?.ToDisplayString() == "System.DateTimeOffset";
42+
3843
bool isDateTimeToday = dateTimeExpression.Name.ToString() == "Today";
39-
string dateTimeTodaySuffix = isDateTimeToday ? ".Date" : string.Empty;
40-
string recommendation = $"{contextParameterName}.CurrentUtcDateTime{dateTimeTodaySuffix}";
44+
45+
// Build the recommendation text
46+
string recommendation;
47+
if (isDateTimeOffset)
48+
{
49+
// For DateTimeOffset, we always just cast CurrentUtcDateTime
50+
recommendation = $"(DateTimeOffset){contextParameterName}.CurrentUtcDateTime";
51+
}
52+
else
53+
{
54+
// For DateTime, we may need to add .Date for Today
55+
string dateTimeTodaySuffix = isDateTimeToday ? ".Date" : string.Empty;
56+
recommendation = $"{contextParameterName}.CurrentUtcDateTime{dateTimeTodaySuffix}";
57+
}
4158

4259
// e.g: "Use 'context.CurrentUtcDateTime' instead of 'DateTime.Now'"
4360
// e.g: "Use 'context.CurrentUtcDateTime.Date' instead of 'DateTime.Today'"
61+
// e.g: "Use '(DateTimeOffset)context.CurrentUtcDateTime' instead of 'DateTimeOffset.Now'"
4462
string title = string.Format(
4563
CultureInfo.InvariantCulture,
4664
Resources.UseInsteadFixerTitle,
@@ -50,15 +68,15 @@ protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationC
5068
context.RegisterCodeFix(
5169
CodeAction.Create(
5270
title: title,
53-
createChangedDocument: c => ReplaceDateTime(context.Document, orchestrationContext.Root, dateTimeExpression, contextParameterName, isDateTimeToday),
71+
createChangedDocument: c => ReplaceDateTime(context.Document, orchestrationContext.Root, dateTimeExpression, contextParameterName, isDateTimeToday, isDateTimeOffset),
5472
equivalenceKey: title), // This key is used to prevent duplicate code fixes.
5573
context.Diagnostics);
5674
}
5775

58-
static Task<Document> ReplaceDateTime(Document document, SyntaxNode oldRoot, MemberAccessExpressionSyntax incorrectDateTimeSyntax, string contextParameterName, bool isDateTimeToday)
76+
static Task<Document> ReplaceDateTime(Document document, SyntaxNode oldRoot, MemberAccessExpressionSyntax incorrectDateTimeSyntax, string contextParameterName, bool isDateTimeToday, bool isDateTimeOffset)
5977
{
6078
// Builds a 'context.CurrentUtcDateTime' syntax node
61-
MemberAccessExpressionSyntax correctDateTimeSyntax =
79+
ExpressionSyntax correctDateTimeSyntax =
6280
MemberAccessExpression(
6381
SyntaxKind.SimpleMemberAccessExpression,
6482
IdentifierName(contextParameterName),
@@ -73,6 +91,15 @@ static Task<Document> ReplaceDateTime(Document document, SyntaxNode oldRoot, Mem
7391
IdentifierName("Date"));
7492
}
7593

94+
// If the original expression was DateTimeOffset, we need to cast the DateTime to DateTimeOffset
95+
// This is done using a CastExpression: (DateTimeOffset)context.CurrentUtcDateTime
96+
if (isDateTimeOffset)
97+
{
98+
correctDateTimeSyntax = CastExpression(
99+
IdentifierName("DateTimeOffset"),
100+
correctDateTimeSyntax);
101+
}
102+
76103
// Replaces the old local declaration with the new local declaration.
77104
SyntaxNode newRoot = oldRoot.ReplaceNode(incorrectDateTimeSyntax, correctDateTimeSyntax);
78105
Document newDocument = document.WithSyntaxRoot(newRoot);

src/Worker/Grpc/Worker.Grpc.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
</ItemGroup>
1414

1515
<ItemGroup>
16+
<PackageReference Include="Google.Protobuf" VersionOverride="3.33.2" />
1617
<SharedSection Include="Core" />
1718
<SharedSection Include="DependencyInjection" />
1819
<SharedSection Include="Grpc" />

test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,82 @@ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix, test =>
377377
}
378378

379379

380+
[Theory]
381+
[InlineData("DateTimeOffset.Now")]
382+
[InlineData("DateTimeOffset.UtcNow")]
383+
public async Task DurableFunctionOrchestrationUsingDateTimeOffsetNonDeterministicPropertiesHasDiag(string expression)
384+
{
385+
string code = Wrapper.WrapDurableFunctionOrchestration($@"
386+
[Function(""Run"")]
387+
DateTimeOffset Run([OrchestrationTrigger] TaskOrchestrationContext context)
388+
{{
389+
return {{|#0:{expression}|}};
390+
}}
391+
");
392+
393+
string fix = Wrapper.WrapDurableFunctionOrchestration($@"
394+
[Function(""Run"")]
395+
DateTimeOffset Run([OrchestrationTrigger] TaskOrchestrationContext context)
396+
{{
397+
return (DateTimeOffset)context.CurrentUtcDateTime;
398+
}}
399+
");
400+
401+
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", $"System.{expression}", "Run");
402+
403+
await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
404+
}
405+
406+
[Fact]
407+
public async Task TaskOrchestratorUsingDateTimeOffsetHasDiag()
408+
{
409+
string code = Wrapper.WrapTaskOrchestrator(@"
410+
public class MyOrchestrator : TaskOrchestrator<string, DateTimeOffset>
411+
{
412+
public override Task<DateTimeOffset> RunAsync(TaskOrchestrationContext context, string input)
413+
{
414+
return Task.FromResult({|#0:DateTimeOffset.Now|});
415+
}
416+
}
417+
");
418+
419+
string fix = Wrapper.WrapTaskOrchestrator(@"
420+
public class MyOrchestrator : TaskOrchestrator<string, DateTimeOffset>
421+
{
422+
public override Task<DateTimeOffset> RunAsync(TaskOrchestrationContext context, string input)
423+
{
424+
return Task.FromResult((DateTimeOffset)context.CurrentUtcDateTime);
425+
}
426+
}
427+
");
428+
429+
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("RunAsync", "System.DateTimeOffset.Now", "MyOrchestrator");
430+
431+
await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
432+
}
433+
434+
[Fact]
435+
public async Task FuncOrchestratorWithDateTimeOffsetHasDiag()
436+
{
437+
string code = Wrapper.WrapFuncOrchestrator(@"
438+
tasks.AddOrchestratorFunc(""HelloSequence"", context =>
439+
{
440+
return {|#0:DateTimeOffset.UtcNow|};
441+
});
442+
");
443+
444+
string fix = Wrapper.WrapFuncOrchestrator(@"
445+
tasks.AddOrchestratorFunc(""HelloSequence"", context =>
446+
{
447+
return (DateTimeOffset)context.CurrentUtcDateTime;
448+
});
449+
");
450+
451+
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Main", "System.DateTimeOffset.UtcNow", "HelloSequence");
452+
453+
await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
454+
}
455+
380456
static DiagnosticResult BuildDiagnostic()
381457
{
382458
return VerifyCS.Diagnostic(DateTimeOrchestrationAnalyzer.DiagnosticId);

test/Benchmarks/Benchmarks.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
</ItemGroup>
1515

1616
<ItemGroup>
17+
<PackageReference Include="Google.Protobuf" />
1718
<ProjectReference Include="$(SrcRoot)Worker/Core/Worker.csproj" />
1819
<ProjectReference Include="$(SrcRoot)Analyzers/Analyzers.csproj" />
1920
</ItemGroup>

test/Generators.Tests/Generators.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
</ItemGroup>
2020

2121
<ItemGroup>
22+
<PackageReference Include="Google.Protobuf" />
2223
<ProjectReference Include="$(SrcRoot)Abstractions/Abstractions.csproj" />
2324
<ProjectReference Include="$(SrcRoot)Generators/Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true" />
2425
</ItemGroup>

0 commit comments

Comments
 (0)