diff --git a/sources/NetArchTest/Condition.cs b/sources/NetArchTest/Condition.cs index 26c5255..e7af3dc 100644 --- a/sources/NetArchTest/Condition.cs +++ b/sources/NetArchTest/Condition.cs @@ -49,7 +49,7 @@ private void AddFunctionCall(FuncAn updated set of conditions that can be applied to a list of types. public ConditionList HaveCustomAttribute(Type attribute) { - AddFunctionCall(x => FunctionDelegates.HaveCustomAttribute(x, attribute, true)); + AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveCustomAttribute(context, inputTypes, attribute, true)); return CreateConditionList(); } /// @@ -68,7 +68,7 @@ public ConditionList HaveCustomAttribute() /// An updated set of conditions that can be applied to a list of types. public ConditionList NotHaveCustomAttribute(Type attribute) { - AddFunctionCall(x => FunctionDelegates.HaveCustomAttribute(x, attribute, false)); + AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveCustomAttribute(context, inputTypes, attribute, false)); return CreateConditionList(); } /// diff --git a/sources/NetArchTest/Dependencies/TypeParser.cs b/sources/NetArchTest/Dependencies/TypeParser.cs index 968dbc0..cc6501d 100644 --- a/sources/NetArchTest/Dependencies/TypeParser.cs +++ b/sources/NetArchTest/Dependencies/TypeParser.cs @@ -26,7 +26,7 @@ public static IEnumerable Parse(string fullName, bool parseNames) var monoTypeParser = Activator.CreateInstance(mono_TypeParserType, BindingFlags.Instance | BindingFlags.NonPublic, null, args: new object[] { fullName }, null); var monoType = mono_ParseTypeMethod.Invoke(monoTypeParser, new object[] { false }); - foreach(var token in WalkThroughMonoType(monoType)) + foreach (var token in WalkThroughMonoType(monoType)) { yield return token; } @@ -83,5 +83,75 @@ private static IEnumerable WalkThroughMonoType(object monoType) } } } + + + + + public static string ParseReflectionNameToRuntimeName(string fullName) + { + var monoTypeParser = Activator.CreateInstance(mono_TypeParserType, BindingFlags.Instance | BindingFlags.NonPublic, null, args: new object[] { fullName }, null); + var monoType = mono_ParseTypeMethod.Invoke(monoTypeParser, new object[] { false }); + return string.Concat(WalkThroughMonoType2(monoType)); + } + + private static IEnumerable WalkThroughMonoType2(object monoType) + { + yield return mono_type_fullnameField.GetValue(monoType) as string; + + var nested = mono_nested_namesField.GetValue(monoType) as string[]; + if (nested != null) + { + foreach (var nestedName in nested) + { + yield return "/"; + yield return nestedName; + } + } + + var generics = mono_generic_argumentsField.GetValue(monoType) as object[]; + if (generics != null) + { + yield return "<"; + for (int i = 0; i < generics.Length; i++) + { + object generic = generics[i]; + foreach (var token in WalkThroughMonoType(generic)) + { + yield return token; + } + if (i < generics.Length - 1) + { + yield return ","; + } + } + yield return ">"; + } + + var specs = mono_specsField.GetValue(monoType) as int[]; + if (specs != null) + { + for (int i = 0; i < specs.Length; ++i) + { + if (specs[i] == -1) + { + yield return "*"; + } + if (specs[i] == -2) + { + yield return "&"; + } + if (specs[i] == -3) + { + yield return "[]"; + } + if (specs[i] >= 2) + { + yield return "[,]"; + } + } + } + } + + } } \ No newline at end of file diff --git a/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs b/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs index 6e68f2b..d8d0fbe 100644 --- a/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs +++ b/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs @@ -7,13 +7,13 @@ namespace Mono.Cecil { static internal class TypeDefinitionExtensions { - /// - /// Tests whether one class inherits from another. - /// - /// The class that is inheriting from the parent. - /// The parent that is inherited. - /// An indication of whether the child inherits from the parent. - public static bool IsSubclassOf(this TypeDefinition child, TypeDefinition parent) + public static bool IsSubclassOf(this TypeReference child, TypeReference parent) + { + var typeDef = child.Resolve(); + return typeDef.IsSubclassOf(parent); + } + + public static bool IsSubclassOf(this TypeDefinition child, TypeReference parent) { if (parent != null) { @@ -22,36 +22,8 @@ public static bool IsSubclassOf(this TypeDefinition child, TypeDefinition parent } return false; - } - - /// - /// Tests whether two type definitions are from the same assembly. - /// The comparison is based on the full assembly names. - /// - /// - /// - /// An indication of whether the both types are from the same assembly. - public static bool IsFromSameAssemblyAs(this TypeDefinition a, TypeDefinition b) - { - return a.Module.Assembly.ToString() == b.Module.Assembly.ToString(); - } + } - /// - /// Tests whether the provided types are the same type. - /// - /// - /// - /// An indication of whether the types are the same. - public static bool IsSameTypeAs(this TypeDefinition a, TypeDefinition b) - { - return a.IsFromSameAssemblyAs(b) && a.MetadataToken == b.MetadataToken; - } - - /// - /// Enumerate the base classes throughout the chain of inheritence. - /// - /// The class to enumerate. - /// The enumeration of base classes. private static IEnumerable EnumerateBaseClasses(this TypeDefinition classType) { for (var typeDefinition = classType; typeDefinition != null; typeDefinition = typeDefinition.BaseType?.Resolve()) @@ -60,6 +32,26 @@ private static IEnumerable EnumerateBaseClasses(this TypeDefinit } } + public static bool IsAlmostEqualTo(this TypeReference child, TypeDefinition parent) + { + if (child is GenericInstanceType genericInstanceTypeB) + { + if (parent.IsSameTypeAs(genericInstanceTypeB.ElementType)) + { + return true; + } + } + + if (parent.IsSameTypeAs(child)) + { + return true; + } + + return false; + } + + + /// /// Convert the definition to a object instance. /// @@ -67,20 +59,10 @@ private static IEnumerable EnumerateBaseClasses(this TypeDefinit /// The equivalent object instance. public static Type ToType(this TypeDefinition typeDefinition) { - var fullName = RuntimeNameToReflectionName(typeDefinition.FullName); + var fullName = typeDefinition.FullName.RuntimeNameToReflectionName(); return Type.GetType(string.Concat(fullName, ", ", typeDefinition.Module.Assembly.FullName), true); } - public static string RuntimeNameToReflectionName(this string cliName) - { - // Nested types have a forward slash that should be replaced with "+" - // C++ template instantiations contain comma separator for template arguments, - // getting address operators and pointer type designations which should be prefixed by backslash - var fullName = cliName.Replace("/", "+") - .Replace(",", "\\,") - .Replace("&", "\\&") - .Replace("*", "\\*"); - return fullName; - } + @@ -150,9 +132,9 @@ public static string GetNameWithoutGenericPart(this TypeDefinition typeDefinitio } return typeDefinition.Name.RemoveGenericPart(); } - + public static bool IsDelegate(this TypeDefinition typeDefinition) diff --git a/sources/NetArchTest/Extensions/Mono.Cecil/TypeReferenceExtensions.cs b/sources/NetArchTest/Extensions/Mono.Cecil/TypeReferenceExtensions.cs index a7881d5..2fa5edb 100644 --- a/sources/NetArchTest/Extensions/Mono.Cecil/TypeReferenceExtensions.cs +++ b/sources/NetArchTest/Extensions/Mono.Cecil/TypeReferenceExtensions.cs @@ -41,5 +41,31 @@ public static string GetFullNameWithoutGenericParameters(this TypeReference type return typeReference.FullName; } + + public static bool IsSameTypeAs(this TypeReference a, TypeReference b) + { + bool sameAssembly = a.IsFromSameAssemblyAs(b); + bool sameName = string.Equals(a.FullName, b.FullName, StringComparison.Ordinal); + return sameAssembly && sameName; + } + private static bool IsFromSameAssemblyAs(this TypeReference a, TypeReference b) + { + var aName = GetAssemblyName(a.Scope); + var bName = GetAssemblyName(b.Scope); + + return aName == bName; + } + private static string GetAssemblyName(IMetadataScope scope) + { + if (scope is ModuleDefinition moduleDefinition) + { + return moduleDefinition.Assembly.FullName; + } + if (scope is AssemblyNameReference assemblyNameReference) + { + return assemblyNameReference.FullName; + } + return scope.Name; + } } } \ No newline at end of file diff --git a/sources/NetArchTest/Extensions/System/StringExtensions.cs b/sources/NetArchTest/Extensions/System/StringExtensions.cs index 256dcbb..ccad166 100644 --- a/sources/NetArchTest/Extensions/System/StringExtensions.cs +++ b/sources/NetArchTest/Extensions/System/StringExtensions.cs @@ -18,5 +18,24 @@ public static string RemoveGenericPart(this string name) } return name; } + + + public static string RuntimeNameToReflectionName(this string cliName) + { + // Nested types have a forward slash that should be replaced with "+" + // C++ template instantiations contain comma separator for template arguments, + // getting address operators and pointer type designations which should be prefixed by backslash + var fullName = cliName.Replace("/", "+") + .Replace(",", "\\,") + .Replace("&", "\\&") + .Replace("*", "\\*"); + return fullName; + } + + public static string ReflectionNameToRuntimeName(this string typeName) + { + var fullName = typeName.Replace("+", "/"); + return fullName; + } } } diff --git a/sources/NetArchTest/Extensions/System/TypeExtensions.cs b/sources/NetArchTest/Extensions/System/TypeExtensions.cs index 76ce559..b67f50c 100644 --- a/sources/NetArchTest/Extensions/System/TypeExtensions.cs +++ b/sources/NetArchTest/Extensions/System/TypeExtensions.cs @@ -1,30 +1,41 @@ -using System.Linq; -using System.Reflection; +using System.Reflection; using Mono.Cecil; +using NetArchTest.Dependencies; namespace System -{ +{ static internal class TypeExtensions { - /// - /// Converts the value to a instance. - /// - /// The type to convert. - /// The converted value. public static TypeDefinition ToTypeDefinition(this Type type) { - // Get the assembly using reflection - var assembly = Assembly.GetAssembly(type); + var reflectionAssembly = Assembly.GetAssembly(type); + var assemblyDef = AssemblyDefinition.ReadAssembly(reflectionAssembly.Location); - // Load the assembly into the Mono.Cecil library - var assemblyDef = AssemblyDefinition.ReadAssembly(assembly.Location); + foreach (var module in assemblyDef.Modules) + { + var typeRef = module.GetType(type.FullName, true); + var typeDef = typeRef?.Resolve(); + if (typeDef is not null) return typeDef; + } - // Find the matching type - var dependencies = (assemblyDef.Modules - .SelectMany(t => t.Types) - .Where(t => t.IsClass && t.Namespace != null && t.FullName.Equals(type.FullName, StringComparison.InvariantCultureIgnoreCase))); + return null; + } + + public static string GetNormalizedFullName(this Type type) + { + var name = type.Name; + var fullName = type.FullName; + var toString = type.ToString(); + var assemblyQualifiedName = type.AssemblyQualifiedName; + + var monoName = TypeParser.ParseReflectionNameToRuntimeName(type.FullName); + + if (type.IsGenericType && type.ContainsGenericParameters == false) + { + //return toString.ReflectionNameToRuntimeName(); + } - return dependencies.FirstOrDefault(); + return monoName; } } -} +} \ No newline at end of file diff --git a/sources/NetArchTest/Functions/FunctionDelegates.cs b/sources/NetArchTest/Functions/FunctionDelegates.cs index 4486efa..282abe1 100644 --- a/sources/NetArchTest/Functions/FunctionDelegates.cs +++ b/sources/NetArchTest/Functions/FunctionDelegates.cs @@ -10,29 +10,31 @@ namespace NetArchTest.Functions { internal static partial class FunctionDelegates { - internal static IEnumerable HaveCustomAttribute(IEnumerable input, Type attribute, bool condition) + internal static IEnumerable HaveCustomAttribute(FunctionSequenceExecutionContext context, IEnumerable input, Type attribute, bool condition) { + var target = attribute.ToTypeDefinition(); + if (condition) { - return input.Where(c => c.Definition.CustomAttributes.Any(a => attribute.FullName.Equals(a.AttributeType.FullName, StringComparison.InvariantCultureIgnoreCase))); + return input.Where(c => c.Definition.CustomAttributes.Any(a => a.AttributeType.IsAlmostEqualTo(target))); } else { - return input.Where(c => !c.Definition.CustomAttributes.Any(a => attribute.FullName.Equals(a.AttributeType.FullName, StringComparison.InvariantCultureIgnoreCase))); + return input.Where(c => !c.Definition.CustomAttributes.Any(a => a.AttributeType.IsAlmostEqualTo(target))); } } internal static IEnumerable HaveCustomAttributeOrInherit(IEnumerable input, Type attribute, bool condition) - { - // Convert the incoming type to a definition + { var target = attribute.ToTypeDefinition(); + if (condition) { - return input.Where(c => c.Definition.CustomAttributes.Any(a => a.AttributeType.Resolve().IsSubclassOf(target) || attribute.FullName.Equals(a.AttributeType.FullName, StringComparison.InvariantCultureIgnoreCase))); + return input.Where(c => c.Definition.CustomAttributes.Any(a => a.AttributeType.IsSubclassOf(target) || a.AttributeType.IsAlmostEqualTo(target))); } else { - return input.Where(c => !c.Definition.CustomAttributes.Any(a => a.AttributeType.Resolve().IsSubclassOf(target) || attribute.FullName.Equals(a.AttributeType.FullName, StringComparison.InvariantCultureIgnoreCase))); + return input.Where(c => !c.Definition.CustomAttributes.Any(a => a.AttributeType.IsSubclassOf(target) || a.AttributeType.IsAlmostEqualTo(target))); } } @@ -42,9 +44,9 @@ internal static IEnumerable Inherit(IEnumerable input, Type { throw new ArgumentException($"The type {type.FullName} is an interface. interfaces are implemented not inherited, please use ImplementInterface instead."); } - - // Convert the incoming type to a definition + var target = type.ToTypeDefinition(); + if (condition) { return input.Where(c => c.Definition.IsSubclassOf(target)); @@ -58,6 +60,7 @@ internal static IEnumerable Inherit(IEnumerable input, Type internal static IEnumerable BeInherited(FunctionSequenceExecutionContext context, IEnumerable input, bool condition) { var InheritedTypes = new HashSet(context.AllTypes.Select(x => x.Definition.BaseType?.GetFullNameWithoutGenericParameters()).Where(x => x is not null)); + if (condition) { return input.Where(c => InheritedTypes.Contains(c.Definition.FullName)); @@ -75,22 +78,15 @@ internal static IEnumerable ImplementInterface(IEnumerable i throw new ArgumentException($"The type {typeInterface.FullName} is not an interface."); } + var target = typeInterface.ToTypeDefinition(); + if (condition) { - return input.Where(c => Implements(c.Definition, typeInterface)); + return input.Where(c => c.Definition.Interfaces.Any(i => i.InterfaceType.IsAlmostEqualTo(target))); } else { - return input.Where(c => !Implements(c.Definition, typeInterface)); - } - - static bool Implements(TypeDefinition c, Type typeInterface) - { - if (typeInterface.IsGenericType) - { - return c.Interfaces.Any(t => t.InterfaceType.FullName.StartsWith(typeInterface.FullName, StringComparison.InvariantCultureIgnoreCase)); - } - return c.Interfaces.Any(t => t.InterfaceType.FullName.Equals(typeInterface.FullName, StringComparison.InvariantCultureIgnoreCase)); + return input.Where(c => !c.Definition.Interfaces.Any(i => i.InterfaceType.IsAlmostEqualTo(target))); } } @@ -105,6 +101,7 @@ internal static IEnumerable MeetCustomRule(IEnumerable input return input.Where(t => !rule.MeetsRule(t.Definition)); } } + internal static IEnumerable MeetCustomRule(IEnumerable input, Func rule, bool condition) { if (condition) diff --git a/sources/NetArchTest/Functions/FunctionDelegates_Names.cs b/sources/NetArchTest/Functions/FunctionDelegates_Names.cs index cdd1e0f..598c985 100644 --- a/sources/NetArchTest/Functions/FunctionDelegates_Names.cs +++ b/sources/NetArchTest/Functions/FunctionDelegates_Names.cs @@ -14,17 +14,16 @@ internal static partial class FunctionDelegates internal static IEnumerable AreOfType(FunctionSequenceExecutionContext context, IEnumerable input, Type[] types, bool condition) { - var fullNames = new HashSet(types.Select(x => x.FullName)); + var targets = types.Select(x => x.ToTypeDefinition()); + if (condition) { - return input.Where(c => HasFullName(c.Definition.FullName.RuntimeNameToReflectionName(), fullNames, context.UserOptions.Comparer)); + return input.Where(c => targets.Any(t => c.Definition.IsAlmostEqualTo(t))); } else { - return input.Where(c => !HasFullName(c.Definition.FullName.RuntimeNameToReflectionName(), fullNames, context.UserOptions.Comparer)); + return input.Where(c => !targets.Any(t => c.Definition.IsAlmostEqualTo(t))); } - - static bool HasFullName(string typeName, HashSet lookigFor, StringComparison comparer) => lookigFor.Contains(typeName); } internal static IEnumerable HaveName(FunctionSequenceExecutionContext context, IEnumerable input, string[] names, bool condition) diff --git a/sources/NetArchTest/Functions/FunctionDelegates_Special.cs b/sources/NetArchTest/Functions/FunctionDelegates_Special.cs index a57cf4f..4d2dd97 100644 --- a/sources/NetArchTest/Functions/FunctionDelegates_Special.cs +++ b/sources/NetArchTest/Functions/FunctionDelegates_Special.cs @@ -116,6 +116,7 @@ static bool IsMatching(string sourceFilePath, string @namespace, StringCompariso internal static IEnumerable HaveMatchingTypeWithName(FunctionSequenceExecutionContext context, IEnumerable input, Func getMatchingTypeName, bool condition) { var exisitingTypes = new HashSet(context.AllTypes.Select(x => x.Definition.GetNameWithoutGenericPart())); + if (condition) { return input.Where(c => exisitingTypes.Contains(getMatchingTypeName(c.Definition))); diff --git a/sources/NetArchTest/Predicate.cs b/sources/NetArchTest/Predicate.cs index 44a7815..cd83126 100644 --- a/sources/NetArchTest/Predicate.cs +++ b/sources/NetArchTest/Predicate.cs @@ -44,7 +44,7 @@ private void AddFunctionCall(FuncAn updated set of predicates that can be applied to a list of types. public PredicateList HaveCustomAttribute(Type attribute) { - AddFunctionCall(x => FunctionDelegates.HaveCustomAttribute(x, attribute, true)); + AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveCustomAttribute(context, inputTypes, attribute, true)); return CreatePredicateList(); } /// @@ -63,7 +63,7 @@ public PredicateList HaveCustomAttribute() /// An updated set of predicates that can be applied to a list of types. public PredicateList DoNotHaveCustomAttribute(Type attribute) { - AddFunctionCall(x => FunctionDelegates.HaveCustomAttribute(x, attribute, false)); + AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveCustomAttribute(context, inputTypes, attribute, false)); return CreatePredicateList(); } /// diff --git a/tests/NetArchTest.CrossAssemblyTest.A/ClassCustomAttributeFromA.cs b/tests/NetArchTest.CrossAssemblyTest.A/ClassCustomAttributeFromA.cs new file mode 100644 index 0000000..887da7c --- /dev/null +++ b/tests/NetArchTest.CrossAssemblyTest.A/ClassCustomAttributeFromA.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetArchTest.CrossAssemblyTest.A +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class ClassCustomAttributeFromA : Attribute + { + } +} diff --git a/tests/NetArchTest.CrossAssemblyTest.B/DerivedClassCustomAttributeFromB.cs b/tests/NetArchTest.CrossAssemblyTest.B/DerivedClassCustomAttributeFromB.cs new file mode 100644 index 0000000..014f82a --- /dev/null +++ b/tests/NetArchTest.CrossAssemblyTest.B/DerivedClassCustomAttributeFromB.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NetArchTest.CrossAssemblyTest.A; + +namespace NetArchTest.CrossAssemblyTest.B +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class DerivedClassCustomAttributeFromB : ClassCustomAttributeFromA + { + } +} diff --git a/tests/NetArchTest.Rules.UnitTests/PredicateTests.cs b/tests/NetArchTest.Rules.UnitTests/PredicateTests.cs index 473e280..8b6560a 100644 --- a/tests/NetArchTest.Rules.UnitTests/PredicateTests.cs +++ b/tests/NetArchTest.Rules.UnitTests/PredicateTests.cs @@ -5,6 +5,7 @@ using NetArchTest.CrossAssemblyTest.B; using NetArchTest.Rules; using NetArchTest.TestStructure.CustomAttributes; +using NetArchTest.TestStructure.CustomAttributes.Attributes; using NetArchTest.TestStructure.Inheritance; using NetArchTest.TestStructure.Interfaces; using NetArchTest.TestStructure.NameMatching.Namespace1; @@ -25,12 +26,73 @@ public void HaveCustomAttribute() .That() .ResideInNamespace(namespaceof()) .And() - .HaveCustomAttribute(typeof(ClassCustomAttribute)).GetReflectionTypes(); + .HaveCustomAttribute(typeof(ClassCustomAttribute)) + .GetReflectionTypes(); + + Assert.Single(result); + Assert.Contains(typeof(AttributePresent), result); + } + + [Fact(DisplayName = "HaveCustomAttribute_Nested")] + public void HaveCustomAttribute_Nested() + { + var result = Types + .InAssembly(Assembly.GetAssembly(typeof(AttributePresent))) + .That() + .ResideInNamespace(namespaceof()) + .And() + .HaveCustomAttribute(typeof(ClassCustomAttribute.ClassNestedCustomAttribute.ClassNestedNestedCustomAttribute)) + .GetReflectionTypes(); + + Assert.Single(result); + Assert.Contains(typeof(AttributePresent), result); + } + + [Fact(DisplayName = "HaveCustomAttribute_Generic_Unbound")] + public void HaveCustomAttribute_Generic_Unbound() + { + var result = Types + .InAssembly(Assembly.GetAssembly(typeof(AttributePresent))) + .That() + .ResideInNamespace(namespaceof()) + .And() + .HaveCustomAttribute(typeof(GenericCustomAttribute<>)) + .GetReflectionTypes(); + + Assert.Single(result); + Assert.Contains(typeof(AttributePresent), result); + } + + [Fact(DisplayName = "HaveCustomAttribute_Generic_Closed")] + public void HaveCustomAttribute_Generic_Closed() + { + var result = Types + .InAssembly(Assembly.GetAssembly(typeof(AttributePresent))) + .That() + .ResideInNamespace(namespaceof()) + .And() + .HaveCustomAttribute(typeof(GenericCustomAttribute)) + .GetReflectionTypes(); Assert.Single(result); Assert.Contains(typeof(AttributePresent), result); } + [Fact(DisplayName = "HaveCustomAttribute_FromDifferentAssembly")] + public void HaveCustomAttribute_FromDifferentAssembly() + { + var result = Types + .InAssembly(Assembly.GetAssembly(typeof(AttributePresent))) + .That() + .ResideInNamespace(namespaceof()) + .And() + .HaveCustomAttribute(typeof(ClassCustomAttributeFromA)) + .GetReflectionTypes(); + + Assert.Single(result); + Assert.Contains(typeof(AttributePresent), result); + } + [Fact(DisplayName = "DoNotHaveCustomAttribute")] public void DoNotHaveCustomAttribute() { @@ -41,7 +103,7 @@ public void DoNotHaveCustomAttribute() .And() .DoNotHaveCustomAttribute(typeof(ClassCustomAttribute)).GetReflectionTypes(); - Assert.Equal(4, result.Count()); + Assert.Equal(7, result.Count()); Assert.Contains(typeof(NoAttributes), result); Assert.Contains(typeof(ClassCustomAttribute), result); Assert.Contains(typeof(InheritAttributePresent), result); @@ -63,6 +125,23 @@ public void HaveCustomAttributeOrInherit() Assert.Contains(typeof(InheritAttributePresent), result); } + [Fact(DisplayName = "HaveCustomAttributeOrInheri_FromDifferentAssemblyt")] + public void HaveCustomAttributeOrInheri_FromDifferentAssemblyt() + { + var result = Types + .InAssembly(Assembly.GetAssembly(typeof(AttributePresent))) + .That() + .ResideInNamespace(typeof(AttributePresent).Namespace) + .And() + .HaveCustomAttributeOrInherit(typeof(ClassCustomAttributeFromA)).GetReflectionTypes(); + + Assert.Equal(2, result.Count()); + Assert.Contains(typeof(AttributePresent), result); + Assert.Contains(typeof(InheritAttributePresent), result); + } + + + [Fact(DisplayName = "DoNotHaveCustomAttributeOrInherit")] public void DoNotHaveCustomAttributeOrInherit() { @@ -73,7 +152,7 @@ public void DoNotHaveCustomAttributeOrInherit() .And() .DoNotHaveCustomAttributeOrInherit(typeof(ClassCustomAttribute)).GetReflectionTypes(); - Assert.Equal(3, result.Count()); + Assert.Equal(6, result.Count()); Assert.Contains(typeof(NoAttributes), result); Assert.Contains(typeof(ClassCustomAttribute), result); Assert.Contains(typeof(InheritClassCustomAttribute), result); diff --git a/tests/NetArchTest.TestStructure/CustomAttributes/AttributePresent.cs b/tests/NetArchTest.TestStructure/CustomAttributes/AttributePresent.cs index cdd60b9..6c6309e 100644 --- a/tests/NetArchTest.TestStructure/CustomAttributes/AttributePresent.cs +++ b/tests/NetArchTest.TestStructure/CustomAttributes/AttributePresent.cs @@ -1,6 +1,14 @@ -namespace NetArchTest.TestStructure.CustomAttributes +using NetArchTest.CrossAssemblyTest.A; +using NetArchTest.CrossAssemblyTest.B; +using NetArchTest.TestStructure.CustomAttributes.Attributes; + +namespace NetArchTest.TestStructure.CustomAttributes { [ClassCustom] + [ClassCustomAttribute.ClassNestedCustom] + [ClassCustomAttribute.ClassNestedCustomAttribute.ClassNestedNestedCustom] + [GenericCustom] + [ClassCustomAttributeFromA] public class AttributePresent { } diff --git a/tests/NetArchTest.TestStructure/CustomAttributes/Attributes/ClassCustomAttribute.cs b/tests/NetArchTest.TestStructure/CustomAttributes/Attributes/ClassCustomAttribute.cs index 8012d85..ac261da 100644 --- a/tests/NetArchTest.TestStructure/CustomAttributes/Attributes/ClassCustomAttribute.cs +++ b/tests/NetArchTest.TestStructure/CustomAttributes/Attributes/ClassCustomAttribute.cs @@ -8,5 +8,15 @@ [AttributeUsage(AttributeTargets.Class, Inherited = false)] public class ClassCustomAttribute : Attribute { + + + public class ClassNestedCustomAttribute : Attribute + { + + + public class ClassNestedNestedCustomAttribute : Attribute + { + } + } } -} +} \ No newline at end of file diff --git a/tests/NetArchTest.TestStructure/CustomAttributes/Attributes/GenericCustomAttribute.cs b/tests/NetArchTest.TestStructure/CustomAttributes/Attributes/GenericCustomAttribute.cs new file mode 100644 index 0000000..0148eb9 --- /dev/null +++ b/tests/NetArchTest.TestStructure/CustomAttributes/Attributes/GenericCustomAttribute.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetArchTest.TestStructure.CustomAttributes.Attributes +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class GenericCustomAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/tests/NetArchTest.TestStructure/CustomAttributes/InheritAttributePresent.cs b/tests/NetArchTest.TestStructure/CustomAttributes/InheritAttributePresent.cs index ff03327..b3fb8cc 100644 --- a/tests/NetArchTest.TestStructure/CustomAttributes/InheritAttributePresent.cs +++ b/tests/NetArchTest.TestStructure/CustomAttributes/InheritAttributePresent.cs @@ -1,6 +1,9 @@ -namespace NetArchTest.TestStructure.CustomAttributes +using NetArchTest.CrossAssemblyTest.B; + +namespace NetArchTest.TestStructure.CustomAttributes { [InheritClassCustom] + [DerivedClassCustomAttributeFromB] public class InheritAttributePresent { } diff --git a/tests/NetArchTest.TestStructure/NetArchTest.TestStructure.csproj b/tests/NetArchTest.TestStructure/NetArchTest.TestStructure.csproj index a21283e..673c6be 100644 --- a/tests/NetArchTest.TestStructure/NetArchTest.TestStructure.csproj +++ b/tests/NetArchTest.TestStructure/NetArchTest.TestStructure.csproj @@ -2,7 +2,7 @@ netstandard2.1 - 10.0 + 11.0 @@ -15,5 +15,9 @@ + + + +