Skip to content

Commit c317f3e

Browse files
authored
feat: Fix dictionary custom methods false diagnostics (#379)
* feat: Fix dictionary custom methods false diagnostics
1 parent ecde7ec commit c317f3e

File tree

3 files changed

+47
-2
lines changed

3 files changed

+47
-2
lines changed

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,31 @@ public class DictionaryTests
1313
[Implemented]
1414
public void DictionaryShouldContainKey_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.DictionaryShouldContainKey_ContainsKeyShouldBeTrue);
1515

16+
[DataTestMethod]
17+
[DataRow(
18+
@"using System.Collections.Generic;
19+
using FluentAssertions;
20+
21+
namespace TestNamespace
22+
{
23+
public class MultiKeyDict<TKey1, TKey2, TValue> : Dictionary<TKey1, Dictionary<TKey2, TValue>>
24+
{
25+
public bool ContainsKey(TKey1 key1, TKey2 key2) => false;
26+
public bool ContainsValue(TKey1 key1, TKey2 key2) => false;
27+
}
28+
29+
public class TestClass
30+
{
31+
public void TestMethod(MultiKeyDict<int, int, string> actual)
32+
{
33+
actual.ContainsKey(0, 1).Should().BeTrue();
34+
actual.ContainsValue(0, 1).Should().BeTrue();
35+
}
36+
}
37+
}")]
38+
[Implemented]
39+
public void DictionaryMethods_CustomMethods_TestNoAnalyzer(string code) => DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(code);
40+
1641
[DataTestMethod]
1742
[AssertionCodeFix(
1843
oldAssertion: "actual.ContainsKey(expectedKey).Should().BeTrue({0});",

src/FluentAssertions.Analyzers/Tips/FluentAssertionsAnalyzer.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs
4949
return;
5050
}
5151

52+
context.Options.AnalyzerConfigOptionsProvider.GetOptions(invocation.Syntax.SyntaxTree).TryGetValue("use_diagnostic_per_assertion", out var useDiagnosticPerAssertion);
5253
if (HasConditionalAccessAncestor(invocation))
5354
{
5455
var expressionStatement = invocation.GetFirstAncestor<IExpressionStatementOperation>();
@@ -211,10 +212,12 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs
211212
case nameof(string.StartsWith) when invocationBeforeShould.IsContainedInType(SpecialType.System_String):
212213
context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.StringShouldStartWith_StartsWithShouldBeTrue));
213214
return;
214-
case nameof(IDictionary<string, object>.ContainsKey) when invocationBeforeShould.ImplementsOrIsInterface(metadata.IDictionaryOfT2) || invocationBeforeShould.ImplementsOrIsInterface(metadata.IReadonlyDictionaryOfT2):
215+
case nameof(Dictionary<string, object>.ContainsKey) when (invocationBeforeShould.ImplementsOrIsInterface(metadata.IDictionaryOfT2) || invocationBeforeShould.ImplementsOrIsInterface(metadata.IReadonlyDictionaryOfT2))
216+
&& invocationBeforeShould.AreMethodParameterSameTypeAsContainingTypeArguments((parameter: 0, typeArgument: 0)):
215217
context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.DictionaryShouldContainKey_ContainsKeyShouldBeTrue));
216218
return;
217-
case nameof(Dictionary<string, object>.ContainsValue) when invocationBeforeShould.IsContainedInType(metadata.DictionaryOfT2):
219+
case nameof(Dictionary<string, object>.ContainsValue) when invocationBeforeShould.IsContainedInType(metadata.DictionaryOfT2)
220+
&& invocationBeforeShould.AreMethodParameterSameTypeAsContainingTypeArguments((parameter: 0, typeArgument: 1)):
218221
context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.DictionaryShouldContainValue_ContainsValueShouldBeTrue));
219222
return;
220223
}

src/FluentAssertions.Analyzers/Utilities/OperartionExtensions.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,23 @@ public static bool IsTypeof(this IArgumentOperation argument, SpecialType type)
8888
public static bool IsTypeof(this IArgumentOperation argument, INamedTypeSymbol type)
8989
=> argument.Value.UnwrapConversion().Type.EqualsSymbol(type);
9090

91+
public static bool AreMethodParameterSameTypeAsContainingTypeArguments(this IInvocationOperation invocation, params (int parameter, int typeArgument)[] parameters)
92+
{
93+
if (invocation.TargetMethod.Parameters.Length != parameters.Length)
94+
return false;
95+
96+
if (invocation.TargetMethod.ContainingType.TypeArguments.Length < parameters.Max(x => x.typeArgument))
97+
return false;
98+
99+
foreach (var (parameter, typeArgument) in parameters)
100+
{
101+
if (!invocation.TargetMethod.Parameters[parameter].Type.EqualsSymbol(invocation.TargetMethod.ContainingType.TypeArguments[typeArgument]))
102+
return false;
103+
}
104+
105+
return true;
106+
}
107+
91108
public static bool IsSameArgumentReference(this IArgumentOperation argument1, IArgumentOperation argument2)
92109
{
93110
return argument1.TryGetFirstDescendent<IParameterReferenceOperation>(out var argument1Reference)

0 commit comments

Comments
 (0)