diff --git a/analyzers/rspec/cs/S6962.html b/analyzers/rspec/cs/S6962.html
new file mode 100644
index 00000000000..815180cfb5a
--- /dev/null
+++ b/analyzers/rspec/cs/S6962.html
@@ -0,0 +1,94 @@
+
In frequently used code paths, such as controller actions, you should avoid using the HttpClient directly and opt for one of the IHttpClientFactory-based mechanisms instead. This
+way, you avoid wasting resources and creating performance overhead.
+Why is this an issue?
+If a code path that creates and disposes of HttpClient objects is frequently used, then the following issues can occur:
+
+ - Under heavy load, there’s the risk of running out of available
+ sockets, leading to SocketException errors. This
+ is because each HttpClient instance uses a separate network connection, and there’s a limit to the number of connections that can be opened
+ simultaneously. Note that even after you dispose of an HttpClient its sockets are not immediately freed up.
+ - Each HttpClient has its own set of resources (like headers, base address, timeout, etc.) that must be managed. Creating a new HttpClient for
+ every request means these resources are not being reused, leading to resource waste.
+ - You introduce a significant performance overhead when creating a new HttpClient for every HTTP request.
+
+How to fix it
+The IHttpClientFactory was introduced in
+ASP.NET Core 2.1 to solve these problems. It handles pooling HTTP connections to optimize performance and reliability.
+There are several ways that you can use
+IHttpClientFactory in your application:
+
+Alternatively, you may cache the HttpClient in a singleton or a static field. You should be aware that by default, the HttpClient doesn’t respect
+the DNS’s Time To Live (TTL) settings. If the IP address associated with a domain name changes, HttpClient might still use the old, cached IP address,
+leading to failed requests.
+Code examples
+Noncompliant code example
+
+[ApiController]
+[Route("controller")]
+public class FooController : Controller
+{
+ [HttpGet]
+ public async Task<string> Foo()
+ {
+ using var client = new HttpClient(); //Noncompliant
+ return await client.GetStringAsync(_url);
+ }
+}
+
+Compliant solution
+
+// File: Startup.cs
+public class Startup
+{
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddHttpClient();
+ // ...
+ }
+}
+
+[ApiController]
+[Route("controller")]
+public class FooController : Controller
+{
+ private readonly IHttpClientFactory _clientFactory;
+
+ public FooController(IHttpClientFactory clientFactory)
+ {
+ _clientFactory = clientFactory;
+ }
+
+ [HttpGet]
+ public async Task<string> Foo()
+ {
+ using var client = _clientFactory.CreateClient(); // Compliant (Basic usage)
+ return await client.GetStringAsync(_url);
+ }
+}
+
+Resources
+Documentation
+
+
diff --git a/analyzers/rspec/cs/S6962.json b/analyzers/rspec/cs/S6962.json
new file mode 100644
index 00000000000..7f75bbd5f10
--- /dev/null
+++ b/analyzers/rspec/cs/S6962.json
@@ -0,0 +1,21 @@
+{
+ "title": "You should pool HTTP connections with HttpClientFactory",
+ "type": "CODE_SMELL",
+ "status": "ready",
+ "remediation": {
+ "func": "Constant\/Issue",
+ "constantCost": "1h"
+ },
+ "tags": [],
+ "defaultSeverity": "Major",
+ "ruleSpecification": "RSPEC-6962",
+ "sqKey": "S6962",
+ "scope": "Main",
+ "quickfix": "infeasible",
+ "code": {
+ "impacts": {
+ "RELIABILITY": "MEDIUM"
+ },
+ "attribute": "COMPLETE"
+ }
+}
diff --git a/analyzers/rspec/cs/Sonar_way_profile.json b/analyzers/rspec/cs/Sonar_way_profile.json
index e408158e3fe..c21443d08e4 100644
--- a/analyzers/rspec/cs/Sonar_way_profile.json
+++ b/analyzers/rspec/cs/Sonar_way_profile.json
@@ -317,6 +317,7 @@
"S6931",
"S6934",
"S6961",
+ "S6962",
"S6965"
]
}
diff --git a/analyzers/src/SonarAnalyzer.CSharp/ReuseClientBase.cs b/analyzers/src/SonarAnalyzer.CSharp/ReuseClientBase.cs
new file mode 100644
index 00000000000..b420f615ba3
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.CSharp/ReuseClientBase.cs
@@ -0,0 +1,56 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace SonarAnalyzer.Rules.CSharp
+{
+ public abstract class ReuseClientBase : SonarDiagnosticAnalyzer
+ {
+ protected abstract ImmutableArray ReusableClients { get; }
+
+ protected static bool IsAssignedForReuse(SonarSyntaxNodeReportingContext context) =>
+ !IsInVariableDeclaration(context.Node)
+ && (IsInConditionalCode(context.Node) || IsInFieldOrPropertyInitializer(context.Node) || IsAssignedToStaticFieldOrProperty(context));
+
+ protected bool IsReusableClient(SonarSyntaxNodeReportingContext context)
+ {
+ var objectCreation = ObjectCreationFactory.Create(context.Node);
+ return ReusableClients.Any(x => objectCreation.IsKnownType(x, context.SemanticModel));
+ }
+
+ private static bool IsInVariableDeclaration(SyntaxNode node) =>
+ node.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax or UsingStatementSyntax } } };
+
+ private static bool IsInFieldOrPropertyInitializer(SyntaxNode node) =>
+ node.Ancestors().Any(x => x.IsAnyKind(SyntaxKind.FieldDeclaration, SyntaxKind.PropertyDeclaration))
+ && !node.Ancestors().Any(x => x.IsAnyKind(SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration))
+ && !node.Parent.IsKind(SyntaxKind.ArrowExpressionClause);
+
+ private static bool IsInConditionalCode(SyntaxNode node) =>
+ node.Ancestors().Any(x => x.IsAnyKind(SyntaxKind.IfStatement,
+ SyntaxKind.SwitchStatement,
+ SyntaxKindEx.SwitchExpression,
+ SyntaxKind.ConditionalExpression,
+ SyntaxKindEx.CoalesceAssignmentExpression));
+
+ private static bool IsAssignedToStaticFieldOrProperty(SonarSyntaxNodeReportingContext context) =>
+ context.Node.Parent.WalkUpParentheses() is AssignmentExpressionSyntax assignment
+ && context.SemanticModel.GetSymbolInfo(assignment.Left, context.Cancel).Symbol is { IsStatic: true, Kind: SymbolKind.Field or SymbolKind.Property };
+ }
+}
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/ControllersReuseClient.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/ControllersReuseClient.cs
new file mode 100644
index 00000000000..7553e397394
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/ControllersReuseClient.cs
@@ -0,0 +1,71 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace SonarAnalyzer.Rules.CSharp;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class ControllersReuseClient : ReuseClientBase
+{
+ private const string DiagnosticId = "S6962";
+ private const string MessageFormat = "Reuse HttpClient instances rather than create new ones with each controller action invocation.";
+
+ private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat);
+
+ public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule);
+
+ protected override ImmutableArray ReusableClients => ImmutableArray.Create(KnownType.System_Net_Http_HttpClient);
+
+ protected override void Initialize(SonarAnalysisContext context) =>
+ context.RegisterCompilationStartAction(compilationStartContext =>
+ {
+ if (!compilationStartContext.Compilation.ReferencesControllers())
+ {
+ return;
+ }
+ compilationStartContext.RegisterSymbolStartAction(symbolStartContext =>
+ {
+ var symbol = (INamedTypeSymbol)symbolStartContext.Symbol;
+ if (symbol.IsControllerType())
+ {
+ symbolStartContext.RegisterSyntaxNodeAction(nodeContext =>
+ {
+ var node = nodeContext.Node;
+ if (IsInPublicMethod(node)
+ && IsReusableClient(nodeContext)
+ && !IsInsideConstructor(node)
+ && !IsAssignedForReuse(nodeContext))
+ {
+ nodeContext.ReportIssue(Rule, node);
+ }
+ },
+ SyntaxKind.ObjectCreationExpression,
+ SyntaxKindEx.ImplicitObjectCreationExpression);
+ }
+ }, SymbolKind.NamedType);
+ });
+
+ public static bool IsInPublicMethod(SyntaxNode node) =>
+ node.FirstAncestorOrSelf() is not { } method
+ || (method.FirstAncestorOrSelf() is null
+ && method.Modifiers.Any(x => x.IsKind(SyntaxKind.PublicKeyword)));
+
+ private static bool IsInsideConstructor(SyntaxNode node) =>
+ node.Ancestors().Any(x => x.IsAnyKind(SyntaxKind.ConstructorDeclaration, SyntaxKindEx.PrimaryConstructorBaseType));
+}
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsReuseClients.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsReuseClients.cs
index c2f878c256e..768f037a433 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsReuseClients.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsReuseClients.cs
@@ -21,14 +21,16 @@
namespace SonarAnalyzer.Rules.CSharp
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
- public sealed class AzureFunctionsReuseClients : SonarDiagnosticAnalyzer
+ public sealed class AzureFunctionsReuseClients : ReuseClientBase
{
private const string DiagnosticId = "S6420";
private const string MessageFormat = "Reuse client instances rather than creating new ones with each function invocation.";
private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat);
- private static readonly KnownType[] ReusableClients =
- {
+
+ public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule);
+
+ protected override ImmutableArray ReusableClients => ImmutableArray.Create(
KnownType.System_Net_Http_HttpClient,
// ComosDb (DocumentClient is superseded by CosmosClient)
KnownType.Microsoft_Azure_Documents_Client_DocumentClient,
@@ -48,41 +50,18 @@ public sealed class AzureFunctionsReuseClients : SonarDiagnosticAnalyzer
KnownType.Azure_Storage_Files_Shares_ShareServiceClient,
KnownType.Azure_Storage_Files_DataLake_DataLakeServiceClient,
// Resource manager
- KnownType.Azure_ResourceManager_ArmClient
- };
-
- public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule);
+ KnownType.Azure_ResourceManager_ArmClient);
protected override void Initialize(SonarAnalysisContext context) =>
context.RegisterNodeAction(c =>
{
if (c.AzureFunctionMethod() is not null
- && IsResuableClient(c)
+ && IsReusableClient(c)
&& !IsAssignedForReuse(c))
{
c.ReportIssue(Rule, c.Node);
}
},
SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression);
-
- private static bool IsAssignedForReuse(SonarSyntaxNodeReportingContext context) =>
- !IsInVariableDeclaration(context.Node)
- && (IsInFieldOrPropertyInitializer(context.Node) || IsAssignedToStaticFieldOrProperty(context));
-
- private static bool IsInVariableDeclaration(SyntaxNode node) =>
- node.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax or UsingStatementSyntax } } };
-
- private static bool IsInFieldOrPropertyInitializer(SyntaxNode node) =>
- node.Ancestors().Any(x => x.IsAnyKind(SyntaxKind.FieldDeclaration, SyntaxKind.PropertyDeclaration));
-
- private static bool IsAssignedToStaticFieldOrProperty(SonarSyntaxNodeReportingContext context) =>
- context.Node.Parent.WalkUpParentheses() is AssignmentExpressionSyntax assignment
- && context.SemanticModel.GetSymbolInfo(assignment.Left, context.Cancel).Symbol is { IsStatic: true, Kind: SymbolKind.Field or SymbolKind.Property };
-
- private static bool IsResuableClient(SonarSyntaxNodeReportingContext context)
- {
- var objectCreation = ObjectCreationFactory.Create(context.Node);
- return ReusableClients.Any(x => objectCreation.IsKnownType(x, context.SemanticModel));
- }
}
}
diff --git a/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs b/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs
index 143e10c8e5f..688addf11ca 100644
--- a/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs
+++ b/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs
@@ -6886,7 +6886,7 @@ internal static class RuleTypeMappingCS
// ["S6959"],
// ["S6960"],
["S6961"] = "CODE_SMELL",
- // ["S6962"],
+ ["S6962"] = "CODE_SMELL",
// ["S6963"],
// ["S6964"],
["S6965"] = "CODE_SMELL",
diff --git a/analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/ControllersReuseClientTest.cs b/analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/ControllersReuseClientTest.cs
new file mode 100644
index 00000000000..3aff4a12c57
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/ControllersReuseClientTest.cs
@@ -0,0 +1,68 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using SonarAnalyzer.Rules.CSharp;
+
+namespace SonarAnalyzer.Test.Rules;
+
+#if NET
+
+[TestClass]
+public class ControllersReuseClientTest
+{
+ private readonly VerifierBuilder builder = new VerifierBuilder()
+ .WithBasePath("AspNet")
+ .AddReferences(
+ [
+ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions,
+ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore,
+ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures,
+ .. MetadataReferenceFacade.SystemThreadingTasks,
+ .. NuGetMetadataReference.SystemNetHttp(),
+ .. NuGetMetadataReference.MicrosoftExtensionsHttp()
+ ]);
+
+ [TestMethod]
+ public void ControllersReuseClient_CS() =>
+ builder
+ .AddPaths("ControllersReuseClient.cs")
+ .Verify();
+
+ [TestMethod]
+ public void ControllersReuseClient_CS8() =>
+ builder
+ .AddPaths("ControllersReuseClient.CSharp8.cs")
+ .WithOptions(ParseOptionsHelper.FromCSharp8)
+ .Verify();
+
+ [TestMethod]
+ public void ControllersReuseClient_CS9() =>
+ builder
+ .AddPaths("ControllersReuseClient.CSharp9.cs")
+ .WithOptions(ParseOptionsHelper.FromCSharp9)
+ .Verify();
+
+ [TestMethod]
+ public void ControllersReuseClient_CS12() =>
+ builder
+ .AddPaths("ControllerReuseClient.CSharp12.cs")
+ .WithOptions(ParseOptionsHelper.FromCSharp12).Verify();
+}
+#endif
diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllerReuseClient.CSharp12.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllerReuseClient.CSharp12.cs
new file mode 100644
index 00000000000..4ca8def6418
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllerReuseClient.CSharp12.cs
@@ -0,0 +1,23 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+[ApiController]
+[Route("SomeRoute")]
+public class C(HttpClient client) : ControllerBase;
+
+[ApiController]
+[Route("SomeRoute")]
+public class D(HttpClient client) : C(new HttpClient()) // Compliant
+{
+ public D() : this(new HttpClient()) { } // Compliant
+}
+
+[ApiController]
+[Route("SomeRoute")]
+public class E : C
+{
+ public E() : base(new HttpClient()) { } // Compliant
+}
diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.CSharp9.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.CSharp9.cs
new file mode 100644
index 00000000000..f8df9b09ec5
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.CSharp9.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+[ApiController]
+[Route("Hello")]
+public class SomeController : ControllerBase
+{
+ [HttpGet("foo")]
+ public async Task Foo()
+ {
+ HttpClient client = new(); // Noncompliant
+ return "bar";
+ }
+}
diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.Csharp8.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.Csharp8.cs
new file mode 100644
index 00000000000..c8a21cea663
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.Csharp8.cs
@@ -0,0 +1,25 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+[ApiController]
+public class SomeController : ControllerBase
+{
+ private readonly IHttpClientFactory _clientFactory;
+ private HttpClient clientField = new HttpClient();
+
+ [HttpGet("foo")]
+ public async Task Foo()
+ {
+ using var clientA = new HttpClient(); // Noncompliant
+ // ^^^^^^^^^^^^^^^^
+ await clientA.GetStringAsync("");
+
+ clientField ??= new HttpClient(); // Compliant
+ using var pooledClient = _clientFactory.CreateClient(); // Compliant
+ _ = true switch { true => new HttpClient() }; // Compliant
+ return "bar";
+ }
+}
diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.cs
new file mode 100644
index 00000000000..a2e0369887a
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.cs
@@ -0,0 +1,74 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+[ApiController]
+[Route("Hello")]
+public class SomeController : ControllerBase
+{
+ private readonly IHttpClientFactory _clientFactory;
+ private HttpClient clientField = new HttpClient(); // Compliant, it can be reused between actions
+ private HttpClient ClientProperty { get; set; } = new HttpClient(); // Compliant, it can be reused between actions
+ private HttpClient ClientPropertyAccessorArrowClause { get => new HttpClient(); } // Noncompliant
+ private HttpClient ClientPropertyAccessorMethodBody { get { var anotherStatement = 1; return new HttpClient(); } } // Noncompliant
+ private HttpClient ClientPropertyAccessorArrow => new HttpClient(); // Noncompliant
+
+ public SomeController()
+ {
+ clientField = new HttpClient(); // Compliant
+ }
+
+ [HttpGet("foo")]
+ public async Task Foo()
+ {
+ using (var clientB = new HttpClient()) //Noncompliant {{Reuse HttpClient instances rather than create new ones with each controller action invocation.}}
+ // ^^^^^^^^^^^^^^^^
+ {
+ await clientB.GetStringAsync("");
+ }
+
+ var client = new HttpClient(); // Noncompliant
+ clientField = new HttpClient(); // Noncompliant
+ ClientProperty = new HttpClient(); // Noncompliant
+ var local = new HttpClient(); // Noncompliant
+ local = new System.Net.Http.HttpClient(); // Noncompliant
+ var fromStaticMethod = StaticCreateClient(); // FN - see https://github.com/SonarSource/rspec/pull/3847#discussion_r1559510167
+ var fromMethod = CreateClient(); // FN - see https://github.com/SonarSource/rspec/pull/3847#discussion_r1559510167
+
+ local = ClientPropertyAccessorArrow; // Compliant
+ local = ClientPropertyAccessorArrow; // Compliant
+
+ // Lambda
+ _ = new Lazy(() => new HttpClient()); // Noncompliant FP
+
+ // Conditional code
+ if (true)
+ _ = new HttpClient(); // Compliant
+ switch (true)
+ {
+ case true:
+ _ = new HttpClient(); // Compliant
+ break;
+ }
+ _ = true ? new HttpClient() : null; // Compliant
+
+ return "bar";
+ }
+
+ private static HttpClient StaticCreateClient()
+ {
+ return new HttpClient(); // Compliant, we raise only in actions
+ }
+
+ private HttpClient CreateClient()
+ {
+ return new HttpClient(); // Compliant, we raise only in actions
+ }
+}
+
+public class NotAController
+{
+ private HttpClient ClientPropertyAccessorArrow => new HttpClient(); // Compliant, it's not in a controller.
+}
diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.CSharp9.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.CSharp9.cs
index 92d67e8f011..9828c5e8062 100644
--- a/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.CSharp9.cs
+++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.CSharp9.cs
@@ -19,7 +19,7 @@ public static void DifferentAssigments()
field ??= new HttpClient(); // Compliant
var local = default(HttpClient);
- local ??= new HttpClient(); // Noncompliant
+ local ??= new HttpClient(); // FN, needs SE to be able to raise an issue.
_ = new HttpClient(); // Noncompliant
using var localUsingStatement = new HttpClient(); // Noncompliant
diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.cs
index bf022b2a752..ecf1688523f 100644
--- a/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.cs
+++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.cs
@@ -38,6 +38,9 @@ public class FunctionApp1
protected static HttpClient ClientProperty { get; set; } = new HttpClient(); // Compliant
protected static Lazy LazyClientProperty { get; set; } = new Lazy(() => new HttpClient()); // Compliant
+ protected HttpClient ClientPropertyAccessor { get => new HttpClient(); } // FN
+ protected HttpClient ClientPropertyAccessorArrow => new HttpClient(); // FN
+
static FunctionApp1()
{
ClientProperty = new HttpClient(); // Compliant
@@ -55,7 +58,7 @@ public static void Assignments()
someField = (new HttpClient() as object); // Noncompliant FP
client = PassThrough(new HttpClient()); // Noncompliant FP
client = client ?? new HttpClient(); // Noncompliant FP
- client = client == null ? new HttpClient() : client; // Noncompliant FP
+ client = client == null ? new HttpClient() : client; // Compliant
PassThrough(new HttpClient()); // Noncompliant
var local = new HttpClient(); // Noncompliant
local = new System.Net.Http.HttpClient(); // Noncompliant