Skip to content

Commit

Permalink
feat: support proprties of objects (#211)
Browse files Browse the repository at this point in the history
* reproduce flow with failing test

* reproduce flow with failing test

* reproduce flow with failing test

* feat: support assertions on property of object

* feat: support assertions on property of object

* revert rename refactoring
  • Loading branch information
Meir017 authored Aug 22, 2023
1 parent 5f0abeb commit 5f4f3f5
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 19 deletions.
46 changes: 43 additions & 3 deletions src/FluentAssertions.Analyzers.Tests/Tips/SanityTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using FluentAssertions.Analyzers.Xunit;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace FluentAssertions.Analyzers.Tests
Expand Down Expand Up @@ -314,5 +311,48 @@ public static void True(bool input)
Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 9) }
});
}

[TestMethod]
[Implemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/207")]
public void PropertiesOfTypes()
{
const string source = @"
using Xunit;
using FluentAssertions;
using FluentAssertions.Extensions;
using System.Collections.Generic;
using System.Linq;
public class TestClass
{
public static void Main()
{
var x = new TestType1();
x.Prop1.Prop2.List.Any().Should().BeTrue();
x.Prop1.Prop2.Count.Should().BeGreaterThan(10);
}
}
public class TestType1
{
public TestType2 Prop1 { get; set; }
}
public class TestType2
{
public TestType3 Prop2 { get; set; }
}
public class TestType3
{
public List<int> List { get; set; }
public int Count { get; set; }
}";

DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(new[] { source }, new DiagnosticResult()
{
Id = CollectionShouldNotBeEmptyAnalyzer.DiagnosticId,
Message = CollectionShouldNotBeEmptyAnalyzer.Message,
Severity = DiagnosticSeverity.Info,
Locations = new[] { new DiagnosticResultLocation("Test0.cs", 12, 9) }
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@ protected virtual Diagnostic AnalyzeExpression(ExpressionSyntax expression, Sema
var variableNameExtractor = new VariableNameExtractor(semanticModel);
expression.Accept(variableNameExtractor);

if (variableNameExtractor.VariableIdentifierName == null) return null;
var typeInfo = semanticModel.GetTypeInfo(variableNameExtractor.VariableIdentifierName);
if (!ShouldAnalyzeVariableTypeCore(typeInfo.Type, semanticModel)) return null;
if (variableNameExtractor.PropertiesAccessed
.ConvertAll(identifier => semanticModel.GetTypeInfo(identifier))
.TrueForAll(typeInfo => !ShouldAnalyzeVariableTypeCore(typeInfo.Type, semanticModel))) {
return null;
}

foreach (var visitor in Visitors)
{
Expand Down
19 changes: 6 additions & 13 deletions src/FluentAssertions.Analyzers/Utilities/VariableNameExtractor.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis;
using System.Collections.Generic;

namespace FluentAssertions.Analyzers
{
public class VariableNameExtractor : CSharpSyntaxWalker
{
private readonly SemanticModel _semanticModel;

public string VariableName { get; private set; }
public IdentifierNameSyntax VariableIdentifierName { get; private set; }
public string VariableName => VariableIdentifierName?.Identifier.Text;
public IdentifierNameSyntax VariableIdentifierName => PropertiesAccessed.Count > 0 ? PropertiesAccessed[0] : null;

public List<IdentifierNameSyntax> PropertiesAccessed { get; } = new List<IdentifierNameSyntax>();

public VariableNameExtractor(SemanticModel semanticModel = null)
{
Expand All @@ -20,17 +23,7 @@ public override void VisitIdentifierName(IdentifierNameSyntax node)
{
if (IsVariable(node))
{
VariableName = node.Identifier.Text;
VariableIdentifierName = node;
}
}

public override void Visit(SyntaxNode node)
{
// the first identifier encountered will be the one at the bottom of the syntax tree
if (VariableName == null)
{
base.Visit(node);
PropertiesAccessed.Add(node);
}
}

Expand Down

0 comments on commit 5f4f3f5

Please sign in to comment.