Skip to content
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
32 changes: 16 additions & 16 deletions src/FluentAssertions.Analyzers.Tests/Tips/ShouldEqualsTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using FluentAssertions.Analyzers.TestUtils;
using FluentAssertions.Analyzers.Tips;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.TestTools.UnitTesting;

Expand All @@ -11,7 +10,7 @@ public class ShouldEqualsTests
[TestMethod]
[Implemented]
public void ShouldEquals_TestAnalyzer()
=> VerifyCSharpDiagnosticExpressionBody("actual.Should().Equals(expected);");
=> VerifyCSharpDiagnosticExpressionBody("actual.Should().Equals(expected);", DiagnosticMetadata.ShouldBe_ShouldEquals);

[TestMethod]
[Implemented]
Expand All @@ -20,13 +19,13 @@ public void ShouldEquals_ShouldBe_ObjectType_TestCodeFix()
var oldSource = GenerateCode.ObjectStatement("actual.Should().Equals(expected);");
var newSource = GenerateCode.ObjectStatement("actual.Should().Be(expected);");

DiagnosticVerifier.VerifyCSharpFix<ShouldEqualsCodeFix, ShouldEqualsAnalyzer>(oldSource, newSource);
DiagnosticVerifier.VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldSource, newSource);
}

[TestMethod]
[Implemented]
public void ShouldEquals_NestedInsideIfBlock_TestAnalyzer()
=> VerifyCSharpDiagnosticExpressionBody("if(true) { actual.Should().Equals(expected); }", 10, 24);
=> VerifyCSharpDiagnosticExpressionBody("if(true) { actual.Should().Equals(expected); }", 10, 24, DiagnosticMetadata.ShouldBe_ShouldEquals);

[TestMethod]
[Implemented]
Expand All @@ -35,13 +34,13 @@ public void ShouldEquals_NestedInsideIfBlock_ShouldBe_ObjectType_TestCodeFix()
var oldSource = GenerateCode.ObjectStatement("if(true) { actual.Should().Equals(expected); }");
var newSource = GenerateCode.ObjectStatement("if(true) { actual.Should().Be(expected); }");

DiagnosticVerifier.VerifyCSharpFix<ShouldEqualsCodeFix, ShouldEqualsAnalyzer>(oldSource, newSource);
DiagnosticVerifier.VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldSource, newSource);
}

[TestMethod]
[Implemented]
public void ShouldEquals_NestedInsideWhileBlock_TestAnalyzer()
=> VerifyCSharpDiagnosticExpressionBody("while(true) { actual.Should().Equals(expected); }", 10, 27);
=> VerifyCSharpDiagnosticExpressionBody("while(true) { actual.Should().Equals(expected); }", 10, 27, DiagnosticMetadata.ShouldBe_ShouldEquals);

[TestMethod]
[Implemented]
Expand All @@ -50,14 +49,14 @@ public void ShouldEquals_NestedInsideWhileBlock_ShouldBe_ObjectType_TestCodeFix(
var oldSource = GenerateCode.ObjectStatement("while(true) { actual.Should().Equals(expected); }");
var newSource = GenerateCode.ObjectStatement("while(true) { actual.Should().Be(expected); }");

DiagnosticVerifier.VerifyCSharpFix<ShouldEqualsCodeFix, ShouldEqualsAnalyzer>(oldSource, newSource);
DiagnosticVerifier.VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldSource, newSource);
}

[TestMethod]
[Implemented]
public void ShouldEquals_ActualIsMethodInvoaction_TestAnalyzer()
=> VerifyCSharpDiagnosticExpressionBody("object ResultSupplier() { return null; } \n"
+ "ResultSupplier().Should().Equals(expected);", 11, 0);
+ "ResultSupplier().Should().Equals(expected);", 11, 0, DiagnosticMetadata.ShouldBe_ShouldEquals);

[TestMethod]
[Implemented]
Expand All @@ -67,7 +66,7 @@ public void ShouldEquals_ActualIsMethodInvoaction_ShouldBe_ObjectType_TestCodeFi
var oldSource = GenerateCode.ObjectStatement(methodInvocation + "ResultSupplier().Should().Equals(expected);");
var newSource = GenerateCode.ObjectStatement(methodInvocation + "ResultSupplier().Should().Be(expected);");

DiagnosticVerifier.VerifyCSharpFix<ShouldEqualsCodeFix, ShouldEqualsAnalyzer>(oldSource, newSource);
DiagnosticVerifier.VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldSource, newSource);
}

[TestMethod]
Expand All @@ -77,7 +76,7 @@ public void ShouldEquals_ShouldBe_NumberType_TestCodeFix()
var oldSource = GenerateCode.DoubleAssertion("actual.Should().Equals(expected);");
var newSource = GenerateCode.DoubleAssertion("actual.Should().Be(expected);");

