From 58fe0683d46870fe4f3ce95e2d2d3184a82e3f35 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 10 Dec 2025 19:19:07 +0000
Subject: [PATCH 1/8] Initial plan
From cbff2aceca6e2d6f39027fc9c4747cd2f5a367d4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 10 Dec 2025 19:35:18 +0000
Subject: [PATCH 2/8] Add LoggerOrchestrationAnalyzer implementation and tests
Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
---
src/Analyzers/AnalyzerReleases.Unshipped.md | 1 +
src/Analyzers/KnownTypeSymbols.Net.cs | 6 +
.../LoggerOrchestrationAnalyzer.cs | 128 +++++++++
src/Analyzers/Resources.resx | 6 +
.../LoggerOrchestrationAnalyzerTests.cs | 260 ++++++++++++++++++
test/Analyzers.Tests/Wrapper.cs | 1 +
6 files changed, 402 insertions(+)
create mode 100644 src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
create mode 100644 test/Analyzers.Tests/Orchestration/LoggerOrchestrationAnalyzerTests.cs
diff --git a/src/Analyzers/AnalyzerReleases.Unshipped.md b/src/Analyzers/AnalyzerReleases.Unshipped.md
index 150fb7356..76cfd85ad 100644
--- a/src/Analyzers/AnalyzerReleases.Unshipped.md
+++ b/src/Analyzers/AnalyzerReleases.Unshipped.md
@@ -5,5 +5,6 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
+DURABLE0009 | Orchestration | Warning | **LoggerOrchestrationAnalyzer**: Warns when a non-contextual ILogger is used in an orchestration method. Orchestrations should use `context.CreateReplaySafeLogger()` instead of injecting ILogger directly.
DURABLE2003 | Activity | Warning | **FunctionNotFoundAnalyzer**: Warns when an activity function call references a name that does not match any defined activity in the compilation.
DURABLE2004 | Orchestration | Warning | **FunctionNotFoundAnalyzer**: Warns when a sub-orchestration call references a name that does not match any defined orchestrator in the compilation.
\ No newline at end of file
diff --git a/src/Analyzers/KnownTypeSymbols.Net.cs b/src/Analyzers/KnownTypeSymbols.Net.cs
index 9803273ae..c9bde3ed9 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? iLogger;
///
/// 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 an ILogger type symbol.
+ ///
+ public INamedTypeSymbol? ILogger => this.GetOrResolveFullyQualifiedType("Microsoft.Extensions.Logging.ILogger", ref this.iLogger);
}
diff --git a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
new file mode 100644
index 000000000..583085af5
--- /dev/null
+++ b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
@@ -0,0 +1,128 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+using static Microsoft.DurableTask.Analyzers.Orchestration.LoggerOrchestrationAnalyzer;
+
+namespace Microsoft.DurableTask.Analyzers.Orchestration;
+
+///
+/// Analyzer that reports a warning when a non-contextual ILogger is used in an orchestration method.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class LoggerOrchestrationAnalyzer : OrchestrationAnalyzer
+{
+ ///
+ /// Diagnostic ID supported for the analyzer.
+ ///
+ public const string DiagnosticId = "DURABLE0009";
+
+ static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.LoggerOrchestrationAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
+ static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.LoggerOrchestrationAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
+
+ static readonly DiagnosticDescriptor Rule = new(
+ DiagnosticId,
+ Title,
+ MessageFormat,
+ AnalyzersCategories.Orchestration,
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ ///
+ public override ImmutableArray SupportedDiagnostics => [Rule];
+
+ ///
+ /// Visitor that inspects the method body for ILogger usage.
+ ///
+ public sealed class LoggerOrchestrationVisitor : MethodProbeOrchestrationVisitor
+ {
+ INamedTypeSymbol? iLoggerSymbol;
+
+ ///
+ public override bool Initialize()
+ {
+ this.iLoggerSymbol = this.KnownTypeSymbols.ILogger;
+ if (this.iLoggerSymbol == null)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ protected override void VisitMethod(SemanticModel semanticModel, SyntaxNode methodSyntax, IMethodSymbol methodSymbol, string orchestrationName, Action reportDiagnostic)
+ {
+ IOperation? methodOperation = semanticModel.GetOperation(methodSyntax);
+ if (methodOperation is null)
+ {
+ return;
+ }
+
+ // Check for ILogger parameters in the method signature
+ foreach (IParameterSymbol parameter in methodSymbol.Parameters)
+ {
+ if (this.IsILoggerType(parameter.Type))
+ {
+ // Found an ILogger parameter - report diagnostic at the parameter location
+ if (parameter.DeclaringSyntaxReferences.Length > 0)
+ {
+ SyntaxNode parameterSyntax = parameter.DeclaringSyntaxReferences[0].GetSyntax();
+ reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameterSyntax, methodSymbol.Name, orchestrationName));
+ }
+ }
+ }
+
+ // Check for ILogger field or property references
+ foreach (IOperation descendant in methodOperation.Descendants())
+ {
+ ITypeSymbol? typeToCheck = null;
+ SyntaxNode? syntaxNode = null;
+
+ switch (descendant)
+ {
+ case IFieldReferenceOperation fieldRef:
+ typeToCheck = fieldRef.Field.Type;
+ syntaxNode = fieldRef.Syntax;
+ break;
+ case IPropertyReferenceOperation propRef:
+ typeToCheck = propRef.Property.Type;
+ syntaxNode = propRef.Syntax;
+ break;
+ case IParameterReferenceOperation paramRef:
+ typeToCheck = paramRef.Parameter.Type;
+ syntaxNode = paramRef.Syntax;
+ break;
+ }
+
+ if (typeToCheck != null && syntaxNode != null && this.IsILoggerType(typeToCheck))
+ {
+ reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, syntaxNode, methodSymbol.Name, orchestrationName));
+ }
+ }
+ }
+
+ bool IsILoggerType(ITypeSymbol type)
+ {
+ if (this.iLoggerSymbol == null)
+ {
+ return false;
+ }
+
+ // Check if the type is ILogger or ILogger
+ if (type is INamedTypeSymbol namedType)
+ {
+ INamedTypeSymbol originalDefinition = namedType.OriginalDefinition;
+ if (SymbolEqualityComparer.Default.Equals(originalDefinition, this.iLoggerSymbol))
+ {
+ return true;
+ }
+ }
+
+ return SymbolEqualityComparer.Default.Equals(type, this.iLoggerSymbol);
+ }
+ }
+}
diff --git a/src/Analyzers/Resources.resx b/src/Analyzers/Resources.resx
index ee14969a3..00656d4bb 100644
--- a/src/Analyzers/Resources.resx
+++ b/src/Analyzers/Resources.resx
@@ -183,6 +183,12 @@
Thread and Task calls must be deterministic inside an orchestrator function
+
+ The method '{0}' uses a non-contextual logger that may cause unexpected behavior when invoked from orchestration '{1}'. Use 'context.CreateReplaySafeLogger()' instead.
+
+
+ Orchestrations should use replay-safe loggers created from the orchestration context
+
CallActivityAsync is passing the incorrect type '{0}' instead of '{1}' to the activity function '{2}'
diff --git a/test/Analyzers.Tests/Orchestration/LoggerOrchestrationAnalyzerTests.cs b/test/Analyzers.Tests/Orchestration/LoggerOrchestrationAnalyzerTests.cs
new file mode 100644
index 000000000..e9aed640a
--- /dev/null
+++ b/test/Analyzers.Tests/Orchestration/LoggerOrchestrationAnalyzerTests.cs
@@ -0,0 +1,260 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.DurableTask.Analyzers.Orchestration;
+
+using VerifyCS = Microsoft.DurableTask.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier;
+
+namespace Microsoft.DurableTask.Analyzers.Tests.Orchestration;
+
+public class LoggerOrchestrationAnalyzerTests
+{
+ [Fact]
+ public async Task EmptyCodeWithNoSymbolsAvailableHasNoDiag()
+ {
+ string code = @"";
+
+ // checks that empty code with no assembly references of Durable Functions has no diagnostics.
+ // this guarantees that if someone adds our analyzer to a project that doesn't use Durable Functions,
+ // the analyzer won't crash/they won't get any diagnostics
+ await VerifyCS.VerifyAnalyzerAsync(code);
+ }
+
+ [Fact]
+ public async Task EmptyCodeWithSymbolsAvailableHasNoDiag()
+ {
+ string code = @"";
+
+ // checks that empty code with access to assembly references of Durable Functions has no diagnostics
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code);
+ }
+
+ [Fact]
+ public async Task NonOrchestrationHasNoDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+void Method(ILogger logger){
+ logger.LogInformation(""Test"");
+}
+");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code);
+ }
+
+ [Fact]
+ public async Task DurableFunctionOrchestrationWithLoggerParameterHasDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+void Run([OrchestrationTrigger] TaskOrchestrationContext context, {|#0:ILogger logger|})
+{
+ logger.LogInformation(""Test"");
+}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", "Run");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ }
+
+ [Fact]
+ public async Task DurableFunctionOrchestrationWithLoggerGenericParameterHasDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+void Run([OrchestrationTrigger] TaskOrchestrationContext context, {|#0:ILogger logger|})
+{
+ logger.LogInformation(""Test"");
+}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", "Run");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ }
+
+ [Fact]
+ public async Task DurableFunctionOrchestrationWithLoggerFieldReferenceHasDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+private readonly ILogger logger;
+
+[Function(""Run"")]
+void Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ {|#0:this.logger|}.LogInformation(""Test"");
+}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", "Run");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ }
+
+ [Fact]
+ public async Task DurableFunctionOrchestrationWithLoggerPropertyReferenceHasDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+private ILogger Logger { get; set; }
+
+[Function(""Run"")]
+void Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ {|#0:this.Logger|}.LogInformation(""Test"");
+}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", "Run");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ }
+
+ [Fact]
+ public async Task DurableFunctionOrchestrationInvokingMethodWithLoggerHasDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+private readonly ILogger logger;
+
+[Function(""Run"")]
+void Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ Helper();
+}
+
+void Helper()
+{
+ {|#0:this.logger|}.LogInformation(""Test"");
+}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Helper", "Run");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ }
+
+ [Fact]
+ public async Task DurableFunctionOrchestrationUsingContextLoggerHasNoDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+void Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ ILogger logger = context.CreateReplaySafeLogger(nameof(Run));
+ logger.LogInformation(""Test"");
+}
+");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code);
+ }
+
+ [Fact]
+ public async Task TaskOrchestratorWithLoggerParameterHasDiag()
+ {
+ string code = Wrapper.WrapTaskOrchestrator(@"
+public class MyOrchestrator : TaskOrchestrator
+{
+ readonly ILogger logger;
+
+ public override Task RunAsync(TaskOrchestrationContext context, string input)
+ {
+ {|#0:this.logger|}.LogInformation(""Test"");
+ return Task.FromResult(""result"");
+ }
+}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("RunAsync", "MyOrchestrator");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ }
+
+ [Fact]
+ public async Task TaskOrchestratorUsingContextLoggerHasNoDiag()
+ {
+ string code = Wrapper.WrapTaskOrchestrator(@"
+public class MyOrchestrator : TaskOrchestrator
+{
+ public override Task RunAsync(TaskOrchestrationContext context, string input)
+ {
+ ILogger logger = context.CreateReplaySafeLogger();
+ logger.LogInformation(""Test"");
+ return Task.FromResult(""result"");
+ }
+}
+");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code);
+ }
+
+ [Fact]
+ public async Task FuncOrchestratorWithLoggerHasDiag()
+ {
+ string code = @"
+using System;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Worker;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+public class Program
+{
+ static ILogger staticLogger;
+
+ public static void Main()
+ {
+ new ServiceCollection().AddDurableTaskWorker(builder =>
+ {
+ builder.AddTasks(tasks =>
+ {
+ tasks.AddOrchestratorFunc(""MyRun"", context =>
+ {
+ {|#0:staticLogger|}.LogInformation(""Test"");
+ return ""result"";
+ });
+ });
+ });
+ }
+}
+";
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Main", "MyRun");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ }
+
+ [Fact]
+ public async Task FuncOrchestratorUsingContextLoggerHasNoDiag()
+ {
+ string code = Wrapper.WrapFuncOrchestrator(@"
+tasks.AddOrchestratorFunc(""HelloSequence"", context =>
+{
+ ILogger logger = context.CreateReplaySafeLogger(""HelloSequence"");
+ logger.LogInformation(""Test"");
+ return ""result"";
+});
+");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code);
+ }
+
+ [Fact]
+ public async Task ActivityFunctionWithLoggerHasNoDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""MyActivity"")]
+string MyActivity([ActivityTrigger] string input, ILogger logger)
+{
+ logger.LogInformation(""Test"");
+ return ""result"";
+}
+");
+
+ await VerifyCS.VerifyDurableTaskAnalyzerAsync(code);
+ }
+
+ static DiagnosticResult BuildDiagnostic()
+ {
+ return VerifyCS.Diagnostic(LoggerOrchestrationAnalyzer.DiagnosticId);
+ }
+}
diff --git a/test/Analyzers.Tests/Wrapper.cs b/test/Analyzers.Tests/Wrapper.cs
index fb5df8708..948753aac 100644
--- a/test/Analyzers.Tests/Wrapper.cs
+++ b/test/Analyzers.Tests/Wrapper.cs
@@ -62,6 +62,7 @@ static string Usings()
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
";
}
}
From 088cc6752ca68df4e649484c6787ce61d5f954d7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 10 Dec 2025 19:38:41 +0000
Subject: [PATCH 3/8] Fix LoggerOrchestrationAnalyzer to handle ILogger and
avoid duplicate diagnostics
Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
---
.../LoggerOrchestrationAnalyzer.cs | 39 ++++++++++++++++---
1 file changed, 34 insertions(+), 5 deletions(-)
diff --git a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
index 583085af5..1b038d257 100644
--- a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
+++ b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
@@ -62,6 +62,9 @@ protected override void VisitMethod(SemanticModel semanticModel, SyntaxNode meth
return;
}
+ // Track which parameters we've already reported on to avoid duplicates
+ HashSet reportedParameters = new(SymbolEqualityComparer.Default);
+
// Check for ILogger parameters in the method signature
foreach (IParameterSymbol parameter in methodSymbol.Parameters)
{
@@ -72,11 +75,12 @@ protected override void VisitMethod(SemanticModel semanticModel, SyntaxNode meth
{
SyntaxNode parameterSyntax = parameter.DeclaringSyntaxReferences[0].GetSyntax();
reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameterSyntax, methodSymbol.Name, orchestrationName));
+ reportedParameters.Add(parameter);
}
}
}
- // Check for ILogger field or property references
+ // Check for ILogger field or property references (but not parameter references, as those were already reported)
foreach (IOperation descendant in methodOperation.Descendants())
{
ITypeSymbol? typeToCheck = null;
@@ -93,6 +97,12 @@ protected override void VisitMethod(SemanticModel semanticModel, SyntaxNode meth
syntaxNode = propRef.Syntax;
break;
case IParameterReferenceOperation paramRef:
+ // Skip parameter references that we already reported on in the parameter list
+ if (reportedParameters.Contains(paramRef.Parameter))
+ {
+ continue;
+ }
+
typeToCheck = paramRef.Parameter.Type;
syntaxNode = paramRef.Syntax;
break;
@@ -112,17 +122,36 @@ bool IsILoggerType(ITypeSymbol type)
return false;
}
- // Check if the type is ILogger or ILogger
+ // First check for exact match with ILogger
+ if (SymbolEqualityComparer.Default.Equals(type, this.iLoggerSymbol))
+ {
+ return true;
+ }
+
+ // Check if the type is ILogger by checking if it implements ILogger
if (type is INamedTypeSymbol namedType)
{
+ // Check all interfaces implemented by the type
+ foreach (INamedTypeSymbol interfaceType in namedType.AllInterfaces)
+ {
+ if (SymbolEqualityComparer.Default.Equals(interfaceType, this.iLoggerSymbol))
+ {
+ return true;
+ }
+ }
+
+ // Also check if the original definition matches (for generic types like ILogger)
INamedTypeSymbol originalDefinition = namedType.OriginalDefinition;
- if (SymbolEqualityComparer.Default.Equals(originalDefinition, this.iLoggerSymbol))
+ foreach (INamedTypeSymbol interfaceType in originalDefinition.AllInterfaces)
{
- return true;
+ if (SymbolEqualityComparer.Default.Equals(interfaceType, this.iLoggerSymbol))
+ {
+ return true;
+ }
}
}
- return SymbolEqualityComparer.Default.Equals(type, this.iLoggerSymbol);
+ return false;
}
}
}
From df1db688ccb7dda34e29a8ca9570a93072040e95 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 10 Dec 2025 19:43:43 +0000
Subject: [PATCH 4/8] Simplify IsILoggerType method per code review feedback
Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
---
.../Orchestration/LoggerOrchestrationAnalyzer.cs | 13 +------------
1 file changed, 1 insertion(+), 12 deletions(-)
diff --git a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
index 1b038d257..2c046bbf1 100644
--- a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
+++ b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
@@ -128,10 +128,9 @@ bool IsILoggerType(ITypeSymbol type)
return true;
}
- // Check if the type is ILogger by checking if it implements ILogger
+ // Check if the type implements ILogger interface (covers ILogger case)
if (type is INamedTypeSymbol namedType)
{
- // Check all interfaces implemented by the type
foreach (INamedTypeSymbol interfaceType in namedType.AllInterfaces)
{
if (SymbolEqualityComparer.Default.Equals(interfaceType, this.iLoggerSymbol))
@@ -139,16 +138,6 @@ bool IsILoggerType(ITypeSymbol type)
return true;
}
}
-
- // Also check if the original definition matches (for generic types like ILogger)
- INamedTypeSymbol originalDefinition = namedType.OriginalDefinition;
- foreach (INamedTypeSymbol interfaceType in originalDefinition.AllInterfaces)
- {
- if (SymbolEqualityComparer.Default.Equals(interfaceType, this.iLoggerSymbol))
- {
- return true;
- }
- }
}
return false;
From 71d8fa7ff5c97b070e49cf451196ffba065587eb Mon Sep 17 00:00:00 2001
From: wangbill
Date: Fri, 19 Dec 2025 12:59:16 -0800
Subject: [PATCH 5/8] Update
src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
index 2c046bbf1..f975013b2 100644
--- a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
+++ b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
@@ -18,7 +18,7 @@ public sealed class LoggerOrchestrationAnalyzer : OrchestrationAnalyzer
/// Diagnostic ID supported for the analyzer.
///
- public const string DiagnosticId = "DURABLE0009";
+ public const string DiagnosticId = "DURABLE0010";
static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.LoggerOrchestrationAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.LoggerOrchestrationAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
From f29b50537900000f2564bac169579b5d8ea59c49 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 19 Dec 2025 21:04:44 +0000
Subject: [PATCH 6/8] Combine nested if statements in
LoggerOrchestrationAnalyzer
Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
---
.../Orchestration/LoggerOrchestrationAnalyzer.cs | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
index f975013b2..66e025fff 100644
--- a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
+++ b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
@@ -68,15 +68,12 @@ protected override void VisitMethod(SemanticModel semanticModel, SyntaxNode meth
// Check for ILogger parameters in the method signature
foreach (IParameterSymbol parameter in methodSymbol.Parameters)
{
- if (this.IsILoggerType(parameter.Type))
+ if (this.IsILoggerType(parameter.Type) && parameter.DeclaringSyntaxReferences.Length > 0)
{
// Found an ILogger parameter - report diagnostic at the parameter location
- if (parameter.DeclaringSyntaxReferences.Length > 0)
- {
- SyntaxNode parameterSyntax = parameter.DeclaringSyntaxReferences[0].GetSyntax();
- reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameterSyntax, methodSymbol.Name, orchestrationName));
- reportedParameters.Add(parameter);
- }
+ SyntaxNode parameterSyntax = parameter.DeclaringSyntaxReferences[0].GetSyntax();
+ reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameterSyntax, methodSymbol.Name, orchestrationName));
+ reportedParameters.Add(parameter);
}
}
From d87ce97c9b55c05e1794acf48818c7c529471315 Mon Sep 17 00:00:00 2001
From: wangbill
Date: Fri, 19 Dec 2025 13:21:52 -0800
Subject: [PATCH 7/8] Potential fix for pull request finding 'Missed
opportunity to use Where'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
---
.../Orchestration/LoggerOrchestrationAnalyzer.cs | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
index 66e025fff..9bb522fb1 100644
--- a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
+++ b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System.Collections.Immutable;
+using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
@@ -128,13 +129,8 @@ bool IsILoggerType(ITypeSymbol type)
// Check if the type implements ILogger interface (covers ILogger case)
if (type is INamedTypeSymbol namedType)
{
- foreach (INamedTypeSymbol interfaceType in namedType.AllInterfaces)
- {
- if (SymbolEqualityComparer.Default.Equals(interfaceType, this.iLoggerSymbol))
- {
- return true;
- }
- }
+ return namedType.AllInterfaces.Any(interfaceType =>
+ SymbolEqualityComparer.Default.Equals(interfaceType, this.iLoggerSymbol));
}
return false;
From 7590aa6fb874df392d8ddbbe297242735b6b14f2 Mon Sep 17 00:00:00 2001
From: wangbill
Date: Fri, 19 Dec 2025 13:27:52 -0800
Subject: [PATCH 8/8] Potential fix for pull request finding 'Missed
opportunity to use Where'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
---
.../Orchestration/LoggerOrchestrationAnalyzer.cs | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
index 9bb522fb1..e1513c5a4 100644
--- a/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
+++ b/src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs
@@ -67,15 +67,14 @@ protected override void VisitMethod(SemanticModel semanticModel, SyntaxNode meth
HashSet reportedParameters = new(SymbolEqualityComparer.Default);
// Check for ILogger parameters in the method signature
- foreach (IParameterSymbol parameter in methodSymbol.Parameters)
+ foreach (IParameterSymbol parameter in methodSymbol.Parameters.Where(
+ parameter => this.IsILoggerType(parameter.Type) &&
+ parameter.DeclaringSyntaxReferences.Length > 0))
{
- if (this.IsILoggerType(parameter.Type) && parameter.DeclaringSyntaxReferences.Length > 0)
- {
- // Found an ILogger parameter - report diagnostic at the parameter location
- SyntaxNode parameterSyntax = parameter.DeclaringSyntaxReferences[0].GetSyntax();
- reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameterSyntax, methodSymbol.Name, orchestrationName));
- reportedParameters.Add(parameter);
- }
+ // Found an ILogger parameter - report diagnostic at the parameter location
+ SyntaxNode parameterSyntax = parameter.DeclaringSyntaxReferences[0].GetSyntax();
+ reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameterSyntax, methodSymbol.Name, orchestrationName));
+ reportedParameters.Add(parameter);
}
// Check for ILogger field or property references (but not parameter references, as those were already reported)