From 992760e37e8396b6507795aafb6fa4225c4b212e Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 17 Jan 2023 16:21:52 +0100 Subject: [PATCH 01/24] WIP: Add support for InheritedAttributes for methods --- .../Rules/TestMethodShouldContainAssertion.cs | 2 +- .../Helpers/SymbolHelper.cs | 58 +++++++++++ .../Helpers/SymbolHelperTest.cs | 99 +++++++++++++++++++ .../TestFramework/SnippetCompiler.cs | 26 +++-- 4 files changed, 176 insertions(+), 9 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/TestMethodShouldContainAssertion.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/TestMethodShouldContainAssertion.cs index c5f0092cac6..504921d4719 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/TestMethodShouldContainAssertion.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/TestMethodShouldContainAssertion.cs @@ -152,6 +152,6 @@ private static bool IsTestIgnored(IMethodSymbol method) || methodSymbol.ContainingType.DerivesFromAny(KnownAssertionTypes); private static bool IsCustomAssertion(ISymbol methodSymbol) => - methodSymbol.GetAttributes().Any(x => x.AttributeClass.Name == CustomAssertionAttributeName); + methodSymbol.GetAttributesWithInherited().Any(x => x.AttributeClass.Name == CustomAssertionAttributeName); } } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs index c61a3476130..42bcd10d744 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs @@ -205,6 +205,64 @@ public static bool IsPubliclyAccessible(this ISymbol symbol) symbol?.GetAttributes().Where(a => a.AttributeClass.Is(attributeType)) ?? Enumerable.Empty(); + internal static IEnumerable GetAttributesWithInherited(this ISymbol symbol) + { + foreach (var attribute in symbol.GetAttributes()) + { + yield return attribute; + } + + var baseSymbol = GetBaseSymbol(symbol); + while (baseSymbol != null) + { + foreach (var attribute in baseSymbol.GetAttributes().Where(IsInherited)) + { + yield return attribute; + } + + baseSymbol = GetBaseSymbol(baseSymbol); + } + } + + private static ISymbol GetBaseSymbol(ISymbol symbol) => + symbol switch + { + INamedTypeSymbol namedType => namedType.BaseType, + IMethodSymbol { OriginalDefinition: { } originalDefinition } method when method != originalDefinition => GetBaseSymbol(originalDefinition), + IMethodSymbol { IsOverride: true, OverriddenMethod: { } overridenMethod } => overridenMethod, + _ => null, + }; + + private static bool IsInherited(this AttributeData attribute) + { + if (attribute.AttributeClass == null) + { + return false; + } + + foreach (var attributeAttribute in attribute.AttributeClass.GetAttributes()) + { + var @class = attributeAttribute.AttributeClass; + if (@class != null && @class.Name == nameof(AttributeUsageAttribute) && + @class.ContainingNamespace?.Name == "System") + { + foreach (var kvp in attributeAttribute.NamedArguments) + { + if (kvp.Key == nameof(AttributeUsageAttribute.Inherited)) + { + return (bool)kvp.Value.Value!; + } + } + + // Default value of Inherited is true + return true; + } + } + + return true; + } + + internal static IEnumerable GetAttributes(this ISymbol symbol, ImmutableArray attributeTypes) => symbol?.GetAttributes().Where(a => a.AttributeClass.IsAny(attributeTypes)) ?? Enumerable.Empty(); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index fb55a96aee8..26715b07261 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.IO; using Microsoft.CodeAnalysis.CSharp.Syntax; using Moq; using SonarAnalyzer.Common; @@ -398,5 +399,103 @@ public void GetClassification_Method(MethodKind methodKind, string expected) fakeSymbol.Object.GetClassification().Should().Be(expected); } + + [DataTestMethod] + [DataRow("BaseClass`1", "VirtualMethod", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedOpenGeneric`1", "VirtualMethod", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedOpenGeneric`1", "NonVirtualMethod")] + [DataRow("DerivedOpenGeneric`1", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedOpenGeneric`1", "GenericNonVirtualMethod`1")] + [DataRow("DerivedClosedGeneric", "VirtualMethod", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedClosedGeneric", "NonVirtualMethod")] + [DataRow("DerivedClosedGeneric", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedClosedGeneric", "GenericNonVirtualMethod`1")] + [DataRow("DerivedNoOverrides`1", "VirtualMethod", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedNoOverrides`1", "NonVirtualMethod", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedNoOverrides`1", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedNoOverrides`1", "GenericNonVirtualMethod`1", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] + public void GetAttributesWithInherited_MethodSymbol(string className, string methodName, params string[] expectedAttributes) + { + var code = $$""" + using System; + + [AttributeUsage(AttributeTargets.All, Inherited = true)] + public class MyInheritedAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.All, Inherited = false)] + public class MyNotInheritedAttribute : Attribute { } + + public class MyUnannotatedAttribute : Attribute { } + + [MyInheritedAttribute] + [MyNotInherited] + [MyUnannotatedAttribute] + public class BaseClass + { + [MyInheritedAttribute] + [MyNotInherited] + [MyUnannotatedAttribute] + public virtual void VirtualMethod() { } + + [MyInheritedAttribute] + [MyNotInherited] + [MyUnannotatedAttribute] + public void NonVirtualMethod() { } + + [MyInheritedAttribute] + [MyNotInherited] + [MyUnannotatedAttribute] + public void GenericNonVirtualMethod() { } + + [MyInheritedAttribute] + [MyNotInherited] + [MyUnannotatedAttribute] + public virtual void GenericVirtualMethod() { } + } + + public class DerivedOpenGeneric: BaseClass + { + public override void VirtualMethod() { } + public new void NonVirtualMethod() { } + public new void GenericNonVirtualMethod() { } + public override void GenericVirtualMethod() { } + } + + public class DerivedClosedGeneric: BaseClass + { + public override void VirtualMethod() { } + public new void NonVirtualMethod() { } + public new void GenericNonVirtualMethod() { } + public override void GenericVirtualMethod() { } + } + + public class DerivedNoOverrides: BaseClass + { + } + + public class Program + { + public static void Main() + { + var baseClass = new BaseClass(); + var derivedOpen = new DerivedOpenGeneric(); + var derivedClosed = new DerivedClosedGeneric(); + var derivedNoOverrides = new DerivedNoOverrides(); + new {{className.Replace(@"`1", "")}}().{{methodName.Replace(@"`1", "")}}(); + } + } + """; + var compiler = new SnippetCompiler(code); + var invocationExpression = compiler.GetNodes().Should().ContainSingle().Subject; + var method = compiler.GetSymbol(invocationExpression); + var actual = method.GetAttributesWithInherited().Select(x => x.AttributeClass.Name).ToList(); + actual.Should().BeEquivalentTo(expectedAttributes); + + // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: + var assembly = compiler.GetEmittedAssembly(); + var type = assembly.GetType(className, throwOnError: true); + var methodInfo = type.GetMethod(methodName.Replace("`1", string.Empty)); + methodInfo.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); + } } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs index f140b00b666..1016929c641 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs @@ -18,6 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.IO; +using System.Threading; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.VisualBasic; using SonarAnalyzer.AnalysisContext; @@ -32,8 +34,7 @@ namespace SonarAnalyzer.UnitTest.TestFramework /// internal class SnippetCompiler { - private readonly Compilation compilation; - + public Compilation Compilation { get; } public SyntaxTree SyntaxTree { get; } public SemanticModel SemanticModel { get; } @@ -43,25 +44,25 @@ internal class SnippetCompiler public SnippetCompiler(string code, bool ignoreErrors, AnalyzerLanguage language, IEnumerable additionalReferences = null, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) { - compilation = SolutionBuilder + Compilation = SolutionBuilder .Create() .AddProject(language, createExtraEmptyFile: false, outputKind) .AddSnippet(code) .AddReferences(additionalReferences ?? Enumerable.Empty()) .GetCompilation(); - if (!ignoreErrors && HasCompilationErrors(compilation)) + if (!ignoreErrors && HasCompilationErrors(Compilation)) { - DumpCompilationErrors(compilation); + DumpCompilationErrors(Compilation); throw new InvalidOperationException("Test setup error: test code snippet did not compile. See output window for details."); } - SyntaxTree = compilation.SyntaxTrees.First(); - SemanticModel = compilation.GetSemanticModel(SyntaxTree); + SyntaxTree = Compilation.SyntaxTrees.First(); + SemanticModel = Compilation.GetSemanticModel(SyntaxTree); } public bool IsCSharp() => - compilation.Language == LanguageNames.CSharp; + Compilation.Language == LanguageNames.CSharp; public IEnumerable GetNodes() where TSyntaxNodeType : SyntaxNode => SyntaxTree.GetRoot().DescendantNodes().OfType(); @@ -146,6 +147,15 @@ public SonarSyntaxNodeReportingContext CreateAnalysisContext(SyntaxNode node) return new(AnalysisScaffolding.CreateSonarAnalysisContext(), nodeContext); } + public System.Reflection.Assembly GetEmittedAssembly() + { + using var memoryStream = new MemoryStream(); + var emitResult = Compilation.Emit(memoryStream); + Debug.Assert(emitResult.Success, "The provided snippet could not be emitted."); + var assembly = System.Reflection.Assembly.Load(memoryStream.ToArray()); + return assembly; + } + private static bool HasCompilationErrors(Compilation compilation) => compilation.GetDiagnostics().Any(IsCompilationError); From 5edbd1f1a37a58461cc176366541d1295ca35b10 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 17 Jan 2023 16:22:20 +0100 Subject: [PATCH 02/24] Remove unsed using --- .../tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index 26715b07261..012873608e6 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.IO; using Microsoft.CodeAnalysis.CSharp.Syntax; using Moq; using SonarAnalyzer.Common; From c55ae73ff0182d169a97a809ce841d2ee00ca4cf Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 17 Jan 2023 16:42:08 +0100 Subject: [PATCH 03/24] Add test for TypeSymbol and clean up --- .../Helpers/SymbolHelperTest.cs | 76 +++++++++++++++++-- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index 012873608e6..013e58a530c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -426,9 +426,6 @@ public class MyNotInheritedAttribute : Attribute { } public class MyUnannotatedAttribute : Attribute { } - [MyInheritedAttribute] - [MyNotInherited] - [MyUnannotatedAttribute] public class BaseClass { [MyInheritedAttribute] @@ -476,10 +473,6 @@ public class Program { public static void Main() { - var baseClass = new BaseClass(); - var derivedOpen = new DerivedOpenGeneric(); - var derivedClosed = new DerivedClosedGeneric(); - var derivedNoOverrides = new DerivedNoOverrides(); new {{className.Replace(@"`1", "")}}().{{methodName.Replace(@"`1", "")}}(); } } @@ -496,5 +489,74 @@ public static void Main() var methodInfo = type.GetMethod(methodName.Replace("`1", string.Empty)); methodInfo.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); } + + [DataTestMethod] + [DataRow("BaseClass`1", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedOpenGeneric`1", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedClosedGeneric", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("Implement")] + public void GetAttributesWithInherited_TypeSymbol(string className, params string[] expectedAttributes) + { + var code = $$""" + using System; + + [AttributeUsage(AttributeTargets.All, Inherited = true)] + public class MyInheritedAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.All, Inherited = false)] + public class MyNotInheritedAttribute : Attribute { } + + public class MyUnannotatedAttribute : Attribute { } + + [MyInheritedAttribute] + [MyNotInherited] + [MyUnannotatedAttribute] + public class BaseClass + { + } + + [MyInheritedAttribute] + [MyNotInherited] + [MyUnannotatedAttribute] + public interface IInterface + { + } + + public class DerivedOpenGeneric: BaseClass + { + } + + public class DerivedClosedGeneric: BaseClass + { + } + + public class Implement: IInterface + { + } + + public class Program + { + public static void Main() + { + new {{className.Replace(@"`1", "")}}(); + } + } + """; + var compiler = new SnippetCompiler(code); + var objectCreation = compiler.GetNodes().Should().ContainSingle().Subject; + if (compiler.GetSymbol(objectCreation) is { MethodKind: MethodKind.Constructor, ReceiverType: { } receiver }) + { + var actual = receiver.GetAttributesWithInherited().Select(x => x.AttributeClass.Name).ToList(); + actual.Should().BeEquivalentTo(expectedAttributes); + } + else + { + Assert.Fail("Constructor could not be found."); + } + // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: + var assembly = compiler.GetEmittedAssembly(); + var type = assembly.GetType(className, throwOnError: true); + type.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); + } } } From f00585f41f9f217662f06694b9207549500c0246 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 17 Jan 2023 16:43:30 +0100 Subject: [PATCH 04/24] Fix FP --- .../TestCases/TestMethodShouldContainAssertion.Custom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TestMethodShouldContainAssertion.Custom.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TestMethodShouldContainAssertion.Custom.cs index 982c4c2dc55..0867087d968 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TestMethodShouldContainAssertion.Custom.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TestMethodShouldContainAssertion.Custom.cs @@ -64,7 +64,7 @@ public class BaseTest public class DerivedTest : BaseTest { [TestMethod] - public void Derived() // Noncompliant FP: The overridden method needs to be annotated because Roslyn does not respect AttributeUsage.Inherited in ISymbol.GetAttributes + public void Derived() // Compliant { CustomAssertionMethod(); } From 12f479722bb7caa66125d2f8b46c23c307fc8817 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 23 Jan 2023 12:43:04 +0100 Subject: [PATCH 05/24] Move AttributeUsageInherited and add more tests. --- .../Extensions/AttributeDataExtensions.cs | 18 ++++ .../Helpers/SymbolHelper.cs | 54 +++-------- .../Extensions/AttributeDataExtensionsTest.cs | 96 +++++++++++++++++++ .../Helpers/SymbolHelperTest.cs | 44 ++++++--- 4 files changed, 158 insertions(+), 54 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs index b5d6786de2f..203e9fae464 100644 --- a/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs @@ -46,6 +46,24 @@ public static bool TryGetAttributeValue(this AttributeData attribute, string } } + /// + /// Returns the setting for the attribute associated with . + /// The returned value is in line with the runtime behavior of . + /// + /// The of an . + /// + /// The value of the applied to the . + /// Returns if no is applied to the associated with . + /// + public static bool AttributeUsageInherited(this AttributeData attribute) => + attribute.AttributeClass.GetAttributes() + .Where(x => x.AttributeClass is { Name: nameof(AttributeUsageAttribute), ContainingNamespace.Name: "System" }) + .SelectMany(x => x.NamedArguments.Where(x => x.Key == nameof(AttributeUsageAttribute.Inherited))) + .Select(x => x.Value) + .Where(x => x is { Kind: TypedConstantKind.Primitive, Type.SpecialType: SpecialType.System_Boolean }) + .Select(x => (bool?)x.Value) + .FirstOrDefault() ?? true; // Default value of Inherited is true + private static bool TryConvertConstant(TypedConstant constant, out T value) { value = default; diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs index 42bcd10d744..a01b5df95b8 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs @@ -205,6 +205,10 @@ public static bool IsPubliclyAccessible(this ISymbol symbol) symbol?.GetAttributes().Where(a => a.AttributeClass.Is(attributeType)) ?? Enumerable.Empty(); + internal static IEnumerable GetAttributes(this ISymbol symbol, ImmutableArray attributeTypes) => + symbol?.GetAttributes().Where(a => a.AttributeClass.IsAny(attributeTypes)) + ?? Enumerable.Empty(); + internal static IEnumerable GetAttributesWithInherited(this ISymbol symbol) { foreach (var attribute in symbol.GetAttributes()) @@ -215,58 +219,24 @@ internal static IEnumerable GetAttributesWithInherited(this ISymb var baseSymbol = GetBaseSymbol(symbol); while (baseSymbol != null) { - foreach (var attribute in baseSymbol.GetAttributes().Where(IsInherited)) + foreach (var attribute in baseSymbol.GetAttributes().Where(x => x.AttributeUsageInherited())) { yield return attribute; } baseSymbol = GetBaseSymbol(baseSymbol); } - } - - private static ISymbol GetBaseSymbol(ISymbol symbol) => - symbol switch - { - INamedTypeSymbol namedType => namedType.BaseType, - IMethodSymbol { OriginalDefinition: { } originalDefinition } method when method != originalDefinition => GetBaseSymbol(originalDefinition), - IMethodSymbol { IsOverride: true, OverriddenMethod: { } overridenMethod } => overridenMethod, - _ => null, - }; - - private static bool IsInherited(this AttributeData attribute) - { - if (attribute.AttributeClass == null) - { - return false; - } - foreach (var attributeAttribute in attribute.AttributeClass.GetAttributes()) - { - var @class = attributeAttribute.AttributeClass; - if (@class != null && @class.Name == nameof(AttributeUsageAttribute) && - @class.ContainingNamespace?.Name == "System") + static ISymbol GetBaseSymbol(ISymbol symbol) => + symbol switch { - foreach (var kvp in attributeAttribute.NamedArguments) - { - if (kvp.Key == nameof(AttributeUsageAttribute.Inherited)) - { - return (bool)kvp.Value.Value!; - } - } - - // Default value of Inherited is true - return true; - } - } - - return true; + INamedTypeSymbol namedType => namedType.BaseType, + IMethodSymbol { OriginalDefinition: { } originalDefinition } method when !method.Equals(originalDefinition) => GetBaseSymbol(originalDefinition), + IMethodSymbol { IsOverride: true, OverriddenMethod: { } overridenMethod } => overridenMethod, + _ => null, + }; } - - internal static IEnumerable GetAttributes(this ISymbol symbol, ImmutableArray attributeTypes) => - symbol?.GetAttributes().Where(a => a.AttributeClass.IsAny(attributeTypes)) - ?? Enumerable.Empty(); - internal static bool AnyAttributeDerivesFrom(this ISymbol symbol, KnownType attributeType) => symbol?.GetAttributes().Any(a => a.AttributeClass.DerivesFrom(attributeType)) ?? false; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs index 9d1346f9f73..8a1c9ae1726 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs @@ -141,6 +141,102 @@ public void TryGetAttributeValue_ObjectConversion(object value, Type expectedTyp actualValue.Should().Be(value); } + [DataTestMethod] + [DataRow(true)] + [DataRow(false)] + public void AttributeUsageInherited_InheritedSpecified(bool inherited) + { + var code = $$""" + using System; + + [AttributeUsage(AttributeTargets.All, Inherited = {{(inherited ? "true" : "false")}})] + public class MyAttribute: Attribute + { + } + + [My] + public class Program + { + } + """; + var snippet = new SnippetCompiler(code); + var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); + var actual = attribute.AttributeUsageInherited(); + actual.Should().Be(inherited); + } + + [TestMethod] + public void AttributeUsageInherited_InheritedUnSpecified() + { + var code = $$""" + using System; + + [AttributeUsage(AttributeTargets.All)] + public class MyAttribute: Attribute + { + } + + [My] + public class Program + { + } + """; + var snippet = new SnippetCompiler(code); + var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); + var actual = attribute.AttributeUsageInherited(); + actual.Should().Be(true); + } + + [TestMethod] + public void AttributeUsageInherited_NoUsageAttribute() + { + var code = $$""" + using System; + + public class MyAttribute: Attribute + { + } + + [My] + public class Program + { + } + """; + var snippet = new SnippetCompiler(code); + var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); + var actual = attribute.AttributeUsageInherited(); + actual.Should().Be(true); + } + + [DataTestMethod] + [DataRow(true, true)] + [DataRow(false, true)] // The "Inherited" flag is not inherited for the AttributeUsage attribute itself. See also the SymbolHelperTest.GetAttributesWithInherited... tests, + // where the reflection behavior of MemberInfo.GetCustomAttributes is also tested. + public void AttributeUsageInherited_UsageInherited(bool inherited, bool expected) + { + var code = $$""" + using System; + + [AttributeUsage(AttributeTargets.All, Inherited = {{(inherited ? "true" : "false")}})] + public class BaseAttribute: Attribute + { + } + + public class MyAttribute: BaseAttribute + { + } + + [My] + public class Program + { + } + """; + var snippet = new SnippetCompiler(code); + var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); + var actual = attribute.AttributeUsageInherited(); + actual.Should().Be(expected); + } + private static AttributeData AttributeDataWithName(string attributeClassName) { var namedType = new Mock(); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index 013e58a530c..c4ff6ef7945 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -400,19 +400,19 @@ public void GetClassification_Method(MethodKind methodKind, string expected) } [DataTestMethod] - [DataRow("BaseClass`1", "VirtualMethod", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedOpenGeneric`1", "VirtualMethod", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("BaseClass`1", "VirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedOpenGeneric`1", "VirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] [DataRow("DerivedOpenGeneric`1", "NonVirtualMethod")] - [DataRow("DerivedOpenGeneric`1", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedOpenGeneric`1", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] [DataRow("DerivedOpenGeneric`1", "GenericNonVirtualMethod`1")] - [DataRow("DerivedClosedGeneric", "VirtualMethod", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedClosedGeneric", "VirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] [DataRow("DerivedClosedGeneric", "NonVirtualMethod")] - [DataRow("DerivedClosedGeneric", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedClosedGeneric", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] [DataRow("DerivedClosedGeneric", "GenericNonVirtualMethod`1")] - [DataRow("DerivedNoOverrides`1", "VirtualMethod", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedNoOverrides`1", "NonVirtualMethod", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedNoOverrides`1", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedNoOverrides`1", "GenericNonVirtualMethod`1", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedNoOverrides`1", "VirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedNoOverrides`1", "NonVirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedNoOverrides`1", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedNoOverrides`1", "GenericNonVirtualMethod`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] public void GetAttributesWithInherited_MethodSymbol(string className, string methodName, params string[] expectedAttributes) { var code = $$""" @@ -424,27 +424,39 @@ public class MyInheritedAttribute : Attribute { } [AttributeUsage(AttributeTargets.All, Inherited = false)] public class MyNotInheritedAttribute : Attribute { } + public class MyDerivedInheritedAttribute: MyInheritedAttribute { } + + public class MyDerivedNotInheritedAttribute: MyNotInheritedAttribute { } + public class MyUnannotatedAttribute : Attribute { } public class BaseClass { [MyInheritedAttribute] + [MyDerivedInheritedAttribute] [MyNotInherited] + [MyDerivedNotInherited] [MyUnannotatedAttribute] public virtual void VirtualMethod() { } [MyInheritedAttribute] + [MyDerivedInheritedAttribute] [MyNotInherited] + [MyDerivedNotInherited] [MyUnannotatedAttribute] public void NonVirtualMethod() { } [MyInheritedAttribute] + [MyDerivedInheritedAttribute] [MyNotInherited] + [MyDerivedNotInherited] [MyUnannotatedAttribute] public void GenericNonVirtualMethod() { } [MyInheritedAttribute] + [MyDerivedInheritedAttribute] [MyNotInherited] + [MyDerivedNotInherited] [MyUnannotatedAttribute] public virtual void GenericVirtualMethod() { } } @@ -491,9 +503,9 @@ public static void Main() } [DataTestMethod] - [DataRow("BaseClass`1", "MyInheritedAttribute", "MyNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedOpenGeneric`1", "MyInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedClosedGeneric", "MyInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("BaseClass`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedOpenGeneric`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedClosedGeneric", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] [DataRow("Implement")] public void GetAttributesWithInherited_TypeSymbol(string className, params string[] expectedAttributes) { @@ -506,17 +518,25 @@ public class MyInheritedAttribute : Attribute { } [AttributeUsage(AttributeTargets.All, Inherited = false)] public class MyNotInheritedAttribute : Attribute { } + public class MyDerivedInheritedAttribute: MyInheritedAttribute { } + + public class MyDerivedNotInheritedAttribute: MyNotInheritedAttribute { } + public class MyUnannotatedAttribute : Attribute { } [MyInheritedAttribute] + [MyDerivedInheritedAttribute] [MyNotInherited] + [MyDerivedNotInherited] [MyUnannotatedAttribute] public class BaseClass { } [MyInheritedAttribute] + [MyDerivedInheritedAttribute] [MyNotInherited] + [MyDerivedNotInherited] [MyUnannotatedAttribute] public interface IInterface { From adf4e69ed81a38b2cc577df79780e79cc351f473 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 23 Jan 2023 12:46:44 +0100 Subject: [PATCH 06/24] Improve doc comment. --- .../Extensions/AttributeDataExtensions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs index 203e9fae464..ae097b28376 100644 --- a/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs @@ -48,12 +48,13 @@ public static bool TryGetAttributeValue(this AttributeData attribute, string /// /// Returns the setting for the attribute associated with . - /// The returned value is in line with the runtime behavior of . + /// The returned value is in line with the runtime behavior of . /// /// The of an . /// /// The value of the applied to the . - /// Returns if no is applied to the associated with . + /// Returns if no is applied to the associated with + /// as this is the default behavior of . /// public static bool AttributeUsageInherited(this AttributeData attribute) => attribute.AttributeClass.GetAttributes() From 7bd37c974f0f26200c1fe24db0b53c01127c0f23 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 23 Jan 2023 12:47:20 +0100 Subject: [PATCH 07/24] More code comments for clarity --- .../Extensions/AttributeDataExtensionsTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs index 8a1c9ae1726..c19e2494afc 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs @@ -184,7 +184,7 @@ public class Program var snippet = new SnippetCompiler(code); var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); var actual = attribute.AttributeUsageInherited(); - actual.Should().Be(true); + actual.Should().Be(true); // The default for Inherited = true } [TestMethod] @@ -205,7 +205,7 @@ public class Program var snippet = new SnippetCompiler(code); var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); var actual = attribute.AttributeUsageInherited(); - actual.Should().Be(true); + actual.Should().Be(true); // The default for Inherited = true } [DataTestMethod] From e6819a8efe4e3ec947792aab4aa78cd1bb6d04bf Mon Sep 17 00:00:00 2001 From: Martin Strecker <103252490+martin-strecker-sonarsource@users.noreply.github.com> Date: Mon, 23 Jan 2023 21:15:36 +0100 Subject: [PATCH 08/24] Apply suggestions from code review Co-authored-by: Pavel Mikula <57188685+pavel-mikula-sonarsource@users.noreply.github.com> --- .../Extensions/AttributeDataExtensions.cs | 13 +++---------- .../SonarAnalyzer.Common/Helpers/SymbolHelper.cs | 8 ++++---- .../TestFramework/SnippetCompiler.cs | 8 +++----- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs index ae097b28376..d012ccfeb1c 100644 --- a/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs @@ -50,18 +50,11 @@ public static bool TryGetAttributeValue(this AttributeData attribute, string /// Returns the setting for the attribute associated with . /// The returned value is in line with the runtime behavior of . /// - /// The of an . - /// - /// The value of the applied to the . - /// Returns if no is applied to the associated with - /// as this is the default behavior of . - /// - public static bool AttributeUsageInherited(this AttributeData attribute) => + public static bool HasAttributeUsageInherited(this AttributeData attribute) => attribute.AttributeClass.GetAttributes() - .Where(x => x.AttributeClass is { Name: nameof(AttributeUsageAttribute), ContainingNamespace.Name: "System" }) + .Where(x => x.AttributeClass.Is(KnownType.System_AttributeUsageAttribute)) .SelectMany(x => x.NamedArguments.Where(x => x.Key == nameof(AttributeUsageAttribute.Inherited))) - .Select(x => x.Value) - .Where(x => x is { Kind: TypedConstantKind.Primitive, Type.SpecialType: SpecialType.System_Boolean }) + .Where(x => x.Value is { Kind: TypedConstantKind.Primitive, Type.SpecialType: SpecialType.System_Boolean }) .Select(x => (bool?)x.Value) .FirstOrDefault() ?? true; // Default value of Inherited is true diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs index a01b5df95b8..5de8eb99ca3 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs @@ -209,7 +209,7 @@ public static bool IsPubliclyAccessible(this ISymbol symbol) symbol?.GetAttributes().Where(a => a.AttributeClass.IsAny(attributeTypes)) ?? Enumerable.Empty(); - internal static IEnumerable GetAttributesWithInherited(this ISymbol symbol) + public static IEnumerable GetAttributesWithInherited(this ISymbol symbol) { foreach (var attribute in symbol.GetAttributes()) { @@ -217,7 +217,7 @@ internal static IEnumerable GetAttributesWithInherited(this ISymb } var baseSymbol = GetBaseSymbol(symbol); - while (baseSymbol != null) + while (baseSymbol is not null) { foreach (var attribute in baseSymbol.GetAttributes().Where(x => x.AttributeUsageInherited())) { @@ -227,12 +227,12 @@ internal static IEnumerable GetAttributesWithInherited(this ISymb baseSymbol = GetBaseSymbol(baseSymbol); } - static ISymbol GetBaseSymbol(ISymbol symbol) => + static ISymbol BaseSymbol(ISymbol symbol) => symbol switch { INamedTypeSymbol namedType => namedType.BaseType, IMethodSymbol { OriginalDefinition: { } originalDefinition } method when !method.Equals(originalDefinition) => GetBaseSymbol(originalDefinition), - IMethodSymbol { IsOverride: true, OverriddenMethod: { } overridenMethod } => overridenMethod, + IMethodSymbol { OverriddenMethod: { } overridenMethod } => overridenMethod, _ => null, }; } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs index 1016929c641..3db8904e20d 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs @@ -19,7 +19,6 @@ */ using System.IO; -using System.Threading; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.VisualBasic; using SonarAnalyzer.AnalysisContext; @@ -147,12 +146,11 @@ public SonarSyntaxNodeReportingContext CreateAnalysisContext(SyntaxNode node) return new(AnalysisScaffolding.CreateSonarAnalysisContext(), nodeContext); } - public System.Reflection.Assembly GetEmittedAssembly() + public Assembly EmitAssembly() { using var memoryStream = new MemoryStream(); - var emitResult = Compilation.Emit(memoryStream); - Debug.Assert(emitResult.Success, "The provided snippet could not be emitted."); - var assembly = System.Reflection.Assembly.Load(memoryStream.ToArray()); + Compilation.Emit(memoryStream).Should().BeTrue("The provided snippet should emit assembly."); + return Assembly.Load(memoryStream.ToArray()); return assembly; } From f85288be6d1842098859fe10f0924e5be2ae7d4a Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 23 Jan 2023 21:25:43 +0100 Subject: [PATCH 09/24] Fix after merge --- .../Extensions/AttributeDataExtensions.cs | 2 +- .../SonarAnalyzer.Common/Helpers/SymbolHelper.cs | 8 ++++---- .../Extensions/AttributeDataExtensionsTest.cs | 16 ++++++++-------- .../Helpers/SymbolHelperTest.cs | 4 ++-- .../TestFramework/SnippetCompiler.cs | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs index d012ccfeb1c..7195b466953 100644 --- a/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs @@ -55,7 +55,7 @@ public static bool TryGetAttributeValue(this AttributeData attribute, string .Where(x => x.AttributeClass.Is(KnownType.System_AttributeUsageAttribute)) .SelectMany(x => x.NamedArguments.Where(x => x.Key == nameof(AttributeUsageAttribute.Inherited))) .Where(x => x.Value is { Kind: TypedConstantKind.Primitive, Type.SpecialType: SpecialType.System_Boolean }) - .Select(x => (bool?)x.Value) + .Select(x => (bool?)x.Value.Value) .FirstOrDefault() ?? true; // Default value of Inherited is true private static bool TryConvertConstant(TypedConstant constant, out T value) diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs index 5de8eb99ca3..3c701ff46ca 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs @@ -216,22 +216,22 @@ public static IEnumerable GetAttributesWithInherited(this ISymbol yield return attribute; } - var baseSymbol = GetBaseSymbol(symbol); + var baseSymbol = BaseSymbol(symbol); while (baseSymbol is not null) { - foreach (var attribute in baseSymbol.GetAttributes().Where(x => x.AttributeUsageInherited())) + foreach (var attribute in baseSymbol.GetAttributes().Where(x => x.HasAttributeUsageInherited())) { yield return attribute; } - baseSymbol = GetBaseSymbol(baseSymbol); + baseSymbol = BaseSymbol(baseSymbol); } static ISymbol BaseSymbol(ISymbol symbol) => symbol switch { INamedTypeSymbol namedType => namedType.BaseType, - IMethodSymbol { OriginalDefinition: { } originalDefinition } method when !method.Equals(originalDefinition) => GetBaseSymbol(originalDefinition), + IMethodSymbol { OriginalDefinition: { } originalDefinition } method when !method.Equals(originalDefinition) => BaseSymbol(originalDefinition), IMethodSymbol { OverriddenMethod: { } overridenMethod } => overridenMethod, _ => null, }; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs index c19e2494afc..8738b0640ea 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs @@ -144,7 +144,7 @@ public void TryGetAttributeValue_ObjectConversion(object value, Type expectedTyp [DataTestMethod] [DataRow(true)] [DataRow(false)] - public void AttributeUsageInherited_InheritedSpecified(bool inherited) + public void HasAttributeUsageInherited_InheritedSpecified(bool inherited) { var code = $$""" using System; @@ -161,12 +161,12 @@ public class Program """; var snippet = new SnippetCompiler(code); var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); - var actual = attribute.AttributeUsageInherited(); + var actual = attribute.HasAttributeUsageInherited(); actual.Should().Be(inherited); } [TestMethod] - public void AttributeUsageInherited_InheritedUnSpecified() + public void HasAttributeUsageInherited_InheritedUnSpecified() { var code = $$""" using System; @@ -183,12 +183,12 @@ public class Program """; var snippet = new SnippetCompiler(code); var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); - var actual = attribute.AttributeUsageInherited(); + var actual = attribute.HasAttributeUsageInherited(); actual.Should().Be(true); // The default for Inherited = true } [TestMethod] - public void AttributeUsageInherited_NoUsageAttribute() + public void HasAttributeUsageInherited_NoUsageAttribute() { var code = $$""" using System; @@ -204,7 +204,7 @@ public class Program """; var snippet = new SnippetCompiler(code); var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); - var actual = attribute.AttributeUsageInherited(); + var actual = attribute.HasAttributeUsageInherited(); actual.Should().Be(true); // The default for Inherited = true } @@ -212,7 +212,7 @@ public class Program [DataRow(true, true)] [DataRow(false, true)] // The "Inherited" flag is not inherited for the AttributeUsage attribute itself. See also the SymbolHelperTest.GetAttributesWithInherited... tests, // where the reflection behavior of MemberInfo.GetCustomAttributes is also tested. - public void AttributeUsageInherited_UsageInherited(bool inherited, bool expected) + public void HasAttributeUsageInherited_UsageInherited(bool inherited, bool expected) { var code = $$""" using System; @@ -233,7 +233,7 @@ public class Program """; var snippet = new SnippetCompiler(code); var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); - var actual = attribute.AttributeUsageInherited(); + var actual = attribute.HasAttributeUsageInherited(); actual.Should().Be(expected); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index c4ff6ef7945..44388694d15 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -496,7 +496,7 @@ public static void Main() actual.Should().BeEquivalentTo(expectedAttributes); // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: - var assembly = compiler.GetEmittedAssembly(); + var assembly = compiler.EmitAssembly(); var type = assembly.GetType(className, throwOnError: true); var methodInfo = type.GetMethod(methodName.Replace("`1", string.Empty)); methodInfo.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); @@ -574,7 +574,7 @@ public static void Main() Assert.Fail("Constructor could not be found."); } // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: - var assembly = compiler.GetEmittedAssembly(); + var assembly = compiler.EmitAssembly(); var type = assembly.GetType(className, throwOnError: true); type.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs index 3db8904e20d..fe66bd00b5c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs @@ -19,6 +19,7 @@ */ using System.IO; +using System.Reflection; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.VisualBasic; using SonarAnalyzer.AnalysisContext; @@ -149,9 +150,8 @@ public SonarSyntaxNodeReportingContext CreateAnalysisContext(SyntaxNode node) public Assembly EmitAssembly() { using var memoryStream = new MemoryStream(); - Compilation.Emit(memoryStream).Should().BeTrue("The provided snippet should emit assembly."); + Compilation.Emit(memoryStream).Success.Should().BeTrue("The provided snippet should emit assembly."); return Assembly.Load(memoryStream.ToArray()); - return assembly; } private static bool HasCompilationErrors(Compilation compilation) => From 31a72bed810eb68099f0602ce7c6946702fd3cb7 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 23 Jan 2023 21:28:09 +0100 Subject: [PATCH 10/24] Make compilation private field --- .../TestFramework/SnippetCompiler.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs index fe66bd00b5c..261bd7a71ba 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs @@ -34,7 +34,8 @@ namespace SonarAnalyzer.UnitTest.TestFramework /// internal class SnippetCompiler { - public Compilation Compilation { get; } + private readonly Compilation compilation; + public SyntaxTree SyntaxTree { get; } public SemanticModel SemanticModel { get; } @@ -44,25 +45,25 @@ internal class SnippetCompiler public SnippetCompiler(string code, bool ignoreErrors, AnalyzerLanguage language, IEnumerable additionalReferences = null, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) { - Compilation = SolutionBuilder + compilation = SolutionBuilder .Create() .AddProject(language, createExtraEmptyFile: false, outputKind) .AddSnippet(code) .AddReferences(additionalReferences ?? Enumerable.Empty()) .GetCompilation(); - if (!ignoreErrors && HasCompilationErrors(Compilation)) + if (!ignoreErrors && HasCompilationErrors(compilation)) { - DumpCompilationErrors(Compilation); + DumpCompilationErrors(compilation); throw new InvalidOperationException("Test setup error: test code snippet did not compile. See output window for details."); } - SyntaxTree = Compilation.SyntaxTrees.First(); - SemanticModel = Compilation.GetSemanticModel(SyntaxTree); + SyntaxTree = compilation.SyntaxTrees.First(); + SemanticModel = compilation.GetSemanticModel(SyntaxTree); } public bool IsCSharp() => - Compilation.Language == LanguageNames.CSharp; + compilation.Language == LanguageNames.CSharp; public IEnumerable GetNodes() where TSyntaxNodeType : SyntaxNode => SyntaxTree.GetRoot().DescendantNodes().OfType(); @@ -150,7 +151,7 @@ public SonarSyntaxNodeReportingContext CreateAnalysisContext(SyntaxNode node) public Assembly EmitAssembly() { using var memoryStream = new MemoryStream(); - Compilation.Emit(memoryStream).Success.Should().BeTrue("The provided snippet should emit assembly."); + compilation.Emit(memoryStream).Success.Should().BeTrue("The provided snippet should emit assembly."); return Assembly.Load(memoryStream.ToArray()); } From 339746a9d436de453aaaf4361144b4dee961ea90 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 23 Jan 2023 21:36:00 +0100 Subject: [PATCH 11/24] Add comment --- analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs index 3c701ff46ca..1ae833f2674 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs @@ -19,6 +19,7 @@ */ using System.Reflection; +using static System.Net.WebRequestMethods; namespace SonarAnalyzer.Helpers { @@ -233,6 +234,8 @@ symbol switch INamedTypeSymbol namedType => namedType.BaseType, IMethodSymbol { OriginalDefinition: { } originalDefinition } method when !method.Equals(originalDefinition) => BaseSymbol(originalDefinition), IMethodSymbol { OverriddenMethod: { } overridenMethod } => overridenMethod, + // Other symbols kind supported needs to be implemented/tested as needed. A full list can be found here: + // https://learn.microsoft.com/dotnet/api/system.attributetargets _ => null, }; } From a594007c9cb5e4cc815cf4011bb9b007122616d2 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 23 Jan 2023 21:38:11 +0100 Subject: [PATCH 12/24] Fix comment --- analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs index 1ae833f2674..48fd6e5cbbd 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs @@ -234,7 +234,7 @@ symbol switch INamedTypeSymbol namedType => namedType.BaseType, IMethodSymbol { OriginalDefinition: { } originalDefinition } method when !method.Equals(originalDefinition) => BaseSymbol(originalDefinition), IMethodSymbol { OverriddenMethod: { } overridenMethod } => overridenMethod, - // Other symbols kind supported needs to be implemented/tested as needed. A full list can be found here: + // Support for other kinds of symbols needs to be implemented/tested as needed. A full list can be found here: // https://learn.microsoft.com/dotnet/api/system.attributetargets _ => null, }; From 2ebc6ac64730b5c4694d9c83d105fd8565b88cb9 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 13 Feb 2023 10:40:37 +0100 Subject: [PATCH 13/24] Add doc comment --- analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs index 48fd6e5cbbd..2cddb9853f0 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs @@ -210,6 +210,10 @@ public static bool IsPubliclyAccessible(this ISymbol symbol) symbol?.GetAttributes().Where(a => a.AttributeClass.IsAny(attributeTypes)) ?? Enumerable.Empty(); + /// + /// Returns attributes for the symbol by also respecting . + /// The returned is consistent with the results from . + /// public static IEnumerable GetAttributesWithInherited(this ISymbol symbol) { foreach (var attribute in symbol.GetAttributes()) From 1fdc7ba6ba74c411478d087a8d4053925df6dbc8 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 13 Feb 2023 10:43:46 +0100 Subject: [PATCH 14/24] Remove doc comment. --- .../Extensions/AttributeDataExtensions.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs index 7195b466953..e1bb52f2321 100644 --- a/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/AttributeDataExtensions.cs @@ -46,10 +46,6 @@ public static bool TryGetAttributeValue(this AttributeData attribute, string } } - /// - /// Returns the setting for the attribute associated with . - /// The returned value is in line with the runtime behavior of . - /// public static bool HasAttributeUsageInherited(this AttributeData attribute) => attribute.AttributeClass.GetAttributes() .Where(x => x.AttributeClass.Is(KnownType.System_AttributeUsageAttribute)) From 8810dffd813e3c3469e407cb192033ccbd979b98 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 16 Feb 2023 14:38:02 +0100 Subject: [PATCH 15/24] Add test for duplicate AttributeUsage attribute --- .../Extensions/AttributeDataExtensionsTest.cs | 25 +++++++++++++++++++ .../TestFramework/SnippetCompiler.cs | 3 +++ 2 files changed, 28 insertions(+) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs index 8738b0640ea..a9b7de0a461 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs @@ -21,6 +21,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Moq; using Moq.Protected; +using SonarAnalyzer.Common; using SonarAnalyzer.Extensions; namespace SonarAnalyzer.UnitTest.Extensions @@ -237,6 +238,30 @@ public class Program actual.Should().Be(expected); } + [TestMethod] + public void HasAttributeUsageInherited_DuplicateAttributeUsage() + { + var code = $$""" + using System; + + [AttributeUsage(AttributeTargets.All, Inherited = true)] + [AttributeUsage(AttributeTargets.All, Inherited = false)] // Compiler error + public class MyAttribute: Attribute + { + } + + [My] + public class Program + { + } + """; + var snippet = new SnippetCompiler(code, ignoreErrors: true, AnalyzerLanguage.CSharp); + var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); + var actual = attribute.HasAttributeUsageInherited(); + snippet.GetDiagnostics().Should().ContainEquivalentOf(new { Id = "CS0579" }); // "Duplicate 'AttributeUsage' attribute" + actual.Should().BeTrue(); + } + private static AttributeData AttributeDataWithName(string attributeClassName) { var namedType = new Mock(); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs index 261bd7a71ba..5d90cdad98a 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs @@ -71,6 +71,9 @@ public SnippetCompiler(string code, bool ignoreErrors, AnalyzerLanguage language public TSymbolType GetSymbol(SyntaxNode node) where TSymbolType : class, ISymbol => SemanticModel.GetSymbolInfo(node).Symbol as TSymbolType; + public ImmutableArray GetDiagnostics() => + compilation.GetDiagnostics(); + public SyntaxNode GetMethodDeclaration(string typeDotMethodName) { var nameParts = typeDotMethodName.Split('.'); From afff95471dd49f08a063c9ee3a428cde42ac090d Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 16 Feb 2023 14:44:38 +0100 Subject: [PATCH 16/24] Add inherit doc --- .../SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs index 5d90cdad98a..4a4fa511d78 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs @@ -71,6 +71,7 @@ public SnippetCompiler(string code, bool ignoreErrors, AnalyzerLanguage language public TSymbolType GetSymbol(SyntaxNode node) where TSymbolType : class, ISymbol => SemanticModel.GetSymbolInfo(node).Symbol as TSymbolType; + /// public ImmutableArray GetDiagnostics() => compilation.GetDiagnostics(); From 835c6f0e031bca0202466e982330a64cdc6e1437 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 2 Mar 2023 14:59:59 +0100 Subject: [PATCH 17/24] Remove GetDiagnostics from SnippetCompiler and make Compilation public instead --- .../Extensions/AttributeDataExtensionsTest.cs | 2 +- .../TestFramework/SnippetCompiler.cs | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs index a9b7de0a461..8acf596a5e9 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs @@ -258,7 +258,7 @@ public class Program var snippet = new SnippetCompiler(code, ignoreErrors: true, AnalyzerLanguage.CSharp); var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); var actual = attribute.HasAttributeUsageInherited(); - snippet.GetDiagnostics().Should().ContainEquivalentOf(new { Id = "CS0579" }); // "Duplicate 'AttributeUsage' attribute" + snippet.Compilation.GetDiagnostics().Should().ContainEquivalentOf(new { Id = "CS0579" }); // "Duplicate 'AttributeUsage' attribute" actual.Should().BeTrue(); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs index 4a4fa511d78..d7f2d71dfba 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs @@ -34,8 +34,7 @@ namespace SonarAnalyzer.UnitTest.TestFramework /// internal class SnippetCompiler { - private readonly Compilation compilation; - + public Compilation Compilation { get; } public SyntaxTree SyntaxTree { get; } public SemanticModel SemanticModel { get; } @@ -45,25 +44,25 @@ internal class SnippetCompiler public SnippetCompiler(string code, bool ignoreErrors, AnalyzerLanguage language, IEnumerable additionalReferences = null, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) { - compilation = SolutionBuilder + Compilation = SolutionBuilder .Create() .AddProject(language, createExtraEmptyFile: false, outputKind) .AddSnippet(code) .AddReferences(additionalReferences ?? Enumerable.Empty()) .GetCompilation(); - if (!ignoreErrors && HasCompilationErrors(compilation)) + if (!ignoreErrors && HasCompilationErrors(Compilation)) { - DumpCompilationErrors(compilation); + DumpCompilationErrors(Compilation); throw new InvalidOperationException("Test setup error: test code snippet did not compile. See output window for details."); } - SyntaxTree = compilation.SyntaxTrees.First(); - SemanticModel = compilation.GetSemanticModel(SyntaxTree); + SyntaxTree = Compilation.SyntaxTrees.First(); + SemanticModel = Compilation.GetSemanticModel(SyntaxTree); } public bool IsCSharp() => - compilation.Language == LanguageNames.CSharp; + Compilation.Language == LanguageNames.CSharp; public IEnumerable GetNodes() where TSyntaxNodeType : SyntaxNode => SyntaxTree.GetRoot().DescendantNodes().OfType(); @@ -73,7 +72,7 @@ public SnippetCompiler(string code, bool ignoreErrors, AnalyzerLanguage language /// public ImmutableArray GetDiagnostics() => - compilation.GetDiagnostics(); + Compilation.GetDiagnostics(); public SyntaxNode GetMethodDeclaration(string typeDotMethodName) { @@ -155,7 +154,7 @@ public SonarSyntaxNodeReportingContext CreateAnalysisContext(SyntaxNode node) public Assembly EmitAssembly() { using var memoryStream = new MemoryStream(); - compilation.Emit(memoryStream).Success.Should().BeTrue("The provided snippet should emit assembly."); + Compilation.Emit(memoryStream).Success.Should().BeTrue("The provided snippet should emit assembly."); return Assembly.Load(memoryStream.ToArray()); } From e2224f8ce7778f282d23e0e7ff295aac2cb86d56 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 2 Mar 2023 15:20:21 +0100 Subject: [PATCH 18/24] Code review --- .../Extensions/AttributeDataExtensionsTest.cs | 142 +++++++----------- .../TestFramework/SnippetCompiler.cs | 4 - 2 files changed, 54 insertions(+), 92 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs index 8acf596a5e9..7d2fb22e3b7 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Extensions/AttributeDataExtensionsTest.cs @@ -148,65 +148,44 @@ public void TryGetAttributeValue_ObjectConversion(object value, Type expectedTyp public void HasAttributeUsageInherited_InheritedSpecified(bool inherited) { var code = $$""" - using System; + using System; - [AttributeUsage(AttributeTargets.All, Inherited = {{(inherited ? "true" : "false")}})] - public class MyAttribute: Attribute - { - } + [AttributeUsage(AttributeTargets.All, Inherited = {{inherited.ToString().ToLower()}})] + public class MyAttribute: Attribute { } - [My] - public class Program - { - } - """; - var snippet = new SnippetCompiler(code); - var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); - var actual = attribute.HasAttributeUsageInherited(); - actual.Should().Be(inherited); + [My] + public class Program { } + """; + CompileAttribute(code).HasAttributeUsageInherited().Should().Be(inherited); } [TestMethod] public void HasAttributeUsageInherited_InheritedUnSpecified() { - var code = $$""" - using System; + const string code = """ + using System; - [AttributeUsage(AttributeTargets.All)] - public class MyAttribute: Attribute - { - } + [AttributeUsage(AttributeTargets.All)] + public class MyAttribute: Attribute { } - [My] - public class Program - { - } - """; - var snippet = new SnippetCompiler(code); - var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); - var actual = attribute.HasAttributeUsageInherited(); - actual.Should().Be(true); // The default for Inherited = true + [My] + public class Program { } + """; + CompileAttribute(code).HasAttributeUsageInherited().Should().Be(true); // The default for Inherited = true } [TestMethod] public void HasAttributeUsageInherited_NoUsageAttribute() { - var code = $$""" - using System; + const string code = """ + using System; - public class MyAttribute: Attribute - { - } + public class MyAttribute: Attribute { } - [My] - public class Program - { - } - """; - var snippet = new SnippetCompiler(code); - var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); - var actual = attribute.HasAttributeUsageInherited(); - actual.Should().Be(true); // The default for Inherited = true + [My] + public class Program { } + """; + CompileAttribute(code).HasAttributeUsageInherited().Should().Be(true); // The default for Inherited = true } [DataTestMethod] @@ -216,52 +195,38 @@ public class Program public void HasAttributeUsageInherited_UsageInherited(bool inherited, bool expected) { var code = $$""" - using System; + using System; - [AttributeUsage(AttributeTargets.All, Inherited = {{(inherited ? "true" : "false")}})] - public class BaseAttribute: Attribute - { - } + [AttributeUsage(AttributeTargets.All, Inherited = {{inherited.ToString().ToLower()}})] + public class BaseAttribute: Attribute { } - public class MyAttribute: BaseAttribute - { - } + public class MyAttribute: BaseAttribute { } - [My] - public class Program - { - } - """; - var snippet = new SnippetCompiler(code); - var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); - var actual = attribute.HasAttributeUsageInherited(); - actual.Should().Be(expected); + [My] + public class Program { } + """; + CompileAttribute(code).HasAttributeUsageInherited().Should().Be(expected); } [TestMethod] public void HasAttributeUsageInherited_DuplicateAttributeUsage() { - var code = $$""" - using System; + const string code = """ + using System; - [AttributeUsage(AttributeTargets.All, Inherited = true)] - [AttributeUsage(AttributeTargets.All, Inherited = false)] // Compiler error - public class MyAttribute: Attribute - { - } + [AttributeUsage(AttributeTargets.All, Inherited = true)] + [AttributeUsage(AttributeTargets.All, Inherited = false)] // Compiler error + public class MyAttribute: Attribute { } - [My] - public class Program - { - } - """; - var snippet = new SnippetCompiler(code, ignoreErrors: true, AnalyzerLanguage.CSharp); - var attribute = snippet.GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); - var actual = attribute.HasAttributeUsageInherited(); - snippet.Compilation.GetDiagnostics().Should().ContainEquivalentOf(new { Id = "CS0579" }); // "Duplicate 'AttributeUsage' attribute" - actual.Should().BeTrue(); + [My] + public class Program { } + """; + CompileAttribute(code, ignoreErrors: true).HasAttributeUsageInherited().Should().BeTrue(); } + private static AttributeData CompileAttribute(string code, bool ignoreErrors = false) => + new SnippetCompiler(code, ignoreErrors, AnalyzerLanguage.CSharp).GetTypeSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); + private static AttributeData AttributeDataWithName(string attributeClassName) { var namedType = new Mock(); @@ -276,20 +241,21 @@ private static AttributeData AttributeDataWithArguments(Dictionary $"{TypeName(x.Value)} {x.Key}").JoinStr(", ")}) - {{ - }} + public class MyAttribute: Attribute + { + public MyAttribute({{constructorArguments.Select(x => $"{TypeName(x.Value)} {x.Key}").JoinStr(", ")}}) + { + } - {namedArguments.Select(x => $@"public {TypeName(x.Value)} {x.Key} {{ get; set; }}").JoinStr("\r\n")} -}} + {{namedArguments.Select(x => $@"public {TypeName(x.Value)} {x.Key} {{ get; set; }}").JoinStr("\r\n")}} + } -[My({constructorArguments.Select(x => Quote(x.Value)).JoinStr(", ")}{separator}{namedArguments.Select(x => $"{x.Key}={Quote(x.Value)}").JoinStr(", ")})] -public class Dummy {{ }}"; + [My({{constructorArguments.Select(x => Quote(x.Value)).JoinStr(", ")}}{{separator}}{{namedArguments.Select(x => $"{x.Key}={Quote(x.Value)}").JoinStr(", ")}})] + public class Dummy { } + """; var snippet = new SnippetCompiler(code); var classDeclaration = snippet.SyntaxTree.GetRoot().DescendantNodes().OfType().Last(); var symbol = (INamedTypeSymbol)snippet.SemanticModel.GetDeclaredSymbol(classDeclaration); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs index d7f2d71dfba..fe66bd00b5c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/SnippetCompiler.cs @@ -70,10 +70,6 @@ public SnippetCompiler(string code, bool ignoreErrors, AnalyzerLanguage language public TSymbolType GetSymbol(SyntaxNode node) where TSymbolType : class, ISymbol => SemanticModel.GetSymbolInfo(node).Symbol as TSymbolType; - /// - public ImmutableArray GetDiagnostics() => - Compilation.GetDiagnostics(); - public SyntaxNode GetMethodDeclaration(string typeDotMethodName) { var nameParts = typeDotMethodName.Split('.'); From 3db33a073e00f05b9308076761b54569d1403c11 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 2 Mar 2023 16:04:18 +0100 Subject: [PATCH 19/24] Better align test cases --- .../Helpers/SymbolHelperTest.cs | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index 44388694d15..70663fc7d88 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -400,21 +400,23 @@ public void GetClassification_Method(MethodKind methodKind, string expected) } [DataTestMethod] - [DataRow("BaseClass`1", "VirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedOpenGeneric`1", "VirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedOpenGeneric`1", "NonVirtualMethod")] - [DataRow("DerivedOpenGeneric`1", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedOpenGeneric`1", "GenericNonVirtualMethod`1")] - [DataRow("DerivedClosedGeneric", "VirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedClosedGeneric", "NonVirtualMethod")] - [DataRow("DerivedClosedGeneric", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedClosedGeneric", "GenericNonVirtualMethod`1")] - [DataRow("DerivedNoOverrides`1", "VirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedNoOverrides`1", "NonVirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedNoOverrides`1", "GenericVirtualMethod`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedNoOverrides`1", "GenericNonVirtualMethod`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("BaseClass ", "VirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] + [DataRow("DerivedOpenGeneric", "VirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedNoOverrides", "VirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] + [DataRow("DerivedClosedGeneric ", "VirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedOpenGeneric", "NonVirtualMethod ")] + [DataRow("DerivedNoOverrides", "NonVirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] + [DataRow("DerivedClosedGeneric ", "NonVirtualMethod ")] + [DataRow("DerivedOpenGeneric", "GenericVirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedNoOverrides", "GenericVirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] + [DataRow("DerivedClosedGeneric ", "GenericVirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedOpenGeneric", "GenericNonVirtualMethod")] + [DataRow("DerivedNoOverrides", "GenericNonVirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] + [DataRow("DerivedClosedGeneric ", "GenericNonVirtualMethod")] public void GetAttributesWithInherited_MethodSymbol(string className, string methodName, params string[] expectedAttributes) { + className = className.TrimEnd(); + methodName = methodName.TrimEnd(); var code = $$""" using System; @@ -485,7 +487,7 @@ public class Program { public static void Main() { - new {{className.Replace(@"`1", "")}}().{{methodName.Replace(@"`1", "")}}(); + new {{className}}().{{methodName}}(); } } """; @@ -497,18 +499,19 @@ public static void Main() // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: var assembly = compiler.EmitAssembly(); - var type = assembly.GetType(className, throwOnError: true); - var methodInfo = type.GetMethod(methodName.Replace("`1", string.Empty)); + var type = assembly.GetType(className.Replace("", @"`1"), throwOnError: true); + var methodInfo = type.GetMethod(methodName.Replace("", string.Empty)); methodInfo.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); } [DataTestMethod] - [DataRow("BaseClass`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyNotInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedOpenGeneric`1", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedClosedGeneric", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("Implement")] + [DataRow("BaseClass ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] + [DataRow("DerivedOpenGeneric", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedClosedGeneric ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("Implement ")] public void GetAttributesWithInherited_TypeSymbol(string className, params string[] expectedAttributes) { + className = className.TrimEnd(); var code = $$""" using System; @@ -558,7 +561,7 @@ public class Program { public static void Main() { - new {{className.Replace(@"`1", "")}}(); + new {{className}}(); } } """; @@ -575,7 +578,7 @@ public static void Main() } // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: var assembly = compiler.EmitAssembly(); - var type = assembly.GetType(className, throwOnError: true); + var type = assembly.GetType(className.Replace("", @"`1"), throwOnError: true); type.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); } } From 23501da57c47d4e3b0a7187c58c95d8d9ca3e85b Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 2 Mar 2023 16:09:47 +0100 Subject: [PATCH 20/24] Simplify string --- .../tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index 70663fc7d88..6e2516b2df0 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -499,7 +499,7 @@ public static void Main() // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: var assembly = compiler.EmitAssembly(); - var type = assembly.GetType(className.Replace("", @"`1"), throwOnError: true); + var type = assembly.GetType(className.Replace("", "`1"), throwOnError: true); var methodInfo = type.GetMethod(methodName.Replace("", string.Empty)); methodInfo.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); } @@ -578,7 +578,7 @@ public static void Main() } // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: var assembly = compiler.EmitAssembly(); - var type = assembly.GetType(className.Replace("", @"`1"), throwOnError: true); + var type = assembly.GetType(className.Replace("", "`1"), throwOnError: true); type.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); } } From 6e39239cdca621530100afb3e1c905e082c24431 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 2 Mar 2023 16:11:00 +0100 Subject: [PATCH 21/24] inline assembly --- .../SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index 6e2516b2df0..ba362098678 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -498,8 +498,7 @@ public static void Main() actual.Should().BeEquivalentTo(expectedAttributes); // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: - var assembly = compiler.EmitAssembly(); - var type = assembly.GetType(className.Replace("", "`1"), throwOnError: true); + var type = compiler.EmitAssembly().GetType(className.Replace("", "`1"), throwOnError: true); var methodInfo = type.GetMethod(methodName.Replace("", string.Empty)); methodInfo.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); } @@ -577,8 +576,7 @@ public static void Main() Assert.Fail("Constructor could not be found."); } // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: - var assembly = compiler.EmitAssembly(); - var type = assembly.GetType(className.Replace("", "`1"), throwOnError: true); + var type = compiler.EmitAssembly().GetType(className.Replace("", "`1"), throwOnError: true); type.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); } } From 825fedf9df5f48b041deac3e6a37f5fb4dfa42c1 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 2 Mar 2023 16:33:29 +0100 Subject: [PATCH 22/24] Simplify test class --- .../Helpers/SymbolHelperTest.cs | 60 ++++++++----------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index ba362098678..254e4c57149 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -434,32 +434,32 @@ public class MyUnannotatedAttribute : Attribute { } public class BaseClass { - [MyInheritedAttribute] - [MyDerivedInheritedAttribute] + [MyInherited] + [MyDerivedInherited] [MyNotInherited] [MyDerivedNotInherited] - [MyUnannotatedAttribute] + [MyUnannotated] public virtual void VirtualMethod() { } - [MyInheritedAttribute] - [MyDerivedInheritedAttribute] + [MyInherited] + [MyDerivedInherited] [MyNotInherited] [MyDerivedNotInherited] - [MyUnannotatedAttribute] + [MyUnannotated] public void NonVirtualMethod() { } - [MyInheritedAttribute] - [MyDerivedInheritedAttribute] + [MyInherited] + [MyDerivedInherited] [MyNotInherited] [MyDerivedNotInherited] - [MyUnannotatedAttribute] + [MyUnannotated] public void GenericNonVirtualMethod() { } - [MyInheritedAttribute] - [MyDerivedInheritedAttribute] + [MyInherited] + [MyDerivedInherited] [MyNotInherited] [MyDerivedNotInherited] - [MyUnannotatedAttribute] + [MyUnannotated] public virtual void GenericVirtualMethod() { } } @@ -479,9 +479,7 @@ public class DerivedClosedGeneric: BaseClass public override void GenericVirtualMethod() { } } - public class DerivedNoOverrides: BaseClass - { - } + public class DerivedNoOverrides: BaseClass { } public class Program { @@ -526,35 +524,25 @@ public class MyDerivedNotInheritedAttribute: MyNotInheritedAttribute { } public class MyUnannotatedAttribute : Attribute { } - [MyInheritedAttribute] - [MyDerivedInheritedAttribute] + [MyInherited] + [MyDerivedInherited] [MyNotInherited] [MyDerivedNotInherited] - [MyUnannotatedAttribute] - public class BaseClass - { - } + [MyUnannotated] + public class BaseClass { } - [MyInheritedAttribute] - [MyDerivedInheritedAttribute] + [MyInherited] + [MyDerivedInherited] [MyNotInherited] [MyDerivedNotInherited] - [MyUnannotatedAttribute] - public interface IInterface - { - } + [MyUnannotated] + public interface IInterface { } - public class DerivedOpenGeneric: BaseClass - { - } + public class DerivedOpenGeneric: BaseClass { } - public class DerivedClosedGeneric: BaseClass - { - } + public class DerivedClosedGeneric: BaseClass { } - public class Implement: IInterface - { - } + public class Implement: IInterface { } public class Program { From 297a0311bd61bbe5d9b91e446e6b65931bc47c14 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 2 Mar 2023 16:35:04 +0100 Subject: [PATCH 23/24] Remove "My" prefix from attributes --- .../Helpers/SymbolHelperTest.cs | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index 254e4c57149..9bae033f78d 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -400,18 +400,18 @@ public void GetClassification_Method(MethodKind methodKind, string expected) } [DataTestMethod] - [DataRow("BaseClass ", "VirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] - [DataRow("DerivedOpenGeneric", "VirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedNoOverrides", "VirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] - [DataRow("DerivedClosedGeneric ", "VirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("BaseClass ", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] + [DataRow("DerivedOpenGeneric", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] + [DataRow("DerivedNoOverrides", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] + [DataRow("DerivedClosedGeneric ", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] [DataRow("DerivedOpenGeneric", "NonVirtualMethod ")] - [DataRow("DerivedNoOverrides", "NonVirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] + [DataRow("DerivedNoOverrides", "NonVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedClosedGeneric ", "NonVirtualMethod ")] - [DataRow("DerivedOpenGeneric", "GenericVirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedNoOverrides", "GenericVirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] - [DataRow("DerivedClosedGeneric ", "GenericVirtualMethod ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("DerivedOpenGeneric", "GenericVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] + [DataRow("DerivedNoOverrides", "GenericVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] + [DataRow("DerivedClosedGeneric ", "GenericVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] [DataRow("DerivedOpenGeneric", "GenericNonVirtualMethod")] - [DataRow("DerivedNoOverrides", "GenericNonVirtualMethod", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] + [DataRow("DerivedNoOverrides", "GenericNonVirtualMethod", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedClosedGeneric ", "GenericNonVirtualMethod")] public void GetAttributesWithInherited_MethodSymbol(string className, string methodName, params string[] expectedAttributes) { @@ -421,45 +421,45 @@ public void GetAttributesWithInherited_MethodSymbol(string className, string met using System; [AttributeUsage(AttributeTargets.All, Inherited = true)] - public class MyInheritedAttribute : Attribute { } + public class InheritedAttribute : Attribute { } [AttributeUsage(AttributeTargets.All, Inherited = false)] - public class MyNotInheritedAttribute : Attribute { } + public class NotInheritedAttribute : Attribute { } - public class MyDerivedInheritedAttribute: MyInheritedAttribute { } + public class DerivedInheritedAttribute: InheritedAttribute { } - public class MyDerivedNotInheritedAttribute: MyNotInheritedAttribute { } + public class DerivedNotInheritedAttribute: NotInheritedAttribute { } - public class MyUnannotatedAttribute : Attribute { } + public class UnannotatedAttribute : Attribute { } public class BaseClass { - [MyInherited] - [MyDerivedInherited] - [MyNotInherited] - [MyDerivedNotInherited] - [MyUnannotated] + [Inherited] + [DerivedInherited] + [NotInherited] + [DerivedNotInherited] + [Unannotated] public virtual void VirtualMethod() { } - [MyInherited] - [MyDerivedInherited] - [MyNotInherited] - [MyDerivedNotInherited] - [MyUnannotated] + [Inherited] + [DerivedInherited] + [NotInherited] + [DerivedNotInherited] + [Unannotated] public void NonVirtualMethod() { } - [MyInherited] - [MyDerivedInherited] - [MyNotInherited] - [MyDerivedNotInherited] - [MyUnannotated] + [Inherited] + [DerivedInherited] + [NotInherited] + [DerivedNotInherited] + [Unannotated] public void GenericNonVirtualMethod() { } - [MyInherited] - [MyDerivedInherited] - [MyNotInherited] - [MyDerivedNotInherited] - [MyUnannotated] + [Inherited] + [DerivedInherited] + [NotInherited] + [DerivedNotInherited] + [Unannotated] public virtual void GenericVirtualMethod() { } } @@ -502,9 +502,9 @@ public static void Main() } [DataTestMethod] - [DataRow("BaseClass ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute", "MyNotInheritedAttribute")] - [DataRow("DerivedOpenGeneric", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] - [DataRow("DerivedClosedGeneric ", "MyInheritedAttribute", "MyDerivedInheritedAttribute", "MyDerivedNotInheritedAttribute", "MyUnannotatedAttribute")] + [DataRow("BaseClass ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] + [DataRow("DerivedOpenGeneric", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] + [DataRow("DerivedClosedGeneric ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] [DataRow("Implement ")] public void GetAttributesWithInherited_TypeSymbol(string className, params string[] expectedAttributes) { @@ -513,29 +513,29 @@ public void GetAttributesWithInherited_TypeSymbol(string className, params strin using System; [AttributeUsage(AttributeTargets.All, Inherited = true)] - public class MyInheritedAttribute : Attribute { } + public class InheritedAttribute : Attribute { } [AttributeUsage(AttributeTargets.All, Inherited = false)] - public class MyNotInheritedAttribute : Attribute { } + public class NotInheritedAttribute : Attribute { } - public class MyDerivedInheritedAttribute: MyInheritedAttribute { } + public class DerivedInheritedAttribute: InheritedAttribute { } - public class MyDerivedNotInheritedAttribute: MyNotInheritedAttribute { } + public class DerivedNotInheritedAttribute: NotInheritedAttribute { } - public class MyUnannotatedAttribute : Attribute { } + public class UnannotatedAttribute : Attribute { } - [MyInherited] - [MyDerivedInherited] - [MyNotInherited] - [MyDerivedNotInherited] - [MyUnannotated] + [Inherited] + [DerivedInherited] + [NotInherited] + [DerivedNotInherited] + [Unannotated] public class BaseClass { } - [MyInherited] - [MyDerivedInherited] - [MyNotInherited] - [MyDerivedNotInherited] - [MyUnannotated] + [Inherited] + [DerivedInherited] + [NotInherited] + [DerivedNotInherited] + [Unannotated] public interface IInterface { } public class DerivedOpenGeneric: BaseClass { } From 42e1eb49976e5257eda849c1f05b25663be006b8 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 2 Mar 2023 16:43:29 +0100 Subject: [PATCH 24/24] Rearrange test cases --- .../Helpers/SymbolHelperTest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs index 9bae033f78d..0eb4d5bb03d 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SymbolHelperTest.cs @@ -402,17 +402,17 @@ public void GetClassification_Method(MethodKind methodKind, string expected) [DataTestMethod] [DataRow("BaseClass ", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedOpenGeneric", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] - [DataRow("DerivedNoOverrides", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedClosedGeneric ", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] - [DataRow("DerivedOpenGeneric", "NonVirtualMethod ")] - [DataRow("DerivedNoOverrides", "NonVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] - [DataRow("DerivedClosedGeneric ", "NonVirtualMethod ")] + [DataRow("DerivedNoOverrides", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedOpenGeneric", "GenericVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] - [DataRow("DerivedNoOverrides", "GenericVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedClosedGeneric ", "GenericVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] + [DataRow("DerivedNoOverrides", "GenericVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] + [DataRow("DerivedOpenGeneric", "NonVirtualMethod ")] + [DataRow("DerivedClosedGeneric ", "NonVirtualMethod ")] + [DataRow("DerivedNoOverrides", "NonVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedOpenGeneric", "GenericNonVirtualMethod")] - [DataRow("DerivedNoOverrides", "GenericNonVirtualMethod", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedClosedGeneric ", "GenericNonVirtualMethod")] + [DataRow("DerivedNoOverrides", "GenericNonVirtualMethod", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] public void GetAttributesWithInherited_MethodSymbol(string className, string methodName, params string[] expectedAttributes) { className = className.TrimEnd();