From 84a2751b1a48831c6322deeed972b235cda1b30c Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Tue, 16 Jan 2024 16:02:36 +0100 Subject: [PATCH 1/4] Fix S4507 FP for top level statements --- .../Hotspots/DeliveringDebugFeaturesInProduction.cs | 12 +++++++++--- .../DeliveringDebugFeaturesInProduction.Net7.cs | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs index 2a9d1bdb47d..5e77e492415 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs @@ -30,9 +30,15 @@ public DeliveringDebugFeaturesInProduction() : this(AnalyzerConfiguration.Hotspo internal /*for testing*/ DeliveringDebugFeaturesInProduction(IAnalyzerConfiguration configuration) : base(configuration) { } // For simplicity we will avoid creating noise if the validation is invoked in the same method (https://github.com/SonarSource/sonar-dotnet/issues/5032) - protected override bool IsDevelopmentCheckInvoked(SyntaxNode node, SemanticModel semanticModel) => - node.FirstAncestorOrSelf() is { } parentMethodDeclaration - && parentMethodDeclaration.DescendantNodes().Any(x => IsDevelopmentCheck(x, semanticModel)); + protected override bool IsDevelopmentCheckInvoked(SyntaxNode node, SemanticModel semanticModel) + { + SyntaxNode parentMethod = node.FirstAncestorOrSelf(); + if (parentMethod is null && node.FirstAncestorOrSelf() is { }) // for top-level statements + { + parentMethod = node.FirstAncestorOrSelf(); + } + return parentMethod is not null && parentMethod.DescendantNodes().Any(x => IsDevelopmentCheck(x, semanticModel)); + } protected override bool IsInDevelopmentContext(SyntaxNode node) => node.Ancestors() diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/Hotspots/DeliveringDebugFeaturesInProduction.Net7.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/Hotspots/DeliveringDebugFeaturesInProduction.Net7.cs index 534fb49bfbc..5671014fdb8 100644 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/Hotspots/DeliveringDebugFeaturesInProduction.Net7.cs +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/Hotspots/DeliveringDebugFeaturesInProduction.Net7.cs @@ -4,9 +4,10 @@ var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); +// https://github.com/SonarSource/sonar-dotnet/issues/6772 if (app.Environment.IsDevelopment()) { - app.UseDeveloperExceptionPage(); // Noncompliant FP https://github.com/SonarSource/sonar-dotnet/issues/6772 + app.UseDeveloperExceptionPage(); } app.Run(); From af39139625e045c4ce314d657df97fb7838d6679 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Mon, 22 Jan 2024 19:06:07 +0100 Subject: [PATCH 2/4] Review --- .../DeliveringDebugFeaturesInProduction.cs | 27 +++++++---- ...eringDebugFeaturesInProduction.NetCore2.cs | 47 ++++++++++++++++++- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs index 5e77e492415..a4aa3f5ffd0 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs @@ -30,15 +30,9 @@ public DeliveringDebugFeaturesInProduction() : this(AnalyzerConfiguration.Hotspo internal /*for testing*/ DeliveringDebugFeaturesInProduction(IAnalyzerConfiguration configuration) : base(configuration) { } // For simplicity we will avoid creating noise if the validation is invoked in the same method (https://github.com/SonarSource/sonar-dotnet/issues/5032) - protected override bool IsDevelopmentCheckInvoked(SyntaxNode node, SemanticModel semanticModel) - { - SyntaxNode parentMethod = node.FirstAncestorOrSelf(); - if (parentMethod is null && node.FirstAncestorOrSelf() is { }) // for top-level statements - { - parentMethod = node.FirstAncestorOrSelf(); - } - return parentMethod is not null && parentMethod.DescendantNodes().Any(x => IsDevelopmentCheck(x, semanticModel)); - } + protected override bool IsDevelopmentCheckInvoked(SyntaxNode node, SemanticModel semanticModel) => + EnclosingScope(node) is { } enclosingScope + && enclosingScope.DescendantNodes().Any(x => IsDevelopmentCheck(x, semanticModel)); protected override bool IsInDevelopmentContext(SyntaxNode node) => node.Ancestors() @@ -48,5 +42,20 @@ protected override bool IsInDevelopmentContext(SyntaxNode node) => private bool IsDevelopmentCheck(SyntaxNode node, SemanticModel semanticModel) => node is InvocationExpressionSyntax condition && IsValidationMethod(semanticModel, condition, condition.Expression.GetIdentifier()?.ValueText); + + private static SyntaxNode EnclosingScope(SyntaxNode node) => + node.Ancestors().FirstOrDefault(x => x.IsAnyKind( + SyntaxKind.AddAccessorDeclaration, + SyntaxKind.ConstructorDeclaration, + SyntaxKind.DestructorDeclaration, + SyntaxKind.GetAccessorDeclaration, + SyntaxKind.GlobalStatement, + SyntaxKindEx.InitAccessorDeclaration, + SyntaxKindEx.LocalFunctionStatement, + SyntaxKind.MethodDeclaration, + SyntaxKind.ParenthesizedLambdaExpression, + SyntaxKind.RemoveAccessorDeclaration, + SyntaxKind.SetAccessorDeclaration, + SyntaxKind.SimpleLambdaExpression)); } } diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/Hotspots/DeliveringDebugFeaturesInProduction.NetCore2.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/Hotspots/DeliveringDebugFeaturesInProduction.NetCore2.cs index 31aaa647cef..6266b6bf811 100644 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/Hotspots/DeliveringDebugFeaturesInProduction.NetCore2.cs +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/Hotspots/DeliveringDebugFeaturesInProduction.NetCore2.cs @@ -7,16 +7,61 @@ namespace Tests.Diagnostics public class Startup { // for coverage + private IHostingEnvironment env; + private IApplicationBuilder foo; public IApplicationBuilder Foo { + get + { + if (env.IsDevelopment()) + { + foo.UseDeveloperExceptionPage(); // Compliant + } + return foo; + } set { - var x = value.UseDeveloperExceptionPage(); // Noncompliant + var x = value.UseDeveloperExceptionPage(); // Noncompliant foo = value; } } + event EventHandler SomeEvent + { + add { foo.UseDeveloperExceptionPage(); } // Noncompliant + remove + { + if (env.IsDevelopment()) + { + foo.UseDeveloperExceptionPage(); // Compliant + } + } + } + + public Startup() + { + foo.UseDeveloperExceptionPage(); // Noncompliant + } + + public void Lambda() + { + Action action1 = () => + { + foo.UseDeveloperExceptionPage(); // Noncompliant + }; + action1(); + + Action action2 = () => + { + if (env.IsDevelopment()) + { + foo.UseDeveloperExceptionPage(); // Compliant + } + }; + action2(); + } + public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // Invoking as extension methods From 1fea20640f8d63e798715382ffb6fcabc017a371 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Sun, 25 Feb 2024 19:56:20 +0100 Subject: [PATCH 3/4] Move EnclosingScope to utility class and add UTs --- .../Extensions/SyntaxNodeExtensions.cs | 69 ++++++++++++------- .../DeliveringDebugFeaturesInProduction.cs | 20 +----- .../Extensions/SyntaxNodeExtensionsTest.cs | 43 ++++++++++++ 3 files changed, 92 insertions(+), 40 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs index 97eecabf797..2fa4d3d76eb 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs @@ -19,14 +19,32 @@ */ using SonarAnalyzer.CFG.Roslyn; -using StyleCop.Analyzers.Lightup; namespace SonarAnalyzer.Extensions { internal static partial class SyntaxNodeExtensions { private static readonly ControlFlowGraphCache CfgCache = new(); - private static readonly SyntaxKind[] ParenthesizedNodeKinds = new[] { SyntaxKind.ParenthesizedExpression, SyntaxKindEx.ParenthesizedPattern }; + private static readonly SyntaxKind[] ParenthesizedNodeKinds = [SyntaxKind.ParenthesizedExpression, SyntaxKindEx.ParenthesizedPattern]; + private static readonly SyntaxKind[] EnclosingScopeSyntaxKinds = [ + SyntaxKind.AddAccessorDeclaration, + SyntaxKind.AddAccessorDeclaration, + SyntaxKind.AnonymousMethodExpression, + SyntaxKind.BaseConstructorInitializer, + SyntaxKind.ConstructorDeclaration, + SyntaxKind.DestructorDeclaration, + SyntaxKind.EqualsValueClause, + SyntaxKind.GetAccessorDeclaration, + SyntaxKind.GlobalStatement, + SyntaxKindEx.InitAccessorDeclaration, + SyntaxKindEx.LocalFunctionStatement, + SyntaxKind.MethodDeclaration, + SyntaxKind.ParenthesizedLambdaExpression, + SyntaxKindEx.PrimaryConstructorBaseType, + SyntaxKind.RemoveAccessorDeclaration, + SyntaxKind.SetAccessorDeclaration, + SyntaxKind.SimpleLambdaExpression, + SyntaxKind.ThisConstructorInitializer]; public static ControlFlowGraph CreateCfg(this SyntaxNode node, SemanticModel model, CancellationToken cancel) => CfgCache.FindOrCreate(node, model, cancel); @@ -34,12 +52,12 @@ public static ControlFlowGraph CreateCfg(this SyntaxNode node, SemanticModel mod public static bool ContainsConditionalConstructs(this SyntaxNode node) => node != null && node.DescendantNodes() - .Any(descendant => descendant.IsAnyKind(SyntaxKind.IfStatement, - SyntaxKind.ConditionalExpression, - SyntaxKind.CoalesceExpression, - SyntaxKind.SwitchStatement, - SyntaxKindEx.SwitchExpression, - SyntaxKindEx.CoalesceAssignmentExpression)); + .Any(descendant => descendant.Kind() is SyntaxKind.IfStatement + or SyntaxKind.ConditionalExpression + or SyntaxKind.CoalesceExpression + or SyntaxKind.SwitchStatement + or SyntaxKindEx.SwitchExpression + or SyntaxKindEx.CoalesceAssignmentExpression); public static object FindConstantValue(this SyntaxNode node, SemanticModel semanticModel) => new CSharpConstantValueFinder(semanticModel).FindConstant(node); @@ -49,7 +67,7 @@ public static string FindStringConstant(this SyntaxNode node, SemanticModel sema public static bool IsPartOfBinaryNegationOrCondition(this SyntaxNode node) { - if (!(node.Parent is MemberAccessExpressionSyntax)) + if (node.Parent is not MemberAccessExpressionSyntax) { return false; } @@ -61,12 +79,12 @@ public static bool IsPartOfBinaryNegationOrCondition(this SyntaxNode node) } var current = topNode; - while (!current.Parent?.IsAnyKind(SyntaxKind.BitwiseNotExpression, - SyntaxKind.IfStatement, - SyntaxKind.WhileStatement, - SyntaxKind.ConditionalExpression, - SyntaxKind.MethodDeclaration, - SyntaxKind.SimpleLambdaExpression) ?? false) + while (current.Parent != null && current.Parent?.Kind() is not (SyntaxKind.BitwiseNotExpression + or SyntaxKind.IfStatement + or SyntaxKind.WhileStatement + or SyntaxKind.ConditionalExpression + or SyntaxKind.MethodDeclaration + or SyntaxKind.SimpleLambdaExpression)) { current = current.Parent; } @@ -400,14 +418,13 @@ public static ConditionalAccessExpressionSyntax GetParentConditionalAccessExpres // Effectively, if we're on the RHS of the ? we have to walk up the RHS spine first until we hit the first // conditional access. - while (current.IsAnyKind( - SyntaxKind.InvocationExpression, - SyntaxKind.ElementAccessExpression, - SyntaxKind.SimpleMemberAccessExpression, - SyntaxKind.MemberBindingExpression, - SyntaxKind.ElementBindingExpression, + while ((current.Kind() is SyntaxKind.InvocationExpression + or SyntaxKind.ElementAccessExpression + or SyntaxKind.SimpleMemberAccessExpression + or SyntaxKind.MemberBindingExpression + or SyntaxKind.ElementBindingExpression // Optional exclamations might follow the conditional operation. For example: a.b?.$$c!!!!() - SyntaxKindEx.SuppressNullableWarningExpression) && + or SyntaxKindEx.SuppressNullableWarningExpression) && current.Parent is not ConditionalAccessExpressionSyntax) { current = current.Parent; @@ -539,6 +556,9 @@ public static bool IsFalse(this SyntaxNode node) => _ => false, }; + public static SyntaxNode EnclosingScope(this SyntaxNode node) => + node.Ancestors().FirstOrDefault(x => x.IsAnyKind(EnclosingScopeSyntaxKinds)); + private readonly record struct PathPosition(int Index, int TupleLength); private sealed class ControlFlowGraphCache : ControlFlowGraphCacheBase @@ -547,7 +567,10 @@ protected override bool IsLocalFunction(SyntaxNode node) => node.IsKind(SyntaxKindEx.LocalFunctionStatement); protected override bool HasNestedCfg(SyntaxNode node) => - node.IsAnyKind(SyntaxKindEx.LocalFunctionStatement, SyntaxKind.SimpleLambdaExpression, SyntaxKind.AnonymousMethodExpression, SyntaxKind.ParenthesizedLambdaExpression); + node.Kind() is SyntaxKindEx.LocalFunctionStatement + or SyntaxKind.SimpleLambdaExpression + or SyntaxKind.AnonymousMethodExpression + or SyntaxKind.ParenthesizedLambdaExpression; } } } diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs index a4aa3f5ffd0..c147db6a274 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs @@ -31,8 +31,9 @@ public DeliveringDebugFeaturesInProduction() : this(AnalyzerConfiguration.Hotspo // For simplicity we will avoid creating noise if the validation is invoked in the same method (https://github.com/SonarSource/sonar-dotnet/issues/5032) protected override bool IsDevelopmentCheckInvoked(SyntaxNode node, SemanticModel semanticModel) => - EnclosingScope(node) is { } enclosingScope - && enclosingScope.DescendantNodes().Any(x => IsDevelopmentCheck(x, semanticModel)); + node.EnclosingScope() + .DescendantNodes() + .Any(x => IsDevelopmentCheck(x, semanticModel)); protected override bool IsInDevelopmentContext(SyntaxNode node) => node.Ancestors() @@ -42,20 +43,5 @@ protected override bool IsInDevelopmentContext(SyntaxNode node) => private bool IsDevelopmentCheck(SyntaxNode node, SemanticModel semanticModel) => node is InvocationExpressionSyntax condition && IsValidationMethod(semanticModel, condition, condition.Expression.GetIdentifier()?.ValueText); - - private static SyntaxNode EnclosingScope(SyntaxNode node) => - node.Ancestors().FirstOrDefault(x => x.IsAnyKind( - SyntaxKind.AddAccessorDeclaration, - SyntaxKind.ConstructorDeclaration, - SyntaxKind.DestructorDeclaration, - SyntaxKind.GetAccessorDeclaration, - SyntaxKind.GlobalStatement, - SyntaxKindEx.InitAccessorDeclaration, - SyntaxKindEx.LocalFunctionStatement, - SyntaxKind.MethodDeclaration, - SyntaxKind.ParenthesizedLambdaExpression, - SyntaxKind.RemoveAccessorDeclaration, - SyntaxKind.SetAccessorDeclaration, - SyntaxKind.SimpleLambdaExpression)); } } diff --git a/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs index 3a6e85e75af..7f8a6c62773 100644 --- a/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs +++ b/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs @@ -1200,6 +1200,49 @@ public class Derived(int i) {{baseType}} { } } } + [DataTestMethod] + [DataRow("""event EventHandler SomeEvent { add { $$int x = 42;$$ } remove { int x = 42; } }""", SyntaxKind.AddAccessorDeclaration)] + [DataRow("""int Method() { Func add = delegate (int a, int b) { return $$a + b$$; }; return add(1, 2); }""", SyntaxKind.AnonymousMethodExpression)] + [DataRow("""Derived(int arg) : base($$arg$$) { }""", SyntaxKind.BaseConstructorInitializer)] + [DataRow("""Derived() { $$var x = 42;$$ }""", SyntaxKind.ConstructorDeclaration)] + [DataRow("""~Derived() { $$var x = 42;$$ }""", SyntaxKind.DestructorDeclaration)] + [DataRow("""int field = $$int.Parse("42")$$;""", SyntaxKind.EqualsValueClause)] + [DataRow("""int Property { get; set; } = $$int.Parse("42")$$;""", SyntaxKind.EqualsValueClause)] + [DataRow("""int Property { set { $$_ = value;$$ } }""", SyntaxKind.SetAccessorDeclaration)] + [DataRow("""int Property { set { $$_ = value;$$ } }""", SyntaxKind.SetAccessorDeclaration)] + [DataRow("""int Method() { return LocalFunction(); int LocalFunction() { $$return 42;$$ } }""", SyntaxKindEx.LocalFunctionStatement)] + [DataRow("""int Method() { return LocalFunction(); int LocalFunction() => $$42$$; }""", SyntaxKindEx.LocalFunctionStatement)] + [DataRow("""int Method() { $$return 42;$$ }""", SyntaxKind.MethodDeclaration)] + [DataRow("""int Method() => $$42$$;""", SyntaxKind.MethodDeclaration)] + [DataRow("""int Method() { var lambda = () => $$42$$; return lambda(); }""", SyntaxKind.ParenthesizedLambdaExpression)] + [DataRow("""int Method() { Func lambda = x => $$x + 1$$; return lambda(42); }""", SyntaxKind.SimpleLambdaExpression)] + [DataRow("""event EventHandler SomeEvent { add { int x = 42; } remove { $$int x = 42;$$ } }""", SyntaxKind.RemoveAccessorDeclaration)] + [DataRow("""Derived(int arg) : this($$arg.ToString()$$) { }""", SyntaxKind.ThisConstructorInitializer)] +#if NET + [DataRow("""int Property { init { $$_ = value;$$ } }""", SyntaxKindEx.InitAccessorDeclaration)] + [DataRow("""record BaseRec(int I); record DerivedRec(int I): BaseRec($$I++$$);""", SyntaxKindEx.PrimaryConstructorBaseType)] +#endif + public void EnclosingScope_Members(string member, SyntaxKind expectedSyntaxKind) + { + var node = NodeBetweenMarkers($$""" + using System; + + public class Base + { + public Base() { } + public Base(int arg) { } + } + + public class Derived: Base + { + Derived(string arg) { } + {{member}} + } + """, LanguageNames.CSharp); + var actual = ExtensionsCS.EnclosingScope(node)?.Kind() ?? SyntaxKind.None; + actual.Should().Be(expectedSyntaxKind); + } + private static SyntaxNode NodeBetweenMarkers(string code, string language, bool getInnermostNodeForTie = false) { var position = code.IndexOf("$$"); From e72fa1c659103af3d35785c30e94262eaf96f46a Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Mon, 26 Feb 2024 18:24:03 +0100 Subject: [PATCH 4/4] Review 2 --- .../Extensions/SyntaxNodeExtensions.cs | 22 +++++++++++++------ .../Extensions/SyntaxNodeExtensionsTest.cs | 2 ++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs index 2fa4d3d76eb..4dfe05a601d 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs @@ -26,12 +26,13 @@ internal static partial class SyntaxNodeExtensions { private static readonly ControlFlowGraphCache CfgCache = new(); private static readonly SyntaxKind[] ParenthesizedNodeKinds = [SyntaxKind.ParenthesizedExpression, SyntaxKindEx.ParenthesizedPattern]; + private static readonly SyntaxKind[] EnclosingScopeSyntaxKinds = [ - SyntaxKind.AddAccessorDeclaration, SyntaxKind.AddAccessorDeclaration, SyntaxKind.AnonymousMethodExpression, SyntaxKind.BaseConstructorInitializer, SyntaxKind.ConstructorDeclaration, + SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.EqualsValueClause, SyntaxKind.GetAccessorDeclaration, @@ -39,6 +40,7 @@ internal static partial class SyntaxNodeExtensions SyntaxKindEx.InitAccessorDeclaration, SyntaxKindEx.LocalFunctionStatement, SyntaxKind.MethodDeclaration, + SyntaxKind.OperatorDeclaration, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKindEx.PrimaryConstructorBaseType, SyntaxKind.RemoveAccessorDeclaration, @@ -46,6 +48,16 @@ internal static partial class SyntaxNodeExtensions SyntaxKind.SimpleLambdaExpression, SyntaxKind.ThisConstructorInitializer]; + private static readonly SyntaxKind[] NegationOrConditionEnclosingSyntaxKinds = [ + SyntaxKind.AnonymousMethodExpression, + SyntaxKind.BitwiseNotExpression, + SyntaxKind.ConditionalExpression, + SyntaxKind.IfStatement, + SyntaxKind.MethodDeclaration, + SyntaxKind.ParenthesizedLambdaExpression, + SyntaxKind.SimpleLambdaExpression, + SyntaxKind.WhileStatement]; + public static ControlFlowGraph CreateCfg(this SyntaxNode node, SemanticModel model, CancellationToken cancel) => CfgCache.FindOrCreate(node, model, cancel); @@ -79,12 +91,8 @@ public static bool IsPartOfBinaryNegationOrCondition(this SyntaxNode node) } var current = topNode; - while (current.Parent != null && current.Parent?.Kind() is not (SyntaxKind.BitwiseNotExpression - or SyntaxKind.IfStatement - or SyntaxKind.WhileStatement - or SyntaxKind.ConditionalExpression - or SyntaxKind.MethodDeclaration - or SyntaxKind.SimpleLambdaExpression)) + while (current.Parent != null + && !NegationOrConditionEnclosingSyntaxKinds.Contains(current.Parent.Kind())) { current = current.Parent; } diff --git a/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs index 7f8a6c62773..27f024af618 100644 --- a/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs +++ b/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs @@ -1205,6 +1205,7 @@ public class Derived(int i) {{baseType}} { } [DataRow("""int Method() { Func add = delegate (int a, int b) { return $$a + b$$; }; return add(1, 2); }""", SyntaxKind.AnonymousMethodExpression)] [DataRow("""Derived(int arg) : base($$arg$$) { }""", SyntaxKind.BaseConstructorInitializer)] [DataRow("""Derived() { $$var x = 42;$$ }""", SyntaxKind.ConstructorDeclaration)] + [DataRow("""public static implicit operator int(Derived d) => $$42$$;""", SyntaxKind.ConversionOperatorDeclaration)] [DataRow("""~Derived() { $$var x = 42;$$ }""", SyntaxKind.DestructorDeclaration)] [DataRow("""int field = $$int.Parse("42")$$;""", SyntaxKind.EqualsValueClause)] [DataRow("""int Property { get; set; } = $$int.Parse("42")$$;""", SyntaxKind.EqualsValueClause)] @@ -1214,6 +1215,7 @@ public class Derived(int i) {{baseType}} { } [DataRow("""int Method() { return LocalFunction(); int LocalFunction() => $$42$$; }""", SyntaxKindEx.LocalFunctionStatement)] [DataRow("""int Method() { $$return 42;$$ }""", SyntaxKind.MethodDeclaration)] [DataRow("""int Method() => $$42$$;""", SyntaxKind.MethodDeclaration)] + [DataRow("""public static Derived operator +(Derived d) => $$d$$;""", SyntaxKind.OperatorDeclaration)] [DataRow("""int Method() { var lambda = () => $$42$$; return lambda(); }""", SyntaxKind.ParenthesizedLambdaExpression)] [DataRow("""int Method() { Func lambda = x => $$x + 1$$; return lambda(42); }""", SyntaxKind.SimpleLambdaExpression)] [DataRow("""event EventHandler SomeEvent { add { int x = 42; } remove { $$int x = 42;$$ } }""", SyntaxKind.RemoveAccessorDeclaration)]