diff --git a/CHANGELOG.md b/CHANGELOG.md
index e719a95..b3e8418 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,9 @@
# CHANGELOG
https://keepachangelog.com/en/1.0.0/
+## [1.31.1] - 2026-01-25
+- `MultipleFromBodyParameters`: Support Minimal Web APIs (lambda expressions)
+
## [1.31.0] - 2026-01-24
- `LoggerMessageAttribute`: This analyzer suggests converting regular logging calls to the source-generated logging pattern.
diff --git a/README.md b/README.md
index 0e4bb8d..1af9e5d 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ or add a reference yourself:
```xml
-
+
```
diff --git a/SharpSource/SharpSource.Package/SharpSource.Package.csproj b/SharpSource/SharpSource.Package/SharpSource.Package.csproj
index 704ebcd..0412d15 100644
--- a/SharpSource/SharpSource.Package/SharpSource.Package.csproj
+++ b/SharpSource/SharpSource.Package/SharpSource.Package.csproj
@@ -9,7 +9,7 @@
SharpSource
- 1.31.0
+ 1.31.1
Jeroen Vannevel
https://github.com/Vannevelj/SharpSource/blob/master/LICENSE.md
https://github.com/Vannevelj/SharpSource
diff --git a/SharpSource/SharpSource.Test/ComparingStringsWithoutStringComparisonTests.cs b/SharpSource/SharpSource.Test/ComparingStringsWithoutStringComparisonTests.cs
index 13a87c0..8ead44f 100644
--- a/SharpSource/SharpSource.Test/ComparingStringsWithoutStringComparisonTests.cs
+++ b/SharpSource/SharpSource.Test/ComparingStringsWithoutStringComparisonTests.cs
@@ -482,7 +482,6 @@ public async Task ComparingStringsWithoutStringComparison_PassedAsArgument()
}
[BugVerificationTest(IssueUrl = "https://github.com/Vannevelj/SharpSource/issues/56")]
- [Ignore("Code fix introduces a \r\n newline which fails the equality check because the test expects \n. Presumably fixed when https://github.com/Vannevelj/SharpSource/issues/274 is done")]
public async Task ComparingStringsWithoutStringComparison_WithOtherUsingStatements()
{
var original = @"
@@ -493,8 +492,8 @@ public async Task ComparingStringsWithoutStringComparison_WithOtherUsingStatemen
bool result = {|#0:s1.ToLower()|} == s2.ToLower();";
var result = @$"
-using System.Text;
using System;
+using System.Text;
string s1 = string.Empty;
string s2 = string.Empty;
diff --git a/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs b/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs
index 40bfc6e..081807c 100644
--- a/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs
+++ b/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs
@@ -18,6 +18,12 @@ public static partial class CSharpCodeFixVerifier
public static DiagnosticResult Diagnostic(int location = 0)
=> CSharpCodeFixVerifier.Diagnostic().WithLocation(location);
+ ///
+ /// Gets a without a predefined location.
+ ///
+ public static DiagnosticResult DiagnosticWithoutLocation()
+ => CSharpCodeFixVerifier.Diagnostic();
+
///
public static DiagnosticResult Diagnostic(string diagnosticId)
=> CSharpCodeFixVerifier.Diagnostic(diagnosticId);
@@ -62,14 +68,23 @@ public static async Task VerifyCodeFix(string source, DiagnosticResult expected,
=> await VerifyCodeFix(source, [expected], fixedSource, codeActionIndex, additionalFiles: null, batchFixedSource: null, disabledDiagnostics);
///
- public static async Task VerifyCodeFix(string source, DiagnosticResult[] expected, string fixedSource, int codeActionIndex = 0, string[]? additionalFiles = null, string? batchFixedSource = null, string[]? disabledDiagnostics = null)
+ public static async Task VerifyCodeFix(
+ string source,
+ DiagnosticResult[] expected,
+ string fixedSource,
+ int codeActionIndex = 0,
+ string[]? additionalFiles = null,
+ string? batchFixedSource = null,
+ string[]? disabledDiagnostics = null,
+ int? numberOfIncrementalIterations = null)
{
var test = new Test
{
TestCode = source,
FixedCode = fixedSource,
BatchFixedCode = batchFixedSource!,
- CodeActionIndex = codeActionIndex
+ CodeActionIndex = codeActionIndex,
+ NumberOfIncrementalIterations = numberOfIncrementalIterations
};
if (disabledDiagnostics != null)
diff --git a/SharpSource/SharpSource.Test/MultipleFromBodyParametersTests.cs b/SharpSource/SharpSource.Test/MultipleFromBodyParametersTests.cs
index ec58208..f93c2a6 100644
--- a/SharpSource/SharpSource.Test/MultipleFromBodyParametersTests.cs
+++ b/SharpSource/SharpSource.Test/MultipleFromBodyParametersTests.cs
@@ -25,7 +25,6 @@ class MyController
}
[TestMethod]
- [Ignore("Minimal Web API is not supported yet. See https://github.com/Vannevelj/SharpSource/issues/140")]
[DataRow("[FromBody]")]
[DataRow("[FromBodyAttribute]")]
[DataRow("[Microsoft.AspNetCore.Mvc.FromBody]")]
@@ -35,14 +34,14 @@ public async Task MultipleFromBodyParameters_MinimalWebApiAsync(string attribute
using Microsoft.AspNetCore.Mvc;
var app = new WebApplication();
-app.MapGet(""/"", ({attribute} string first, {attribute} string second, Service service) => {{ }});
+app.MapGet(""/"", {{|#0:({attribute} string first, {attribute} string second, Service service) => {{ }}|}});
class WebApplication {{
public void MapGet(string path, System.Action handler) {{ }}
}}
class Service {{ }}";
- await VerifyCS.VerifyDiagnosticWithoutFix(original, VerifyCS.Diagnostic().WithMessage("Method DoThing specifies multiple [FromBody] parameters but only one is allowed. Specify a wrapper type or use [FromForm], [FromRoute], [FromHeader] and [FromQuery] instead."));
+ await VerifyCS.VerifyDiagnosticWithoutFix(original, VerifyCS.Diagnostic().WithMessage("Method lambda expression specifies multiple [FromBody] parameters but only one is allowed. Specify a wrapper type or use [FromForm], [FromRoute], [FromHeader] and [FromQuery] instead."));
}
[TestMethod]
diff --git a/SharpSource/SharpSource.Test/UnnecessaryEnumerableMaterializationTests.cs b/SharpSource/SharpSource.Test/UnnecessaryEnumerableMaterializationTests.cs
index 7e0cf09..0ce6f58 100644
--- a/SharpSource/SharpSource.Test/UnnecessaryEnumerableMaterializationTests.cs
+++ b/SharpSource/SharpSource.Test/UnnecessaryEnumerableMaterializationTests.cs
@@ -213,7 +213,6 @@ public async Task UnnecessaryEnumerableMaterialization_ConditionalAccess()
}
[TestMethod]
- [Ignore("Need to find a way to handle the testing of a code fix when there are multiple issues, see https://github.com/Vannevelj/SharpSource/issues/288")]
public async Task UnnecessaryEnumerableMaterialization_ConditionalAccess_Chained()
{
var original = @"
@@ -221,19 +220,39 @@ public async Task UnnecessaryEnumerableMaterialization_ConditionalAccess_Chained
using System.Collections.Generic;
IEnumerable values = new [] { ""test"" };
-values?.ToArray().ToList().AsEnumerable();";
+values?{|#0:.ToArray().ToList()|}.AsEnumerable();";
- var expected = $@"
+ var expected = @"
using System.Linq;
using System.Collections.Generic;
-IEnumerable values = new [] {{ ""test"" }};
+IEnumerable values = new [] { ""test"" };
values?.ToList().AsEnumerable();";
- await VerifyCS.VerifyCodeFix(original, new[] {
- VerifyCS.Diagnostic().WithNoLocation().WithMessage("ToArray is unnecessarily materializing the IEnumerable and can be omitted").WithSpan(6, 8, 6, 27),
- VerifyCS.Diagnostic().WithNoLocation().WithMessage("ToList is unnecessarily materializing the IEnumerable and can be omitted").WithSpan(6, 8, 6, 42)
- }, expected);
+ await VerifyCS.VerifyCodeFix(original, VerifyCS.Diagnostic().WithMessage("ToArray is unnecessarily materializing the IEnumerable and can be omitted"), expected);
+ }
+
+ [TestMethod]
+ public async Task UnnecessaryEnumerableMaterialization_MultipleMaterializations_IncrementalFixes()
+ {
+ var original = @"
+using System.Linq;
+using System.Collections.Generic;
+
+IEnumerable values = new [] { ""test"" };
+values.ToArray().Where(x => true).ToList().Where(x => true);";
+
+ var expected = @"
+using System.Linq;
+using System.Collections.Generic;
+
+IEnumerable values = new [] { ""test"" };
+values.Where(x => true).Where(x => true);";
+
+ await VerifyCS.VerifyCodeFix(original, [
+ VerifyCS.DiagnosticWithoutLocation().WithSpan(6, 1, 6, 34).WithMessage("ToArray is unnecessarily materializing the IEnumerable and can be omitted"),
+ VerifyCS.DiagnosticWithoutLocation().WithSpan(6, 1, 6, 60).WithMessage("ToList is unnecessarily materializing the IEnumerable and can be omitted")
+ ], expected, numberOfIncrementalIterations: 2);
}
[TestMethod]
diff --git a/SharpSource/SharpSource/Diagnostics/MultipleFromBodyParametersAnalyzer.cs b/SharpSource/SharpSource/Diagnostics/MultipleFromBodyParametersAnalyzer.cs
index 40b2adf..ba99b9c 100644
--- a/SharpSource/SharpSource/Diagnostics/MultipleFromBodyParametersAnalyzer.cs
+++ b/SharpSource/SharpSource/Diagnostics/MultipleFromBodyParametersAnalyzer.cs
@@ -1,6 +1,8 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using SharpSource.Utilities;
@@ -30,12 +32,13 @@ public override void Initialize(AnalysisContext context)
var fromBodySymbol = compilationContext.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Mvc.FromBodyAttribute");
if (fromBodySymbol is not null)
{
- compilationContext.RegisterSymbolAction(context => Analyze(context, fromBodySymbol), SymbolKind.Method);
+ compilationContext.RegisterSymbolAction(context => AnalyzeMethod(context, fromBodySymbol), SymbolKind.Method);
+ compilationContext.RegisterSyntaxNodeAction(context => AnalyzeLambda(context, fromBodySymbol), SyntaxKind.ParenthesizedLambdaExpression);
}
});
}
- private static void Analyze(SymbolAnalysisContext context, INamedTypeSymbol fromBodySymbol)
+ private static void AnalyzeMethod(SymbolAnalysisContext context, INamedTypeSymbol fromBodySymbol)
{
var methodSymbol = (IMethodSymbol)context.Symbol;
var attributesOnParameters = methodSymbol.Parameters
@@ -47,4 +50,30 @@ private static void Analyze(SymbolAnalysisContext context, INamedTypeSymbol from
context.ReportDiagnostic(Diagnostic.Create(Rule, methodSymbol.Locations[0], methodSymbol.Name));
}
}
+
+ private static void AnalyzeLambda(SyntaxNodeAnalysisContext context, INamedTypeSymbol fromBodySymbol)
+ {
+ var lambda = (ParenthesizedLambdaExpressionSyntax)context.Node;
+ var fromBodyCount = 0;
+
+ foreach (var parameter in lambda.ParameterList.Parameters)
+ {
+ foreach (var attributeList in parameter.AttributeLists)
+ {
+ foreach (var attribute in attributeList.Attributes)
+ {
+ var attributeSymbol = context.SemanticModel.GetTypeInfo(attribute).Type;
+ if (fromBodySymbol.Equals(attributeSymbol, SymbolEqualityComparer.Default))
+ {
+ fromBodyCount++;
+ if (fromBodyCount > 1)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(Rule, lambda.GetLocation(), "lambda expression"));
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
}
\ No newline at end of file