Skip to content

Commit 7386e87

Browse files
authored
add support for xunit TypeAsserts IsAssignableFrom (#238)
* add support for xunit TypeAsserts IsAssignableFrom * add support for xunit TypeAsserts IsAssignableFrom
1 parent b9f16b0 commit 7386e87

File tree

4 files changed

+113
-1
lines changed

4 files changed

+113
-1
lines changed

src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ private static void VerifyFix(string language, DiagnosticAnalyzer analyzer, Code
170170

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

176176
/// <summary>

src/FluentAssertions.Analyzers.Tests/Tips/XunitTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,28 @@ public void AssertSubset_TestAnalyzer(string assertion) =>
554554
public void AssertSubset_TestCodeFix(string oldAssertion, string newAssertion)
555555
=> VerifyCSharpFix<AssertSubsetCodeFix, AssertSubsetAnalyzer>("ISet<string> actual, ISet<string> expected", oldAssertion, newAssertion);
556556

557+
[DataTestMethod]
558+
[DataRow("Assert.IsAssignableFrom(expected, actual);")]
559+
[DataRow("Assert.IsAssignableFrom(typeof(string), actual);")]
560+
[DataRow("Assert.IsAssignableFrom<string>(actual);")]
561+
[Implemented]
562+
public void AssertIsAssignableFrom_TestAnalyzer(string assertion) =>
563+
VerifyCSharpDiagnostic<AssertIsAssignableFromAnalyzer>("string actual, Type expected", assertion);
564+
565+
[DataTestMethod]
566+
[DataRow(
567+
/* oldAssertion: */ "Assert.IsAssignableFrom(expected, actual);",
568+
/* newAssertion: */ "actual.Should().BeAssignableTo(expected);")]
569+
[DataRow(
570+
/* oldAssertion: */ "Assert.IsAssignableFrom(typeof(string), actual);",
571+
/* newAssertion: */ "actual.Should().BeAssignableTo<string>();")]
572+
[DataRow(
573+
/* oldAssertion: */ "Assert.IsAssignableFrom<string>(actual);",
574+
/* newAssertion: */ "actual.Should().BeAssignableTo<string>();")]
575+
[Implemented]
576+
public void AssertIsAssignableFrom_TestCodeFix(string oldAssertion, string newAssertion)
577+
=> VerifyCSharpFix<AssertIsAssignableFromCodeFix, AssertIsAssignableFromAnalyzer>("string actual, Type expected", oldAssertion, newAssertion);
578+
557579
private void VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(string methodArguments, string assertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
558580
{
559581
var source = GenerateCode.XunitAssertion(methodArguments, assertion);

src/FluentAssertions.Analyzers/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public static class Xunit
137137
public const string AssertEndsWith = $"{DiagnosticProperties.IdPrefix}0715";
138138
public const string AssertStartsWith = $"{DiagnosticProperties.IdPrefix}0716";
139139
public const string AssertSubset = $"{DiagnosticProperties.IdPrefix}0717";
140+
public const string AssertIsAssignableFrom = $"{DiagnosticProperties.IdPrefix}0718";
140141
}
141142
}
142143

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System.Collections.Generic;
2+
using System.Collections.Immutable;
3+
using System.Composition;
4+
using System.Linq;
5+
using FluentAssertions.Analyzers.Utilities;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CodeFixes;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
11+
12+
namespace FluentAssertions.Analyzers.Xunit;
13+
14+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
15+
public class AssertIsAssignableFromAnalyzer : XunitAnalyzer
16+
{
17+
public const string DiagnosticId = Constants.Tips.Xunit.AssertIsAssignableFrom;
18+
public const string Category = Constants.Tips.Category;
19+
20+
public const string Message = "Use .Should().BeAssignableTo().";
21+
22+
protected override DiagnosticDescriptor Rule => new(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true);
23+
24+
protected override IEnumerable<FluentAssertionsCSharpSyntaxVisitor> Visitors => new FluentAssertionsCSharpSyntaxVisitor[]
25+
{
26+
new AssertIsAssignableFromGenericTypeSyntaxVisitor(),
27+
new AssertIsAssignableFromTypeSyntaxVisitor()
28+
};
29+
30+
//public static T IsAssignableFrom<T>(object? @object)
31+
public class AssertIsAssignableFromGenericTypeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
32+
{
33+
public AssertIsAssignableFromGenericTypeSyntaxVisitor() : base(
34+
MemberValidator.HasArguments("IsAssignableFrom", 1)
35+
)
36+
{
37+
}
38+
}
39+
40+
//public static T IsAssignableFrom(Type expectedType, object? @object)
41+
public class AssertIsAssignableFromTypeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
42+
{
43+
public AssertIsAssignableFromTypeSyntaxVisitor() : base(
44+
MemberValidator.HasArguments("IsAssignableFrom", 2)
45+
)
46+
{
47+
}
48+
}
49+
}
50+
51+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AssertIsAssignableFromCodeFix)), Shared]
52+
public class AssertIsAssignableFromCodeFix : XunitCodeFixProvider
53+
{
54+
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(AssertIsAssignableFromAnalyzer.DiagnosticId);
55+
56+
protected override ExpressionSyntax GetNewExpression(
57+
ExpressionSyntax expression,
58+
FluentAssertionsDiagnosticProperties properties)
59+
{
60+
switch (properties.VisitorName)
61+
{
62+
case nameof(AssertIsAssignableFromAnalyzer.AssertIsAssignableFromGenericTypeSyntaxVisitor):
63+
return RenameMethodAndReorderActualExpectedAndReplaceWithSubjectShould(expression, "IsAssignableFrom", "BeAssignableTo");
64+
case nameof(AssertIsAssignableFromAnalyzer.AssertIsAssignableFromTypeSyntaxVisitor):
65+
var newExpression = RenameMethodAndReorderActualExpectedAndReplaceWithSubjectShould(expression, "IsAssignableFrom", "BeAssignableTo");
66+
67+
var beAssignableTo = newExpression.DescendantNodes()
68+
.OfType<MemberAccessExpressionSyntax>()
69+
.First(node => node.Name.Identifier.Text == "BeAssignableTo");
70+
71+
if (beAssignableTo.Parent is InvocationExpressionSyntax invocation)
72+
{
73+
var arguments = invocation.ArgumentList.Arguments;
74+
if (arguments.Any() && arguments[0].Expression is TypeOfExpressionSyntax typeOfExpression)
75+
{
76+
var genericBeOfType = beAssignableTo.WithName(SF.GenericName(beAssignableTo.Name.Identifier.Text)
77+
.AddTypeArgumentListArguments(typeOfExpression.Type)
78+
);
79+
newExpression = newExpression.ReplaceNode(beAssignableTo, genericBeOfType);
80+
return GetNewExpression(newExpression, NodeReplacement.RemoveFirstArgument("BeAssignableTo"));
81+
}
82+
}
83+
84+
return newExpression;
85+
default:
86+
throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}");
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)