Skip to content

Commit 10739f5

Browse files
authored
chore: refactor chained nunit assertions (#358)
1 parent 7b4d609 commit 10739f5

File tree

1 file changed

+87
-66
lines changed

1 file changed

+87
-66
lines changed

src/FluentAssertions.Analyzers/Tips/NunitCodeFixProvider.cs

Lines changed: 87 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -431,82 +431,50 @@ private CreateChangedDocument TryComputeFixForNunitThat(IInvocationOperation inv
431431
var constraint = invocation.Arguments[1].Value.UnwrapConversion();
432432
var subject = invocation.Arguments[0].Value;
433433

434-
if (MatchesProperties(t.Is, "True") // Assert.That(subject, Is.True)
435-
|| MatchesProperties(t.Is, "Not", "False")) // Assert.That(subject, Is.Not.False)
436-
return RenameAssertThatAssertionToShould("BeTrue");
437-
else if (MatchesProperties(t.Is, "False") // Assert.That(subject, Is.False)
438-
|| MatchesProperties(t.Is, "Not", "True")) // Assert.That(subject, Is.Not.True)
439-
return RenameAssertThatAssertionToShould("BeFalse");
440-
else if (MatchesProperties(t.Is, "Null")) // Assert.That(subject, Is.Null)
441-
return RenameAssertThatAssertionToShould("BeNull");
442-
else if (MatchesProperties(t.Is, "Not", "Null")) // Assert.That(subject, Is.Not.Null)
443-
return RenameAssertThatAssertionToShould("NotBeNull");
434+
var rewriter = new AssertThatRewriter(invocation, context, constraint);
435+
var matcher = new AssertThatMatcher(constraint, t);
436+
437+
if (matcher.Is("True") // Assert.That(subject, Is.True)
438+
|| matcher.Is("Not", "False")) // Assert.That(subject, Is.Not.False)
439+
return rewriter.Should("BeTrue");
440+
else if (matcher.Is("False") // Assert.That(subject, Is.False)
441+
|| matcher.Is("Not", "True")) // Assert.That(subject, Is.Not.True)
442+
return rewriter.Should("BeFalse");
443+
else if (matcher.Is("Null")) // Assert.That(subject, Is.Null)
444+
return rewriter.Should("BeNull");
445+
else if (matcher.Is("Not", "Null")) // Assert.That(subject, Is.Not.Null)
446+
return rewriter.Should("NotBeNull");
444447
else if (!IsArgumentTypeOfNonGenericEnumerable(invocation, argumentIndex: 0))
445448
{
446-
if (MatchesProperties(t.Is, "Empty") // Assert.That(subject, Is.Empty)
447-
|| MatchesProperties(t.Has, "Count", "Zero")) // Assert.That(subject, Has.Count.Zero)
448-
return RenameAssertThatAssertionToShould("BeEmpty");
449-
else if (MatchesProperties(t.Is, "Not", "Empty") // Assert.That(subject, Is.Not.Empty)
450-
|| MatchesProperties(t.Has, "Count", "Not", "Zero")) // Assert.That(subject, Has.Not.Zero)
451-
return RenameAssertThatAssertionToShould("NotBeEmpty");
452-
else if (MatchesMethod(t.Has, [Property("Count")], Method("EqualTo"), out var argument))
449+
if (matcher.Is("Empty") // Assert.That(subject, Is.Empty)
450+
|| matcher.Has("Count", "Zero")) // Assert.That(subject, Has.Count.Zero)
451+
return rewriter.Should("BeEmpty");
452+
else if (matcher.Is("Not", "Empty") // Assert.That(subject, Is.Not.Empty)
453+
|| matcher.Has("Count", "Not", "Zero")) // Assert.That(subject, Has.Not.Zero)
454+
return rewriter.Should("NotBeEmpty");
455+
else if (matcher.Has([Property("Count")], Method("EqualTo"), out var argument))
453456
{
454457
if (argument.IsLiteralValue(0))
455-
return RenameAssertThatAssertionToShould("BeEmpty");
458+
return rewriter.Should("BeEmpty");
456459
else if (argument.IsLiteralValue(1))
457-
return RenameAssertThatAssertionToShould("ContainSingle");
460+
return rewriter.Should("ContainSingle");
458461
else
459-
return RenameAssertThatToShouldWithArgument("HaveCount", generator => argument.Syntax);
462+
return rewriter.Should("HaveCount", argument);
460463
}
461-
else if (MatchesMethod(t.Has, [Property("Count")], Method("GreaterThan"), out argument) && argument.IsLiteralValue(0))
462-
return RenameAssertThatAssertionToShould("NotBeEmpty");
463-
}
464-
if (MatchesProperties(t.Is, "Zero"))
465-
return RenameAssertThatToShouldWithArgument("Be", generator => generator.LiteralExpression(0));
466-
else if (MatchesProperties(t.Is, "Not", "Zero"))
467-
return RenameAssertThatToShouldWithArgument("NotBe", generator => generator.LiteralExpression(0));
468-
469-
return null;
470-
471-
CreateChangedDocument RenameAssertThatAssertionToShould(string assertionName)
472-
=> DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, assertionName, subjectIndex: 0, argumentsToRemove: [1]);
473-
CreateChangedDocument RenameAssertThatToShouldWithArgument(string assertionName, Func<SyntaxGenerator, SyntaxNode> argumentGenerator)
474-
{
475-
return DocumentEditorUtils.RewriteExpression(invocation, [
476-
EditAction.ReplaceAssertionArgument(index: 1, argumentGenerator),
477-
EditAction.SubjectShouldAssertion(argumentIndex: 0, assertionName),
478-
], context);
479-
}
480-
481-
bool MatchesProperties(INamedTypeSymbol type, params string[] matchers)
482-
=> Matches(type, Array.ConvertAll(matchers, matcher => new PropertyReferenceMatcher(matcher)));
483-
bool MatchesMethod(INamedTypeSymbol type, IOperationMatcher[] matchers, MethodInvocationMatcher methodMatcher, out IArgumentOperation argument)
484-
{
485-
argument = null;
486-
var result = Matches(type, [.. matchers, methodMatcher]);
487-
if (result) argument = methodMatcher.Argument;
488-
return result;
489-
}
490-
bool Matches(INamedTypeSymbol type, params IOperationMatcher[] matchers)
491-
{
492-
IOperation currentOp = constraint;
493-
for (var i = matchers.Length - 1; i >= 0; i--)
464+
else if (matcher.Has([Property("Count")], Method("GreaterThan"), out argument))
494465
{
495-
var (nextOp, containingType) = matchers[i].TryGetNext(currentOp);
496-
if (containingType is null) return false;
497-
498-
if (i is 0)
499-
{
500-
return containingType.EqualsSymbol(type);
501-
}
502-
503-
if (nextOp is null) return false;
504-
505-
currentOp = nextOp;
466+
if (argument.IsLiteralValue(0))
467+
return rewriter.Should("NotBeEmpty");
468+
else
469+
return rewriter.Should("HaveCountGreaterThan", argument);
506470
}
507-
508-
return false;
509471
}
472+
if (matcher.Is("Zero"))
473+
return rewriter.Should("Be", g => g.LiteralExpression(0));
474+
else if (matcher.Is("Not", "Zero"))
475+
return rewriter.Should("NotBe", g => g.LiteralExpression(0));
476+
477+
return null;
510478
}
511479