DiagnosticVerifier.VerifyCSharpFix<ShouldEqualsCodeFix, ShouldEqualsAnalyzer>(oldSource, newSource);
DiagnosticVerifier.VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldSource, newSource);
}

[TestMethod]
Expand All @@ -87,7 +86,7 @@ public void ShouldEquals_ShouldBe_StringType_TestCodeFix()
var oldSource = GenerateCode.StringAssertion("actual.Should().Equals(expected);");
var newSource = GenerateCode.StringAssertion("actual.Should().Be(expected);");

DiagnosticVerifier.VerifyCSharpFix<ShouldEqualsCodeFix, ShouldEqualsAnalyzer>(oldSource, newSource);
DiagnosticVerifier.VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldSource, newSource);
}

[TestMethod]
Expand All @@ -97,17 +96,18 @@ public void ShouldEquals_ShouldEqual_EnumerableType_TestCodeFix()
var oldSource = GenerateCode.GenericIListCodeBlockAssertion("actual.Should().Equals(expected);");
var newSource = GenerateCode.GenericIListCodeBlockAssertion("actual.Should().Equal(expected);");

DiagnosticVerifier.VerifyCSharpFix<ShouldEqualsCodeFix, ShouldEqualsAnalyzer>(oldSource, newSource);
DiagnosticVerifier.VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldSource, newSource);
}

private void VerifyCSharpDiagnosticExpressionBody(string sourceAssertion) => VerifyCSharpDiagnosticExpressionBody(sourceAssertion, 10, 13);
private void VerifyCSharpDiagnosticExpressionBody(string sourceAssertion, int line, int column)
private void VerifyCSharpDiagnosticExpressionBody(string sourceAssertion, DiagnosticMetadata metadata) => VerifyCSharpDiagnosticExpressionBody(sourceAssertion, 10, 13, metadata);
private void VerifyCSharpDiagnosticExpressionBody(string sourceAssertion, int line, int column, DiagnosticMetadata metadata)
{
var source = GenerateCode.ObjectStatement(sourceAssertion);
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source, new DiagnosticResult
{
Id = ShouldEqualsAnalyzer.DiagnosticId,
Message = ShouldEqualsAnalyzer.Message,
Id = FluentAssertionsOperationAnalyzer.DiagnosticId,
Message = metadata.Message,
VisitorName = metadata.Name,
Locations = new DiagnosticResultLocation[]
{
new DiagnosticResultLocation("Test0.cs", line, column)
Expand Down
4 changes: 4 additions & 0 deletions src/FluentAssertions.Analyzers/Tips/DiagnosticMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ private DiagnosticMetadata(string message, string helpLink, [CallerMemberName] s
public static DiagnosticMetadata ExceptionShouldThrowExactlyWithInnerException_ShouldThrowExactlyAndInnerExceptionShouldBeAssignableTo = new("Use .Should().ThrowExactly<TException>().WithInnerException<TInnerException>()", string.Empty);
public static DiagnosticMetadata ExceptionShouldThrowExactlyWithInnerException_ShouldThrowExactlyWhichInnerExceptionShouldBeAssignableTo = new("Use .Should().ThrowExactly<TException>().WithInnerException<TInnerException>()", string.Empty);

public static DiagnosticMetadata StringShouldBe_StringShouldEquals = new("Use .Should().Be()", string.Empty);
public static DiagnosticMetadata CollectionShouldEqual_CollectionShouldEquals = new("Use .Should().Equal()", string.Empty);
public static DiagnosticMetadata ShouldEquals = new("Use .Should().Be() or .Should().BeEquivalentTo or .Should().Equal() or other equivalency assertion", string.Empty);
public static DiagnosticMetadata ShouldBe_ShouldEquals = new("Use .Should().Be()", string.Empty);

private static string GetHelpLink(string id) => $"https://fluentassertions.com/tips/#{id}";
}
21 changes: 20 additions & 1 deletion src/FluentAssertions.Analyzers/Tips/FluentAssertionsCodeFix.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -13,6 +14,20 @@ public partial class FluentAssertionsCodeFix : FluentAssertionsCodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(FluentAssertionsOperationAnalyzer.DiagnosticId);

protected override Task<bool> CanRewriteAssertion(ExpressionSyntax expression, CodeFixContext context, Diagnostic diagnostic)
{
if (diagnostic.Properties.TryGetValue(Constants.DiagnosticProperties.VisitorName, out var visitorName))
{
return visitorName switch
{
nameof(DiagnosticMetadata.ShouldEquals) => Task.FromResult(false),
_ => Task.FromResult(true),
};
}

return base.CanRewriteAssertion(expression, context, diagnostic);
}

protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties)
{
// oldAssertion: subject.<remove>(arg1).Should().<rename>(arg2, {reasonArgs});
Expand Down Expand Up @@ -337,7 +352,11 @@ ExpressionSyntax GetCombinedAssertionsWithArgumentsReversedOrder(string remove,
case nameof(DiagnosticMetadata.ExceptionShouldThrowWithMessage_ShouldThrowAndMessageShouldEndWith):
case nameof(DiagnosticMetadata.ExceptionShouldThrowExactlyWithMessage_ShouldThrowExactlyAndMessageShouldEndWith):
return ReplaceEndWithMessage(expression, "And");

case nameof(DiagnosticMetadata.CollectionShouldEqual_CollectionShouldEquals):
return GetNewExpression(expression, NodeReplacement.Rename("Equals", "Equal"));
case nameof(DiagnosticMetadata.StringShouldBe_StringShouldEquals):
case nameof(DiagnosticMetadata.ShouldBe_ShouldEquals):
return GetNewExpression(expression, NodeReplacement.Rename("Equals", "Be"));
default: throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}");
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;

Expand Down Expand Up @@ -52,7 +54,10 @@ public FluentAssertionsMetadata(Compilation compilation)
DictionaryOfT2 = compilation.GetTypeByMetadataName(typeof(Dictionary<,>).FullName);
IReadonlyDictionaryOfT2 = compilation.GetTypeByMetadataName(typeof(IReadOnlyDictionary<,>).FullName);
Enumerable = compilation.GetTypeByMetadataName(typeof(Enumerable).FullName);
IEnumerable = compilation.GetTypeByMetadataName(typeof(IEnumerable).FullName);
Math = compilation.GetTypeByMetadataName(typeof(Math).FullName);
TaskCompletionSourceOfT1 = compilation.GetTypeByMetadataName(typeof(TaskCompletionSource<>).FullName);
Stream = compilation.GetTypeByMetadataName(typeof(Stream).FullName);
}
public INamedTypeSymbol AssertionExtensions { get; }
public INamedTypeSymbol ReferenceTypeAssertionsOfT2 { get; }
Expand All @@ -68,6 +73,9 @@ public FluentAssertionsMetadata(Compilation compilation)
public INamedTypeSymbol BooleanAssertionsOfT1 { get; }
public INamedTypeSymbol NumericAssertionsOfT2 { get; }
public INamedTypeSymbol Enumerable { get; }
public INamedTypeSymbol IEnumerable { get; }
public INamedTypeSymbol Math { get; }
public INamedTypeSymbol TaskCompletionSourceOfT1 { get; }
public INamedTypeSymbol Stream { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,29 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs

switch (assertion.TargetMethod.Name)
{
case nameof(object.Equals):

if (subject.Type.SpecialType is SpecialType.System_String)
{
context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.StringShouldBe_StringShouldEquals));
return;
}

if (subject.Type.EqualsSymbol(metadata.TaskCompletionSourceOfT1)
|| subject.Type.AllInterfaces.Contains(metadata.Stream))
{
context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.ShouldEquals));
return;
}

