Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

add support for xunit TypeAsserts IsAssignableFrom #238

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
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}");
}
}
}
Loading