Skip to content

Commit

Permalink
add support for xunit TypeAsserts IsAssignableFrom (#238)
Browse files Browse the repository at this point in the history
* add support for xunit TypeAsserts IsAssignableFrom

* add support for xunit TypeAsserts IsAssignableFrom
  • Loading branch information
Meir017 authored Oct 10, 2023
1 parent b9f16b0 commit 7386e87
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ private static void VerifyFix(string language, DiagnosticAnalyzer analyzer, Code

//after applying all of the code fixes, compare the resulting string to the inputted one
var actual = GetStringFromDocument(document);
actual.Should().Be(newSource);
;
}

/// <summary>
Expand Down
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 @@ -554,6 +554,28 @@ public void AssertSubset_TestAnalyzer(string assertion) =>
public void AssertSubset_TestCodeFix(string oldAssertion, string newAssertion)
=> VerifyCSharpFix<AssertSubsetCodeFix, AssertSubsetAnalyzer>("ISet<string> actual, ISet<string> expected", oldAssertion, newAssertion);

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

[DataTestMethod]
[DataRow(
/* oldAssertion: */ "Assert.IsAssignableFrom(expected, actual);",
/* newAssertion: */ "actual.Should().BeAssignableTo(expected);")]
[DataRow(
/* oldAssertion: */ "Assert.IsAssignableFrom(typeof(string), actual);",
/* newAssertion: */ "actual.Should().BeAssignableTo<string>();")]
[DataRow(
/* oldAssertion: */ "Assert.IsAssignableFrom<string>(actual);",
/* newAssertion: */ "actual.Should().BeAssignableTo<string>();")]
[Implemented]
public void AssertIsAssignableFrom_TestCodeFix(string oldAssertion, string newAssertion)
=> VerifyCSharpFix<AssertIsAssignableFromCodeFix, AssertIsAssignableFromAnalyzer>("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 @@ -137,6 +137,7 @@ public static class Xunit
public const string AssertEndsWith = $"{DiagnosticProperties.IdPrefix}0715";
public const string AssertStartsWith = $"{DiagnosticProperties.IdPrefix}0716";
public const string AssertSubset = $"{DiagnosticProperties.IdPrefix}0717";
public const string AssertIsAssignableFrom = $"{DiagnosticProperties.IdPrefix}0718";
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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 AssertIsAssignableFromAnalyzer : XunitAnalyzer
{
public const string DiagnosticId = Constants.Tips.Xunit.AssertIsAssignableFrom;
public const string Category = Constants.Tips.Category;

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

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

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

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

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

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

protected override ExpressionSyntax GetNewExpression(
ExpressionSyntax expression,
FluentAssertionsDiagnosticProperties properties)
{
switch (properties.VisitorName)
{
case nameof(AssertIsAssignableFromAnalyzer.AssertIsAssignableFromGenericTypeSyntaxVisitor):
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;
default:
throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}");
}
}
}

0 comments on commit 7386e87

Please sign in to comment.