From 676c0b61d3b8abaf1c58bc55ce043d47efbdab4c Mon Sep 17 00:00:00 2001 From: Alexander Linne Date: Fri, 23 Oct 2026 18:27:10 +0200 Subject: [PATCH] feat: identify and name slices by their captures, like ArchUnit A slice was identified by its name, a single string, and named after what the pattern captured with nothing to mark it as a slice. A custom SliceAssignment had no way to say what its slice consists of, and a slice name read like a namespace. Identify a slice by its parts, as ArchUnit's SliceIdentifier does: for a slice pattern, the values its capture groups captured, in group order. SliceIdentifier.Parts holds them, and equality, hash code, CompareTo and therefore the grouping in SliceAssignment.Apply compare the parts (and Ignored) alone. The matcher still captures a single value per type, so every pattern slice has one part and groups exactly as before. SliceIdentifier.Of(IEnumerable) creates a slice with several parts; Of(string) is the same slice as Of(new[] { part }). Name every slice after its parts like ArchUnit's default: "Slice " followed by the parts joined with " - ": App.(*) App.Orders Slice Orders App.(**) App.Orders.Web Slice Orders.Web Of(new[] { "Orders", "Http" }) Slice Orders - Http The prefix keeps a name from being mistaken for a namespace. It applies to every slice, including SlicedBy and custom SliceAssignments, and it is what failure messages and flat diagrams show. Nested diagrams draw slices under their qualified names, so they don't change. SliceIdentifier no longer derives from StringIdentifier: the parts are all that identifies a slice. Violation stores need a string key, which SlicesShould takes from the slice's name when it reports a violation, so a store holds what failure messages show, e.g. "Slice Orders", as ArchUnit's stores, which keep violation messages, do. Hand-built parts that contain " - " can share a name, and thus a key, with another slice: Of(new[] { "A - B" }) and Of(new[] { "A", "B" }) are different slices, both named "Slice A - B". Parts captured by a pattern never contain " - ", and ArchUnit has the same limitation. The exporter rejects two such slices in one diagram, as they would be drawn under the same name. The guide's "Cycle Rule" section becomes "Slice Rules", with how slices are named and frozen, and the NotDependOnEachOther rule. Breaking changes: - Slices are named "Slice ..." from their parts, which changes every slice failure message and flat slice diagram label. - Frozen slice violations are stored by the slice's name, so stores written before no longer match and have to be regenerated. - New are SliceIdentifier.Parts and SliceIdentifier.Of(IEnumerable). - SliceIdentifier no longer derives from StringIdentifier, so its Identifier field is gone, and ToString() returns the slice's name. Signed-off-by: Alexander Linne --- .../Domain/PlantUml/Export/PlantUmlDiagram.cs | 15 +- .../PlantUml/Export/PlantUmlFileBuilder.cs | 27 ++-- ArchUnitNET/Domain/SliceIdentifier.cs | 105 +++++++++++--- .../Fluent/Slices/SliceRuleInitializer.cs | 2 +- ArchUnitNET/Fluent/Slices/SlicesShould.cs | 6 +- .../Storage/CustomPathFrozenRules.json | 12 +- .../ArchUnitNET/Storage/FrozenRules.json | 12 +- .../ArchUnitNET/Storage/FrozenRules.xml | 12 +- .../PlantUml/PlantUmlSliceDiagramTests.cs | 27 ++-- ..._Matching_ColorsFocusedSlices.verified.txt | 16 +- ...ests.BuildUmlBySlices_C4Style.verified.txt | 30 ++-- ...ircle_RendersCircleDependency.verified.txt | 6 +- ...ildUmlBySlices_DoubleAsterisk.verified.txt | 30 ++-- ...cludeNodesWithoutDependencies.verified.txt | 24 +-- ...ces_LimitDependencies_Compact.verified.txt | 26 ++-- ...ySlices_MultipleCaptureGroups.verified.txt | 26 ++-- ...es_NonContiguousCaptureGroups.verified.txt | 26 ++-- ...ildUmlBySlices_SingleAsterisk.verified.txt | 12 +- ...BySlices_WithDependencyFilter.verified.txt | 28 ++-- .../Fluent/Slices/SliceDomainTests.cs | 137 +++++++++++++++++- ArchUnitNETTests/Fluent/Slices/SlicesTests.cs | 55 +++---- ...binesEvaluationAndDescription.verified.txt | 12 +- ...binesEvaluationAndDescription.verified.txt | 12 +- ...binesEvaluationAndDescription.verified.txt | 36 ++--- ...binesEvaluationAndDescription.verified.txt | 24 +-- ...bnamespaces_ReportsEveryCycle.verified.txt | 12 +- ...eturnsDescriptiveCycleMessage.verified.txt | 8 +- ...spaces_ReportsEveryDependency.verified.txt | 54 +++---- ...sDescriptiveDependencyMessage.verified.txt | 12 +- documentation/docs/guide.md | 35 ++++- 30 files changed, 539 insertions(+), 300 deletions(-) diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs index 7d0e01167..a2af58e78 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs @@ -57,13 +57,14 @@ public string GetPlantUmlString(RenderOptions renderOptions) /// /// /// A slice is identified in the diagram by its name, but not every name is a valid id. - /// PlantUML rejects "[Inner] as App.Outer+Inner" and "Container(App.Orders.*.Http, ...)" - /// as syntax errors, and slice names can contain such characters: SlicedBy names slices - /// after type names such as "Outer+Inner" or "List`1", and a hand-built SliceIdentifier - /// can be called anything. No character that a namespace cannot contain works in their - /// place either, so the id is made from the name with those characters replaced by "_", - /// and suffixed until it clashes with no other name in the diagram. The name itself stays - /// the label, and slices whose names are valid ids keep them. + /// PlantUML rejects "[Inner] as App.Outer+Inner" and "Container(Slice Orders, ...)" as + /// syntax errors, and slice names can contain such characters: every slice description + /// starts with "Slice " and joins several parts with " - ", SlicedBy names slices after + /// type names such as "Outer+Inner" or "List`1", and a hand-built SliceIdentifier can be + /// called anything. No character that a namespace cannot contain works in their place either, so + /// the id is made from the name with those characters replaced by "_", and suffixed until + /// it clashes with no other name in the diagram. The name itself stays the label, and + /// slices whose names are valid ids keep them. /// private static void AssignAliases(IReadOnlyCollection elements) { diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlFileBuilder.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlFileBuilder.cs index fc758aa6f..ef647c043 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlFileBuilder.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlFileBuilder.cs @@ -728,12 +728,14 @@ private void ReplaceCirclesWithAppropriateDependencyType() /// nested slice is therefore drawn under its qualified name, the namespace its types /// matched before the first capture group followed by its /// , e.g. "App.Orders.Http", with that namespace - /// as its root package. Every other slice is drawn under its description, without a - /// package. + /// as its root package. Every other slice is drawn under its description, e.g. + /// "Slice Orders - Http", without a package. /// /// - /// Qualified names of one pattern are distinct, but a description can coincide with a - /// qualified name. Drawing two slices as one node would be wrong, so that is rejected. + /// A description always contains a space and a qualified name never does, so the two + /// can't be mistaken for each other, and qualified names of one pattern are distinct. + /// Descriptions can still coincide, for a custom slice whose part contains " - ". + /// Drawing two slices as one node would be wrong, so that is rejected. /// /// /// @@ -757,11 +759,11 @@ private void AssignDiagramNames(bool nestSlicesInPackages) if (slicesByName.TryGetValue(diagramName.Name, out var other)) { throw new ArgumentException( - "The slices \"" - + other.Identifier.Identifier - + "\" and \"" - + slice.Identifier.Identifier - + "\" would both be drawn as \"" + "The slices with parts " + + FormatParts(other) + + " and " + + FormatParts(slice) + + " would both be drawn as \"" + diagramName.Name + "\"." ); @@ -772,6 +774,13 @@ private void AssignDiagramNames(bool nestSlicesInPackages) } } + private static string FormatParts(Slice slice) + { + return "[" + + string.Join(", ", slice.Identifier.Parts.Select(part => "\"" + part + "\"")) + + "]"; + } + /// The name the diagram draws a slice under; see . private string NameOf(Slice slice) { diff --git a/ArchUnitNET/Domain/SliceIdentifier.cs b/ArchUnitNET/Domain/SliceIdentifier.cs index 8dc528989..fc1090391 100644 --- a/ArchUnitNET/Domain/SliceIdentifier.cs +++ b/ArchUnitNET/Domain/SliceIdentifier.cs @@ -1,28 +1,48 @@ -using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; namespace ArchUnitNET.Domain { - public class SliceIdentifier : StringIdentifier, IHasDescription + /// + /// Identifies a slice by its parts: for a slice pattern, the values its capture groups + /// captured, in group order. Two identifiers are the same slice exactly when their parts are + /// equal and both are ignored or both are not. + /// + public class SliceIdentifier : IHasDescription { public static readonly SliceIdentifierComparer Comparer = new SliceIdentifierComparer(); public readonly bool Ignored; private SliceIdentifier( - string identifier, + IReadOnlyList parts, bool ignored, int? countOfAsteriskInPattern = null, string matchedPrefix = null, string diagramPath = null ) - : base(identifier) { + Parts = parts; Ignored = ignored; CountOfAsteriskInPattern = countOfAsteriskInPattern; MatchedPrefix = matchedPrefix; DiagramPath = diagramPath; } - public string Description => Identifier; + /// + /// What identifies the slice: for a slice pattern, the values its capture groups + /// captured, in group order. "App.(**)" over "App.Orders.Web" gives + /// [Orders.Web]. + /// + public IReadOnlyList Parts { get; } + + /// + /// The slice's name, built from its parts: "Slice " followed by the parts joined with + /// " - ", e.g. "Slice Orders - Http". It depends on the parts only, so equal slices always + /// have the same name. + /// + public string Description => "Slice " + string.Join(" - ", Parts); /// /// The part of the type's namespace the pattern matched before the first capture group, @@ -44,9 +64,31 @@ private SliceIdentifier( public readonly int? CountOfAsteriskInPattern; - public static SliceIdentifier Of(string identifier, int? countOfAsteriskInPattern = null) + /// + /// Creates an identifier for a slice with a single part, the same slice as + /// Of(new[] { part }). + /// + /// + /// The slice's only part: a custom SliceAssignment that returns identifiers with the + /// same part for two types puts them into the same slice. + /// + /// + /// The number of "(*)" groups in the pattern, if the slice comes from one. + /// + public static SliceIdentifier Of(string part, int? countOfAsteriskInPattern = null) + { + return new SliceIdentifier(CheckParts(new[] { part }), false, countOfAsteriskInPattern); + } + + /// + /// Creates an identifier for the slice with the given parts. Identifiers with equal parts, + /// in the same order, belong to the same slice. + /// + /// The parts, at least one; none of them may be null. + /// There are no parts, or one of them is null. + public static SliceIdentifier Of(IEnumerable parts) { - return new SliceIdentifier(identifier, false, countOfAsteriskInPattern); + return new SliceIdentifier(CheckParts(parts), false); } /// @@ -54,14 +96,14 @@ public static SliceIdentifier Of(string identifier, int? countOfAsteriskInPatter /// pattern matched, which diagrams use to nest the slice. /// internal static SliceIdentifier OfMatch( - string identifier, + IEnumerable parts, int? countOfAsteriskInPattern, string matchedPrefix, string diagramPath ) { return new SliceIdentifier( - identifier, + CheckParts(parts), false, countOfAsteriskInPattern, matchedPrefix, @@ -71,14 +113,34 @@ string diagramPath public static SliceIdentifier Ignore() { - return new SliceIdentifier("Ignored", true); + return new SliceIdentifier(new[] { "Ignored" }, true); + } + + private static IReadOnlyList CheckParts(IEnumerable parts) + { + if (parts == null) + { + throw new ArgumentNullException(nameof(parts)); + } + + var list = parts.ToList(); + if (list.Count == 0) + { + throw new ArgumentException("A slice needs at least one part.", nameof(parts)); + } + + if (list.Any(part => part == null)) + { + throw new ArgumentException("A slice part can't be null.", nameof(parts)); + } + + return list.AsReadOnly(); } /// - /// Is true when the two SliceIdentifiers belong to the same slice + /// Whether the two identifiers belong to the same slice: both ignored, or both not + /// ignored with the same parts. /// - /// - /// public bool CompareTo(SliceIdentifier other) { if (other == null) @@ -87,12 +149,12 @@ public bool CompareTo(SliceIdentifier other) } return Ignored && other.Ignored - || !Ignored && !other.Ignored && Identifier == other.Identifier; + || !Ignored && !other.Ignored && Parts.SequenceEqual(other.Parts); } private bool Equals(SliceIdentifier other) { - return Identifier == other.Identifier && Ignored == other.Ignored; + return Ignored == other.Ignored && Parts.SequenceEqual(other.Parts); } public override bool Equals(object obj) @@ -114,10 +176,19 @@ public override int GetHashCode() { unchecked { - var hashCode = 397 ^ Identifier.GetHashCode(); - hashCode = (hashCode * 397) ^ Ignored.GetHashCode(); + var hashCode = Ignored.GetHashCode(); + foreach (var part in Parts) + { + hashCode = (hashCode * 397) ^ part.GetHashCode(); + } + return hashCode; } } + + public override string ToString() + { + return Description; + } } } diff --git a/ArchUnitNET/Fluent/Slices/SliceRuleInitializer.cs b/ArchUnitNET/Fluent/Slices/SliceRuleInitializer.cs index 1482a6602..8db081cf7 100644 --- a/ArchUnitNET/Fluent/Slices/SliceRuleInitializer.cs +++ b/ArchUnitNET/Fluent/Slices/SliceRuleInitializer.cs @@ -170,7 +170,7 @@ private static SliceIdentifier AssignFunc( // What the pattern captured is both what identifies the slice and the path a // diagram nests it along. return SliceIdentifier.OfMatch( - sliceString, + new[] { sliceString }, countOfSingleAsterisk, matchedPrefix, sliceString diff --git a/ArchUnitNET/Fluent/Slices/SlicesShould.cs b/ArchUnitNET/Fluent/Slices/SlicesShould.cs index 5e1fc1cfa..c67c60874 100644 --- a/ArchUnitNET/Fluent/Slices/SlicesShould.cs +++ b/ArchUnitNET/Fluent/Slices/SlicesShould.cs @@ -91,7 +91,7 @@ Architecture architecture } var cycleIdentifier = new EnumerableIdentifier( - cycle.Contents.Select(slice => slice.Identifier) + cycle.Contents.Select(slice => new StringIdentifier(slice.Description)) ); yield return new EvaluationResult( cycle, @@ -106,7 +106,7 @@ Architecture architecture else { var slicesIdentifier = new EnumerableIdentifier( - slicesList.Select(slice => slice.Identifier) + slicesList.Select(slice => new StringIdentifier(slice.Description)) ); yield return new EvaluationResult( slicesList, @@ -166,7 +166,7 @@ Architecture architecture yield return new EvaluationResult( slice, - slice.Identifier, + new StringIdentifier(slice.Description), passed, description, archRule, diff --git a/ArchUnitNETTests/ArchUnitNET/Storage/CustomPathFrozenRules.json b/ArchUnitNETTests/ArchUnitNET/Storage/CustomPathFrozenRules.json index 9216791d3..ceb002561 100644 --- a/ArchUnitNETTests/ArchUnitNET/Storage/CustomPathFrozenRules.json +++ b/ArchUnitNETTests/ArchUnitNET/Storage/CustomPathFrozenRules.json @@ -15,12 +15,12 @@ { "ArchRuleDescription": "Slices matching \"SlicesTestAssembly.MultipleSubnamespaces.(**)\" should not depend on each other", "Violations": [ - "Slice3", - "Slice3.Group1", - "Slice2", - "Slice2.Service", - "Slice1", - "Slice1.Service" + "Slice Slice3", + "Slice Slice3.Group1", + "Slice Slice2", + "Slice Slice2.Service", + "Slice Slice1", + "Slice Slice1.Service" ] } ] \ No newline at end of file diff --git a/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.json b/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.json index 5e4531c65..93b5d4c34 100644 --- a/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.json +++ b/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.json @@ -21,12 +21,12 @@ { "ArchRuleDescription": "Slices matching \"SlicesTestAssembly.MultipleSubnamespaces.(**)\" should not depend on each other", "Violations": [ - "Slice3", - "Slice3.Group1", - "Slice2", - "Slice2.Service", - "Slice1", - "Slice1.Service" + "Slice Slice3", + "Slice Slice3.Group1", + "Slice Slice2", + "Slice Slice2.Service", + "Slice Slice1", + "Slice Slice1.Service" ] }, { diff --git a/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.xml b/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.xml index c0905ff78..785f1107e 100644 --- a/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.xml +++ b/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.xml @@ -12,11 +12,11 @@ ArchUnitNETTests.Fluent.FreezeTests+Violation - Slice3 - Slice3.Group1 - Slice2 - Slice2.Service - Slice1 - Slice1.Service + Slice Slice3 + Slice Slice3.Group1 + Slice Slice2 + Slice Slice2.Service + Slice Slice1 + Slice Slice1.Service \ No newline at end of file diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs index d3f13c11b..a02feb79b 100644 --- a/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs +++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs @@ -154,9 +154,8 @@ public Task BuildUmlBySlicesFocusOn_Matching_ColorsFocusedSlices() { // Slices that are not nested have no namespace, so the focused ones take // PlantUmlSlice.BuildString's namespace-less branch with a colour set -- the only - // path on which the exporter renders "[Slice1] #99ffd1" rather than a package. - // They are drawn under their description, which carries no Root prefix, hence the - // bare focus string. + // path on which the exporter renders "[Slice Slice1] #99ffd1" rather than a package. + // They are drawn under their description, which "Slice1" is contained in. var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)")); var uml = new PlantUmlFileBuilder() .WithDependenciesFromFocusOn(slices, "Slice1") @@ -371,9 +370,9 @@ public void BuildUmlBySlices_SameSlicesDrawnFlatAndNested() var nested = new PlantUmlFileBuilder().WithDependenciesFrom(slices, Nested).AsString(); Assert.DoesNotContain("package", flat); - Assert.Contains("[Slice1.Service]", flat); + Assert.Contains("[Slice Slice1] as Slice_Slice1", flat); Assert.Contains("package \"SlicesTestAssembly.MultipleSubnamespaces\"", nested); - Assert.DoesNotContain("[Slice1.Service]", nested); + Assert.DoesNotContain("Slice Slice1", nested); } // --- Two slices may not share a node ---------------------------------------------- @@ -381,20 +380,18 @@ public void BuildUmlBySlices_SameSlicesDrawnFlatAndNested() [Fact] public void BuildUmlBySlices_SameDiagramName_Throws() { - // A slice that is not nested is drawn under its description, which here is the - // qualified name a nested slice is drawn under. - var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)")) - .Concat( - new[] { new Slice(SliceIdentifier.Of(Root + "Slice1"), Array.Empty()) } - ) - .ToList(); + // Different slices, both described "Slice A - B". + var slices = new[] + { + new Slice(SliceIdentifier.Of(new[] { "A - B" }), Array.Empty()), + new Slice(SliceIdentifier.Of(new[] { "A", "B" }), Array.Empty()), + }; var ex = Assert.Throws(() => - new PlantUmlFileBuilder().WithDependenciesFrom(slices, Nested) + new PlantUmlFileBuilder().WithDependenciesFrom(slices) ); Assert.Equal( - "The slices \"Slice1\" and \"SlicesTestAssembly.MultipleSubnamespaces.Slice1\" " - + "would both be drawn as \"SlicesTestAssembly.MultipleSubnamespaces.Slice1\".", + "The slices with parts [\"A - B\"] and [\"A\", \"B\"] would both be drawn as \"Slice A - B\".", ex.Message ); } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_Matching_ColorsFocusedSlices.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_Matching_ColorsFocusedSlices.verified.txt index b96ed6505..efeb37eb4 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_Matching_ColorsFocusedSlices.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_Matching_ColorsFocusedSlices.verified.txt @@ -4,12 +4,12 @@ HIDE_STEREOTYPE() -[Slice1] #99ffd1 -[Slice1.Service] #99ffd1 -[Slice2] -[Slice2.Service] -[Slice3] -[Slice1] --|> [Slice2] -[Slice1.Service] --|> [Slice2.Service] -[Slice3] --|> [Slice1] +[Slice Slice1] as Slice_Slice1 #99ffd1 +[Slice Slice1.Service] as Slice_Slice1.Service #99ffd1 +[Slice Slice2] as Slice_Slice2 +[Slice Slice2.Service] as Slice_Slice2.Service +[Slice Slice3] as Slice_Slice3 +[Slice_Slice1] --|> [Slice_Slice2] +[Slice_Slice1.Service] --|> [Slice_Slice2.Service] +[Slice_Slice3] --|> [Slice_Slice1] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_C4Style.verified.txt index 851e24568..9b02ce5b4 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_C4Style.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_C4Style.verified.txt @@ -4,19 +4,19 @@ HIDE_STEREOTYPE() -Container(Slice1, Slice1) -Container(Slice1.Service, Slice1.Service) -Container(Slice2, Slice2) -Container(Slice2.Service, Slice2.Service) -Container(Slice3, Slice3) -Container(Slice3.Group1, Slice3.Group1) -Container(Slice3.Group1.Inner, Slice3.Group1.Inner) -Container(Slice3.Group2, Slice3.Group2) -Container(Slice3.Group2.Inner, Slice3.Group2.Inner) -[Slice1] --|> [Slice2] -[Slice1.Service] --|> [Slice2.Service] -[Slice2] --|> [Slice3] -[Slice2.Service] --|> [Slice3] -[Slice3] --|> [Slice1] -[Slice3.Group1] --|> [Slice3] +Container(Slice_Slice1, Slice Slice1) +Container(Slice_Slice1.Service, Slice Slice1.Service) +Container(Slice_Slice2, Slice Slice2) +Container(Slice_Slice2.Service, Slice Slice2.Service) +Container(Slice_Slice3, Slice Slice3) +Container(Slice_Slice3.Group1, Slice Slice3.Group1) +Container(Slice_Slice3.Group1.Inner, Slice Slice3.Group1.Inner) +Container(Slice_Slice3.Group2, Slice Slice3.Group2) +Container(Slice_Slice3.Group2.Inner, Slice Slice3.Group2.Inner) +[Slice_Slice1] --|> [Slice_Slice2] +[Slice_Slice1.Service] --|> [Slice_Slice2.Service] +[Slice_Slice2] --|> [Slice_Slice3] +[Slice_Slice2.Service] --|> [Slice_Slice3] +[Slice_Slice3] --|> [Slice_Slice1] +[Slice_Slice3.Group1] --|> [Slice_Slice3] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DirectCircle_RendersCircleDependency.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DirectCircle_RendersCircleDependency.verified.txt index ffc596323..846b74ca6 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DirectCircle_RendersCircleDependency.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DirectCircle_RendersCircleDependency.verified.txt @@ -4,7 +4,7 @@ HIDE_STEREOTYPE() -[Slice1] -[Slice2] -[Slice1] <-[#red]> [Slice2] +[Slice Slice1] as Slice_Slice1 +[Slice Slice2] as Slice_Slice2 +[Slice_Slice1] <-[#red]> [Slice_Slice2] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DoubleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DoubleAsterisk.verified.txt index b33b9dcab..92de1e097 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DoubleAsterisk.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DoubleAsterisk.verified.txt @@ -4,19 +4,19 @@ HIDE_STEREOTYPE() -[Slice1] -[Slice1.Service] -[Slice2] -[Slice2.Service] -[Slice3] -[Slice3.Group1] -[Slice3.Group1.Inner] -[Slice3.Group2] -[Slice3.Group2.Inner] -[Slice1] --|> [Slice2] -[Slice1.Service] --|> [Slice2.Service] -[Slice2] --|> [Slice3] -[Slice2.Service] --|> [Slice3] -[Slice3] --|> [Slice1] -[Slice3.Group1] --|> [Slice3] +[Slice Slice1] as Slice_Slice1 +[Slice Slice1.Service] as Slice_Slice1.Service +[Slice Slice2] as Slice_Slice2 +[Slice Slice2.Service] as Slice_Slice2.Service +[Slice Slice3] as Slice_Slice3 +[Slice Slice3.Group1] as Slice_Slice3.Group1 +[Slice Slice3.Group1.Inner] as Slice_Slice3.Group1.Inner +[Slice Slice3.Group2] as Slice_Slice3.Group2 +[Slice Slice3.Group2.Inner] as Slice_Slice3.Group2.Inner +[Slice_Slice1] --|> [Slice_Slice2] +[Slice_Slice1.Service] --|> [Slice_Slice2.Service] +[Slice_Slice2] --|> [Slice_Slice3] +[Slice_Slice2.Service] --|> [Slice_Slice3] +[Slice_Slice3] --|> [Slice_Slice1] +[Slice_Slice3.Group1] --|> [Slice_Slice3] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_ExcludeNodesWithoutDependencies.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_ExcludeNodesWithoutDependencies.verified.txt index c5667533f..5baf30abd 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_ExcludeNodesWithoutDependencies.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_ExcludeNodesWithoutDependencies.verified.txt @@ -4,16 +4,16 @@ HIDE_STEREOTYPE() -[Slice1] -[Slice1.Service] -[Slice2] -[Slice2.Service] -[Slice3] -[Slice3.Group1] -[Slice1] --|> [Slice2] -[Slice1.Service] --|> [Slice2.Service] -[Slice2] --|> [Slice3] -[Slice2.Service] --|> [Slice3] -[Slice3] --|> [Slice1] -[Slice3.Group1] --|> [Slice3] +[Slice Slice1] as Slice_Slice1 +[Slice Slice1.Service] as Slice_Slice1.Service +[Slice Slice2] as Slice_Slice2 +[Slice Slice2.Service] as Slice_Slice2.Service +[Slice Slice3] as Slice_Slice3 +[Slice Slice3.Group1] as Slice_Slice3.Group1 +[Slice_Slice1] --|> [Slice_Slice2] +[Slice_Slice1.Service] --|> [Slice_Slice2.Service] +[Slice_Slice2] --|> [Slice_Slice3] +[Slice_Slice2.Service] --|> [Slice_Slice3] +[Slice_Slice3] --|> [Slice_Slice1] +[Slice_Slice3.Group1] --|> [Slice_Slice3] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_LimitDependencies_Compact.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_LimitDependencies_Compact.verified.txt index 991ec5378..4d06a235e 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_LimitDependencies_Compact.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_LimitDependencies_Compact.verified.txt @@ -4,17 +4,17 @@ HIDE_STEREOTYPE() -[Slice1] -[Slice1.Service] -[Slice2] -[Slice2.Service] -[Slice3] -[Slice3.Group1] -[Slice3.Group1.Inner] -[Slice3.Group2] -[Slice3.Group2.Inner] -[Slice1] --> [Slice2] -[Slice1.Service] --> [Slice2.Service] -[Slice2] --> [Slice3] -[Slice3] --> [Slice1] +[Slice Slice1] as Slice_Slice1 +[Slice Slice1.Service] as Slice_Slice1.Service +[Slice Slice2] as Slice_Slice2 +[Slice Slice2.Service] as Slice_Slice2.Service +[Slice Slice3] as Slice_Slice3 +[Slice Slice3.Group1] as Slice_Slice3.Group1 +[Slice Slice3.Group1.Inner] as Slice_Slice3.Group1.Inner +[Slice Slice3.Group2] as Slice_Slice3.Group2 +[Slice Slice3.Group2.Inner] as Slice_Slice3.Group2.Inner +[Slice_Slice1] --> [Slice_Slice2] +[Slice_Slice1.Service] --> [Slice_Slice2.Service] +[Slice_Slice2] --> [Slice_Slice3] +[Slice_Slice3] --> [Slice_Slice1] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_MultipleCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_MultipleCaptureGroups.verified.txt index 041ebd608..8e5c21f27 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_MultipleCaptureGroups.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_MultipleCaptureGroups.verified.txt @@ -4,17 +4,17 @@ HIDE_STEREOTYPE() -[Slice1] -[Slice1.Service] -[Slice2] -[Slice2.Service] -[Slice3] -[Slice3.Group1] -[Slice3.Group2] -[Slice1] --|> [Slice2] -[Slice1.Service] --|> [Slice2.Service] -[Slice2] --|> [Slice3] -[Slice2.Service] --|> [Slice3] -[Slice3] --|> [Slice1] -[Slice3.Group1] --|> [Slice3] +[Slice Slice1] as Slice_Slice1 +[Slice Slice1.Service] as Slice_Slice1.Service +[Slice Slice2] as Slice_Slice2 +[Slice Slice2.Service] as Slice_Slice2.Service +[Slice Slice3] as Slice_Slice3 +[Slice Slice3.Group1] as Slice_Slice3.Group1 +[Slice Slice3.Group2] as Slice_Slice3.Group2 +[Slice_Slice1] --|> [Slice_Slice2] +[Slice_Slice1.Service] --|> [Slice_Slice2.Service] +[Slice_Slice2] --|> [Slice_Slice3] +[Slice_Slice2.Service] --|> [Slice_Slice3] +[Slice_Slice3] --|> [Slice_Slice1] +[Slice_Slice3.Group1] --|> [Slice_Slice3] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_NonContiguousCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_NonContiguousCaptureGroups.verified.txt index 041ebd608..8e5c21f27 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_NonContiguousCaptureGroups.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_NonContiguousCaptureGroups.verified.txt @@ -4,17 +4,17 @@ HIDE_STEREOTYPE() -[Slice1] -[Slice1.Service] -[Slice2] -[Slice2.Service] -[Slice3] -[Slice3.Group1] -[Slice3.Group2] -[Slice1] --|> [Slice2] -[Slice1.Service] --|> [Slice2.Service] -[Slice2] --|> [Slice3] -[Slice2.Service] --|> [Slice3] -[Slice3] --|> [Slice1] -[Slice3.Group1] --|> [Slice3] +[Slice Slice1] as Slice_Slice1 +[Slice Slice1.Service] as Slice_Slice1.Service +[Slice Slice2] as Slice_Slice2 +[Slice Slice2.Service] as Slice_Slice2.Service +[Slice Slice3] as Slice_Slice3 +[Slice Slice3.Group1] as Slice_Slice3.Group1 +[Slice Slice3.Group2] as Slice_Slice3.Group2 +[Slice_Slice1] --|> [Slice_Slice2] +[Slice_Slice1.Service] --|> [Slice_Slice2.Service] +[Slice_Slice2] --|> [Slice_Slice3] +[Slice_Slice2.Service] --|> [Slice_Slice3] +[Slice_Slice3] --|> [Slice_Slice1] +[Slice_Slice3.Group1] --|> [Slice_Slice3] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_SingleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_SingleAsterisk.verified.txt index c37caedd7..a4f69a9d3 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_SingleAsterisk.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_SingleAsterisk.verified.txt @@ -4,10 +4,10 @@ HIDE_STEREOTYPE() -[Slice1] -[Slice2] -[Slice3] -[Slice1] --|> [Slice2] -[Slice2] --|> [Slice3] -[Slice3] --|> [Slice1] +[Slice Slice1] as Slice_Slice1 +[Slice Slice2] as Slice_Slice2 +[Slice Slice3] as Slice_Slice3 +[Slice_Slice1] --|> [Slice_Slice2] +[Slice_Slice2] --|> [Slice_Slice3] +[Slice_Slice3] --|> [Slice_Slice1] @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_WithDependencyFilter.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_WithDependencyFilter.verified.txt index 969b504e8..61a2c05ad 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_WithDependencyFilter.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_WithDependencyFilter.verified.txt @@ -4,18 +4,18 @@ HIDE_STEREOTYPE() -[Slice1] -[Slice1.Service] -[Slice2] -[Slice2.Service] -[Slice3] -[Slice3.Group1] -[Slice3.Group1.Inner] -[Slice3.Group2] -[Slice3.Group2.Inner] -[Slice1] --|> [Slice2] -[Slice1.Service] --|> [Slice2.Service] -[Slice2] --|> [Slice3] -[Slice2.Service] --|> [Slice3] -[Slice3] --|> [Slice1] +[Slice Slice1] as Slice_Slice1 +[Slice Slice1.Service] as Slice_Slice1.Service +[Slice Slice2] as Slice_Slice2 +[Slice Slice2.Service] as Slice_Slice2.Service +[Slice Slice3] as Slice_Slice3 +[Slice Slice3.Group1] as Slice_Slice3.Group1 +[Slice Slice3.Group1.Inner] as Slice_Slice3.Group1.Inner +[Slice Slice3.Group2] as Slice_Slice3.Group2 +[Slice Slice3.Group2.Inner] as Slice_Slice3.Group2.Inner +[Slice_Slice1] --|> [Slice_Slice2] +[Slice_Slice1.Service] --|> [Slice_Slice2.Service] +[Slice_Slice2] --|> [Slice_Slice3] +[Slice_Slice2.Service] --|> [Slice_Slice3] +[Slice_Slice3] --|> [Slice_Slice1] @enduml diff --git a/ArchUnitNETTests/Fluent/Slices/SliceDomainTests.cs b/ArchUnitNETTests/Fluent/Slices/SliceDomainTests.cs index 0c2b92e02..f61fc8927 100644 --- a/ArchUnitNETTests/Fluent/Slices/SliceDomainTests.cs +++ b/ArchUnitNETTests/Fluent/Slices/SliceDomainTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using ArchUnitNET.Domain; using ArchUnitNET.Domain.Extensions; @@ -65,7 +65,7 @@ public void Slice_ToString_ReturnsDescription() { var slice = new Slice(SliceIdentifier.Of("SomeSlice"), Array.Empty()); - Assert.Equal("SomeSlice", slice.ToString()); + Assert.Equal("Slice SomeSlice", slice.ToString()); Assert.Equal(slice.Description, slice.ToString()); } @@ -104,6 +104,14 @@ public void SliceIdentifier_CompareTo_Null_ReturnsFalse() Assert.False(SliceIdentifier.Of("A").CompareTo(null)); } + [Fact] + public void SliceIdentifier_CompareTo_OneIgnored_ReturnsFalse() + { + // Same parts, so only the Ignored flag tells the two apart. + Assert.False(SliceIdentifier.Of("Ignored").CompareTo(SliceIdentifier.Ignore())); + Assert.False(SliceIdentifier.Ignore().CompareTo(SliceIdentifier.Of("Ignored"))); + } + [Fact] public void SliceIdentifier_Equals_Branches() { @@ -116,15 +124,132 @@ public void SliceIdentifier_Equals_Branches() Assert.False(identifier.Equals(SliceIdentifier.Of("B"))); } + [Fact] + public void SliceIdentifier_Equals_ComparesParts() + { + var identifier = SliceIdentifier.Of(new[] { "A", "B" }); + + Assert.Equal(SliceIdentifier.Of(new[] { "A", "B" }), identifier); + Assert.Equal( + SliceIdentifier.Of(new[] { "A", "B" }).GetHashCode(), + identifier.GetHashCode() + ); + Assert.NotEqual(SliceIdentifier.Of(new[] { "B", "A" }), identifier); + Assert.NotEqual(SliceIdentifier.Of("A.B"), identifier); + Assert.NotEqual(SliceIdentifier.Of(new[] { "A", "B", "C" }), identifier); + } + [Fact] public void SliceIdentifier_Equals_IgnoresDiagramData() { // Where a diagram draws the slice is no part of its identity. - var nested = SliceIdentifier.OfMatch("A.B", null, "X.", "A.B"); + var nested = SliceIdentifier.OfMatch(new[] { "A", "B" }, null, "X.", "A.B"); + + Assert.Equal(SliceIdentifier.Of(new[] { "A", "B" }), nested); + Assert.Equal( + SliceIdentifier.Of(new[] { "A", "B" }).GetHashCode(), + nested.GetHashCode() + ); + Assert.Equal("Slice A - B", nested.Description); + } + + [Fact] + public void SliceIdentifier_OfString_IsASingleSlicePart() + { + var ofString = SliceIdentifier.Of("Orders"); + var ofParts = SliceIdentifier.Of(new[] { "Orders" }); + + Assert.Equal(new[] { "Orders" }, ofString.Parts); + Assert.Equal(ofParts, ofString); + Assert.Equal("Slice Orders", ofString.Description); + Assert.Equal("Slice Orders", ofParts.Description); + } + + [Fact] + public void SliceIdentifier_Description_PrefixesTheJoinedParts() + { + Assert.Equal("Slice Orders.Web", SliceIdentifier.Of("Orders.Web").Description); + Assert.Equal( + "Slice Orders - Http", + SliceIdentifier.Of(new[] { "Orders", "Http" }).Description + ); + } + + [Fact] + public void SliceIdentifier_Of_RejectsMissingParts() + { + Assert.Throws(() => + SliceIdentifier.Of((System.Collections.Generic.IEnumerable)null) + ); + Assert.Throws(() => SliceIdentifier.Of(new string[0])); + Assert.Throws(() => SliceIdentifier.Of(new[] { "A", null })); + } + + [Fact] + public void SliceIdentifier_PartsContainingTheSeparator_AreADifferentSlice() + { + // Both are described "Slice A - B", but they are different slices. + var onePart = SliceIdentifier.Of(new[] { "A - B" }); + var twoParts = SliceIdentifier.Of(new[] { "A", "B" }); + + Assert.Equal(onePart.Description, twoParts.Description); + Assert.NotEqual(onePart, twoParts); + Assert.False(SliceIdentifier.Comparer.Equals(onePart, twoParts)); + } + + [Fact] + public void SliceIdentifier_ToString_IsTheDescription() + { + Assert.Equal("Slice Orders", SliceIdentifier.Of("Orders").ToString()); + } + + // --- Violation keys, which violation stores keep --------------------------------- + + [Fact] + public void ViolationKey_IsTheDescription() + { + var keys = SliceRuleDefinition + .Slices() + .Matching(Root + "(*)") + .Should() + .NotDependOnEachOther() + .Evaluate(Architecture) + .Select(result => result.EvaluatedObjectIdentifier.Identifier); + + Assert.Contains("Slice Slice1", keys); + } + + [Fact] + public void ViolationKey_OfSeveralParts_IsTheDescription() + { + // Patterns capture a single part, so a custom assignment builds several. + var creator = new SliceRuleCreator(); + creator.SetSliceAssignment( + new SliceAssignment( + type => SliceIdentifier.Of(new[] { "Custom", type.Name }), + "by name" + ) + ); + + var keys = new SlicesShould(creator) + .NotDependOnEachOther() + .Evaluate(Architecture) + .Select(result => result.EvaluatedObjectIdentifier.Identifier); + + Assert.Contains("Slice Custom - Slice1Class", keys); + } + + // --- SlicedBy ------------------------------------------------------------------ + + [Fact] + public void SlicedBy_NamesSlicesAfterTheTypeLikeEveryOtherSlice() + { + var type = Architecture.GetClassOfType(typeof(MixedTypesClass)); + + var slice = Assert.Single(new IType[] { type }.SlicedBy("")); - Assert.Equal(SliceIdentifier.Of("A.B"), nested); - Assert.Equal(SliceIdentifier.Of("A.B").GetHashCode(), nested.GetHashCode()); - Assert.Equal("A.B", nested.Description); + Assert.Equal(new[] { type.FullName }, slice.Identifier.Parts); + Assert.Equal("Slice " + type.FullName, slice.Description); } [Fact] diff --git a/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs b/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs index 232cb4a2b..67290d3ba 100644 --- a/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs +++ b/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs @@ -176,15 +176,15 @@ public void Matching_SingleAsterisk_CapturesEveryDepth() Assert.Equal( new[] { - "Slice1", - "Slice1.Service", - "Slice2", - "Slice2.Service", - "Slice3", - "Slice3.Group1", - "Slice3.Group1.Inner", - "Slice3.Group2", - "Slice3.Group2.Inner", + "Slice Slice1", + "Slice Slice1.Service", + "Slice Slice2", + "Slice Slice2.Service", + "Slice Slice3", + "Slice Slice3.Group1", + "Slice Slice3.Group1.Inner", + "Slice Slice3.Group2", + "Slice Slice3.Group2.Inner", }, Descriptions(Root + "(*)") ); @@ -199,7 +199,10 @@ public void Matching_SingleAsteriskDotDot_CapturesEveryDepth() [Fact] public void Matching_DoubleAsteriskDotDot_FoldsSubnamespacesIntoParent() { - Assert.Equal(new[] { "Slice1", "Slice2", "Slice3" }, Descriptions(Root + "(**)..")); + Assert.Equal( + new[] { "Slice Slice1", "Slice Slice2", "Slice Slice3" }, + Descriptions(Root + "(**)..") + ); } /// @@ -227,8 +230,8 @@ public void Matching_DoubleAsteriskDotDot_DropsTypesDirectlyInCapturedNamespace( .ToArray() ); - Assert.Equal(new[] { Root + "Slice1.Service.Service1Class" }, types["Slice1"]); - Assert.Equal(new[] { Root + "Slice2.Service.Service2Class" }, types["Slice2"]); + Assert.Equal(new[] { Root + "Slice1.Service.Service1Class" }, types["Slice Slice1"]); + Assert.Equal(new[] { Root + "Slice2.Service.Service2Class" }, types["Slice Slice2"]); Assert.Equal( new[] { @@ -237,7 +240,7 @@ public void Matching_DoubleAsteriskDotDot_DropsTypesDirectlyInCapturedNamespace( Root + "Slice3.Group2.Group2Class", Root + "Slice3.Group2.Inner.Inner2Class", }, - types["Slice3"] + types["Slice Slice3"] ); } @@ -276,7 +279,7 @@ public void DuplicatePrefixSegment_CapturesAfterTheFirstOccurrenceOfThePrefix() .ToList(); Assert.Single(slices); - Assert.Equal("DuplicatePrefix.Sub", slices[0].Description); + Assert.Equal("Slice DuplicatePrefix.Sub", slices[0].Description); } // --- ".." semantics ------------------------------------------------------------- @@ -287,11 +290,11 @@ public void DotDot_BetweenCaptureGroups_CapturesEveryDepth() Assert.Equal( new[] { - "Alpha.Service", - "AlphaService", - "Outer.Inner", - "Outer.Mid.Inner", - "Single", + "Slice Alpha.Service", + "Slice AlphaService", + "Slice Outer.Inner", + "Slice Outer.Mid.Inner", + "Slice Single", }, Descriptions(DotDot + "(*)..(*)") ); @@ -303,11 +306,11 @@ public void DotDot_BeforeLiteral_CapturesEveryDepth() Assert.Equal( new[] { - "Alpha.Service", - "AlphaService", - "Outer.Inner", - "Outer.Mid.Inner", - "Single", + "Slice Alpha.Service", + "Slice AlphaService", + "Slice Outer.Inner", + "Slice Outer.Mid.Inner", + "Slice Single", }, Descriptions(DotDot + "(*)..Service") ); @@ -328,8 +331,8 @@ public void DotDot_BeforeLiteral_GivesAlphaServiceAndAlphaDotServiceDistinctSlic // holding exactly the one type it's made of. This is not evidence the matcher tells // the two shapes apart -- it would look identical if it ignored segment boundaries // entirely. - Assert.Single(slices.Single(slice => slice.Description == "AlphaService").Types); - Assert.Single(slices.Single(slice => slice.Description == "Alpha.Service").Types); + Assert.Single(slices.Single(slice => slice.Description == "Slice AlphaService").Types); + Assert.Single(slices.Single(slice => slice.Description == "Slice Alpha.Service").Types); } // --- failure messages ----------------------------------------------------------- diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_FluentDefinition_CombinesEvaluationAndDescription.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_FluentDefinition_CombinesEvaluationAndDescription.verified.txt index 15d5cc49f..7c6f6bb58 100644 --- a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_FluentDefinition_CombinesEvaluationAndDescription.verified.txt +++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_FluentDefinition_CombinesEvaluationAndDescription.verified.txt @@ -21,11 +21,11 @@ All Evaluations passed Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles and Types that have full name "SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass" should exist Result: False Description: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class Result: True @@ -33,11 +33,11 @@ Description: SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass pa Message: "Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_WithArchRule_CombinesEvaluationAndDescription.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_WithArchRule_CombinesEvaluationAndDescription.verified.txt index c22e38343..f13e66cbd 100644 --- a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_WithArchRule_CombinesEvaluationAndDescription.verified.txt +++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_WithArchRule_CombinesEvaluationAndDescription.verified.txt @@ -26,11 +26,11 @@ Message: Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles and Types that have name "NotTheNameOfAnyObject" should exist Result: False Description: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class Result: False @@ -38,11 +38,11 @@ Description: There are no objects matching the criteria Message: "Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_FluentDefinition_CombinesEvaluationAndDescription.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_FluentDefinition_CombinesEvaluationAndDescription.verified.txt index 14faa3188..acbf6584a 100644 --- a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_FluentDefinition_CombinesEvaluationAndDescription.verified.txt +++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_FluentDefinition_CombinesEvaluationAndDescription.verified.txt @@ -3,21 +3,21 @@ Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles Result: False Description: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class Message: "Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class @@ -28,11 +28,11 @@ Slice3 -> Slice1 Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles or Types that have full name "SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass" should exist Result: False Description: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class Result: True @@ -40,11 +40,11 @@ Description: SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass pa Message: "Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class @@ -55,11 +55,11 @@ Slice3 -> Slice1 Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles or Types that have name "NotTheNameOfAnyObject" should exist Result: False Description: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class Result: False @@ -67,11 +67,11 @@ Description: There are no objects matching the criteria Message: "Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_WithArchRule_CombinesEvaluationAndDescription.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_WithArchRule_CombinesEvaluationAndDescription.verified.txt index 42340d70c..c2a19f195 100644 --- a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_WithArchRule_CombinesEvaluationAndDescription.verified.txt +++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_WithArchRule_CombinesEvaluationAndDescription.verified.txt @@ -3,11 +3,11 @@ Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles or Types that have full name "SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass" should exist Result: False Description: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class Result: True @@ -15,11 +15,11 @@ Description: SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass pa Message: "Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class @@ -30,11 +30,11 @@ Slice3 -> Slice1 Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles or Types that have name "NotTheNameOfAnyObject" should exist Result: False Description: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class Result: False @@ -42,11 +42,11 @@ Description: There are no objects matching the criteria Message: "Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_MultipleSubnamespaces_ReportsEveryCycle.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_MultipleSubnamespaces_ReportsEveryCycle.verified.txt index b15623362..88b409548 100644 --- a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_MultipleSubnamespaces_ReportsEveryCycle.verified.txt +++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_MultipleSubnamespaces_ReportsEveryCycle.verified.txt @@ -1,21 +1,21 @@ Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles Result: False Description: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class Message: "Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed: Cycle found: -Slice2 -> Slice3 +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -Slice3 -> Slice1 +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_WithCycle_ReturnsDescriptiveCycleMessage.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_WithCycle_ReturnsDescriptiveCycleMessage.verified.txt index 03a2b704c..0bb65e78b 100644 --- a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_WithCycle_ReturnsDescriptiveCycleMessage.verified.txt +++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_WithCycle_ReturnsDescriptiveCycleMessage.verified.txt @@ -1,17 +1,17 @@ Query: Slices matching "SlicesTestAssembly.DirectCircle.(*)" should be free of cycles Result: False Description: Cycle found: -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.DirectCircle.Slice1.Slice1Class -> SlicesTestAssembly.DirectCircle.Slice2.Slice2Class -Slice2 -> Slice1 +Slice Slice2 -> Slice Slice1 SlicesTestAssembly.DirectCircle.Slice2.Slice2Class -> SlicesTestAssembly.DirectCircle.Slice1.Slice1Class Message: "Slices matching "SlicesTestAssembly.DirectCircle.(*)" should be free of cycles" failed: Cycle found: -Slice1 -> Slice2 +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.DirectCircle.Slice1.Slice1Class -> SlicesTestAssembly.DirectCircle.Slice2.Slice2Class -Slice2 -> Slice1 +Slice Slice2 -> Slice Slice1 SlicesTestAssembly.DirectCircle.Slice2.Slice2Class -> SlicesTestAssembly.DirectCircle.Slice1.Slice1Class diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MultipleSubnamespaces_ReportsEveryDependency.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MultipleSubnamespaces_ReportsEveryDependency.verified.txt index 0a801d01b..d17bbc960 100644 --- a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MultipleSubnamespaces_ReportsEveryDependency.verified.txt +++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MultipleSubnamespaces_ReportsEveryDependency.verified.txt @@ -1,64 +1,64 @@ Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should not depend on each other Result: False -Description: Slice1 does depend on other slices: -Slice1 -> Slice2 +Description: Slice Slice1 does depend on other slices: +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class Result: False -Description: Slice1.Service does depend on other slices: -Slice1.Service -> Slice2.Service +Description: Slice Slice1.Service does depend on other slices: +Slice Slice1.Service -> Slice Slice2.Service SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class Result: False -Description: Slice2 does depend on other slices: -Slice2 -> Slice3 +Description: Slice Slice2 does depend on other slices: +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class Result: False -Description: Slice2.Service does depend on other slices: -Slice2.Service -> Slice3 +Description: Slice Slice2.Service does depend on other slices: +Slice Slice2.Service -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class Result: False -Description: Slice3 does depend on other slices: -Slice3 -> Slice1 +Description: Slice Slice3 does depend on other slices: +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class Result: False -Description: Slice3.Group1 does depend on other slices: -Slice3.Group1 -> Slice3 +Description: Slice Slice3.Group1 does depend on other slices: +Slice Slice3.Group1 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Group1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class Result: True -Description: Slice3.Group1.Inner does not depend on another slice. +Description: Slice Slice3.Group1.Inner does not depend on another slice. Result: True -Description: Slice3.Group2 does not depend on another slice. +Description: Slice Slice3.Group2 does not depend on another slice. Result: True -Description: Slice3.Group2.Inner does not depend on another slice. +Description: Slice Slice3.Group2.Inner does not depend on another slice. Message: "Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should not depend on each other" failed: - Slice1 does depend on other slices: -Slice1 -> Slice2 + Slice Slice1 does depend on other slices: +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class - Slice1.Service does depend on other slices: -Slice1.Service -> Slice2.Service + Slice Slice1.Service does depend on other slices: +Slice Slice1.Service -> Slice Slice2.Service SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class - Slice2 does depend on other slices: -Slice2 -> Slice3 + Slice Slice2 does depend on other slices: +Slice Slice2 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class - Slice2.Service does depend on other slices: -Slice2.Service -> Slice3 + Slice Slice2.Service does depend on other slices: +Slice Slice2.Service -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class - Slice3 does depend on other slices: -Slice3 -> Slice1 + Slice Slice3 does depend on other slices: +Slice Slice3 -> Slice Slice1 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class - Slice3.Group1 does depend on other slices: -Slice3.Group1 -> Slice3 + Slice Slice3.Group1 does depend on other slices: +Slice Slice3.Group1 -> Slice Slice3 SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Group1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_ReturnsDescriptiveDependencyMessage.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_ReturnsDescriptiveDependencyMessage.verified.txt index 448e70e4b..56687bc1e 100644 --- a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_ReturnsDescriptiveDependencyMessage.verified.txt +++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_ReturnsDescriptiveDependencyMessage.verified.txt @@ -1,17 +1,17 @@ Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**).." should not depend on each other Result: False -Description: Slice1 does depend on other slices: -Slice1 -> Slice2 +Description: Slice Slice1 does depend on other slices: +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class Result: True -Description: Slice2 does not depend on another slice. +Description: Slice Slice2 does not depend on another slice. Result: True -Description: Slice3 does not depend on another slice. +Description: Slice Slice3 does not depend on another slice. Message: "Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**).." should not depend on each other" failed: - Slice1 does depend on other slices: -Slice1 -> Slice2 + Slice Slice1 does depend on other slices: +Slice Slice1 -> Slice Slice2 SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class diff --git a/documentation/docs/guide.md b/documentation/docs/guide.md index 2c522a8c1..ccf6d5a52 100644 --- a/documentation/docs/guide.md +++ b/documentation/docs/guide.md @@ -174,13 +174,46 @@ IArchRule rule = Classes().That().DoNotHaveAnyAttributes(typeof(Display)).Should .NotDependOnAny(Classes().That().AreAssignableTo(typeof(ICanvas))); ``` -### 3.6. Cycle Rule +### 3.6. Slice Rules +Slices group types by namespace. A *slice* is the set of types whose namespaces produced the same +captured values from a pattern -- with `"App.(*)"`, the types in `App.Orders` form one slice, those in +`App.Shipping` another. + +To use the `Slices()` entry point directly, add: +```cs +using static ArchUnitNET.Fluent.Slices.SliceRuleDefinition; +``` + +#### How slices are named +A slice is identified by **what its capture groups captured**, in group order. Two types end up in +the same slice exactly when their groups captured the same values. + +A slice is named after its captures: `Slice ` followed by the captured values joined with ` - `. The +name never reads as a namespace, and it is what failure messages and diagrams show. + +| Pattern | Namespace | Slice name | +|---|---|---| +| `App.(*)` | `App.Orders` | `Slice Orders` | +| `App.(**)` | `App.Orders.Web` | `Slice Orders.Web` | + +Frozen violations of a slice rule (see `FreezingArchRule`) are stored by the slice name, e.g. +`Slice Orders`. + +#### Cycle-free rule ![Cycle](img/cycle.svg) ```cs IArchRule rule = Slices().Matching("Module.(*)").Should() .BeFreeOfCycles(); ``` +#### No-dependency rule +```cs +IArchRule rule = Slices().Matching("Module.(*)").Should() + .NotDependOnEachOther(); +``` +`NotDependOnEachOther` forbids *any* dependency between slices, where `BeFreeOfCycles` only forbids +circular ones. + ## 4. How to check To get a meaningful error message we recommend using