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)