diff --git a/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs index 95b858d033..59555f1239 100644 --- a/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs @@ -155,9 +155,10 @@ private static void AnalyzeAttribute(SymbolAnalysisContext context, AttributeDat private static (ISymbol? Member, bool AreTooMany) TryGetMember(INamedTypeSymbol declaringType, string memberName) { INamedTypeSymbol? currentType = declaringType; + bool disallowPrivate = false; while (currentType is not null) { - (ISymbol? Member, bool AreTooMany) result = TryGetMemberCore(currentType, memberName); + (ISymbol? Member, bool AreTooMany) result = TryGetMemberCore(currentType, memberName, disallowPrivate); if (result.Member is not null || result.AreTooMany) { return result; @@ -165,14 +166,21 @@ private static (ISymbol? Member, bool AreTooMany) TryGetMember(INamedTypeSymbol // Only continue to look at base types if the member is not found on the current type and we are not hit by "too many methods" rule. currentType = currentType.BaseType; + disallowPrivate = true; } return (null, false); - static (ISymbol? Member, bool AreTooMany) TryGetMemberCore(INamedTypeSymbol declaringType, string memberName) + static (ISymbol? Member, bool AreTooMany) TryGetMemberCore(INamedTypeSymbol declaringType, string memberName, bool disallowPrivate) { + ImmutableArray potentialMembers = declaringType.GetMembers(memberName); + if (disallowPrivate) + { + potentialMembers = potentialMembers.RemoveAll(m => m.DeclaredAccessibility == Accessibility.Private); + } + // If we cannot find the member on the given type, report a diagnostic. - if (declaringType.GetMembers(memberName) is { Length: 0 } potentialMembers) + if (potentialMembers is { Length: 0 }) { return (null, false); } diff --git a/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs b/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs index 7521a1dd01..be9c634584 100644 --- a/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs +++ b/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs @@ -5,7 +5,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting; internal static class DynamicDataOperations { - private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; + private const BindingFlags MemberLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy; public static IEnumerable GetData(Type? dynamicDataDeclaringType, DynamicDataSourceType dynamicDataSourceType, string dynamicDataSourceName, object?[] dynamicDataSourceArguments, MethodInfo methodInfo) { @@ -19,15 +19,15 @@ public static IEnumerable GetData(Type? dynamicDataDeclaringType, Dyna { case DynamicDataSourceType.AutoDetect: #pragma warning disable IDE0045 // Convert to conditional expression - it becomes less readable. - if (GetPropertyConsideringInheritance(dynamicDataDeclaringType, dynamicDataSourceName) is { } dynamicDataPropertyInfo) + if (dynamicDataDeclaringType.GetProperty(dynamicDataSourceName, MemberLookup) is { } dynamicDataPropertyInfo) { obj = GetDataFromProperty(dynamicDataPropertyInfo); } - else if (GetMethodConsideringInheritance(dynamicDataDeclaringType, dynamicDataSourceName) is { } dynamicDataMethodInfo) + else if (dynamicDataDeclaringType.GetMethod(dynamicDataSourceName, MemberLookup) is { } dynamicDataMethodInfo) { obj = GetDataFromMethod(dynamicDataMethodInfo, dynamicDataSourceArguments); } - else if (GetFieldConsideringInheritance(dynamicDataDeclaringType, dynamicDataSourceName) is { } dynamicDataFieldInfo) + else if (dynamicDataDeclaringType.GetField(dynamicDataSourceName, MemberLookup) is { } dynamicDataFieldInfo) { obj = GetDataFromField(dynamicDataFieldInfo); } @@ -39,21 +39,21 @@ public static IEnumerable GetData(Type? dynamicDataDeclaringType, Dyna break; case DynamicDataSourceType.Property: - PropertyInfo property = GetPropertyConsideringInheritance(dynamicDataDeclaringType, dynamicDataSourceName) + PropertyInfo property = dynamicDataDeclaringType.GetProperty(dynamicDataSourceName, MemberLookup) ?? throw new ArgumentNullException($"{DynamicDataSourceType.Property} {dynamicDataSourceName}"); obj = GetDataFromProperty(property); break; case DynamicDataSourceType.Method: - MethodInfo method = GetMethodConsideringInheritance(dynamicDataDeclaringType, dynamicDataSourceName) + MethodInfo method = dynamicDataDeclaringType.GetMethod(dynamicDataSourceName, MemberLookup) ?? throw new ArgumentNullException($"{DynamicDataSourceType.Method} {dynamicDataSourceName}"); obj = GetDataFromMethod(method, dynamicDataSourceArguments); break; case DynamicDataSourceType.Field: - FieldInfo field = GetFieldConsideringInheritance(dynamicDataDeclaringType, dynamicDataSourceName) + FieldInfo field = dynamicDataDeclaringType.GetField(dynamicDataSourceName, MemberLookup) ?? throw new ArgumentNullException($"{DynamicDataSourceType.Field} {dynamicDataSourceName}"); obj = GetDataFromField(field); @@ -170,58 +170,4 @@ private static bool TryGetData(object dataSource, [NotNullWhen(true)] out IEnume data = null; return false; } - - private static FieldInfo? GetFieldConsideringInheritance(Type type, string fieldName) - { - // NOTE: Don't use GetRuntimeField. It considers inheritance only for instance fields. - Type? currentType = type; - while (currentType is not null) - { - FieldInfo? field = currentType.GetField(fieldName, DeclaredOnlyLookup); - if (field is not null) - { - return field; - } - - currentType = currentType.BaseType; - } - - return null; - } - - private static PropertyInfo? GetPropertyConsideringInheritance(Type type, string propertyName) - { - // NOTE: Don't use GetRuntimeProperty. It considers inheritance only for instance properties. - Type? currentType = type; - while (currentType is not null) - { - PropertyInfo? property = currentType.GetProperty(propertyName, DeclaredOnlyLookup); - if (property is not null) - { - return property; - } - - currentType = currentType.BaseType; - } - - return null; - } - - private static MethodInfo? GetMethodConsideringInheritance(Type type, string methodName) - { - // NOTE: Don't use GetRuntimeMethod. It considers inheritance only for instance methods. - Type? currentType = type; - while (currentType is not null) - { - MethodInfo? method = currentType.GetMethod(methodName, DeclaredOnlyLookup); - if (method is not null) - { - return method; - } - - currentType = currentType.BaseType; - } - - return null; - } } diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs index f4f236ae00..e74adb0abb 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs @@ -1204,6 +1204,43 @@ public void TestMethod2(object[] o) await VerifyCS.VerifyAnalyzerAsync(code); } + [TestMethod] + public async Task WhenPrivateMemberIsFromBase_Diagnostic() + { + string code = """ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public abstract class MyTestClassBase + { + private static IEnumerable Data => new List(); + private static IEnumerable GetData() => new List(); + } + + [TestClass] + public class MyTestClass : MyTestClassBase + { + [{|#0:DynamicData("Data")|}] + [TestMethod] + public void TestMethod1(object[] o) + { + } + + [{|#1:DynamicData("GetData", DynamicDataSourceType.Method)|}] + [TestMethod] + public void TestMethod2(object[] o) + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberNotFoundRule).WithLocation(0).WithArguments("MyTestClass", "Data"), + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberNotFoundRule).WithLocation(1).WithArguments("MyTestClass", "GetData")); + } + [TestMethod] public async Task WhenMethodHasParameters_Diagnostic() {