diff --git a/ArchUnitNET/Domain/Extensions/EnumerableExtensions.cs b/ArchUnitNET/Domain/Extensions/EnumerableExtensions.cs index b7822cdbd..baa0eac53 100644 --- a/ArchUnitNET/Domain/Extensions/EnumerableExtensions.cs +++ b/ArchUnitNET/Domain/Extensions/EnumerableExtensions.cs @@ -62,5 +62,25 @@ Architecture architecture (arg.Item1, arg.Item2 is Type type ? architecture.GetITypeOfType(type) : arg.Item2) ); } + + /// + /// Creates a lookup function for the given collection of elements. + /// For smaller collections, it uses the Contains method of the collection directly. + /// For larger collections, it creates a HashSet for O(1) average time complexity lookups. + /// + /// + /// The type of elements in the collection. + /// + /// The collection of elements to create a lookup function for. + /// + /// A function that checks if an element is in the collection. + public static Func CreateLookupFn(ICollection elements) + { + if (elements.Count < 20) + { + return elements.Contains; + } + return new HashSet(elements).Contains; + } } } diff --git a/ArchUnitNET/Domain/UnavailableType.cs b/ArchUnitNET/Domain/UnavailableType.cs index f5dc33ef2..bbfe0f23c 100644 --- a/ArchUnitNET/Domain/UnavailableType.cs +++ b/ArchUnitNET/Domain/UnavailableType.cs @@ -42,7 +42,7 @@ public override string ToString() return FullName; } - private bool Equals(Struct other) + private bool Equals(UnavailableType other) { return Equals(Type, other.Type); } diff --git a/ArchUnitNET/Fluent/ConditionManager.cs b/ArchUnitNET/Fluent/ConditionManager.cs index b3b1147a9..fa8b70996 100644 --- a/ArchUnitNET/Fluent/ConditionManager.cs +++ b/ArchUnitNET/Fluent/ConditionManager.cs @@ -106,15 +106,34 @@ ICanBeEvaluated archRuleCreator var filteredObjectsList = filteredObjects.ToList(); if (filteredObjectsList.IsNullOrEmpty() && !CheckEmpty()) { - yield return new EvaluationResult( - null, - new StringIdentifier(""), - false, - "There are no objects matching the criteria", - archRuleCreator, - architecture + return new[] + { + new EvaluationResult( + null, + new StringIdentifier(""), + false, + "There are no objects matching the criteria", + archRuleCreator, + architecture + ), + }; + } + + if (_conditionElements.All(e => e.IsOrdered())) + { + var conditionResults = _conditionElements + .Select(conditionElement => + conditionElement.Check(filteredObjectsList, architecture).ToList() + ) + .ToList(); + return filteredObjectsList.Select( + (t, i) => + CreateEvaluationResult( + conditionResults.Select(results => results[i]), + architecture, + archRuleCreator + ) ); - yield break; } //rough heuristic - if we have small number of comparisons, we are fine with sequential search @@ -129,14 +148,13 @@ ICanBeEvaluated archRuleCreator ) .ToList(); - foreach (var t in filteredObjectsList) - { - yield return CreateEvaluationResult( + return filteredObjectsList.Select(t => + CreateEvaluationResult( FindResultsForObject(conditionResults, t), architecture, archRuleCreator - ); - } + ) + ); } else { @@ -145,15 +163,13 @@ ICanBeEvaluated archRuleCreator conditionElement.Check(filteredObjectsList, architecture).ToList() ) .ToList(); - - foreach (var t in filteredObjectsList) - { - yield return CreateEvaluationResult( + return filteredObjectsList.Select(t => + CreateEvaluationResult( FindResultsForObject(conditionResults, t), architecture, archRuleCreator - ); - } + ) + ); } } @@ -349,6 +365,11 @@ Architecture architecture .Select(result => new ConditionElementResult(result, _logicalConjunction)); } + public bool IsOrdered() + { + return _condition is IOrderedCondition; + } + public bool CheckEmpty(bool currentResult) { if (_condition == null) diff --git a/ArchUnitNET/Fluent/Conditions/ArchitectureCondition.cs b/ArchUnitNET/Fluent/Conditions/ArchitectureCondition.cs index 39e2bad18..d588e1cc9 100644 --- a/ArchUnitNET/Fluent/Conditions/ArchitectureCondition.cs +++ b/ArchUnitNET/Fluent/Conditions/ArchitectureCondition.cs @@ -14,31 +14,6 @@ private readonly Func< IEnumerable > _condition; - public ArchitectureCondition( - Func condition, - string description, - string failDescription - ) - { - _condition = (ruleTypes, architecture) => - ruleTypes.Select(type => new ConditionResult( - type, - condition(type, architecture), - failDescription - )); - Description = description; - } - - public ArchitectureCondition( - Func condition, - string description - ) - { - _condition = (ruleTypes, architecture) => - ruleTypes.Select(type => condition(type, architecture)); - Description = description; - } - public ArchitectureCondition( Func, Architecture, IEnumerable> condition, string description @@ -48,21 +23,6 @@ string description Description = description; } - public ArchitectureCondition( - Func condition, - Func dynamicFailDescription, - string description - ) - { - _condition = (ruleTypes, architecture) => - ruleTypes.Select(type => new ConditionResult( - type, - condition(type, architecture), - dynamicFailDescription(type, architecture) - )); - Description = description; - } - public string Description { get; } public IEnumerable Check( diff --git a/ArchUnitNET/Fluent/Conditions/ExistsCondition.cs b/ArchUnitNET/Fluent/Conditions/ExistsCondition.cs index 5eeb47ae8..6622e90a8 100644 --- a/ArchUnitNET/Fluent/Conditions/ExistsCondition.cs +++ b/ArchUnitNET/Fluent/Conditions/ExistsCondition.cs @@ -4,7 +4,7 @@ namespace ArchUnitNET.Fluent.Conditions { - public class ExistsCondition : ICondition + public class ExistsCondition : IOrderedCondition where TRuleType : ICanBeAnalyzed { private readonly bool _valueIfExists; diff --git a/ArchUnitNET/Fluent/Conditions/IOrderedCondition.cs b/ArchUnitNET/Fluent/Conditions/IOrderedCondition.cs new file mode 100644 index 000000000..f2e6b17b0 --- /dev/null +++ b/ArchUnitNET/Fluent/Conditions/IOrderedCondition.cs @@ -0,0 +1,7 @@ +using ArchUnitNET.Domain; + +namespace ArchUnitNET.Fluent.Conditions +{ + public interface IOrderedCondition : ICondition + where TRuleType : ICanBeAnalyzed { } +} diff --git a/ArchUnitNET/Fluent/Conditions/OrderedArchitectureCondition.cs b/ArchUnitNET/Fluent/Conditions/OrderedArchitectureCondition.cs new file mode 100644 index 000000000..0dc3b9d0e --- /dev/null +++ b/ArchUnitNET/Fluent/Conditions/OrderedArchitectureCondition.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ArchUnitNET.Domain; + +namespace ArchUnitNET.Fluent.Conditions +{ + public class OrderedArchitectureCondition + : ArchitectureCondition, + IOrderedCondition + where TRuleType : ICanBeAnalyzed + { + public OrderedArchitectureCondition( + Func condition, + string description, + string failDescription + ) + : base( + (ruleTypes, architecture) => + ruleTypes.Select(type => new ConditionResult( + type, + condition(type, architecture), + failDescription + )), + description + ) { } + + public OrderedArchitectureCondition( + Func condition, + Func dynamicFailDescription, + string description + ) + : base( + (ruleTypes, architecture) => + ruleTypes.Select(type => new ConditionResult( + type, + condition(type, architecture), + dynamicFailDescription(type, architecture) + )), + description + ) { } + + public OrderedArchitectureCondition( + Func condition, + string description + ) + : base( + (ruleTypes, architecture) => + ruleTypes.Select(type => condition(type, architecture)), + description + ) { } + + public OrderedArchitectureCondition( + Func, Architecture, IEnumerable> condition, + string description + ) + : base(condition, description) { } + } +} diff --git a/ArchUnitNET/Fluent/Conditions/SimpleCondition.cs b/ArchUnitNET/Fluent/Conditions/SimpleCondition.cs index 871334a5f..fc7ca1502 100644 --- a/ArchUnitNET/Fluent/Conditions/SimpleCondition.cs +++ b/ArchUnitNET/Fluent/Conditions/SimpleCondition.cs @@ -5,7 +5,7 @@ namespace ArchUnitNET.Fluent.Conditions { - public class SimpleCondition : ICondition + public class SimpleCondition : IOrderedCondition where TRuleType : ICanBeAnalyzed { private readonly Func _condition; diff --git a/ArchUnitNET/Fluent/Syntax/Elements/ObjectConditionsDefinition.cs b/ArchUnitNET/Fluent/Syntax/Elements/ObjectConditionsDefinition.cs index 52cb0b59d..b4e7f928b 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/ObjectConditionsDefinition.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/ObjectConditionsDefinition.cs @@ -5,6 +5,7 @@ using ArchUnitNET.Domain.Extensions; using ArchUnitNET.Fluent.Conditions; using JetBrains.Annotations; +using static ArchUnitNET.Domain.Extensions.EnumerableExtensions; using static ArchUnitNET.Domain.Visibility; using Attribute = ArchUnitNET.Domain.Attribute; @@ -26,28 +27,29 @@ IEnumerable Condition( Architecture architecture ) { - var objectList = objectProvider.GetObjects(architecture).ToList(); - var typeList = ruleTypes.ToList(); - var passedObjects = objectList.OfType().Intersect(typeList).ToList(); - foreach (var failedObject in typeList.Except(passedObjects)) - { - yield return new ConditionResult( - failedObject, - false, - (sizedObjectProvider != null && sizedObjectProvider.Count == 0) - ? "does exist" - : "is not " + objectProvider.Description - ); - } - - foreach (var passedObject in passedObjects) + var isAllowedObject = CreateLookupFn( + objectProvider.GetObjects(architecture).ToList() + ); + foreach (var ruleType in ruleTypes) { - yield return new ConditionResult(passedObject, true); + if (isAllowedObject(ruleType)) + { + yield return new ConditionResult(ruleType, true); + } + else + { + yield return new ConditionResult( + ruleType, + false, + (sizedObjectProvider != null && sizedObjectProvider.Count == 0) + ? "does exist" + : "is not " + objectProvider.Description + ); + } } } - var description = objectProvider.FormatDescription("not exist", "be", "be"); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition CallAny(IObjectProvider objectProvider) @@ -57,27 +59,28 @@ IEnumerable Condition( Architecture architecture ) { - var methodList = objectProvider.GetObjects(architecture).ToList(); - var typeList = ruleTypes.ToList(); - var passedObjects = typeList - .Where(type => type.GetCalledMethods().Intersect(methodList).Any()) - .ToList(); - foreach (var failedObject in typeList.Except(passedObjects)) - { - var calledMethods = failedObject.GetCalledMethods().ToList(); - var dynamicFailDescription = - calledMethods.Count == 0 - ? "does not call any methods" - : "only calls " - + string.Join( - " and ", - calledMethods.Select(method => $"\"{method.FullName}\"") - ); - yield return new ConditionResult(failedObject, false, dynamicFailDescription); - } - foreach (var passedObject in passedObjects) + var isRequiredMethod = CreateLookupFn( + objectProvider.GetObjects(architecture).ToList() + ); + foreach (var ruleType in ruleTypes) { - yield return new ConditionResult(passedObject, true); + if (ruleType.GetCalledMethods().Any(isRequiredMethod)) + { + yield return new ConditionResult(ruleType, true); + } + else + { + var calledMethods = ruleType.GetCalledMethods().ToList(); + var dynamicFailDescription = + calledMethods.Count == 0 + ? "does not call any methods" + : "only calls " + + string.Join( + " and ", + calledMethods.Select(method => $"\"{method.FullName}\"") + ); + yield return new ConditionResult(ruleType, false, dynamicFailDescription); + } } } var description = objectProvider.FormatDescription( @@ -85,7 +88,7 @@ Architecture architecture "call", "call any" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition DependOnAny(IObjectProvider objectProvider) @@ -95,27 +98,28 @@ IEnumerable Condition( Architecture architecture ) { - var typeList = objectProvider.GetObjects(architecture).ToList(); - var ruleTypeList = ruleTypes.ToList(); - var passedObjects = ruleTypeList - .Where(type => type.GetTypeDependencies().Intersect(typeList).Any()) - .ToList(); - foreach (var failedObject in ruleTypeList.Except(passedObjects)) - { - var dependants = failedObject.GetTypeDependencies(architecture).ToList(); - var dynamicFailDescription = - dependants.Count == 0 - ? "does not depend on any type" - : "only depends on " - + string.Join( - " and ", - dependants.Select(type => $"\"{type.FullName}\"") - ); - yield return new ConditionResult(failedObject, false, dynamicFailDescription); - } - foreach (var passedObject in passedObjects) + var isRequiredDependency = CreateLookupFn( + objectProvider.GetObjects(architecture).ToList() + ); + foreach (var ruleType in ruleTypes) { - yield return new ConditionResult(passedObject, true); + if (ruleType.GetTypeDependencies().Any(isRequiredDependency)) + { + yield return new ConditionResult(ruleType, true); + } + else + { + var dependants = ruleType.GetTypeDependencies(architecture).ToList(); + var dynamicFailDescription = + dependants.Count == 0 + ? "does not depend on any type" + : "only depends on " + + string.Join( + " and ", + dependants.Select(type => $"\"{type.FullName}\"") + ); + yield return new ConditionResult(ruleType, false, dynamicFailDescription); + } } } var description = objectProvider.FormatDescription( @@ -123,7 +127,7 @@ Architecture architecture "depend on", "depend on any" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition FollowCustomCondition( @@ -150,38 +154,37 @@ IEnumerable Condition( Architecture architecture ) { - var typeList = objectProvider.GetObjects(architecture).ToList(); - var ruleTypeList = ruleTypes.ToList(); - var failedObjects = ruleTypeList - .Where(type => type.GetTypeDependencies(architecture).Except(typeList).Any()) - .ToList(); - foreach (var failedObject in failedObjects) + var isAllowedDependency = CreateLookupFn( + objectProvider.GetObjects(architecture).ToList() + ); + foreach (var ruleType in ruleTypes) { - var dynamicFailDescription = "does depend on"; - var first = true; - foreach (var type in failedObject.GetTypeDependencies().Except(typeList)) + if (ruleType.GetTypeDependencies(architecture).All(isAllowedDependency)) { - dynamicFailDescription += first - ? " " + type.FullName - : " and " + type.FullName; - first = false; + yield return new ConditionResult(ruleType, true); + } + else + { + var dynamicFailDescription = + "does depend on " + + string.Join( + " and ", + ruleType + .GetTypeDependencies() + .Where(t => !isAllowedDependency(t)) + .Distinct() + .Select(t => t.FullName) + ); + yield return new ConditionResult(ruleType, false, dynamicFailDescription); } - - yield return new ConditionResult(failedObject, false, dynamicFailDescription); - } - - foreach (var passedObject in ruleTypeList.Except(failedObjects)) - { - yield return new ConditionResult(passedObject, true); } } - var description = objectProvider.FormatDescription( "have no dependencies", "only depend on", "only depend on" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition HaveAnyAttributes( @@ -193,37 +196,36 @@ IEnumerable Condition( Architecture architecture ) { - var attributeList = objectProvider.GetObjects(architecture).ToList(); - var ruleTypeList = ruleTypes.ToList(); - var passedObjects = ruleTypeList - .Where(type => type.Attributes.Intersect(attributeList).Any()) - .ToList(); - foreach (var failedObject in ruleTypeList.Except(passedObjects)) - { - var attributes = failedObject.Attributes.ToList(); - var failDescription = - attributes.Count == 0 - ? "does not have any attributes" - : "only has attributes " - + string.Join( - " and ", - attributes.Select(attribute => $"\"{attribute.FullName}\"") - ); - yield return new ConditionResult(failedObject, false, failDescription); - } - - foreach (var passedObject in passedObjects) + var isRequiredAttribute = CreateLookupFn( + objectProvider.GetObjects(architecture).ToList() + ); + foreach (var ruleType in ruleTypes) { - yield return new ConditionResult(passedObject, true); + if (ruleType.Attributes.Any(isRequiredAttribute)) + { + yield return new ConditionResult(ruleType, true); + } + else + { + var attributes = ruleType.Attributes.ToList(); + var dynamicFailDescription = + attributes.Count == 0 + ? "does not have any attributes" + : "only has attributes " + + string.Join( + " and ", + attributes.Select(attribute => $"\"{attribute.FullName}\"") + ); + yield return new ConditionResult(ruleType, false, dynamicFailDescription); + } } } - var description = objectProvider.FormatDescription( "have any of no attributes (impossible)", "have", "have any" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition OnlyHaveAttributes( @@ -235,38 +237,35 @@ IEnumerable Condition( Architecture architecture ) { - var attributeList = objectProvider.GetObjects(architecture).ToList(); - var ruleTypeList = ruleTypes.ToList(); - var failedObjects = ruleTypeList - .Where(type => type.Attributes.Except(attributeList).Any()) - .ToList(); - foreach (var failedObject in failedObjects) + var isAllowedAttribute = CreateLookupFn( + objectProvider.GetObjects(architecture).ToList() + ); + foreach (var ruleType in ruleTypes) { - var dynamicFailDescription = "does have attribute"; - var first = true; - foreach (var attribute in failedObject.Attributes.Except(attributeList)) + if (ruleType.Attributes.All(isAllowedAttribute)) { - dynamicFailDescription += first - ? " " + attribute.FullName - : " and " + attribute.FullName; - first = false; + yield return new ConditionResult(ruleType, true); + } + else + { + var dynamicFailDescription = + "does have attribute " + + string.Join( + " and ", + ruleType + .Attributes.Where(attr => !isAllowedAttribute(attr)) + .Select(a => a.FullName) + ); + yield return new ConditionResult(ruleType, false, dynamicFailDescription); } - - yield return new ConditionResult(failedObject, false, dynamicFailDescription); - } - - foreach (var passedObject in ruleTypeList.Except(failedObjects)) - { - yield return new ConditionResult(passedObject, true); } } - var description = objectProvider.FormatDescription( "have no attributes", "only have", "only have any" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition HaveAnyAttributesWithArguments( @@ -324,7 +323,7 @@ Architecture architecture "have any attributes with argument", "have any attributes with arguments" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition HaveAttributeWithArguments( @@ -388,7 +387,7 @@ Architecture architecture $"have attribute \"{attributeFullName}\" with argument", $"have attribute \"{attributeFullName}\" with arguments" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition HaveAnyAttributesWithNamedArguments( @@ -449,7 +448,7 @@ Architecture architecture "have any attributes with named arguments", elementDescription: arg => $"\"{arg.Item1}={arg.Item2}\"" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition HaveAttributeWithNamedArguments( @@ -516,7 +515,7 @@ Architecture architecture $"have attribute \"{attributeFullName}\" with named arguments", elementDescription: arg => $"\"{arg.Item1}={arg.Item2}\"" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition HaveName(string name) @@ -760,30 +759,30 @@ IEnumerable Condition( Architecture architecture ) { - var objectList = objectProvider.GetObjects(architecture).ToList(); - var typeList = ruleTypes.ToList(); - var failedObjects = objectList.OfType().Intersect(typeList).ToList(); - foreach (var failedObject in failedObjects) - { - yield return new ConditionResult( - failedObject, - false, - "is " + objectProvider.Description - ); - } - - foreach (var passedObject in typeList.Except(failedObjects)) + var objects = objectProvider.GetObjects(architecture).ToList(); + var isForbiddenObject = CreateLookupFn(objects); + foreach (var ruleType in ruleTypes) { - yield return new ConditionResult(passedObject, true); + if (!isForbiddenObject(ruleType)) + { + yield return new ConditionResult(ruleType, true); + } + else + { + yield return new ConditionResult( + ruleType, + false, + objects.Count == 0 ? "does exist" : "is " + objectProvider.Description + ); + } } } - var description = objectProvider.FormatDescription( "not be any of no objects (always true)", "not be", "not be" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition NotCallAny(IObjectProvider objectProvider) @@ -793,32 +792,33 @@ IEnumerable Condition( Architecture architecture ) { - var methodList = objectProvider.GetObjects(architecture).ToList(); - var typeList = ruleTypes.ToList(); - var failedObjects = typeList - .Where(type => type.GetCalledMethods().Intersect(methodList).Any()) - .ToList(); - foreach (var failedObject in failedObjects) - { - var calledMethods = failedObject - .GetCalledMethods() - .Intersect(methodList) - .Select(method => $"\"{method.FullName}\""); - var dynamicFailDescription = "does call " + string.Join(" and ", calledMethods); - yield return new ConditionResult(failedObject, false, dynamicFailDescription); - } - foreach (var passedObject in typeList.Except(failedObjects)) + var isForbiddenMethod = CreateLookupFn( + objectProvider.GetObjects(architecture).ToList() + ); + foreach (var ruleType in ruleTypes) { - yield return new ConditionResult(passedObject, true); + if (!ruleType.GetCalledMethods().Any(isForbiddenMethod)) + { + yield return new ConditionResult(ruleType, true); + } + else + { + var calledMethods = ruleType + .GetCalledMethods() + .Where(isForbiddenMethod) + .Select(method => $"\"{method.FullName}\""); + var dynamicFailDescription = + "does call " + string.Join(" and ", calledMethods); + yield return new ConditionResult(ruleType, false, dynamicFailDescription); + } } } - var description = objectProvider.FormatDescription( "not call any of no methods (always true)", "not call", "not call any" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition NotDependOnAny(IObjectProvider objectProvider) @@ -828,33 +828,34 @@ IEnumerable Condition( Architecture architecture ) { - var typeList = objectProvider.GetObjects(architecture).ToList(); - var ruleTypeList = ruleTypes.ToList(); - var failedObjects = ruleTypeList - .Where(type => type.GetTypeDependencies().Intersect(typeList).Any()) - .ToList(); - foreach (var failedObject in failedObjects) - { - var dependants = failedObject - .GetTypeDependencies() - .Intersect(typeList) - .Select(type => $"\"{type.FullName}\""); - var dynamicFailDescription = - "does depend on " + string.Join(" and ", dependants); - yield return new ConditionResult(failedObject, false, dynamicFailDescription); - } - foreach (var passedObject in ruleTypeList.Except(failedObjects)) + var isForbiddenDependency = CreateLookupFn( + objectProvider.GetObjects(architecture).ToList() + ); + foreach (var ruleType in ruleTypes) { - yield return new ConditionResult(passedObject, true); + if (!ruleType.GetTypeDependencies().Any(isForbiddenDependency)) + { + yield return new ConditionResult(ruleType, true); + } + else + { + var dependants = ruleType + .GetTypeDependencies() + .Where(isForbiddenDependency) + .Distinct() + .Select(type => $"\"{type.FullName}\""); + var dynamicFailDescription = + "does depend on " + string.Join(" and ", dependants); + yield return new ConditionResult(ruleType, false, dynamicFailDescription); + } } } - var description = objectProvider.FormatDescription( "not depend on any of no types (always true)", "not depend on", "not depend on any" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition NotHaveAnyAttributes( @@ -866,38 +867,34 @@ IEnumerable Condition( Architecture architecture ) { - var attributeList = objectProvider.GetObjects(architecture).ToList(); - var ruleTypeList = ruleTypes.ToList(); - var failedObjects = ruleTypeList - .Where(type => type.Attributes.Intersect(attributeList).Any()) - .ToList(); - foreach (var failedObject in failedObjects) + var isForbiddenAttribute = CreateLookupFn( + objectProvider.GetObjects(architecture).ToList() + ); + foreach (var ruleType in ruleTypes) { - var dynamicFailDescription = "does have attribute"; - var first = true; - foreach (var attribute in failedObject.Attributes.Intersect(attributeList)) + if (!ruleType.Attributes.Any(isForbiddenAttribute)) { - dynamicFailDescription += first - ? " " + attribute.FullName - : " and " + attribute.FullName; - first = false; + yield return new ConditionResult(ruleType, true); + } + else + { + var attributes = ruleType.Attributes.Where(isForbiddenAttribute).ToList(); + var dynamicFailDescription = + "does have attribute " + + string.Join( + " and ", + attributes.Select(attribute => attribute.FullName) + ); + yield return new ConditionResult(ruleType, false, dynamicFailDescription); } - - yield return new ConditionResult(failedObject, false, dynamicFailDescription); - } - - foreach (var passedObject in ruleTypeList.Except(failedObjects)) - { - yield return new ConditionResult(passedObject, true); } } - var description = objectProvider.FormatDescription( "not have any of no attributes (always true)", "not have", "not have any" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition NotHaveAnyAttributesWithArguments( @@ -956,7 +953,7 @@ Architecture architecture "not have any attributes with argument", "not have any attributes with arguments" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition NotHaveAttributeWithArguments( @@ -1004,7 +1001,7 @@ Architecture architecture $"not have attribute \"{attributeFullName}\" with argument", $"not have attribute \"{attributeFullName}\" with arguments" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition NotHaveAnyAttributesWithNamedArguments( @@ -1066,7 +1063,7 @@ Architecture architecture "not have any attributes with named arguments", elementDescription: arg => $"\"{arg.Item1}={arg.Item2}\"" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition NotHaveAttributeWithNamedArguments( @@ -1120,7 +1117,7 @@ Architecture architecture $"not have attribute \"{attributeFullName}\" with named arguments", elementDescription: arg => $"\"{arg.Item1}={arg.Item2}\"" ); - return new ArchitectureCondition(Condition, description); + return new OrderedArchitectureCondition(Condition, description); } public static ICondition NotHaveName(string name) diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs b/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs index c4b1eb3d3..bfe0a5f6f 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs @@ -625,7 +625,7 @@ bool Condition(TRuleType ruleType, Architecture architecture) (current, asm) => current + " or \"" + asm.FullName + "\"" ); - return new ArchitectureCondition( + return new OrderedArchitectureCondition( Condition, (type, architecture) => "does reside in " + type.Assembly.FullName, description @@ -1247,7 +1247,7 @@ bool Condition(TRuleType ruleType, Architecture architecture) (current, asm) => current + " or \"" + asm.FullName + "\"" ); - return new ArchitectureCondition( + return new OrderedArchitectureCondition( Condition, (type, architecture) => "does reside in " + type.Assembly.FullName, description