Skip to content

Commit 0d5aa69

Browse files
authored
Fix MSTEST0002 codefix to not change modifier order (#6479)
1 parent dbe6640 commit 0d5aa69

File tree

2 files changed

+97
-21
lines changed

2 files changed

+97
-21
lines changed

src/Analyzers/MSTest.Analyzers.CodeFixes/TestClassShouldBeValidFixer.cs

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using Microsoft.CodeAnalysis;
1010
using Microsoft.CodeAnalysis.CodeActions;
1111
using Microsoft.CodeAnalysis.CodeFixes;
12-
using Microsoft.CodeAnalysis.CSharp;
1312
using Microsoft.CodeAnalysis.CSharp.Syntax;
1413
using Microsoft.CodeAnalysis.Editing;
1514
using Microsoft.CodeAnalysis.Text;
@@ -62,32 +61,24 @@ private static async Task<Document> FixClassDeclarationAsync(Document document,
6261
{
6362
cancellationToken.ThrowIfCancellationRequested();
6463

65-
// Get the SemanticModel and Compilation
64+
SyntaxNode root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
6665
SemanticModel semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
6766
bool canDiscoverInternals = semanticModel.Compilation.CanDiscoverInternals();
6867

69-
DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
68+
var generator = SyntaxGenerator.GetGenerator(document);
7069

71-
// Remove the static modifier if it exists
72-
SyntaxTokenList modifiers = SyntaxFactory.TokenList(
73-
typeDeclaration.Modifiers.Where(modifier => !modifier.IsKind(SyntaxKind.StaticKeyword)));
70+
Accessibility existingAccessibility = generator.GetAccessibility(typeDeclaration);
71+
bool isGoodAccessibility = existingAccessibility == Accessibility.Public ||
72+
(canDiscoverInternals && existingAccessibility == Accessibility.Internal);
7473

75-
if (!typeDeclaration.Modifiers.Any(SyntaxKind.PublicKeyword))
76-
{
77-
// Determine the visibility modifier
78-
SyntaxToken visibilityModifier = canDiscoverInternals
79-
? SyntaxFactory.Token(SyntaxKind.InternalKeyword)
80-
: SyntaxFactory.Token(SyntaxKind.PublicKeyword);
74+
SyntaxNode newTypeDeclaration = generator
75+
.WithModifiers(typeDeclaration, generator.GetModifiers(typeDeclaration).WithIsStatic(false));
8176

82-
modifiers = SyntaxFactory.TokenList(
83-
modifiers.Where(modifier => !modifier.IsKind(SyntaxKind.PrivateKeyword) && !modifier.IsKind(SyntaxKind.InternalKeyword))).Add(visibilityModifier);
77+
if (!isGoodAccessibility)
78+
{
79+
newTypeDeclaration = generator.WithAccessibility(newTypeDeclaration, Accessibility.Public);
8480
}
8581

86-
// Create a new class declaration with the updated modifiers.
87-
TypeDeclarationSyntax newTypeDeclaration = typeDeclaration.WithModifiers(modifiers);
88-
editor.ReplaceNode(typeDeclaration, newTypeDeclaration);
89-
SyntaxNode newRoot = editor.GetChangedRoot();
90-
91-
return document.WithSyntaxRoot(newRoot);
82+
return document.WithSyntaxRoot(root.ReplaceNode(typeDeclaration, newTypeDeclaration));
9283
}
9384
}

test/UnitTests/MSTest.Analyzers.UnitTests/TestClassShouldBeValidAnalyzerTests.cs

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ internal class MyTestClass
192192
[TestMethod]
193193
public async Task WhenDiscoverInternalsAndTypeIsPrivate_Diagnostic()
194194
{
195+
// NOTE: As we are fixing the modifier, we still prefer to make it public, even if discover internals is set.
195196
string code = """
196197
using Microsoft.VisualStudio.TestTools.UnitTesting;
197198
@@ -214,7 +215,7 @@ private class {|#0:MyTestClass|}
214215
public class A
215216
{
216217
[TestClass]
217-
internal class MyTestClass
218+
public class MyTestClass
218219
{
219220
}
220221
}
@@ -460,4 +461,88 @@ await VerifyCS.VerifyCodeFixAsync(
460461
.WithArguments("MyTestClass"),
461462
fixedCode);
462463
}
464+
465+
[TestMethod]
466+
public async Task WhenClassIsInternalSealed_Diagnostic()
467+
{
468+
string code = """
469+
using Microsoft.VisualStudio.TestTools.UnitTesting;
470+
471+
namespace MyNamespace
472+
{
473+
[TestClass]
474+
internal sealed class {|#0:MyTestClass|}
475+
{
476+
[TestMethod]
477+
public void TestMethod()
478+
{
479+
}
480+
}
481+
}
482+
""";
483+
string fixedCode = """
484+
using Microsoft.VisualStudio.TestTools.UnitTesting;
485+
486+
namespace MyNamespace
487+
{
488+
[TestClass]
489+
public sealed class MyTestClass
490+
{
491+
[TestMethod]
492+
public void TestMethod()
493+
{
494+
}
495+
}
496+
}
497+
""";
498+
499+
await VerifyCS.VerifyCodeFixAsync(
500+
code,
501+
VerifyCS.Diagnostic(TestClassShouldBeValidAnalyzer.TestClassShouldBeValidRule)
502+
.WithLocation(0)
503+
.WithArguments("MyTestClass"),
504+
fixedCode);
505+
}
506+
507+
[TestMethod]
508+
public async Task WhenClassIsInternalStatic_Diagnostic()
509+
{
510+
string code = """
511+
using Microsoft.VisualStudio.TestTools.UnitTesting;
512+
513+
namespace MyNamespace
514+
{
515+
[TestClass]
516+
internal static class {|#0:MyTestClass|}
517+
{
518+
[TestMethod]
519+
public static void TestMethod()
520+
{
521+
}
522+
}
523+
}
524+
""";
525+
string fixedCode = """
526+
using Microsoft.VisualStudio.TestTools.UnitTesting;
527+
528+
namespace MyNamespace
529+
{
530+
[TestClass]
531+
public class MyTestClass
532+
{
533+
[TestMethod]
534+
public static void TestMethod()
535+
{
536+
}
537+
}
538+
}
539+
""";
540+
541+
await VerifyCS.VerifyCodeFixAsync(
542+
code,
543+
VerifyCS.Diagnostic(TestClassShouldBeValidAnalyzer.TestClassShouldBeValidRule)
544+
.WithLocation(0)
545+
.WithArguments("MyTestClass"),
546+
fixedCode);
547+
}
463548
}

0 commit comments

Comments
 (0)