Skip to content

add support for xunit TypeAsserts IsNotAssignableFrom #239

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/FluentAssertions.Analyzers.Tests/Tips/XunitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,28 @@ public void AssertIsAssignableFrom_TestAnalyzer(string assertion) =>
public void AssertIsAssignableFrom_TestCodeFix(string oldAssertion, string newAssertion)
=> VerifyCSharpFix<AssertIsAssignableFromCodeFix, AssertIsAssignableFromAnalyzer>("string actual, Type expected", oldAssertion, newAssertion);

[DataTestMethod]
[DataRow("Assert.IsNotAssignableFrom(expected, actual);")]
[DataRow("Assert.IsNotAssignableFrom(typeof(string), actual);")]
[DataRow("Assert.IsNotAssignableFrom<string>(actual);")]
[Implemented]
public void AssertIsNotAssignableFrom_TestAnalyzer(string assertion) =>
VerifyCSharpDiagnostic<AssertIsNotAssignableFromAnalyzer>("string actual, Type expected", assertion);

[DataTestMethod]
[DataRow(
/* oldAssertion: */ "Assert.IsNotAssignableFrom(expected, actual);",
/* newAssertion: */ "actual.Should().NotBeAssignableTo(expected);")]
[DataRow(
/* oldAssertion: */ "Assert.IsNotAssignableFrom(typeof(string), actual);",
/* newAssertion: */ "actual.Should().NotBeAssignableTo<string>();")]
[DataRow(
/* oldAssertion: */ "Assert.IsNotAssignableFrom<string>(actual);",
/* newAssertion: */ "actual.Should().NotBeAssignableTo<string>();")]
[Implemented]
public void AssertIsNotAssignableFrom_TestCodeFix(string oldAssertion, string newAssertion)
=> VerifyCSharpFix<AssertIsNotAssignableFromCodeFix, AssertIsNotAssignableFromAnalyzer>("string actual, Type expected", oldAssertion, newAssertion);

private void VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(string methodArguments, string assertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
{
var source = GenerateCode.XunitAssertion(methodArguments, assertion);
Expand Down
1 change: 1 addition & 0 deletions src/FluentAssertions.Analyzers/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ public static class Xunit
public const string AssertStartsWith = $"{DiagnosticProperties.IdPrefix}0716";
public const string AssertSubset = $"{DiagnosticProperties.IdPrefix}0717";
public const string AssertIsAssignableFrom = $"{DiagnosticProperties.IdPrefix}0718";
public const string AssertIsNotAssignableFrom = $"{DiagnosticProperties.IdPrefix}0719";
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,6 @@ public class AssertIsInstanceOfTypeCodeFix : MsTestAssertCodeFixProvider
protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties)
{
var newExpression = RenameMethodAndReplaceWithSubjectShould(expression, "IsInstanceOfType", "BeOfType");

var beOfType = newExpression.DescendantNodes()
.OfType<MemberAccessExpressionSyntax>()
.First(node => node.Name.Identifier.Text == "BeOfType");

if (beOfType.Parent is InvocationExpressionSyntax invocation)
{
var arguments = invocation.ArgumentList.Arguments;
if (arguments.Any() && arguments[0].Expression is TypeOfExpressionSyntax typeOfExpression)
{
var genericBeOfType = beOfType.WithName(SF.GenericName(beOfType.Name.Identifier.Text)
.AddTypeArgumentListArguments(typeOfExpression.Type)
);
newExpression = newExpression.ReplaceNode(beOfType, genericBeOfType);
return GetNewExpression(newExpression, NodeReplacement.RemoveFirstArgument("BeOfType"));
}
}

return newExpression;
return ReplaceTypeOfArgumentWithGenericTypeIfExists(newExpression, "BeOfType");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,6 @@ public class AssertIsNotInstanceOfTypeCodeFix : MsTestAssertCodeFixProvider
protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties)
{
var newExpression = RenameMethodAndReplaceWithSubjectShould(expression, "IsNotInstanceOfType", "NotBeOfType");

var beOfType = newExpression.DescendantNodes()
.OfType<MemberAccessExpressionSyntax>()
.First(node => node.Name.Identifier.Text == "NotBeOfType");

if (beOfType.Parent is InvocationExpressionSyntax invocation)
{
var arguments = invocation.ArgumentList.Arguments;
if (arguments.Any() && arguments[0].Expression is TypeOfExpressionSyntax typeOfExpression)
{
var genericBeOfType = beOfType.WithName(SF.GenericName(beOfType.Name.Identifier.Text)
.AddTypeArgumentListArguments(typeOfExpression.Type)
);
newExpression = newExpression.ReplaceNode(beOfType, genericBeOfType);
return GetNewExpression(newExpression, NodeReplacement.RemoveFirstArgument("NotBeOfType"));
}
}

return newExpression;
return ReplaceTypeOfArgumentWithGenericTypeIfExists(newExpression, "NotBeOfType");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,25 +63,7 @@ protected override ExpressionSyntax GetNewExpression(
return RenameMethodAndReorderActualExpectedAndReplaceWithSubjectShould(expression, "IsAssignableFrom", "BeAssignableTo");
case nameof(AssertIsAssignableFromAnalyzer.AssertIsAssignableFromTypeSyntaxVisitor):
var newExpression = RenameMethodAndReorderActualExpectedAndReplaceWithSubjectShould(expression, "IsAssignableFrom", "BeAssignableTo");

var beAssignableTo = newExpression.DescendantNodes()
.OfType<MemberAccessExpressionSyntax>()
.First(node => node.Name.Identifier.Text == "BeAssignableTo");

if (beAssignableTo.Parent is InvocationExpressionSyntax invocation)
{
var arguments = invocation.ArgumentList.Arguments;
if (arguments.Any() && arguments[0].Expression is TypeOfExpressionSyntax typeOfExpression)
{
var genericBeOfType = beAssignableTo.WithName(SF.GenericName(beAssignableTo.Name.Identifier.Text)
.AddTypeArgumentListArguments(typeOfExpression.Type)
);
newExpression = newExpression.ReplaceNode(beAssignableTo, genericBeOfType);
return GetNewExpression(newExpression, NodeReplacement.RemoveFirstArgument("BeAssignableTo"));
}
}

return newExpression;
return ReplaceTypeOfArgumentWithGenericTypeIfExists(newExpression, "BeAssignableTo");
default:
throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using FluentAssertions.Analyzers.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace FluentAssertions.Analyzers.Xunit;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AssertIsNotAssignableFromAnalyzer : XunitAnalyzer
{
public const string DiagnosticId = Constants.Tips.Xunit.AssertIsNotAssignableFrom;
public const string Category = Constants.Tips.Category;

public const string Message = "Use .Should().NotBeAssignableTo().";

protected override DiagnosticDescriptor Rule => new(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true);

protected override IEnumerable<FluentAssertionsCSharpSyntaxVisitor> Visitors => new FluentAssertionsCSharpSyntaxVisitor[]
{
new AssertIsNotAssignableFromGenericTypeSyntaxVisitor(),
new AssertIsNotAssignableFromTypeSyntaxVisitor()
};

//public static T IsNotAssignableFrom<T>(object? @object)
public class AssertIsNotAssignableFromGenericTypeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
{
public AssertIsNotAssignableFromGenericTypeSyntaxVisitor() : base(
MemberValidator.HasArguments("IsNotAssignableFrom", 1)
)
{
}
}

//public static T IsNotAssignableFrom(Type expectedType, object? @object)
public class AssertIsNotAssignableFromTypeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
{
public AssertIsNotAssignableFromTypeSyntaxVisitor() : base(
MemberValidator.HasArguments("IsNotAssignableFrom", 2)
)
{
}
}
}

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AssertIsNotAssignableFromCodeFix)), Shared]
public class AssertIsNotAssignableFromCodeFix : XunitCodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(AssertIsNotAssignableFromAnalyzer.DiagnosticId);

