diff --git a/analyzers/rspec/cs/S6802.html b/analyzers/rspec/cs/S6802.html index 35fb99e84c2..698b7718b85 100644 --- a/analyzers/rspec/cs/S6802.html +++ b/analyzers/rspec/cs/S6802.html @@ -5,8 +5,13 @@
The reason behind this is that Blazor rebuilds all lambda expressions within the loop every time the UI elements are rendered.
Ensure to not use a delegate in elements rendered in loops by using a collection of objects containing the delegate as an Action.
+Ensure to not use a delegate in elements rendered in loops, you can try:
+@@ -15,7 +20,7 @@+Noncompliant code example
var buttonNumber = i; <button @onclick="@(e => DoAction(e, buttonNumber))"> @* Noncompliant *@ - Button #buttonNumber + Button #@buttonNumber </button> } @@ -62,6 +67,67 @@Compliant solution
} }
+@* Component.razor *@ + +@for (var i = 1; i < 100; i++) +{ + var buttonNumber = i; + + <button @onclick="@(e => DoAction(e, buttonNumber))"> @* Noncompliant *@ + Button #@buttonNumber + </button> +} + +@code { + private void DoAction(MouseEventArgs e, int button) + { + // Do something here + } +} ++
+@* MyButton.razor *@ + +<button @onclick="OnClickCallback"> + @ChildContent +</button> + +@code { + [Parameter] + public int Id { get; set; } + + [Parameter] + public EventCallback<int> OnClick { get; set; } + + [Parameter] + public RenderFragment ChildContent { get; set; } + + private void OnClickCallback() + { + OnClick.InvokeAsync(Id); + } +} + +@* Component.razor *@ + +@for (var i = 1; i < 100; i++) +{ + var buttonNumber = i; + <MyButton Id="buttonNumber" OnClick="DoAction"> + Button #@buttonNumber + </MyButton> +} + +@code { + private void DoAction(int button) + { + // Do something here + } +} +
The results were generated with the help of BenchmarkDotNet and
{
var node = (LambdaExpressionSyntax)c.Node;
- if (IsWithinLoop(node)
+ if (IsWithinLoopBody(node)
&& IsWithinRenderTreeBuilderInvocation(node, c.SemanticModel))
{
c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation()));
@@ -61,6 +61,6 @@ private static bool IsWithinRenderTreeBuilderInvocation(SyntaxNode node, Semanti
&& semanticModel.GetSymbolInfo(invocation.Expression).Symbol is IMethodSymbol symbol
&& symbol.ContainingType.GetSymbolType().Is(KnownType.Microsoft_AspNetCore_Components_Rendering_RenderTreeBuilder));
- private static bool IsWithinLoop(SyntaxNode node) =>
- node.AncestorsAndSelf().Any(x => x is ForStatementSyntax or ForEachStatementSyntax or WhileStatementSyntax or DoStatementSyntax);
+ private static bool IsWithinLoopBody(SyntaxNode node) =>
+ node.AncestorsAndSelf().Any(x => x is BlockSyntax && x.Parent is ForStatementSyntax or ForEachStatementSyntax or WhileStatementSyntax or DoStatementSyntax);
}
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/AvoidLambdaExpressionInLoopsInBlazorTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/AvoidLambdaExpressionInLoopsInBlazorTest.cs
index 5b148f2fcb4..ee26e6e855a 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/AvoidLambdaExpressionInLoopsInBlazorTest.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/AvoidLambdaExpressionInLoopsInBlazorTest.cs
@@ -37,6 +37,12 @@ public void AvoidLambdaExpressionInLoopsInBlazor_Blazor() =>
.WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product))
.Verify();
+ [TestMethod]
+ public void AvoidLambdaExpressionInLoopsInBlazor_BlazorLoopsWithNoBody() =>
+ builder.AddPaths("AvoidLambdaExpressionInLoopsInBlazor.LoopsWithNoBody.razor")
+ .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product))
+ .Verify();
+
[TestMethod]
public void AvoidLambdaExpressionInLoopsInBlazor_UsingRenderFragment() =>
builder.AddPaths("AvoidLambdaExpressionInLoopsInBlazor.RenderFragment.razor", "AvoidLambdaExpressionInLoopsInBlazor.RenderFragmentConsumer.razor")
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.LoopsWithNoBody.razor b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.LoopsWithNoBody.razor
new file mode 100644
index 00000000000..bf51604d39f
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.LoopsWithNoBody.razor
@@ -0,0 +1,102 @@
+@* https://github.com/SonarSource/sonar-dotnet/issues/8394 *@
+
+@foreach (var item in Buttons.Where(x => x.Id == "idToFind"))
+ if (item.Id == "idToFind")
+ {
+ @* FN *@
+ }
+
+@for (int i = 0; i < Buttons.Count; i++)
+ @if (i % 2 == 0)
+ {
+ var buttonNumber = i;
+
+ }
+
+@{
+ var j = 0;
+ while (j < 5)
+ if (j % 2 == 0)
+ {
+ j += 2;
+
+ j += 2;
+ }
+
+ do
+ if (j % 2 == 0)
+ {
+
+ j += 2;
+ }
+ while (j < 10);
+}
+
+@foreach (var item in Buttons.Where(x => x.Id == "idToFind"))
+ @if (item.Id == "idToFind")
+ {
+ @* FN *@
+ }
+ else if (item.Id == "idToFind")
+ {
+ @* FN *@
+ }
+ else
+ {
+ @* FN *@
+ }
+
+@foreach (var item in Buttons.Where(x => x.Id == "idToFind"))
+ @if (true)
+ @if (item.Id == "idToFind")
+ {
+ @* FN *@
+ }
+
+@foreach (var item in Buttons.Where(x => x.Id == "idToFind"))
+ @switch(item.Id)
+ {
+ case "idToFind":
+ @* FN *@
+ break;
+ default:
+ {
+ @* FN *@
+ break;
+ }
+ }
+
+@foreach (var button in Buttons)
+{
+ {
+
+ }
+}
+
+@code {
+ private List
}
+
+ @foreach (var button in Buttons.Where(x => x.Id == "SomeId")) @* Compliant *@
+ {
+
+ }
@code {
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.cs
index 9e8d7c9b04d..c5280552bdc 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.cs
@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Web;
using System;
+using System.Linq;
using System.Collections.Generic;
class LambdaInLoopInMethod
@@ -77,5 +78,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
builder.AddMarkupContent(14, "\r\n Button");
builder.CloseElement();
}
+
+ foreach (var button in Buttons.OrderByDescending(x => x.Id)) { } // Compliant, the lambda is executed outside of the loop
}
}
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.razor b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.razor
index 63d79383d6c..dc9a54898d9 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.razor
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/AvoidLambdaExpressionInLoopsInBlazor.razor
@@ -46,6 +46,11 @@
@* Compliant *@
}
+@foreach (var button in Buttons.OrderByDescending(x => x.Id)) @* Compliant, the lambda is executed outside of the loop *@
+{
+ @button.Id