diff --git a/src/Meziantou.Analyzer.CodeFixers/Meziantou.Analyzer.CodeFixers.csproj b/src/Meziantou.Analyzer.CodeFixers/Meziantou.Analyzer.CodeFixers.csproj
index c94d010ed..d6ff52599 100644
--- a/src/Meziantou.Analyzer.CodeFixers/Meziantou.Analyzer.CodeFixers.csproj
+++ b/src/Meziantou.Analyzer.CodeFixers/Meziantou.Analyzer.CodeFixers.csproj
@@ -22,6 +22,9 @@
+
+
+
diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparisonFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparisonFixer.cs
index 1e8a3ef22..d4610be67 100644
--- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparisonFixer.cs
+++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparisonFixer.cs
@@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Operations;
namespace Meziantou.Analyzer.Rules;
@@ -59,7 +60,38 @@ private static async Task AddStringComparison(Document document, Synta
generator.TypeExpression(stringComparison, addImport: true),
stringComparisonMode));
- editor.ReplaceNode(invocationExpression, invocationExpression.AddArgumentListArguments(newArgument));
+ var insertionIndex = FindStringComparisonParameterIndex(semanticModel, invocationExpression, stringComparison, cancellationToken);
+ SyntaxNode newInvocation;
+ if (insertionIndex >= 0 && insertionIndex < invocationExpression.ArgumentList.Arguments.Count)
+ {
+ var newArgList = invocationExpression.ArgumentList.Arguments.Insert(insertionIndex, newArgument);
+ newInvocation = invocationExpression.WithArgumentList(invocationExpression.ArgumentList.WithArguments(newArgList));
+ }
+ else
+ {
+ newInvocation = invocationExpression.AddArgumentListArguments(newArgument);
+ }
+
+ editor.ReplaceNode(invocationExpression, newInvocation);
return editor.GetChangedDocument();
}
+
+ private static int FindStringComparisonParameterIndex(SemanticModel semanticModel, SyntaxNode node, INamedTypeSymbol stringComparison, CancellationToken cancellationToken)
+ {
+ if (semanticModel.GetOperation(node, cancellationToken) is not IInvocationOperation operation)
+ return -1;
+
+ var overloadFinder = new OverloadFinder(semanticModel.Compilation);
+ var overload = overloadFinder.FindOverloadWithAdditionalParameterOfType(operation, options: default, [stringComparison]);
+ if (overload is null)
+ return -1;
+
+ for (var i = 0; i < overload.Parameters.Length; i++)
+ {
+ if (overload.Parameters[i].Type.IsEqualTo(stringComparison))
+ return i;
+ }
+
+ return -1;
+ }
}
diff --git a/tests/Meziantou.Analyzer.Test/Rules/UseStringComparisonAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/UseStringComparisonAnalyzerTests.cs
index 77f7dd7aa..e18a2c52a 100644
--- a/tests/Meziantou.Analyzer.Test/Rules/UseStringComparisonAnalyzerTests.cs
+++ b/tests/Meziantou.Analyzer.Test/Rules/UseStringComparisonAnalyzerTests.cs
@@ -248,4 +248,43 @@ await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}
+
+ [Fact]
+ public async Task Contains_WithTrailingMessageParameter_ShouldInsertStringComparisonBeforeMessage()
+ {
+ const string SourceCode = """
+ class TypeName
+ {
+ public void Test()
+ {
+ [||]MyAssert.Contains("a", "b", "message");
+ }
+ }
+
+ static class MyAssert
+ {
+ public static void Contains(string value, string substring, string message) { }
+ public static void Contains(string value, string substring, System.StringComparison comparison, string message) { }
+ }
+ """;
+ const string CodeFix = """
+ class TypeName
+ {
+ public void Test()
+ {
+ MyAssert.Contains("a", "b", System.StringComparison.Ordinal, "message");
+ }
+ }
+
+ static class MyAssert
+ {
+ public static void Contains(string value, string substring, string message) { }
+ public static void Contains(string value, string substring, System.StringComparison comparison, string message) { }
+ }
+ """;
+ await CreateProjectBuilder()
+ .WithSourceCode(SourceCode)
+ .ShouldFixCodeWith(CodeFix)
+ .ValidateAsync();
+ }
}