protected override ExpressionSyntax GetNewExpression(
ExpressionSyntax expression,
FluentAssertionsDiagnosticProperties properties)
{
switch (properties.VisitorName)
{
case nameof(AssertIsNotAssignableFromAnalyzer.AssertIsNotAssignableFromGenericTypeSyntaxVisitor):
return RenameMethodAndReorderActualExpectedAndReplaceWithSubjectShould(expression, "IsNotAssignableFrom", "NotBeAssignableTo");
case nameof(AssertIsNotAssignableFromAnalyzer.AssertIsNotAssignableFromTypeSyntaxVisitor):
var newExpression = RenameMethodAndReorderActualExpectedAndReplaceWithSubjectShould(expression, "IsNotAssignableFrom", "NotBeAssignableTo");
return ReplaceTypeOfArgumentWithGenericTypeIfExists(newExpression, "NotBeAssignableTo");
default:
throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace FluentAssertions.Analyzers.Utilities;

Expand All @@ -25,4 +28,26 @@ protected ExpressionSyntax RenameMethodAndReorderActualExpectedAndReplaceWithSub

return GetNewExpression(newExpression, NodeReplacement.WithArguments(newName, rename.Arguments.RemoveAt(1)));
}

protected ExpressionSyntax ReplaceTypeOfArgumentWithGenericTypeIfExists(ExpressionSyntax expression, string method)
{
var methodExpression = expression.DescendantNodes()
.OfType<MemberAccessExpressionSyntax>()
.First(node => node.Name.Identifier.Text == method);

if (methodExpression.Parent is InvocationExpressionSyntax invocation)
{
var arguments = invocation.ArgumentList.Arguments;
if (arguments.Any() && arguments[0].Expression is TypeOfExpressionSyntax typeOfExpression)
{
var genericBeOfType = methodExpression.WithName(SF.GenericName(methodExpression.Name.Identifier.Text)
.AddTypeArgumentListArguments(typeOfExpression.Type)
);
var newExpression = expression.ReplaceNode(methodExpression, genericBeOfType);
return GetNewExpression(newExpression, NodeReplacement.RemoveFirstArgument(method));
}
}

return expression;
}
}