if (subject.Type.ImplementsOrIsInterface(metadata.IEnumerable))
{
context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldEqual_CollectionShouldEquals));
return;
}

context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.ShouldBe_ShouldEquals));
return;
case "NotBeEmpty" when assertion.IsContainedInType(metadata.GenericCollectionAssertionsOfT3):
{
if (assertion.TryGetChainedInvocationAfterAndConstraint("NotBeNull", out var chainedInvocation))
Expand Down
90 changes: 0 additions & 90 deletions src/FluentAssertions.Analyzers/Tips/ShouldEquals.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
foreach (var diagnostic in context.Diagnostics)
{
var expression = (ExpressionSyntax)root.FindNode(diagnostic.Location.SourceSpan);
if (await CanRewriteAssertion(expression, context).ConfigureAwait(false))
if (await CanRewriteAssertion(expression, context, diagnostic).ConfigureAwait(false))
{
context.RegisterCodeFix(CodeAction.Create(
title: diagnostic.Properties[Constants.DiagnosticProperties.Title],
Expand All @@ -33,7 +33,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
}
}

protected virtual Task<bool> CanRewriteAssertion(ExpressionSyntax expression, CodeFixContext context) => Task.FromResult(true);
protected virtual Task<bool> CanRewriteAssertion(ExpressionSyntax expression, CodeFixContext context, Diagnostic diagnostic) => Task.FromResult(true);

protected async Task<Document> RewriteAssertion(Document document, ExpressionSyntax expression, ImmutableDictionary<string, string> properties, CancellationToken cancellationToken)
{
Expand Down