512480
private CreateChangedDocument RewriteContainsAssertion(IInvocationOperation invocation, CodeFixContext context, string assertion, IArgumentOperation subject, IArgumentOperation expectation)
@@ -576,4 +544,57 @@ public class NunitCodeFixContext(Compilation compilation) : TestingFrameworkCode
576544
public INamedTypeSymbol ConstraintExpression { get; } = compilation.GetTypeByMetadataName("NUnit.Framework.Constraints.ConstraintExpression");
577545
public INamedTypeSymbol NUnitString { get; } = compilation.GetTypeByMetadataName("NUnit.Framework.NUnitString");
578546
}
547+
548+
private class AssertThatMatcher(IOperation constraint, NunitCodeFixContext t)
549+
{
550+
public bool Is(params string[] matchers) => Matches(t.Is, matchers);
551+
public bool Has(params string[] matchers) => Matches(t.Has, matchers);
552+
public bool Has(IOperationMatcher[] matchers, MethodInvocationMatcher methodMatcher, out IArgumentOperation argument) => Matches(t.Has, matchers, methodMatcher, out argument);
553+
554+
public bool Matches(INamedTypeSymbol type, params string[] matchers)
555+
=> Matches(type, Array.ConvertAll(matchers, matcher => new PropertyReferenceMatcher(matcher)));
556+
public bool Matches(INamedTypeSymbol type, IOperationMatcher[] matchers, MethodInvocationMatcher methodMatcher, out IArgumentOperation argument)
557+
{
558+
argument = null;
559+
var result = Matches(type, [.. matchers, methodMatcher]);
560+
if (result) argument = methodMatcher.Argument;
561+
return result;
562+
}
563+
public bool Matches(INamedTypeSymbol type, params IOperationMatcher[] matchers)
564+
{
565+
IOperation currentOp = constraint;
566+
for (var i = matchers.Length - 1; i >= 0; i--)
567+
{
568+
var (nextOp, containingType) = matchers[i].TryGetNext(currentOp);
569+
if (containingType is null) return false;
570+
571+
if (i is 0)
572+
{
573+
return containingType.EqualsSymbol(type);
574+
}
575+
576+
if (nextOp is null) return false;
577+
578+
currentOp = nextOp;
579+
}
580+
581+
return false;
582+
}
583+
}
584+
private class AssertThatRewriter(IInvocationOperation invocation, CodeFixContext context, IOperation constraint)
585+
{
586+
public CreateChangedDocument Should(string assertion)
587+
{
588+
return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, assertion, subjectIndex: 0, argumentsToRemove: [1]);
589+
}
590+
591+
public CreateChangedDocument Should(string assertion, IArgumentOperation argument) => Should(assertion, _ => argument.Syntax);
592+
public CreateChangedDocument Should(string assertion, Func<SyntaxGenerator, SyntaxNode> argumentGenerator)
593+
{
594+
return DocumentEditorUtils.RewriteExpression(invocation, [
595+
EditAction.ReplaceAssertionArgument(index: 1, argumentGenerator),
596+
EditAction.SubjectShouldAssertion(argumentIndex: 0, assertion),
597+
], context);
598+
}
599+
}
579600
}

0 commit comments

Comments
 (0)