From 5f4f3f530361d1a80a8b020583d1143fab305e9c Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Tue, 22 Aug 2023 19:11:33 +0300 Subject: [PATCH] feat: support proprties of objects (#211) * 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 --- .../Tips/SanityTests.cs | 46 +++++++++++++++++-- .../Utilities/FluentAssertionsAnalyzer.cs | 8 ++-- .../Utilities/VariableNameExtractor.cs | 19 +++----- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/FluentAssertions.Analyzers.Tests/Tips/SanityTests.cs b/src/FluentAssertions.Analyzers.Tests/Tips/SanityTests.cs index 216271cb..cdd7ff3a 100644 --- a/src/FluentAssertions.Analyzers.Tests/Tips/SanityTests.cs +++ b/src/FluentAssertions.Analyzers.Tests/Tips/SanityTests.cs @@ -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 @@ -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 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) } + }); + } } } \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs index d7425bfa..da71de5b 100644 --- a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs +++ b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs @@ -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) { diff --git a/src/FluentAssertions.Analyzers/Utilities/VariableNameExtractor.cs b/src/FluentAssertions.Analyzers/Utilities/VariableNameExtractor.cs index e3379e0a..e337f5ec 100644 --- a/src/FluentAssertions.Analyzers/Utilities/VariableNameExtractor.cs +++ b/src/FluentAssertions.Analyzers/Utilities/VariableNameExtractor.cs @@ -1,6 +1,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis; +using System.Collections.Generic; namespace FluentAssertions.Analyzers { @@ -8,8 +9,10 @@ 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 PropertiesAccessed { get; } = new List(); public VariableNameExtractor(SemanticModel semanticModel = null) { @@ -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); } }