Skip to content

Commit a34ac27

Browse files
authored
feat: Migrate NullConditionalAssertion to IOperation (#280)
* feat: Migrate NullConditionalAssertion to IOperation * feat: Migrate NullConditionalAssertion to IOperation
1 parent ca48078 commit a34ac27

8 files changed

+34
-110
lines changed

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ public class NullConditionalAssertionTests
1818
public void NullConditionalMayNotExecuteTest(string assertion)
1919
{
2020
DiagnosticVerifier.VerifyDiagnostic(new DiagnosticVerifierArguments()
21-
.WithDiagnosticAnalyzer<NullConditionalAssertionAnalyzer>()
21+
.WithDiagnosticAnalyzer<FluentAssertionsOperationAnalyzer>()
2222
.WithSources(Code(assertion))
2323
.WithPackageReferences(PackageReference.FluentAssertions_6_12_0)
2424
.WithExpectedDiagnostics(new DiagnosticResult
2525
{
26-
Id = NullConditionalAssertionAnalyzer.DiagnosticId,
27-
Message = NullConditionalAssertionAnalyzer.Message,
28-
Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning,
26+
Id = FluentAssertionsOperationAnalyzer.DiagnosticId,
27+
Message = DiagnosticMetadata.NullConditionalMayNotExecute.Message,
28+
Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Info, // TODO: change to warning
29+
VisitorName = nameof(DiagnosticMetadata.NullConditionalMayNotExecute),
2930
Locations = new DiagnosticResultLocation[]
3031
{
3132
new DiagnosticResultLocation("Test0.cs", 11, 13)
@@ -37,12 +38,12 @@ public void NullConditionalMayNotExecuteTest(string assertion)
3738
[DataTestMethod]
3839
[AssertionDiagnostic("(actual?.MyProperty).Should().Be(\"test\"{0});")]
3940
[AssertionDiagnostic("actual.MyProperty.Should().Be(actual?.MyProperty{0});")]
40-
[AssertionDiagnostic("actual.MyList.Where(obj => obj?.ToString() == null).Count().Should().Be(0{0});")]
41+
[AssertionDiagnostic("actual.MyList.Where(obj => obj?.ToString() == null).Should().HaveCount(6{0});")]
4142
[Implemented]
4243
public void NullConditionalWillStillExecuteTest(string assertion)
4344
{
4445
DiagnosticVerifier.VerifyDiagnostic(new DiagnosticVerifierArguments()
45-
.WithDiagnosticAnalyzer<NullConditionalAssertionAnalyzer>()
46+
.WithDiagnosticAnalyzer<FluentAssertionsOperationAnalyzer>()
4647
.WithSources(Code(assertion))
4748
.WithPackageReferences(PackageReference.FluentAssertions_6_12_0)
4849
);

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -441,9 +441,9 @@ public class MyCollectionType { }";
441441
.WithPackageReferences(PackageReference.FluentAssertions_6_12_0)
442442
.WithExpectedDiagnostics(new DiagnosticResult()
443443
{
444-
Id = NullConditionalAssertionAnalyzer.DiagnosticId,
445-
Message = NullConditionalAssertionAnalyzer.Message,
446-
Severity = DiagnosticSeverity.Warning,
444+
Id = FluentAssertionsOperationAnalyzer.DiagnosticId,
445+
Message = DiagnosticMetadata.NullConditionalMayNotExecute.Message,
446+
Severity = DiagnosticSeverity.Info, // TODO: change to warning
447447
Locations = new[] { new DiagnosticResultLocation("Test0.cs", 13, 9) }
448448
})
449449
);

src/FluentAssertions.Analyzers/Constants.cs

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public static class CodeSmell
1818
{
1919
public const string Category = "FluentAssertionCodeSmell";
2020

21-
public const string NullConditionalAssertion = $"{DiagnosticProperties.IdPrefix}0800";
2221
public const string AsyncVoid = $"{DiagnosticProperties.IdPrefix}0801";
2322
public const string ShouldEquals = $"{DiagnosticProperties.IdPrefix}0802";
2423
}

src/FluentAssertions.Analyzers/Tips/DiagnosticMetadata.cs

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ private DiagnosticMetadata(string message, string helpLink, [CallerMemberName] s
108108
public static DiagnosticMetadata CollectionShouldEqual_CollectionShouldEquals = new("Use .Should().Equal()", string.Empty);
109109
public static DiagnosticMetadata ShouldEquals = new("Use .Should().Be() or .Should().BeEquivalentTo or .Should().Equal() or other equivalency assertion", string.Empty);
110110
public static DiagnosticMetadata ShouldBe_ShouldEquals = new("Use .Should().Be()", string.Empty);
111+
public static DiagnosticMetadata NullConditionalMayNotExecute = new("Use .Should() instead of ?.Should()", string.Empty);
111112

112113
private static string GetHelpLink(string id) => $"https://fluentassertions.com/tips/#{id}";
113114
}

src/FluentAssertions.Analyzers/Tips/FluentAssertionsCodeFixProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected override CreateChangedDocument TryComputeFix(IInvocationOperation asse
2525
return null;
2626
}
2727

28-
if (visitorName is nameof(DiagnosticMetadata.ShouldEquals))
28+
if (visitorName is nameof(DiagnosticMetadata.ShouldEquals) or nameof(DiagnosticMetadata.NullConditionalMayNotExecute))
2929
{
3030
return null;
3131
}

src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.Utils.cs

+17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq;
66
using System.Threading.Tasks;
77
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.Editing;
89
using Microsoft.CodeAnalysis.Operations;
910

1011
namespace FluentAssertions.Analyzers;
@@ -36,6 +37,22 @@ private static bool TryGetExceptionPropertyAssertion(IInvocationOperation assert
3637
return false;
3738
}
3839

40+
private static bool HasConditionalAccessAncestor(IInvocationOperation invocation)
41+
{
42+
var current = invocation.Parent;
43+
while (current is not null)
44+
{
45+
if (current.Kind is OperationKind.ConditionalAccess)
46+
{
47+
return true;
48+
}
49+
50+
current = current.Parent;
51+
}
52+
53+
return false;
54+
}
55+
3956
private class FluentAssertionsMetadata
4057
{
4158
public FluentAssertionsMetadata(Compilation compilation)

src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs
4949
{
5050
return;
5151
}
52-
53-
if (assertion.Parent.Kind is OperationKind.ConditionalAccess)
52+
53+
if (HasConditionalAccessAncestor(invocation))
5454
{
55-
return; // Handled by NullConditionalAssertionAnalyzer
55+
var expressionStatement = invocation.GetFirstAncestor<IExpressionStatementOperation>();
56+
context.ReportDiagnostic(CreateDiagnostic(expressionStatement, DiagnosticMetadata.NullConditionalMayNotExecute));
57+
return;
5658
}
5759

5860
var subject = invocation.Arguments[0].Value;

src/FluentAssertions.Analyzers/Tips/NullConditionalAssertionAnalyzer.cs

-96
This file was deleted.

0 commit comments

Comments
 (0)