-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
xunit/xunit#1435: Flip Assert.True/False when passed a negated expres…
…sion
- Loading branch information
1 parent
51d5c17
commit 4a9c2c3
Showing
5 changed files
with
197 additions
and
1 deletion.
There are no files selected for viewing
71 changes: 71 additions & 0 deletions
71
src/xunit.analyzers.fixes/X2000/BooleanAssertsShouldNotBeNegatedFixer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
using System.Composition; | ||
using System.Globalization; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Editing; | ||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | ||
|
||
namespace Xunit.Analyzers.Fixes; | ||
|
||
[ExportCodeFixProvider(LanguageNames.CSharp), Shared] | ||
public class BooleanAssertsShouldNotBeNegatedFixer : BatchedCodeFixProvider | ||
{ | ||
public const string Key_UseSuggestedAssert = "xUnit2022_UseSuggestedAssert"; | ||
|
||
public BooleanAssertsShouldNotBeNegatedFixer() : | ||
base(Descriptors.X2022_BooleanAssertionsShouldNotBeNegated.Id) | ||
{ } | ||
|
||
public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
if (root is null) | ||
return; | ||
|
||
var invocation = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>(); | ||
if (invocation is null) | ||
return; | ||
|
||
var diagnostic = context.Diagnostics.FirstOrDefault(); | ||
if (diagnostic is null) | ||
return; | ||
if (!diagnostic.Properties.TryGetValue(Constants.Properties.Replacement, out var replacement)) | ||
return; | ||
if (replacement is null) | ||
return; | ||
|
||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
string.Format(CultureInfo.CurrentCulture, "Use Assert.{0}", replacement), | ||
ct => UseSuggestedAssert(context.Document, invocation, replacement, ct), | ||
Key_UseSuggestedAssert | ||
), | ||
context.Diagnostics | ||
); | ||
} | ||
|
||
static async Task<Document> UseSuggestedAssert( | ||
Document document, | ||
InvocationExpressionSyntax invocation, | ||
string replacement, | ||
CancellationToken cancellationToken) | ||
{ | ||
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); | ||
|
||
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess) | ||
if (invocation.ArgumentList.Arguments[0].Expression is PrefixUnaryExpressionSyntax prefixUnaryExpression) | ||
editor.ReplaceNode( | ||
invocation, | ||
invocation | ||
.WithArgumentList(ArgumentList(SeparatedList(new[] { Argument(prefixUnaryExpression.Operand) }))) | ||
.WithExpression(memberAccess.WithName(IdentifierName(replacement))) | ||
); | ||
|
||
return editor.GetChangedDocument(); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
src/xunit.analyzers.tests/Analyzers/X2000/BooleanAssertsShouldNotBeNegatedTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using Xunit; | ||
using Verify = CSharpVerifier<Xunit.Analyzers.BooleanAssertsShouldNotBeNegated>; | ||
|
||
public class BooleanAssertsShouldNotBeNegatedTests | ||
{ | ||
[Theory] | ||
[InlineData("False", "True")] | ||
[InlineData("True", "False")] | ||
public async void NegatedBooleanAssertionTriggers( | ||
string assertion, | ||
string replacement) | ||
{ | ||
var code = $@" | ||
using Xunit; | ||
public class TestClass {{ | ||
[Fact] | ||
public void TestMethod() {{ | ||
bool condition = true; | ||
Assert.{assertion}(!condition); | ||
}} | ||
}}"; | ||
var expected = | ||
Verify | ||
.Diagnostic() | ||
.WithSpan(9, 9, 9, 28 + assertion.Length) | ||
.WithArguments(assertion, replacement); | ||
|
||
await Verify.VerifyAnalyzer(code, expected); | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
src/xunit.analyzers.tests/Fixes/X2000/BooleanAssertsShouldNotBeNegatedFixerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using Xunit; | ||
using Xunit.Analyzers.Fixes; | ||
using Verify = CSharpVerifier<Xunit.Analyzers.BooleanAssertsShouldNotBeNegated>; | ||
|
||
public class BooleanAssertsShouldNotBeNegatedFixerTests | ||
{ | ||
const string template = @" | ||
using Xunit; | ||
public class TestClass {{ | ||
[Fact] | ||
public void TestMethod() {{ | ||
bool condition = true; | ||
{0}; | ||
}} | ||
}}"; | ||
|
||
[Theory] | ||
[InlineData("False", "True")] | ||
[InlineData("True", "False")] | ||
public async void ReplacesBooleanAssert( | ||
string assertion, | ||
string replacement) | ||
{ | ||
var before = string.Format(template, $"[|Assert.{assertion}(!condition)|]"); | ||
var after = string.Format(template, $"Assert.{replacement}(condition)"); | ||
|
||
await Verify.VerifyCodeFix(before, after, BooleanAssertsShouldNotBeNegatedFixer.Key_UseSuggestedAssert); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
src/xunit.analyzers/X2000/BooleanAssertsShouldNotBeNegated.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
|
||
namespace Xunit.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class BooleanAssertsShouldNotBeNegated : AssertUsageAnalyzerBase | ||
{ | ||
static readonly string[] targetMethods = | ||
{ | ||
Constants.Asserts.False, | ||
Constants.Asserts.True, | ||
}; | ||
|
||
public BooleanAssertsShouldNotBeNegated() : | ||
base(Descriptors.X2022_BooleanAssertionsShouldNotBeNegated, targetMethods) | ||
{ } | ||
|
||
protected override void AnalyzeInvocation( | ||
OperationAnalysisContext context, | ||
XunitContext xunitContext, | ||
IInvocationOperation invocationOperation, | ||
IMethodSymbol method) | ||
{ | ||
if (invocationOperation.Arguments.Length < 1) | ||
return; | ||
|
||
if (invocationOperation.Arguments[0].Value is not IUnaryOperation unaryOperation) | ||
return; | ||
|
||
if (!unaryOperation.Syntax.IsKind(SyntaxKind.LogicalNotExpression)) | ||
return; | ||
|
||
var suggestedAssertion = | ||
method.Name == Constants.Asserts.False | ||
? Constants.Asserts.True | ||
: Constants.Asserts.False; | ||
|
||
var builder = ImmutableDictionary.CreateBuilder<string, string?>(); | ||
builder[Constants.Properties.Replacement] = suggestedAssertion; | ||
|
||
context.ReportDiagnostic( | ||
Diagnostic.Create( | ||
Descriptors.X2022_BooleanAssertionsShouldNotBeNegated, | ||
invocationOperation.Syntax.GetLocation(), | ||
builder.ToImmutable(), | ||
method.Name, | ||
suggestedAssertion | ||
) | ||
); | ||
} | ||
} |