Skip to content

Commit 8f49f34

Browse files
Fix StringAssertToAssertFixer to swap StringComparison and message arguments by @Youssef1313 in #6481 (backport to rel/3.10) (#6487)
Co-authored-by: Youssef1313 <youssefvictor00@gmail.com>
1 parent 35bab11 commit 8f49f34

File tree

2 files changed

+51
-9
lines changed

2 files changed

+51
-9
lines changed

src/Analyzers/MSTest.Analyzers.CodeFixes/StringAssertToAssertFixer.cs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using Microsoft.CodeAnalysis.CodeFixes;
1212
using Microsoft.CodeAnalysis.CSharp;
1313
using Microsoft.CodeAnalysis.CSharp.Syntax;
14-
using Microsoft.CodeAnalysis.Editing;
1514

1615
using MSTest.Analyzers.Helpers;
1716

@@ -78,13 +77,30 @@ private static async Task<Document> FixStringAssertAsync(
7877
return document;
7978
}
8079

81-
DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
80+
SyntaxNode root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
8281

8382
// Create new argument list with swapped first two arguments
84-
ArgumentSyntax[] newArguments = [.. arguments];
85-
(newArguments[0], newArguments[1]) = (newArguments[1], newArguments[0]);
83+
// We keep the existing separators in case there is trivia attached to them.
84+
var newArguments = arguments.GetWithSeparators().ToList();
85+
// NOTE: Index 1 has the "separator" (comma) between the first and second arguments.
86+
(newArguments[0], newArguments[2]) = (newArguments[2], newArguments[0]);
87+
88+
// StringAssert has overloads with parameter types (string, string, string, StringComparison)
89+
// In Assert class, these are (string, string, StringComparison, string)
90+
// So we need to do the swap.
91+
if (arguments.Count >= 4)
92+
{
93+
SemanticModel semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
94+
if (semanticModel.GetSymbolInfo(invocationExpr, cancellationToken).Symbol is IMethodSymbol stringAssertMethod &&
95+
stringAssertMethod.Parameters.Length >= 4 &&
96+
stringAssertMethod.Parameters[2].Type.SpecialType == SpecialType.System_String &&
97+
stringAssertMethod.Parameters[3].Type.Name == "StringComparison")
98+
{
99+
(newArguments[4], newArguments[6]) = (newArguments[6], newArguments[4]);
100+
}
101+
}
86102

87-
ArgumentListSyntax newArgumentList = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(newArguments));
103+
ArgumentListSyntax newArgumentList = invocationExpr.ArgumentList.WithArguments(SyntaxFactory.SeparatedList<ArgumentSyntax>(newArguments));
88104
InvocationExpressionSyntax newInvocationExpr = invocationExpr.WithArgumentList(newArgumentList);
89105

90106
// Replace StringAssert with Assert in the member access expression
@@ -93,7 +109,9 @@ private static async Task<Document> FixStringAssertAsync(
93109
.WithName(SyntaxFactory.IdentifierName(properAssertMethodName));
94110
newInvocationExpr = newInvocationExpr.WithExpression(newMemberAccess);
95111

96-
editor.ReplaceNode(invocationExpr, newInvocationExpr);
97-
return editor.GetChangedDocument();
112+
// Preserve leading trivia (including empty lines) from the original invocation
113+
newInvocationExpr = newInvocationExpr.WithLeadingTrivia(invocationExpr.GetLeadingTrivia());
114+
115+
return document.WithSyntaxRoot(root.ReplaceNode(invocationExpr, newInvocationExpr));
98116
}
99117
}

test/UnitTests/MSTest.Analyzers.UnitTests/StringAssertToAssertAnalyzerTests.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public sealed class StringAssertToAssertAnalyzerTests
1414
public async Task WhenStringAssertContains()
1515
{
1616
string code = """
17+
using System;
1718
using Microsoft.VisualStudio.TestTools.UnitTesting;
1819
1920
[TestClass]
@@ -25,11 +26,17 @@ public void MyTestMethod()
2526
string value = "Hello World";
2627
string substring = "World";
2728
{|#0:StringAssert.Contains(value, substring)|};
29+
{|#1:StringAssert.Contains(value, substring, "message")|};
30+
{|#2:StringAssert.Contains(value, substring, StringComparison.Ordinal)|};
31+
{|#3:StringAssert.Contains(value, substring, "message", StringComparison.Ordinal)|};
32+
{|#4:StringAssert.Contains(value, substring, "message {0}", "arg")|};
33+
{|#5:StringAssert.Contains(value, substring, "message {0}", StringComparison.Ordinal, "arg")|};
2834
}
2935
}
3036
""";
3137

3238
string fixedCode = """
39+
using System;
3340
using Microsoft.VisualStudio.TestTools.UnitTesting;
3441
3542
[TestClass]
@@ -41,14 +48,31 @@ public void MyTestMethod()
4148
string value = "Hello World";
4249
string substring = "World";
4350
Assert.Contains(substring, value);
51+
Assert.Contains(substring, value, "message");
52+
Assert.Contains(substring, value, StringComparison.Ordinal);
53+
Assert.Contains(substring, value, StringComparison.Ordinal, "message");
54+
Assert.Contains(substring, value, "message {0}", "arg");
55+
Assert.Contains(substring, value, StringComparison.Ordinal, "message {0}", "arg");
4456
}
4557
}
4658
""";
4759

4860
await VerifyCS.VerifyCodeFixAsync(
4961
code,
50-
// /0/Test0.cs(11,9): info MSTEST0046: Use 'Assert.Contains' instead of 'StringAssert.Contains'
51-
VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(0).WithArguments("Contains", "Contains"),
62+
[
63+
// /0/Test0.cs(12,9): info MSTEST0046: Use 'Assert.Contains' instead of 'StringAssert.Contains'
64+
VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(0).WithArguments("Contains", "Contains"),
65+
// /0/Test0.cs(13,9): info MSTEST0046: Use 'Assert.Contains' instead of 'StringAssert.Contains'
66+
VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(1).WithArguments("Contains", "Contains"),
67+
// /0/Test0.cs(14,9): info MSTEST0046: Use 'Assert.Contains' instead of 'StringAssert.Contains'
68+
VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(2).WithArguments("Contains", "Contains"),
69+
// /0/Test0.cs(15,9): info MSTEST0046: Use 'Assert.Contains' instead of 'StringAssert.Contains'
70+
VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(3).WithArguments("Contains", "Contains"),
71+
// /0/Test0.cs(16,9): info MSTEST0046: Use 'Assert.Contains' instead of 'StringAssert.Contains'
72+
VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(4).WithArguments("Contains", "Contains"),
73+
// /0/Test0.cs(17,9): info MSTEST0046: Use 'Assert.Contains' instead of 'StringAssert.Contains'
74+
VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(5).WithArguments("Contains", "Contains"),
75+
],
5276
fixedCode);
5377
}
5478

0 commit comments

Comments
 (0)