From 89327d5d09ee65b4afcf467fb359876de257de2b Mon Sep 17 00:00:00 2001 From: Jeroen Vannevel Date: Sat, 24 Jan 2026 23:04:44 +0000 Subject: [PATCH 1/5] enable an ignored test ComparingStringsWithoutStringComparison_WithOtherUsingStatements --- .../ComparingStringsWithoutStringComparisonTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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; From c50916a3dffcc1c37dc54b0edada595574b5d4ec Mon Sep 17 00:00:00 2001 From: Jeroen Vannevel Date: Sun, 25 Jan 2026 00:06:06 +0000 Subject: [PATCH 2/5] Added test case UnnecessaryEnumerableMaterialization_MultipleMaterializations_IncrementalFixes --- .../SharpSource.Test/Helpers/CSCodeFix.cs | 31 +++++++++++----- ...necessaryEnumerableMaterializationTests.cs | 35 ++++++++++++++----- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs b/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs index 40bfc6e..c81ba59 100644 --- a/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs +++ b/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs @@ -14,13 +14,19 @@ public static partial class CSharpCodeFixVerifier where TAnalyzer : DiagnosticAnalyzer, new() where TCodeFix : CodeFixProvider, new() { - /// - public static DiagnosticResult Diagnostic(int location = 0) - => CSharpCodeFixVerifier.Diagnostic().WithLocation(location); +/// +public static DiagnosticResult Diagnostic(int location = 0) + => CSharpCodeFixVerifier.Diagnostic().WithLocation(location); - /// - public static DiagnosticResult Diagnostic(string diagnosticId) - => CSharpCodeFixVerifier.Diagnostic(diagnosticId); +/// +/// Gets a without a predefined location. +/// +public static DiagnosticResult DiagnosticWithoutLocation() + => CSharpCodeFixVerifier.Diagnostic(); + +/// +public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpCodeFixVerifier.Diagnostic(diagnosticId); /// public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor, int location = 0) @@ -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/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] From 46ba963ba404e92d3fde4af68cc1eeec6ea50e3a Mon Sep 17 00:00:00 2001 From: Jeroen Vannevel Date: Sun, 25 Jan 2026 00:45:21 +0000 Subject: [PATCH 3/5] `MultipleFromBodyParameters`: Support Minimal Web APIs (lambda expressions) --- CHANGELOG.md | 3 ++ .../SharpSource.Package.csproj | 2 +- .../MultipleFromBodyParametersTests.cs | 5 ++- .../MultipleFromBodyParametersAnalyzer.cs | 33 +++++++++++++++++-- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e719a95..e7206f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # CHANGELOG https://keepachangelog.com/en/1.0.0/ +## [1.31.1] - 2026-01-24 +- `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/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/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/Diagnostics/MultipleFromBodyParametersAnalyzer.cs b/SharpSource/SharpSource/Diagnostics/MultipleFromBodyParametersAnalyzer.cs index 40b2adf..169ab17 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 From 287c829cb8c0c23745816f0c3a305adb9792a180 Mon Sep 17 00:00:00 2001 From: Jeroen Vannevel Date: Sun, 25 Jan 2026 00:47:54 +0000 Subject: [PATCH 4/5] versions --- CHANGELOG.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7206f9..b3e8418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # CHANGELOG https://keepachangelog.com/en/1.0.0/ -## [1.31.1] - 2026-01-24 +## [1.31.1] - 2026-01-25 - `MultipleFromBodyParameters`: Support Minimal Web APIs (lambda expressions) ## [1.31.0] - 2026-01-24 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 - + ``` From 27defa3ee30535248823efb6d36d02f4a8ba6298 Mon Sep 17 00:00:00 2001 From: Jeroen Vannevel Date: Sun, 25 Jan 2026 00:48:19 +0000 Subject: [PATCH 5/5] format --- .../SharpSource.Test/Helpers/CSCodeFix.cs | 26 +++++++++---------- .../MultipleFromBodyParametersAnalyzer.cs | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs b/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs index c81ba59..081807c 100644 --- a/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs +++ b/SharpSource/SharpSource.Test/Helpers/CSCodeFix.cs @@ -14,19 +14,19 @@ public static partial class CSharpCodeFixVerifier where TAnalyzer : DiagnosticAnalyzer, new() where TCodeFix : CodeFixProvider, new() { -/// -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); + /// + 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); /// public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor, int location = 0) diff --git a/SharpSource/SharpSource/Diagnostics/MultipleFromBodyParametersAnalyzer.cs b/SharpSource/SharpSource/Diagnostics/MultipleFromBodyParametersAnalyzer.cs index 169ab17..ba99b9c 100644 --- a/SharpSource/SharpSource/Diagnostics/MultipleFromBodyParametersAnalyzer.cs +++ b/SharpSource/SharpSource/Diagnostics/MultipleFromBodyParametersAnalyzer.cs @@ -74,6 +74,6 @@ private static void AnalyzeLambda(SyntaxNodeAnalysisContext context, INamedTypeS } } } - } + } } } \ No newline at end of file