diff --git a/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md b/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md index 43b4b1fb3b..60a1fce5b5 100644 --- a/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md @@ -10,3 +10,4 @@ MSTEST0065 | Usage | Warning | AvoidAssertAreEqualOnCollectionsAnalyzer, [Docume MSTEST0066 | Design | Info | IgnoreShouldHaveJustificationAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0066) MSTEST0067 | Usage | Disabled | AvoidThreadSleepAndTaskWaitInTestsAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0067) MSTEST0068 | Usage | Info | CollectionAssertToAssertAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0068) +MSTEST0070 | Usage | Warning | MemberConditionShouldBeValidAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0070) diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs index 6774a4ef0b..585c7b2398 100644 --- a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs +++ b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace MSTest.Analyzers.Helpers; @@ -74,4 +74,5 @@ internal static class DiagnosticIds public const string AvoidThreadSleepAndTaskWaitInTestsRuleId = "MSTEST0067"; public const string CollectionAssertToAssertRuleId = "MSTEST0068"; // public const string InheritedTestClassAttributeWithSourceGeneratorRuleId = "MSTEST0069"; - // Reserved. Owned by MSTest.SourceGeneration analyzer; don't reuse this ID. + public const string MemberConditionShouldBeValidRuleId = "MSTEST0070"; } diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs b/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs index 69a0d144e2..1336545d01 100644 --- a/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs +++ b/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace MSTest.Analyzers.Helpers; @@ -29,6 +29,8 @@ internal static class WellKnownTypeNames public const string MicrosoftVisualStudioTestToolsUnitTestingGlobalTestInitializeAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.GlobalTestInitializeAttribute"; public const string MicrosoftVisualStudioTestToolsUnitTestingIgnoreAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.IgnoreAttribute"; public const string MicrosoftVisualStudioTestToolsUnitTestingInheritanceBehavior = "Microsoft.VisualStudio.TestTools.UnitTesting.InheritanceBehavior"; + public const string MicrosoftVisualStudioTestToolsUnitTestingMemberConditionAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.MemberConditionAttribute"; + public const string MicrosoftVisualStudioTestToolsUnitTestingOSConditionAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.OSConditionAttribute"; public const string MicrosoftVisualStudioTestToolsUnitTestingOwnerAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.OwnerAttribute"; public const string MicrosoftVisualStudioTestToolsUnitTestingParallelizeAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.ParallelizeAttribute"; public const string MicrosoftVisualStudioTestToolsUnitTestingPriorityAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.PriorityAttribute"; @@ -42,7 +44,6 @@ internal static class WellKnownTypeNames public const string MicrosoftVisualStudioTestToolsUnitTestingTestPropertyAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute"; public const string MicrosoftVisualStudioTestToolsUnitTestingTimeoutAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.TimeoutAttribute"; public const string MicrosoftVisualStudioTestToolsUnitTestingWorkItemAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.WorkItemAttribute"; - public const string MicrosoftVisualStudioTestToolsUnitTestingOSConditionAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.OSConditionAttribute"; public const string System = "System"; public const string SystemRuntimeInteropServicesRuntimeInformation = "System.Runtime.InteropServices.RuntimeInformation"; diff --git a/src/Analyzers/MSTest.Analyzers/MemberConditionShouldBeValidAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/MemberConditionShouldBeValidAnalyzer.cs new file mode 100644 index 0000000000..297a9ae60f --- /dev/null +++ b/src/Analyzers/MSTest.Analyzers/MemberConditionShouldBeValidAnalyzer.cs @@ -0,0 +1,344 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; + +using Analyzer.Utilities.Extensions; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +using MSTest.Analyzers.Helpers; + +namespace MSTest.Analyzers; + +/// +/// MSTEST0070: . +/// +[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] +public sealed class MemberConditionShouldBeValidAnalyzer : DiagnosticAnalyzer +{ + private static readonly LocalizableResourceString Title = new(nameof(Resources.MemberConditionShouldBeValidTitle), Resources.ResourceManager, typeof(Resources)); + private static readonly LocalizableResourceString Description = new(nameof(Resources.MemberConditionShouldBeValidDescription), Resources.ResourceManager, typeof(Resources)); + + /// + public static readonly DiagnosticDescriptor MemberNotFoundRule = DiagnosticDescriptorHelper.Create( + DiagnosticIds.MemberConditionShouldBeValidRuleId, + Title, + new LocalizableResourceString(nameof(Resources.MemberConditionShouldBeValidMessageFormat_MemberNotFound), Resources.ResourceManager, typeof(Resources)), + Description, + Category.Usage, + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + /// + public static readonly DiagnosticDescriptor MemberNotPublicRule = MemberNotFoundRule + .WithMessage(new(nameof(Resources.MemberConditionShouldBeValidMessageFormat_MemberNotPublic), Resources.ResourceManager, typeof(Resources))); + + /// + public static readonly DiagnosticDescriptor MemberNotStaticRule = MemberNotFoundRule + .WithMessage(new(nameof(Resources.MemberConditionShouldBeValidMessageFormat_MemberNotStatic), Resources.ResourceManager, typeof(Resources))); + + /// + public static readonly DiagnosticDescriptor MemberWrongKindRule = MemberNotFoundRule + .WithMessage(new(nameof(Resources.MemberConditionShouldBeValidMessageFormat_MemberWrongKind), Resources.ResourceManager, typeof(Resources))); + + /// + public static readonly DiagnosticDescriptor MemberWrongReturnTypeRule = MemberNotFoundRule + .WithMessage(new(nameof(Resources.MemberConditionShouldBeValidMessageFormat_MemberWrongReturnType), Resources.ResourceManager, typeof(Resources))); + + /// + public static readonly DiagnosticDescriptor MethodHasParametersRule = MemberNotFoundRule + .WithMessage(new(nameof(Resources.MemberConditionShouldBeValidMessageFormat_MethodHasParameters), Resources.ResourceManager, typeof(Resources))); + + /// + public static readonly DiagnosticDescriptor PropertyNotReadableRule = MemberNotFoundRule + .WithMessage(new(nameof(Resources.MemberConditionShouldBeValidMessageFormat_PropertyNotReadable), Resources.ResourceManager, typeof(Resources))); + + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( + MemberNotFoundRule, + MemberNotPublicRule, + MemberNotStaticRule, + MemberWrongKindRule, + MemberWrongReturnTypeRule, + MethodHasParametersRule, + PropertyNotReadableRule); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(context => + { + if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingMemberConditionAttribute, out INamedTypeSymbol? conditionAttributeSymbol)) + { + return; + } + + context.RegisterSymbolAction( + ctx => AnalyzeSymbol(ctx, conditionAttributeSymbol), + SymbolKind.Method, + SymbolKind.NamedType); + }); + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol conditionAttributeSymbol) + { + foreach (AttributeData attribute in context.Symbol.GetAttributes()) + { + if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, conditionAttributeSymbol)) + { + continue; + } + + AnalyzeAttribute(context, attribute); + } + } + + private static void AnalyzeAttribute(SymbolAnalysisContext context, AttributeData attribute) + { + if (attribute.ApplicationSyntaxReference?.GetSyntax(context.CancellationToken) is not { } attributeSyntax) + { + return; + } + + // Walk the constructor arguments. Across the 4 ctor overloads + // ( (Type, string), (Type, string, params string[]), + // (ConditionMode, Type, string), (ConditionMode, Type, string, params string[]) ) + // we can identify the condition type, the first member name, and the optional params array + // by inspecting argument kinds and types. + ITypeSymbol? conditionType = null; + var memberNames = new List(); + foreach (TypedConstant argument in attribute.ConstructorArguments) + { + if (argument.IsNull) + { + continue; + } + + if (argument.Kind == TypedConstantKind.Type && argument.Value is ITypeSymbol typeValue) + { + conditionType = typeValue; + } + else if (argument.Kind == TypedConstantKind.Primitive && argument.Value is string singleName) + { + memberNames.Add(singleName); + } + else if (argument.Kind == TypedConstantKind.Array) + { + foreach (TypedConstant element in argument.Values.Where(static e => !e.IsNull && e.Value is string)) + { + memberNames.Add((string)element.Value!); + } + } + } + + if (conditionType is null || memberNames.Count == 0) + { + return; + } + + string typeName = conditionType.Name; + + // Non-named types (arrays, pointers, function pointers) can't carry user-declared static + // bool members the way [MemberCondition] requires. The runtime will throw + // ``InvalidOperationException`` at first ``IsConditionMet`` access; surface that as + // MSTEST0070 (MemberNotFound) here so the user sees it at edit-time. + if (conditionType is not INamedTypeSymbol namedConditionType) + { + string nonNamedTypeName = string.IsNullOrEmpty(conditionType.Name) + ? conditionType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat) + : conditionType.Name; + foreach (string memberName in memberNames) + { + if (!string.IsNullOrWhiteSpace(memberName)) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotFoundRule, nonNamedTypeName, memberName)); + } + } + + return; + } + + foreach (string memberName in memberNames) + { + ValidateMember(context, attributeSyntax, namedConditionType, typeName, memberName); + } + } + + private static void ValidateMember(SymbolAnalysisContext context, SyntaxNode attributeSyntax, INamedTypeSymbol conditionType, string typeName, string memberName) + { + if (string.IsNullOrWhiteSpace(memberName)) + { + // The runtime constructor already throws ArgumentException for null/empty/whitespace + // names. Nothing useful to validate here. + return; + } + + ImmutableArray candidates = LookupMember(conditionType, memberName); + if (candidates.IsEmpty) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotFoundRule, typeName, memberName)); + return; + } + + // Match the runtime resolution order: GetProperty / GetField / GetMethod with + // BindingFlags.Public | Static | FlattenHierarchy, where GetMethod looks for the + // *parameterless* overload only. If the runtime would bind to a candidate that + // satisfies those filters, prefer it so we don't report a false positive against an + // instance member or a parameterized overload that shadows the real binding target. + ISymbol? selected = + // Property: public + static + non-indexer (runtime rejects indexers). + candidates.OfType() + .FirstOrDefault(static p => p.DeclaredAccessibility == Accessibility.Public && p.IsStatic && !p.IsIndexer) + // Field: public + static. + ?? (ISymbol?)candidates.OfType() + .FirstOrDefault(static f => f.DeclaredAccessibility == Accessibility.Public && f.IsStatic) + // Method: public + static + ordinary + parameterless. + ?? candidates.OfType() + .FirstOrDefault(static m => + m.DeclaredAccessibility == Accessibility.Public + && m.IsStatic + && m.MethodKind == MethodKind.Ordinary + && m.Parameters.Length == 0) + // Fallback when no runtime-binding candidate exists: pick the first member by + // kind so the more specific diagnostic (not-public, not-static, etc.) is reported. + ?? candidates.FirstOrDefault(static s => s.Kind == SymbolKind.Property) + ?? candidates.FirstOrDefault(static s => s.Kind == SymbolKind.Field) + ?? candidates.FirstOrDefault(static s => s.Kind == SymbolKind.Method); + + if (selected is null) + { + // A nested type, event, or other unsupported member kind is shadowing the name. + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberWrongKindRule, typeName, memberName)); + return; + } + + switch (selected) + { + case IPropertySymbol property: + ValidateProperty(context, attributeSyntax, typeName, memberName, property); + break; + + case IFieldSymbol field: + ValidateField(context, attributeSyntax, typeName, memberName, field); + break; + + case IMethodSymbol method: + ValidateMethod(context, attributeSyntax, typeName, memberName, method); + break; + + default: + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberWrongKindRule, typeName, memberName)); + break; + } + } + + private static ImmutableArray LookupMember(INamedTypeSymbol type, string memberName) + { + // Walk the type hierarchy so inherited public static members are also recognized, + // matching what reflection with `BindingFlags.Public | Static | FlattenHierarchy` would find. + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(); + INamedTypeSymbol? current = type; + while (current is not null) + { + foreach (ISymbol member in current.GetMembers(memberName)) + { + builder.Add(member); + } + + current = current.BaseType; + } + + return builder.ToImmutable(); + } + + private static void ValidateProperty(SymbolAnalysisContext context, SyntaxNode attributeSyntax, string typeName, string memberName, IPropertySymbol property) + { + if (property.IsIndexer) + { + // Indexer properties (e.g. public static bool this[int i] in C# 13+) are rejected by + // the runtime because the attribute requires a *parameterless* readable property. + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberWrongKindRule, typeName, memberName)); + return; + } + + if (property.DeclaredAccessibility != Accessibility.Public) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotPublicRule, typeName, memberName)); + return; + } + + if (!property.IsStatic) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotStaticRule, typeName, memberName)); + return; + } + + if (property.GetMethod is null || property.GetMethod.DeclaredAccessibility != Accessibility.Public) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(PropertyNotReadableRule, typeName, memberName)); + return; + } + + if (property.Type.SpecialType != SpecialType.System_Boolean) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberWrongReturnTypeRule, typeName, memberName)); + } + } + + private static void ValidateField(SymbolAnalysisContext context, SyntaxNode attributeSyntax, string typeName, string memberName, IFieldSymbol field) + { + if (field.DeclaredAccessibility != Accessibility.Public) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotPublicRule, typeName, memberName)); + return; + } + + if (!field.IsStatic) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotStaticRule, typeName, memberName)); + return; + } + + if (field.Type.SpecialType != SpecialType.System_Boolean) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberWrongReturnTypeRule, typeName, memberName)); + } + } + + private static void ValidateMethod(SymbolAnalysisContext context, SyntaxNode attributeSyntax, string typeName, string memberName, IMethodSymbol method) + { + if (method.MethodKind != MethodKind.Ordinary) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberWrongKindRule, typeName, memberName)); + return; + } + + if (method.DeclaredAccessibility != Accessibility.Public) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotPublicRule, typeName, memberName)); + return; + } + + if (!method.IsStatic) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberNotStaticRule, typeName, memberName)); + return; + } + + if (method.Parameters.Length > 0) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MethodHasParametersRule, typeName, memberName)); + return; + } + + if (method.ReturnType.SpecialType != SpecialType.System_Boolean) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberWrongReturnTypeRule, typeName, memberName)); + } + } +} diff --git a/src/Analyzers/MSTest.Analyzers/Resources.resx b/src/Analyzers/MSTest.Analyzers/Resources.resx index 0db7f0d359..af9597b960 100644 --- a/src/Analyzers/MSTest.Analyzers/Resources.resx +++ b/src/Analyzers/MSTest.Analyzers/Resources.resx @@ -950,4 +950,40 @@ The type declaring these methods should also respect the following rules: An '[Ignore]' attribute applied to a test method or test class should include a non-empty message explaining why the test or class is ignored. A justification message makes it easier to triage skipped tests, helps reviewers understand the intent, and prevents tests from being silently disabled forever. {Locked="[Ignore]"}{Locked="class"} + + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf index b2f8fb7724..c3132e9b4b 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: Použijte Assert namísto CollectionAssert {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf index 89a2aa5fe3..87669ddb46 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: Verwenden Sie „Assert“ anstelle von „CollectionAssert“ {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf index f71bc0367e..624caa85e9 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: Usar "Assert" en lugar de "CollectionAssert" {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf index 236d5160c7..c0cc28c4ac 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: Utilisez « Assert » au lieu de « CollectionAssert » {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf index efcf5c9cbb..edc65b275e 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ Anche il tipo che dichiara questi metodi deve rispettare le regole seguenti: Usare "Assert" invece di "CollectionAssert" {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf index 9bd0e25a33..294a7b4f3a 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: 'CollectionAssert' の代わりに 'Assert' を使用する {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf index 68815c26a1..eecdcdbdb8 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: 'CollectionAssert' 대신 'Assert'를 사용하세요. {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf index c98ef31983..5fd7f07552 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: Użyj instrukcji „Assert” zamiast elementu „CollectionAssert” {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf index 2ef921e5f3..68515e8476 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: Usar 'Assert' em vez de 'CollectionAssert' {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf index 931012b83e..7b46e2628c 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: Использование "Assert" вместо "CollectionAssert" {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf index 8714361ba3..a575d33ed4 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: 'CollectionAssert' yerine 'Assert' kullanın {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf index 5bd0b32459..449f14dff7 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: 使用 "Assert" 而非 "CollectionAssert" {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf index 494ee01b4c..afe1d93a42 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + @@ -295,6 +295,51 @@ The type declaring these methods should also respect the following rules: 使用 'Assert' 而不是 'CollectionAssert' {Locked="CollectionAssert"}{Locked="Assert"} + + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + The members referenced by a '[MemberCondition]' attribute must exist on the referenced type, be 'public static', return 'bool', and (for methods) be parameterless. The attribute throws at runtime when these rules are violated; this analyzer surfaces the same problems at build time so typos and refactors do not silently break test gating. + {Locked="[MemberCondition]"}{Locked="public static"}{Locked="bool"} + + + '[MemberCondition]' member '{0}.{1}' cannot be found + '[MemberCondition]' member '{0}.{1}' cannot be found + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + '[MemberCondition]' referenced member '{0}.{1}' must be 'public' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="public"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + '[MemberCondition]' referenced member '{0}.{1}' must be 'static' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="static"} + + + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + '[MemberCondition]' referenced member '{0}.{1}' must be a property, field, or parameterless method + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + '[MemberCondition]' referenced member '{0}.{1}' must return 'bool' + {0} is the containing type name. {1} is the member name. {Locked="[MemberCondition]"}{Locked="bool"} + + + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + '[MemberCondition]' referenced method '{0}.{1}' must be parameterless + {0} is the containing type name. {1} is the method name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + '[MemberCondition]' referenced property '{0}.{1}' must have a getter + {0} is the containing type name. {1} is the property name. {Locked="[MemberCondition]"} + + + '[MemberCondition]' arguments should be valid + '[MemberCondition]' arguments should be valid + {Locked="[MemberCondition]"} + DataRow entry should have the following layout to be valid: - should only be set on a test method; diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/MemberConditionShouldBeValidAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/MemberConditionShouldBeValidAnalyzerTests.cs new file mode 100644 index 0000000000..1532038c3f --- /dev/null +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/MemberConditionShouldBeValidAnalyzerTests.cs @@ -0,0 +1,579 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using VerifyCS = MSTest.Analyzers.Test.CSharpCodeFixVerifier< + MSTest.Analyzers.MemberConditionShouldBeValidAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace MSTest.Analyzers.Test; + +[TestClass] +public sealed class MemberConditionShouldBeValidAnalyzerTests +{ + [TestMethod] + public async Task WhenMemberIsValidPublicStaticBoolProperty_NoDiagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static bool IsTrue => true; + } + + [TestClass] + public class MyTestClass + { + [MemberCondition(typeof(Conditions), nameof(Conditions.IsTrue))] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + [TestMethod] + public async Task WhenMemberIsValidPublicStaticBoolField_NoDiagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static readonly bool IsTrue = true; + } + + [TestClass] + public class MyTestClass + { + [MemberCondition(typeof(Conditions), nameof(Conditions.IsTrue))] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + [TestMethod] + public async Task WhenMemberIsValidPublicStaticBoolMethod_NoDiagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static bool IsTrue() => true; + } + + [TestClass] + public class MyTestClass + { + [MemberCondition(typeof(Conditions), nameof(Conditions.IsTrue))] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + [TestMethod] + public async Task WhenAttributeIsOnTestClass_StillValidated() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static bool IsTrue => true; + } + + [{|#0:MemberCondition(typeof(Conditions), "DoesNotExist")|}] + [TestClass] + public class MyTestClass + { + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberNotFoundRule) + .WithLocation(0) + .WithArguments("Conditions", "DoesNotExist")); + } + + [TestMethod] + public async Task WhenMemberDoesNotExist_MemberNotFound() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static bool IsTrue => true; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), "DoesNotExist")|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberNotFoundRule) + .WithLocation(0) + .WithArguments("Conditions", "DoesNotExist")); + } + + [TestMethod] + public async Task WhenMemberIsInternal_MemberNotPublic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + internal static bool InternalIsTrue => true; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), nameof(Conditions.InternalIsTrue))|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberNotPublicRule) + .WithLocation(0) + .WithArguments("Conditions", "InternalIsTrue")); + } + + [TestMethod] + public async Task WhenPropertyIsInstance_MemberNotStatic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public class Conditions + { + public bool InstanceIsTrue => true; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), nameof(Conditions.InstanceIsTrue))|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberNotStaticRule) + .WithLocation(0) + .WithArguments("Conditions", "InstanceIsTrue")); + } + + [TestMethod] + public async Task WhenPropertyReturnsNonBool_MemberWrongReturnType() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static int NotBool => 42; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), nameof(Conditions.NotBool))|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberWrongReturnTypeRule) + .WithLocation(0) + .WithArguments("Conditions", "NotBool")); + } + + [TestMethod] + public async Task WhenMethodHasParameters_MethodHasParameters() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static bool WithParam(int x) => x > 0; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), nameof(Conditions.WithParam))|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MethodHasParametersRule) + .WithLocation(0) + .WithArguments("Conditions", "WithParam")); + } + + [TestMethod] + public async Task WhenMethodReturnsNonBool_MemberWrongReturnType() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static int NotBoolMethod() => 0; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), nameof(Conditions.NotBoolMethod))|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberWrongReturnTypeRule) + .WithLocation(0) + .WithArguments("Conditions", "NotBoolMethod")); + } + + [TestMethod] + public async Task WhenPropertyIsWriteOnly_PropertyNotReadable() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static bool WriteOnly { set { } } + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), nameof(Conditions.WriteOnly))|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.PropertyNotReadableRule) + .WithLocation(0) + .WithArguments("Conditions", "WriteOnly")); + } + + [TestMethod] + public async Task WhenFieldIsNonBool_MemberWrongReturnType() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static readonly string NotBoolField = "x"; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), nameof(Conditions.NotBoolField))|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberWrongReturnTypeRule) + .WithLocation(0) + .WithArguments("Conditions", "NotBoolField")); + } + + [TestMethod] + public async Task WhenAdditionalMembersAreInvalid_AllReported() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static bool IsTrue => true; + public static int NotBool => 0; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), nameof(Conditions.IsTrue), "Missing", nameof(Conditions.NotBool))|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberNotFoundRule) + .WithLocation(0) + .WithArguments("Conditions", "Missing"), + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberWrongReturnTypeRule) + .WithLocation(0) + .WithArguments("Conditions", "NotBool")); + } + + [TestMethod] + public async Task WhenExplicitConditionMode_StillValidated() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static bool IsTrue => true; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(ConditionMode.Exclude, typeof(Conditions), "DoesNotExist")|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberNotFoundRule) + .WithLocation(0) + .WithArguments("Conditions", "DoesNotExist")); + } + + [TestMethod] + public async Task WhenInheritedPublicStaticMember_NoDiagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public class BaseConditions + { + public static bool InheritedIsTrue => true; + } + + public class DerivedConditions : BaseConditions + { + } + + [TestClass] + public class MyTestClass + { + [MemberCondition(typeof(DerivedConditions), nameof(DerivedConditions.InheritedIsTrue))] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + [TestMethod] + public async Task WhenMemberIsEvent_MemberWrongKind() + { + string code = """ + using System; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static event EventHandler SomeEvent; + + public static void Raise() => SomeEvent?.Invoke(null, EventArgs.Empty); + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), nameof(Conditions.SomeEvent))|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberWrongKindRule) + .WithLocation(0) + .WithArguments("Conditions", "SomeEvent")); + } + + [TestMethod] + public async Task WhenFieldIsInstance_MemberNotStatic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public class Conditions + { + public bool InstanceField = true; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), nameof(Conditions.InstanceField))|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberNotStaticRule) + .WithLocation(0) + .WithArguments("Conditions", "InstanceField")); + } + + [TestMethod] + public async Task WhenMultipleConditionAttributes_EachValidated() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static bool IsTrue => true; + } + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(Conditions), "First")|}] + [{|#1:MemberCondition(typeof(Conditions), "Second")|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberNotFoundRule) + .WithLocation(0) + .WithArguments("Conditions", "First"), + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberNotFoundRule) + .WithLocation(1) + .WithArguments("Conditions", "Second")); + } + + [TestMethod] + public async Task WhenMethodHasParameterlessAndParameterizedOverloads_PicksParameterless_NoDiagnostic() + { + // Runtime binding uses Type.GetMethod(name, ..., types: Type.EmptyTypes), which selects + // the parameterless overload. The analyzer must not falsely flag this just because the + // parameterized overload comes first in declaration order. + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Conditions + { + public static bool IsTrue(int unused) => true; + public static bool IsTrue() => true; + } + + [TestClass] + public class MyTestClass + { + [MemberCondition(typeof(Conditions), nameof(Conditions.IsTrue))] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + [TestMethod] + public async Task WhenDerivedHasInstanceMemberShadowingBaseStatic_PicksBaseStatic_NoDiagnostic() + { + // Runtime FlattenHierarchy + Public + Static binds to Base.IsTrue (the static one), + // not Derived.IsTrue (the instance one). The analyzer must mirror that. + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public class Base + { + public static bool IsTrue => true; + } + + public class Derived : Base + { + public new bool IsTrue => false; // instance, shadows the static base member + } + + [TestClass] + public class MyTestClass + { + [MemberCondition(typeof(Derived), nameof(Derived.IsTrue))] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + [TestMethod] + public async Task WhenConditionTypeIsArrayType_MemberNotFound() + { + // typeof(int[]) is an IArrayTypeSymbol, not INamedTypeSymbol. The runtime would still throw + // InvalidOperationException because int[] has no user-declared static bool members; the + // analyzer must surface MSTEST0070 rather than silently skipping the attribute. + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [{|#0:MemberCondition(typeof(int[]), "AnyName")|}] + [TestMethod] + public void TestMethod() { } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + code, + VerifyCS.Diagnostic(MemberConditionShouldBeValidAnalyzer.MemberNotFoundRule) + .WithLocation(0) + .WithArguments("int[]", "AnyName")); + } +}